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?
CodePudding user response:
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.
CodePudding user response:
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
}
}
}

