0

假设我有一个 Swift 类,它存储一个完成块,并执行一些异步任务。

我希望该块由首先完成的任务中的任何一个调用,但只有那个 - 我不希望在第二个任务完成时再次调用它。

我怎样才能以干净的方式实现这一点?

4

2 回答 2

0

曾经有一个标准的表达方式。不幸的是,标准的 Objective-C 在 Swift (GCD) 中不可用dispatch_once,但标准的 Swift 技术可以正常工作,即具有惰性定义和调用初始化程序的属性。

具体如何执行此操作取决于您希望强制执行的级别。在此示例中,它位于类实例级别:

class MyClass {
    // private part
    private let completion : (() -> ())
    private lazy var once : Void = {
        self.completion()
    }()
    private func doCompletionOnce() {
        _ = self.once
    }
    // public-facing part
    init(completion:@escaping () -> ()) {
        self.completion = completion
    }
    func doCompletion() {
        self.doCompletionOnce()
    }
}

在这里我们将对其进行测试:

    let c = MyClass() {
        print("howdy")
    }
    c.doCompletion() // howdy
    c.doCompletion()
    let cc = MyClass() {
        print("howdy2")
    }
    cc.doCompletion() // howdy2
    cc.doCompletion()

如果将私有内容提升到类的级别(使用静态once属性),则在整个程序的生命周期中只能执行一次完成。

于 2020-02-28T02:41:28.070 回答
0

只要你不需要它是线程安全的,你可以用一个相当简单的@propertyWrapper.

@propertyWrapper
struct ReadableOnce<T> {
    var wrappedValue: T? {
        mutating get {
            defer { self._value = nil }
            return self._value
        }
        set {
            self._value = newValue
        }
    }
    private var _value: T? = nil
}

用标记完成块var@ReadableOnce它会在第一次读取它的值后被销毁。

像这样的东西:

class MyClass {
    @ReadableOnce private var completion: ((Error?) -> Void)?

    init(completion: @escaping ((Error?) -> Void)) {
        self.completion = completion
    }

    public func doSomething() {
        // These could all be invoked from different places, like your separate tasks' asynchronous callbacks
        self.completion?(error) // This triggers the callback, then the property wrapper sets it to nil.
        self.completion?(error) // This does nothing
        self.completion?(error) // This does nothing
    }
}

我在这里写了更多详细的讨论,但要注意的关键是读取值会将其设置为 nil,即使您不调用闭包!对于不熟悉您编写的聪明的属性包装器的人来说,这可能会让他们感到惊讶。

于 2020-02-28T02:07:07.237 回答