Implicit capturing of self in Swift 5.3
Discover page available: SwiftUIIn Swift 5.2 and earlier, whenever we’re accessing an instance method or property within an escaping closure, we need to explicitly use self
in order to opt in to the required capturing semantics.
For example, the following SwiftUI view uses its viewModel
within two escaping closures, which means that we need to use self.viewModel
when accessing it:
struct ListView: View {
@ObservedObject var viewModel: ListViewModel
var body: some View {
List {
ForEach(viewModel.items) { item in
Text(item.text)
}
.onDelete {
self.viewModel.deleteItems(at: $0)
}
Button("Delete all items") {
self.viewModel.deleteAllItems()
}
.foregroundColor(.red)
}
}
}
In Swift 5.3, however, using self
in the above kind of situation is no longer required, and we can simply access properties and methods belonging to value types (such as SwiftUI views, or any other type of struct) without any additional prefixes — regardless if that’s done within an escaping closure or not:
struct ListView: View {
@ObservedObject var viewModel: ListViewModel
var body: some View {
List {
ForEach(viewModel.items) { item in
Text(item.text)
}
.onDelete {
viewModel.deleteItems(at: $0)
}
Button("Delete all items") {
viewModel.deleteAllItems()
}
.foregroundColor(.red)
}
}
}
That’s really neat, and further makes SwiftUI’s DSL feel even more lightweight. However, it’s important to remember that the same kind of capturing still occurs — meaning that the above ListView
value (and the ListViewModel
instance that it contains) will still be captured by our closures. Within the context of SwiftUI, though, that’s not likely to become a problem — since SwiftUI views are just lightweight structs that don’t hold any strong references to their subviews.
To learn more about closure capturing and memory management within that context, check out “Swift’s closure capturing mechanics”.
Finally, in this particular case, there’s also a third option that works in both Swift 5.3 and earlier versions — which is to pass the ListViewModel
methods that we’d like to call directly as closures — like this:
struct ListView: View {
@ObservedObject var viewModel: ListViewModel
var body: some View {
List {
ForEach(viewModel.items) { item in
Text(item.text)
}
.onDelete(perform: viewModel.deleteItems)
Button("Delete all items",
action: viewModel.deleteAllItems
)
.foregroundColor(.red)
}
}
}
In the above scenario, our viewModel
will still be retained, since passing an instance method as a closure also retains its enclosing instance (which again won’t be a problem in this case), but we end up with a slightly more concise implementation. To learn more about what makes the above work, check out the “First class functions” episode of Swift Clips.
Thanks for reading! 🚀