0

I'm trying to save a struct in background but I get this error :

closure cannot implicitly capture a mutating self parameter

This is my code :

//MARK: Parse self methods
fileprivate mutating func ParseSave(_ completionBlock:  @escaping SuccessCompletionBlock) {
    let message: PFObject = PFObject(className: "Message")

    if let id = self.id {
        //this object exit just update it
        message.objectId = id
    }

    // set attributes

    if let text = self.text {
        message["text"] = text
    }
    message["sender"] = PFUser(withoutDataWithObjectId: self.sender.id)
    message["conversation"] = PFObject(withoutDataWithClassName: "Conversation", objectId: conversationId)

    message["viewed"] = self.viewed

    message.saveInBackground { (success, error) in
        if success {
// the next 3 lines cause the error : (when I try to update the struct - self )
            self.id = message.objectId
            self.createdAt = message.createdAt ?? self.createdAt
            self.updatedAt = message.updatedAt ?? self.updatedAt

        }
        completionBlock(success, error)
    }
}

I've checked those question: 1 - 2 I've added the @escaping but didn't work.

Community
  • 1
  • 1
Chlebta
  • 2,949
  • 12
  • 44
  • 96
  • Which line generates the error message? (And what the heck is `ibutes`?) – matt Mar 09 '17 at 16:55
  • 2
    It doesn't really make sense to mutate a struct inside a completion handler. The completion handler would be working on a copy of the struct that is captured by the block so no changes made inside the block would be reflected in the caller. – dan Mar 09 '17 at 17:06
  • 1
    See http://stackoverflow.com/a/41941810/1271826. Or search Stack Overflow for "closure cannot implicitly capture a mutating self parameter" (search with quotes around the message). This has been asked and answered before. – Rob Mar 09 '17 at 17:34
  • @dan Even when I use `myMessage.ParseSave() { success, error in }` myMessage will be captured by copy inside the completion block ?? – Chlebta Mar 10 '17 at 08:44
  • 1
    So `self` is a struct? You do understand, don't you, that struct mutation doesn't really exist? It actually involves copying and substitution. So you would be trying to replace `self` with another object after a delay. That seems incoherent. – matt Mar 10 '17 at 13:33
  • @matt thank you now it's more clear how things works :D – Chlebta Mar 10 '17 at 13:41

1 Answers1

4

I think it will help if we minimally elicit the error message you're getting. (For delay, see dispatch_after - GCD in swift?.)

struct S {
    var name = ""
    mutating func test() {
        delay(1) {
            self.name = "Matt" // Error: Closure cannot ...
            // ... implicitly capture a mutating self parameter
        }
    }
}

The reason lies in the peculiar nature of struct (and enum) mutation: namely, it doesn't really exist. When you set a property of a struct, what you're really doing is copying the struct instance and replacing it with another. That is why only a var-referenced struct instance can be mutated: the reference must be replaceable in order for the instance to be mutable.

Now we can see what's wrong with our code. Obviously it is legal for a mutating method to mutate self; that is what mutating means. But in this case we are offering to go away for a while and then suddenly reappear on the scene (after 1 second, in this case) and now mutate self. So we are going to maintain a copy of self until some disconnected moment in the future, when self will suddenly be somehow replaced. That is incoherent, not least because who knows how the original self may have been mutated in the meantime, rendering our copy imperfect; and the compiler prevents it.

The same issue does not arise with a nonescaping closure:

func f(_ f:()->()) {}

struct S {
    var name = ""
    mutating func test() {
        f {
            self.name = "Matt" // fine
        }
    }
}

That's because the closure is nonescaping; it is executed now, so the incoherency about what will happen in the future is absent. This is an important difference between escaping and nonescaping closures, and is one of the reasons why they are differentiated.

Also, the same issue does not arise with a class:

class C {
    var name = ""
    func test() {
        delay(1) {
            self.name = "Matt" // fine
        }
    }
}

That's because the class instance is captured by reference in the closure, and a class instance is mutable in place.

(See also my little essay here: https://stackoverflow.com/a/27366050/341994.)

Community
  • 1
  • 1
matt
  • 447,615
  • 74
  • 748
  • 977