When working with legacy code in a Swift project, you may encounter situations where the code doesn't fully adhere to best practices, leading to the occurrence of retain cycles.
This often arises due to closures capturing self
strongly. In this article, I want to share my approach to more easily identifying these culprits.
All we need is a decent regular expression (regex) and the "secret" hidden menu in Xcode, which, until now, I personally hadn't found much use for.
Here is the search menu
Alright, now that we know where this menu is located, it's time to figure out what exactly we want to search for. In my experience with legacy code, the most common places where retain cycles occur are closures that need to access self
when executed.
This would be a short definition of closures in Swift language:
- In Swift, closures are self-contained blocks of code that can be passed around and used in your program. They can capture and store references to variables and constants from the surrounding context, allowing them to be used later. Closures are similar to functions but are more lightweight and flexible.
These closures can capture self
strongly, creating retain cycles when executed asynchronously. To prevent this, use weak self
within the closure, allowing self to be deallocated if there are no other strong references.
These are some basic code examples where the issues might appear:
swiftvar myVar: Int = 0 func executeClosure(completion: @escaping () -> Void) { // Simulating an asynchronous operation DispatchQueue.main.asyncAfter(deadline: .now() + 1) { completion() } } myClassInstance.executeClosure { _ in self.myVar // here we capture self strongly }
Typically, we want to create a regex that searches for these kinds of code blocks:
{ _ in }
.sink { _ in }
{ _, param2 in }
{ in }
and so on so forth.
Regex construction
This is the regex that has worked for me really well so far to identify and resolve these types of issues across numerous classes, thereby creating a more robust application and reducing retain cycles.
All you have to do is copy and paste this into the search menu I presented above.
regex{ *(_|[a-zA-Z0-9]*)? +in
Here are some brief steps that will outline and explain the expression:
- Since curly braces are special characters in regular expressions, we need to escape it with a backslash
(\)
, so\{
looks for a literal{
. *
This allows for any amount of whitespace (including none) after the{
(_|[a-zA-Z0-9]*)
Matches either an underscore _OR
any valid parameter name (consisting of alphanumeric characters).?
makes the entire preceding group optional. This means the closure can have:- No parameters at all.
- An underscore
_
. - A valid identifier.
- The plus sign
(+)
requires at least one space before the keywordin
. This is helpful because there are many keywords that end within
or have custom naming in the project. We know that thein
keyword will cause a compile-time error in Xcode if there isn’t a space before it. in
matches the literal keyword.