Closure cannot implicitly capture a mutating self parameter

58,283

Solution 1

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 ...

Solution 2

Sync Solution

If you need to mutate a value type (struct) in a closure, that may only work synchronously, but not for async calls, if you write it like this:

struct Banana {
    var isPeeled = false
    mutating func peel() {
        var result =  self

        SomeService.synchronousClosure { foo in
            result.isPeeled = foo.peelingSuccess
        }

        self = result
    }
}

You cannot otherwise capture a "mutating self" with value types except by providing a mutable (hence var) copy.

Why not Async?

The reason this does not work in async contexts is: you can still mutate result without compiler error, but you cannot assign the mutated result back to self. Still, there'll be no error, but self will never change because the method (peel()) exits before the closure is even dispatched.

To circumvent this, you may try to change your code to change the async call to synchronous execution by waiting for it to finish. While technically possible, this probably defeats the purpose of the async API you're interacting with, and you'd be better off changing your approach.

Changing struct to class is a technically sound option, but doesn't address the real problem. In our example, now being a class Banana, its property can be changed asynchronously who-knows-when. That will cause trouble because it's hard to understand. You're better off writing an API handler outside the model itself and upon finished execution fetch and change the model object. Without more context, it is hard to give a fitting example. (I assume this is model code because self.img is mutated in the OP's code.)

Adding "async anti-corruption" objects may help

I'm thinking about something among the lines of this:

  • a BananaNetworkRequestHandler executes requests asynchronously and then reports the resulting BananaPeelingResult back to a BananaStore
  • The BananaStore then takes the appropriate Banana from its inside by looking for peelingResult.bananaID
  • Having found an object with banana.bananaID == peelingResult.bananaID, it then sets banana.isPeeled = peelingResult.isPeeled,
  • finally replacing the original object with the mutated instance.

You see, from the quest to find a simple fix it can become quite involved easily, especially if the necessary changes include changing the architecture of the app.

Solution 3

If someone is stumbling upon this page (from search) and you are defining a protocol / protocol extension, then it might help if you declare your protocol as class bound. Like this:

protocol MyProtocol: class {
   ...
}
Share:
58,283

Related videos on Youtube

coding_999
Author by

coding_999

Updated on July 05, 2022

Comments

  • coding_999
    coding_999 almost 2 years

    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

    • pstued
      pstued over 7 years
      In what environment do you call FirebaseRef.observeSingleEvent . Is it called within a struct?
    • coding_999
      coding_999 over 7 years
      Yes its called within a struct
    • Mikael Weiss
      Mikael Weiss about 3 years
      This seemed to work for me: .escapingFunction { [self] _ in self.someFunction }
  • redent84
    redent84 about 7 years
    Last paragraph is important. Be careful with capturing self in @escape blocks, you may end with retention cycles which may cause memory leaks. Better use [weak self] (or [unowned self] if you're sure about the block lifecycle) in all @escape blocks that capture self.
  • Novellizator
    Novellizator almost 7 years
    Soo, why is the problem solved with a reference type? Why is it any different?
  • Islam
    Islam over 6 years
    Yes, but due to the nature of the value types now your closureSelf's property will be newValue and your's won't change. Happy debugging.
  • ctietze
    ctietze over 6 years
    Yeah. Does not solve the problem but breaks the code instead.
  • Vijay Vepakomma
    Vijay Vepakomma about 6 years
    I initially got around it by setting var _self = self and setting _self.property`. I will try this solution.