Jeff Gu Kang
Jeff Gu Kang

Reputation: 4879

Different process between Struct and Class in mutating asynchronously in Swift3

In struct type, mutating self in async process makes error as below.

closure cannot implicitly captured a mutating self

If I change the struct to class type, the error disappear. What is difference between struct and class when mutate self in asynchronously?

struct Media {
static let loadedDataNoti = "loadedDataNotification"
let imagePath: String
let originalPath: String
let description: String
var imageData: Data?
let tag: String
var likeCount: Int?
var commentCount: Int?
var username: String?
var delegate: MediaDelegate?

public init(imagePath: String, originalPath: String, description: String, tag: String, imageData: Data? = nil) {
    self.imagePath = imagePath
    self.originalPath = originalPath
    self.description = description
    self.tag = tag

    if imageData != nil {
        self.imageData = imageData
    } else {
        loadImageData()
    }
}

mutating func loadImageData() {
    if let url = URL(string: imagePath) {
        Data.getDataFromUrl(url: url, completion: { (data, response, error) in
            if (error != nil) {
                print(error.debugDescription)
                return
            }
            if data != nil {
                self.imageData = data! // Error: closure cannot implicitly captured a mutating self
                NotificationCenter.default.post(name: NSNotification.Name(rawValue: Media.loadedDataNoti), object: data)
            }
        })
    }
}

Upvotes: 3

Views: 963

Answers (2)

matt
matt

Reputation: 535586

A struct is a value type. How does struct mutating work? It works by making a completely new struct and substituting it for the original. Even in a simple case like this:

    struct S {
        var name = "matt"
    }

    var s = S()
    s.name = "me"

... you are actually replacing one S instance by another — that is exactly why s must be declared as var in order to do this.

Thus, when you capture a struct's self into an asynchronously executed closure and ask to mutate it, you are threatening to appear at some future time and suddenly rip away the existing struct and replace it by another one in the middle of executing this very code. That is an incoherent concept and the compiler rightly stops you. It is incoherent especially because how do you even know that this same self will even exist at that time? An intervening mutation may have destroyed and replaced it.

Thus, this is legal:

struct S {
    var name = "matt"
    mutating func change() {self.name = "me"}
}

But this is not:

func delay(_ delay:Double, closure:@escaping ()->()) {
    let when = DispatchTime.now() + delay
    DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}

struct S {
    var name = "matt"
    mutating func change() {delay(1) {self.name = "me"}} // error
}

Upvotes: 7

Andreas
Andreas

Reputation: 2735

When you mutate an instance of a value type -- such as a struct -- you're conceptually replacing it with a new instance of the same type, i.e. doing this:

myMedia.mutatingFuncToLoadImageData()

...can be thought of as doing something like this:

myMedia = Media(withLoadedData: theDownloadedData)

...except you don't see the assignment in code.

You're effectively replacing the instance that you call a mutating function on. In this case myMedia. As you may realize, the mutation has to have finished at the end of the mutating function for this to work, or your instance would keep changing after calling the mutating function.

You're handing off a reference to self to an asynchronous function that will try to mutate your instance after your mutating function has ended.

You could compile your code by doing something like

var myself = self // making a copy of self
let closure = {
    myself.myThing = "thing"
}

but that would only change the value of the variable myself, and not affect anything outside of your function.

Upvotes: 1

Related Questions