skip to Main Content

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

enter image description here

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?

2

Answers


  1. Chosen as BEST ANSWER

    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
            }
        }
    }
    
    

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

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search