Многократные привязки и утилизация в стиле «Бокс» - PullRequest
0 голосов
/ 02 мая 2018

Это очень конкретный и длинный вопрос, но я не достаточно умен, чтобы понять это самостоятельно.

Я был очень заинтригован этим YouTube-видео от raywenderlich.com, которое использует метод "бокса" для наблюдения значения.

Их Box выглядит так:

class Box<T> {
    typealias Listener = T -> Void
    var listener: Listener?

    var value: T {
        didSet {
            listener?(value)
        }
    init(_ value: T) {
        self.value = value
    }
    func bind(listener: Listener?) {
        self.listener = listener
        listener?(value)
    }
}

Очевидно, что только «1011» * один слушатель разрешен для «ящика».

let bindable:Box<String> = Box("Some text")
bindable.bind { (text) in
    //This will only be called once (from initial setup)
}
bindable.bind { (text) in
    // - because this listener has replaced it. This one will be called when value changes.
}

Всякий раз, когда устанавливается такая привязка, предыдущие привязки удаляются, поскольку Box заменяет listener новым слушателем.

Мне нужно иметь возможность наблюдать одно и то же значение из разных мест. Я переделал Box так:

class Box<T> {
    typealias Listener = (T) -> Void
    var listeners:[Listener?] = []

    var value:T{
        didSet{
            listeners.forEach({$0?(value)})
        }
    }
    init(_ value:T){
        self.value = value
    }
    func bind(listener:Listener?){
        self.listeners.append(listener)
        listener?(value)
    }
}

Однако - это также доставляет мне неприятности, очевидно .. Есть места, где я хочу новое связывание, чтобы удалить старое связывание. Например, если я наблюдаю значение в объекте из многократно используемого UITableViewCell, оно будет связано несколько раз при прокрутке. Теперь мне нужен контролируемый способ избавиться от определенных привязок.

Я попытался решить эту проблему, добавив эту функцию в Box:

func freshBind(listener:Listener?){
    self.listeners.removeAll()
    self.bind(listener)
}

Это сработало, теперь я мог использовать freshBind({}) всякий раз, когда хотел удалить старых слушателей, но это не совсем то, чего я хочу. Я должен был бы использовать это при наблюдении значения от UITableViewCell s, но мне также нужно наблюдать то же значение из другого места. Как только камера использовалась повторно, я удалил старых наблюдателей , а также других наблюдателей, которые мне были нужны.

Теперь я уверен, что мне нужен способ сохранить одноразовый предмет там, где я впоследствии захочу его утилизировать.

Я не достаточно умен, чтобы решить это самостоятельно, поэтому мне нужна помощь.

Я едва использовал некоторые из сред реактивного программирования (например, ReactiveCocoa), и теперь я понимаю, почему их подписки возвращают Disposable объекты, которые я должен сохранять и распоряжаться ими, когда мне нужно. Мне нужен этот функционал.

Я хочу вот что: func bind(listener:Listener?)->Disposable{}

Я планировал создать класс с именем Disposable, который содержал (необязательный) слушатель, и превратить listeners:[Listener?] в listeners:[Disposable], но у меня возникли проблемы с <T> ..

Cannot convert value of type 'Box<String?>.Disposable' to expected argument type 'Box<Any>.Disposable'

Какие-нибудь умные предложения?

1 Ответ

0 голосов
/ 02 мая 2018

Я хотел бы решить эту проблему, чтобы дать каждому наблюдателю уникальный идентификатор (например, UUID) и использовать его, чтобы позволить Disposable удалить наблюдателя, когда пришло время. Например:

import Foundation

// A Disposable holds a `dispose` closure and calls it when it is released
class Disposable {
    let dispose: () -> Void
    init(_ dispose: @escaping () -> Void) { self.dispose = dispose }
    deinit { dispose() }
}

class Box<T> {
    typealias Listener = (T) -> Void

    // Replace your array with a dictionary mapping
    // I also made the Observer method mandatory. I don't believe it makes
    // sense for it to be optional. I also made it private.
    private var listeners: [UUID: Listener] = [:]

    var value: T {
        didSet {
            listeners.values.forEach { $0(value) }
        }
    }

    init(_ value: T){
        self.value = value
    }

    // Now return a Disposable. You'll get a warning if you fail
    // to retain it (and it will immediately be destroyed)
    func bind(listener: @escaping Listener) -> Disposable {

        // UUID is a nice way to create a unique identifier; that's what it's for
        let identifier = UUID()

        // Keep track of it
        self.listeners[identifier] = listener

        listener(value)

        // And create a Disposable to clean it up later. The Disposable
        // doesn't have to know anything about T. 
        // Note that Disposable has a strong referene to the Box
        // This means the Box can't go away until the last observer has been removed
        return Disposable { self.listeners.removeValue(forKey: identifier) }
    }
}

let b = Box(10)

var disposer: Disposable? = b.bind(listener: { x in print(x)})
b.value = 5
disposer = nil
b.value = 1

// Prints:
// 10
// 5
// (Doesn't print 1)

Это может быть расширено до таких понятий, как DisposeBag:

// Nothing but an array of Disposables.
class DisposeBag {
    private var disposables: [Disposable] = []
    func append(_ disposable: Disposable) { disposables.append(disposable) }
}

extension Disposable {
    func disposed(by bag: DisposeBag) {
        bag.append(self)
    }
}

var disposeBag: DisposeBag? = DisposeBag()

b.bind { x in print("bag: \(x)") }
    .disposed(by: disposeBag!)

b.value = 100
disposeBag = nil
b.value = 500

// Prints:
// bag: 1
// bag: 100
// (Doesn't print "bag: 500")

Приятно создавать некоторые из этих вещей самостоятельно, чтобы вы поняли, как они работают, но для серьезных проектов этого часто не достаточно. Основная проблема заключается в том, что он не является поточно-ориентированным, поэтому, если что-то удаляется в фоновом потоке, вы можете повредить всю систему. Конечно, вы можете сделать его поточно-ориентированным, и это не так сложно.

Но потом вы понимаете, что действительно хотите сочинять слушателей. Вам нужен слушатель, который наблюдает за другим слушателем и преобразует его. Итак, вы строите map. И затем вы понимаете, что хотите отфильтровать случаи, когда значение было установлено на его старое значение, поэтому вы строите «отправляете мне только отдельные значения», и затем вы понимаете, что хотите, чтобы filter помог вам там, а затем ...

вы понимаете, что восстанавливаете RxSwift.

Это вовсе не причина для того, чтобы вообще не писать свои собственные, и RxSwift включает в себя массу функций (возможно, слишком много функций), которые многим проектам не нужны, но по мере продвижения вперед вы должны продолжать задавать себе вопрос: «Должен ли я просто переключиться на RxSwift сейчас? "

...