Как написать блок завершения Swift, который можно вызвать только один раз?

Допустим, у меня есть класс Swift, который хранит блок завершения и выполняет несколько асинхронных задач.

Я хочу, чтобы этот блок вызывался той из задач, которая завершается первой, но только этой - я не хочу, чтобы он вызывался снова, когда завершается вторая задача.

Как я могу реализовать это чистым способом?


person bkbeachlabs    schedule 28.02.2020    source источник
comment
Добавьте код, который вы уже пробовали.   -  person PGDev    schedule 28.02.2020
comment
Привет, @PGDev, я включил код в ответ ниже (ответил на свой вопрос :))   -  person bkbeachlabs    schedule 06.03.2020


Ответы (2)


Пока вам не нужно, чтобы это было потокобезопасным, вы можете решить эту проблему с помощью довольно простого @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
    }
}

Я написал более подробное обсуждение этого здесь, но главное, о чем следует помнить, это то, что чтение значения устанавливает его равным нулю, даже если вы не вызываете замыкание! Это может удивить тех, кто не знаком с умной оболочкой свойств, которую вы написали.

person bkbeachlabs    schedule 28.02.2020
comment
Я бы просто был осторожен с этим. В вашем случае ничего не может быть хорошо, но в целом вы, вероятно, захотите войти в систему, провалить тест и т. д., когда происходят более поздние вызовы. В противном случае вы можете столкнуться со странными задачами, выполняемыми в фоновом режиме, которые вы никогда не замечаете и которые потребляют память/батарею. - person Alexander; 28.02.2020
comment
Полностью согласен - это может не подходить для всех случаев (или любых, в зависимости от того, как вы к этому относитесь: D) - person bkbeachlabs; 28.02.2020
comment
Я не совсем понимаю, зачем нужна оболочка свойства. Уже существует стандартный встроенный способ выражения единообразия; почему бы просто не использовать его? - person matt; 28.02.2020
comment
@matt О, есть ли лучший способ, который я пропустил? Это, конечно, возможно - о чем вы говорите? - person bkbeachlabs; 28.02.2020
comment
См. stackoverflow.com/questions/37886994/, stackoverflow.com/questions/37801407/ и другие. - person matt; 28.02.2020

Уже есть стандартное выражение одноразовости. К сожалению, стандартный 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), завершение может быть выполнено только один раз за время жизни всей программы.

person matt    schedule 28.02.2020
comment
Интересная техника. ???? - person bandejapaisa; 03.12.2020