Home > Enterprise >  Escaping closure captures mutating 'self' parameter: struct
Escaping closure captures mutating 'self' parameter: struct

Time:01-10

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?

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

  •  Tags:  
  • Related