简体   繁体   中英

SwiftUI Escaping closure captures mutating 'self' parameter

I have a view which can be opened in two ways. One with the data provided to it, and the other with a document reference to a Firestore document. I have created two constructors and in the first one I'm providing the data and in the other one I'm a providing the document reference. Then I'm using this reference to make a network call bit I'm getting an error:

Escaping closure captures mutating 'self' parameter

Any idea on how to solve this problem?

@State var request: RequestModel?

init(request: RequestModel) {
    self.request = request
}

init(reference: DocumentReference) {
    FirestoreService().fetchDocument(documentReference: reference) { (request: RequestModel) in
        self.request = request
    }
}

An object's initializer cannot do anything asynchronous. Its job is to produce the object immediately, with all its properties initialized.

I'm sorry to say the above answer was wrong.

An object's initializer cannot do anything asynchronous. Its job is to produce the object immediately, with all its properties initialized

--> This is absolutely wrong.

I've done successfully tons of multi-threaded programming for 10 years in Objective-C/C/Swift: without any memory leakage nor data accesses nor access races.

I've successfully made some objects initialized asynchronously in making my games, esp. when I need to make as many objects as being initialized in a performant way along with lots of lazy objects.

initializers can do asynchronous but the problem here is 'self' in the escaping closure of the following can not be modified.

init(reference: DocumentReference) {
    FirestoreService().fetchDocument(documentReference: reference) {    
        (request: RequestModel) in
         self.request = request
}

The closure in the initializer in detail is

{        
    @escaping [unowned self] (request: RequestModel) in

        self.request = request
}

Therefore, 'self' in the closure will outlive the function. And the code above seems that it is part of View type, created as structure.

From https://docs.swift.org/swift-book/LanguageGuide/Closures.html ,

"If self is an instance of a structure or an enumeration, you can always refer to self implicitly. However, an escaping closure can't capture a mutable reference to self when self is an instance of a structure or an enumeration. Structures and enumerations don't allow shared mutability, as discussed in Structures and Enumerations Are Value Types."

Therefore, the 'self' can not be mutable.

It is why your code is getting the error messgage:" Escaping closure captures mutating 'self' parameter".

A better solution to this is moving the part to another place inside of the modifier like 'didAppear()'.

Because the questioner didn't provide more of his or her codes here. I can't provide a more specific answer here.

This can probably be divided into two different questions:

1: properties of Struct can't be altered in Closures like 'didSet' or 'willSet'.

2: Only Observable struct that has been declared with @ObservedObject or @EnvironmentObject .etc can call UI to update while the properties is altered. Because such modifier give property special getter and setter functions

I was about to post a question regarding exactly this issue. With the answer from @Sungwood Kim, I understand now that I actually try to change the value-type (struct) from within itself while not knowing when this will happen (async).

So, if you need such a construct like:

  1. A struct where its content is read asynchronously (your case "Firestone", mine is "HealthKit")
  2. Within the struct, a mutating function handles the asynchron read and wants to update itself with the received data.

Then the workaround moves the update to be done to the outside: Move the update code into a closure and call the closure within the reading function.

Here's some code:

struct Value {
    var localData: Int = 0

    mutating func thisOneWorks(_ completion: @escaping () -> Void) {
        DispatchQueue.main.async { completion() }
    }

    // Cannot compile the following function: "Escaping closure captures mutating 'self' parameter"
    mutating func thisOneDoesNotWork(_ value: Int) {
        DispatchQueue.main.async { self.localData = value }
    }
 }

Where completion contains the updating code:

    var value = Value()
    value.thisOneWorks { value.localData = 4711 }

Note: The updating code might be big and complex. You can put it into a synchronously executed, mutating function and have the completion-closure call it. With this, the updating code is encapsulated within the struct.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM