Renaming variables in Swift closure capture lists

marionauta

Mario Nachbaur

Posted on March 28, 2024

Renaming variables in Swift closure capture lists

If you have written any Swift code, you probably have seen capture lists. Let me show them to you in case you're not sure what they are:

class Some {
  var number: Int = 42

  func method() {
    someNetworkRequest(callback: { [weak self] result in
      self?.number = result //     ^ this thing right here
    })
  }
}
Enter fullscreen mode Exit fullscreen mode

That thing between the square brackets ([ ]) is the capture list. It allows us to define the context of the callback. In this case, we capture self weakly, to avoid memory cycles.

The problem

With the introduction to iOS 17, SwiftUI gained a new View modifier: onChange(of:action:) where action takes two values, the old one and the new one. This allows code such as:

struct Some: View {
  @State private var isCreating: Bool = false

  var body: some View {
    Text("")
      .onChange(of: isCreating) { wasCreating, isCreating in
        guard wasCreating && !isCreating else { return }
        print("Finished creating")
      }
    }
}
Enter fullscreen mode Exit fullscreen mode

This is just a simplified example to show the modifier. Just imagine that instead of a Bool we are using an enum with many cases.

On iOS 16 and prior, the modifier only provides the new value, making it difficult to check the current value and the previous one. There is however, a small workaround using capture lists:

onChange(of: isCreating) { [isCreating] newValue in
  guard isCreating && !newValue else { return }
  print("Finished creating")
}
Enter fullscreen mode Exit fullscreen mode

Using a capture list, we can capture the current value, which is equivalent to the old value. And the callback provides us with newValue.

The solution

The previous code works, but the naming is awkward. isCreating points to the old value, and newValue is too generic. We could rename it to newIsCreating, but still, not the best name.

Here is when renaming variables inside the capture list shines. Let's look at the example:

onChange(of: isCreating) { [wasCreating = isCreating] isCreating in
  guard wasCreating && !isCreating else { return }
  print("Finished creating")
}
Enter fullscreen mode Exit fullscreen mode

Inside the capture group, we are declaring a new variable wasCreating which the value of the captured variable isCreating (the old value). Then the callback provides us with isCreating (the new value)

Finishing

The examples shown may be obsolete with iOS 17 new modifier, but there are a lot of developers supporting iOS 16 and below. I wrote this article solving the exact problem I faced, but I'm sure there will be many, many situations where renaming a variable would be very helpful.

πŸ’– πŸ’ͺ πŸ™… 🚩
marionauta
Mario Nachbaur

Posted on March 28, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related