首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用"Boxing"-style的多个绑定和处理

使用"Boxing"-style的多个绑定和处理
EN

Stack Overflow用户
提问于 2018-05-02 13:22:02
回答 1查看 941关注 0票数 9

这是一个非常具体而又冗长的问题,但我还不够聪明,无法自己解决。

我对来自raywenderlich.com的raywenderlich.com非常感兴趣,它使用“装箱”方法来观察值。

他们的Box看起来如下:

代码语言:javascript
复制
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)
    }
}

很明显,每个“框”只允许一个侦听器。

代码语言:javascript
复制
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做了如下修改:

代码语言:javascript
复制
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来解决这个问题。

代码语言:javascript
复制
func freshBind(listener:Listener?){
    self.listeners.removeAll()
    self.bind(listener)
}

这在某种程度上起了作用,我现在可以随时使用freshBind({})来删除旧的侦听器,但这也不是我想要的。在观察来自UITableViewCell的值时,我必须使用这个值,但我也需要从其他地方观察到相同的值。一旦一个细胞被重复使用,我就移除了旧的观察者和我需要的其他观察者。

我现在有信心,我需要一个方法来保留一个一次性的物体,无论我以后想要处理它们。

我还不够聪明自己解决这个问题,所以我需要帮助。

我几乎没有使用过一些反应式编程框架(比如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'

有什么明智的建议吗?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-05-02 13:55:58

我喜欢解决这个问题的方法是给每个观察者一个唯一的标识符(如UUID),并使用它来允许Disposable在时间允许时移除观察者。例如:

代码语言:javascript
复制
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这样的概念。

代码语言:javascript
复制
// 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吗?”

票数 16
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/50135742

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档