使用 "Boxing" 风格的多重绑定和处置
Multiple bindings and disposal using "Boxing"-style
这是一个非常具体和长的问题,但我不够聪明,无法自己解决..
我对 raywenderlich.com 中的 this YouTube-video 非常感兴趣,它使用 "boxing" 方法来观察一个值。
他们的 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)
}
}
很明显,每个 "box".
只允许 一个 侦听器
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({[=13=]?(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),现在我明白了为什么他们的订阅 return Disposable
我必须保留和处理的对象当我需要的时候。我需要这个功能。
我想要的是:func bind(listener:Listener?)->Disposable{}
我的计划是创建一个名为 Disposable
的 class,其中包含(可选的)侦听器,并将 listeners:[Listener?]
变成 listeners:[Disposable]
,但我 运行 进入 <T>
..
的问题
Cannot convert value of type 'Box<String?>.Disposable' to expected argument type 'Box<Any>.Disposable'
有什么明智的建议吗?
我喜欢解决这个问题的方法是给每个观察者一个唯一的标识符(如 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 { [=10=](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
。然后你意识到你想要过滤值被设置为其旧值的情况,所以你构建了一个 "only send me distinct values" 然后你意识到你想要 filter
来帮助你,然后......
你意识到你正在重建 RxSwift。
这根本不是避免自己编写的理由,RxSwift 包含许多项目永远不需要的功能(可能是太多的功能),但在进行过程中,您应该不断问自己 "should I just switch to RxSwift now?"
这是一个非常具体和长的问题,但我不够聪明,无法自己解决..
我对 raywenderlich.com 中的 this YouTube-video 非常感兴趣,它使用 "boxing" 方法来观察一个值。
他们的 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)
}
}
很明显,每个 "box".
只允许 一个 侦听器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({[=13=]?(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),现在我明白了为什么他们的订阅 return Disposable
我必须保留和处理的对象当我需要的时候。我需要这个功能。
我想要的是:func bind(listener:Listener?)->Disposable{}
我的计划是创建一个名为 Disposable
的 class,其中包含(可选的)侦听器,并将 listeners:[Listener?]
变成 listeners:[Disposable]
,但我 运行 进入 <T>
..
Cannot convert value of type 'Box<String?>.Disposable' to expected argument type 'Box<Any>.Disposable'
有什么明智的建议吗?
我喜欢解决这个问题的方法是给每个观察者一个唯一的标识符(如 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 { [=10=](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
。然后你意识到你想要过滤值被设置为其旧值的情况,所以你构建了一个 "only send me distinct values" 然后你意识到你想要 filter
来帮助你,然后......
你意识到你正在重建 RxSwift。
这根本不是避免自己编写的理由,RxSwift 包含许多项目永远不需要的功能(可能是太多的功能),但在进行过程中,您应该不断问自己 "should I just switch to RxSwift now?"