Search code examples
iosswiftfirebaseswift3firebase-realtime-database

Closure cannot implicitly capture a mutating self parameter


I am using Firebase to observe event and then setting an image inside completion handler

FirebaseRef.observeSingleEvent(of: .value, with: { (snapshot) in
        if let _ = snapshot.value as? NSNull {
            self.img = UIImage(named:"Some-image")!
        } else {
            self.img = UIImage(named: "some-other-image")!
        }
})

However I am getting this error

Closure cannot implicitly capture a mutating self parameter

I am not sure what this error is about and searching for solutions hasn't helped


Solution

  • The short version

    The type owning your call to FirebaseRef.observeSingleEvent(of:with:) is most likely a value type (a struct?), in which case a mutating context may not explicitly capture self in an @escaping closure.

    The simple solution is to update your owning type to a reference once (class).


    The longer version

    The observeSingleEvent(of:with:) method of Firebase is declared as follows

    func observeSingleEvent(of eventType: FIRDataEventType, 
         with block: @escaping (FIRDataSnapshot) -> Void)
    

    The block closure is marked with the @escaping parameter attribute, which means it may escape the body of its function, and even the lifetime of self (in your context). Using this knowledge, we construct a more minimal example which we may analyze:

    struct Foo {
        private func bar(with block: @escaping () -> ()) { block() }
    
        mutating func bax() {
            bar { print(self) } // this closure may outlive 'self'
            /* error: closure cannot implicitly capture a 
                      mutating self parameter              */
        }
    }
    

    Now, the error message becomes more telling, and we turn to the following evolution proposal was implemented in Swift 3:

    Stating [emphasis mine]:

    Capturing an inout parameter, including self in a mutating method, becomes an error in an escapable closure literal, unless the capture is made explicit (and thereby immutable).

    Now, this is a key point. For a value type (e.g. struct), which I believe is also the case for the type that owns the call to observeSingleEvent(...) in your example, such an explicit capture is not possible, afaik (since we are working with a value type, and not a reference one).

    The simplest solution to this issue would be making the type owning the observeSingleEvent(...) a reference type, e.g. a class, rather than a struct:

    class Foo {
        init() {}
        private func bar(with block: @escaping () -> ()) { block() }
    
        func bax() {
            bar { print(self) }
        }
    }
    

    Just beware that this will capture self by a strong reference; depending on your context (I haven't used Firebase myself, so I wouldn't know), you might want to explicitly capture self weakly, e.g.

    FirebaseRef.observeSingleEvent(of: .value, with: { [weak self] (snapshot) in ...