How to fix "error: escaping closure captures mutating 'self' parameter" in init()?

import SwiftUI

struct MyView: View {
    @State var toggle = false

    let toggleBinding: Binding<Bool>

    init() {
        // error: escaping closure captures mutating 'self' parameter
        self.toggleBinding = Binding<Bool>(get: { self.toggle }, set: { self.toggle = $0 })
    }

    var body: some View {
        // I want to avoid making the binding here, do it once in init() instead
        let toggleBindingIsOkayHere = Binding<Bool>(get: { self.toggle }, set: { self.toggle = $0 })
        Text("Help")
    }
}

As the error said, in the escaping closure, you're capturing and mutating self (actually self.toggle). This is not allowed.

Even if you can bypass that, you still have the problem of using self before all of its variables are initialized (toggleBinding specifically).

One way to achieve what you want would be to make it lazy:

lazy private(set) var toggleBinding = $toggle

PS.

Getting Binding is very cheap. Is there any particular reason you're avoiding that?

No particular reason. Just want to know if I can avoid making the binding inside var body, just want to learn if I can just make the binding once.

lazy private(set) var toggleBinding = $toggle

I actually need to observe the Binding.set() and do something, so I have to customize Binding<Bool> with my own define setter()...

lazy var toggleBinding = $toggle

compile, but not

lazy var toggleBinding = Binding<Bool>(get: { self.toggle }, set: { self.toggle = $0 })

same "error: escaping closure captures mutating 'self' parameter"

Why using $toggle is okay? Doesn't it also need to capture self in the generated code?

Even if you could get it to work, I think you would get an error for accessing a @State variable outside the body of the view.

The binding is passed to something like a Toggle. I don't believe it will have any access scope problem with whatever the Toggle does.

$toggle (probably) doesn't capture the container MyView, it capture only the wrapper State<Bool>.

You can do

lazy private(set) var toggleBinding = Binding<Bool>(get:
    { [_toggle] in
        _toggle.wrappedValue
    }, set: { [_toggle] in
        _toggle.wrappedValue = $0
})

But since you plan on using it in body which is immutable, you'll need to initialize it before body is called.

    var toggleBinding: Binding<Bool>!
    
    init() {
        toggleBinding = Binding<Bool>(get:
            { [_toggle] in
                _toggle.wrappedValue
            }, set: { [_toggle] in
                _toggle.wrappedValue = $0
        })
    }

Though as @suyashsrijan said, it could easily get you in a situation where SwiftUI may behave unpredictably. In fact, it causes my view not to be updated (even when I use $toggle).

There is no way to make this work. self simply does not have a persistent, unique identity for value types that could possibly be captured by an escaping closure. Even if you unwisely find a way to capture a pointer to the place in memory that the self variable is bound to during some specific init call, that value can be moved and/or copied around to different places in memory, immediately making that pointer stale. In fact, copying view values and otherwise taking advantage of the non-identity of self is a significant part of how SwiftUI does some of the things it does.

4 Likes

Thank you for the explanation :slight_smile:

SwiftUI is simply amazing, so well designed. Hopefully some explanation of how things work is forthcoming. Right now I only find out what's not allowed by making mistake, like mutating @State inside view body, it doesn't crash, but there is runtime warning...

Terms of Service

Privacy Policy

Cookie Policy