Save struct in background mutating function

2019-09-08 17:48发布

问题:

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.

回答1:

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