简体   繁体   中英

Escaping closure captures mutating 'self' parameter: struct

I have the following code I'm using with SwiftUI:

import Foundation

public struct Trigger {
    public var value = false

    public mutating func toggle() {
        value = true
        let responseDate = Date().advanced(by: 3)

        OperationQueue.main.schedule(after: .init(responseDate)) {
            moveBack()
        }
    }

    private mutating func moveBack() {
        value = false
    }
}

However, I'm getting an error:

Escaping closure captures mutating 'self' parameter

在此处输入图像描述

I understand that changing the struct to a class would solve this issue, but is there any way to actually capture a mutating self in an escaping closure in a struct?

As you have found, the quick solution is to use a reference type , a class . But why is this the case?

Swift structs are value types, so they are immutable. You can mark a function as mutating to indicate to the compiler that a function mutates the struct, but what does that actually mean?

Consider a simple struct:

struct Counter {
   var count

   init(_ count: Int = 0)
   {
       self.count = count
   }

   mutating func increment() {
       self.count+=1
   }
}

Now, try and assign an instance of this to a let constant:

let someCounter = Counter()
someCounter.increment()
print(someCounter.count)

You will get an error; you need to use a var .

var someCounter = Counter()
someCounter.increment()
print(someCounter.count)

What actually happens when you call a mutating func is that a new Counter is created, with the new count and it is assigned to someCounter . It is effectively saying someCounter = Counter(someCounter.count+1)

Now, think what would happen if you could mutate self in an escaping closure - That new Counter is going to be created at some unspecified time in the future, but execution has already moved on. It is too late to update someCounter .

The other advantage of using a class , as you have found, is that you can use ObservableObject , which makes updating your SwiftUI views much easier.

Solution I finished with:

import Foundation
import Combine

public final class IntervalTrigger: ObservableObject {
    private let timeInterval: TimeInterval
    @Published var value = false

    public init(_ timeInterval: TimeInterval) {
        self.timeInterval = timeInterval
    }

    public func toggle() {
        value = true
        let responseDate = Date().advanced(by: timeInterval)
        OperationQueue.main.schedule(after: .init(responseDate)) { [weak self] in
            self?.value = false
        }
    }
}

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