Weekly Swift articles, podcasts and tips by John Sundell.

Implicit capturing of self in Swift 5.3

Published on 10 Jul 2020
Basics article available: SwiftUI

In 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)
        }
    }
}

⚠️ Note that Swift 5.3 is currently, at the time of writing, in beta as part of Xcode 12.

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! 🚀