Swift 中的一组弱观察者

Set of weak observers in Swift

我正在尝试实现一个允许我存储一组弱观察者的结构。

这是观察者包装器:

public func ==<T: Hashable>(lhs: WeakObserver<T>, rhs: WeakObserver<T>) -> Bool {
  return lhs.hashValue == rhs.hashValue
}

public struct WeakObserver<T where T: AnyObject, T: Hashable> : Hashable {

  private weak var weakObserver : T?

  public init(weakObserver: T){
    self.weakObserver = weakObserver
  }

  public var hashValue : Int {
    return self.weakObserver!.hashValue
  }

}

这是每个观察者都需要遵守的协议:

public protocol DataModelObserverProtocol : class, Hashable, AnyObject {

  func someFunc()

}

用法:

public class DataModel: NSObject, DataModelInterface {

  public var observers = Set<WeakObserver<DataModelObserverProtocol>>()
  //^^^^^ Using 'DataModelObserverProtocol' as a concrete type conforming to protocol 'AnyObject' is not supported
}

现在,虽然我知道这可能是 Swift 本身的限制,但我正在寻找一个没有具体 class 作为类型约束的替代解决方案(如果那不可能,恐怕是这样,我仍然希望获得替代 "non-hacky" 解决方案)。

使用 Set 来保存引用存在一个风险,即 Set 最终将需要使用其 hashValue 来引用一个元素,并且当弱引用变为 nil 时,hashValue 函数将崩溃。

我无法使用协议来做到这一点,但我找到了一种使用通用函数来获得类似功能的方法,该通用函数 returns 一个函数元组。

struct WeakReference<T>
{
    weak var _reference:AnyObject?
    init(_ object:T) {_reference = object as? AnyObject}
    var reference:T? { return _reference as? T }
}

func weakReferences<T>(_:T.Type) -> ( 
                                     getObjects: ()->[T],
                                     addObject:  (T)->()
                                    )
{  
   var references : [WeakReference<T>] = []

   func getObjects() -> [T]
   { 
      return references.filter({ [=10=].reference != nil }).map({[=10=].reference!}) 
   }

   func addObject(object:T)
   { 
      if getObjects().contains({ ([=10=] as! AnyObject) === (object as! AnyObject) }) 
      { return }
      references.append(WeakReference(object)) 
   } 

  return (getObjects:getObjects, addObject:addObject)   
}

public protocol DataModelObserverProtocol: class, AnyObject
{
   func someFunc() -> String
}

public class DataModel: NSObject, DataModelInterface  
{
   var observers = weakReferences(DataModelObserverProtocol)
}

要添加观察者,您可以使用:

observers.addObject( yourObserver )

遍历观察者:

for observer in observers.objects()
{
   observer.someFunc()
}

这两个函数都是类型安全的,并且只会 accept/return DataModelObserverProtocol 兼容的对象

我知道会有更好的方法来做到这一点,但对于这样一个简单的概念来说它似乎太复杂了,所以我采用了不同的方法:

我刚刚使用 NSNotificationCenter 并重构了我的代码,使其不必具有紧密耦合的结构,因此我没有从通知中传递信息,而是设法以一种适合我的方式将其抽象化通知不需要将参数传递给它们的观察者的方式。由于通知只支持通过 userInfo 字典传递信息,我不得不考虑这个问题。

因此,正如我提到的,我需要一种 context-decoupled 的方式来完成它。 这是一个非常抽象的答案,但我认为它可能会帮助某些人规避必须弄乱 "dysfunctional generics" 的问题。

为了通知我的观察者,我只是注册了他们:

NSNotificationCenter.defaultCenter().addObserver(...)

和 post 没有参数的通知:

NSNotificationCenter.defaultCenter().postNotification...

我在以前的项目中已经使用过built-in NSNotificationCenter,我完全了解它的内部工作原理,但本来我打算将信息传递给观察者,这就是我想要的原因限制它多一点创建一个 WeakSet 的种类。

这在我的案例中再次起作用,因为我的观察者不一定需要通过观察者的细节,因为它通常存在并且观察者无论如何都可以访问(这是我的结构提供的东西,它可能不是那样的在 reader 的情况下)。很难对其进行解释,以便让阅读本文的任何人都能理解,但我对提议的 WeakSet 感到非常沮丧,所以我接受了这个。它更具可读性(虽然第三方可能不太容易理解)并且它似乎更适合我的 use-case。

我遇到了同样的问题,并提出了这个非常简单的解决方案。从一个简陋的脆弱容器开始:

class WeakObserverBox {
    weak var unbox: Observer?

    init(_ observer: Observer) {
        self.unbox = observer
    }
}

将您的存储声明为字典,并使用唯一的 ObjectIdentifier 作为键。一个方便的计算 属性 如下(那个将只包含在调用时还活着的观察者):

var observerDict: [ObjectIdentifier: WeakObserverBox] = [:]
var observers: [Observer] {
    return observerDict.values.compactMap { [=11=].unbox }
}

对所有观察者的操作都是通过 observers 数组完成的。增删如下:

func add(_ observer: O) {
    observerDict[ObjectIdentifier(observer)] = WeakObserverBox(observer)
}

func remove(_ observer: Observer) {
    observerDict.removeValue(forKey: ObjectIdentifier(observer))
}

顺便说一句,如果你需要修剪过期的引用,这可以在这个 getter 中完成,或者在 add 方法中(或两者,取决于你的性能考虑),像这样:

for observerKey in observerDict.keys {
    if observerDict[observerKey]?.unbox == nil {
        observerDict.removeValue(forKey: observerKey)
    }
}