Swift中的弱观察者集合

6
我将尝试实现一种结构,使我能够存储一组弱观察者。以下是观察者包装器:
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本身的限制,但我正在寻找一种没有具体类作为类型限制的替代解决方案(如果不可能,我很担心这种情况,我仍然希望得到替代的“非hacky”解决方案)。


1
值得一看 https://gist.github.com/preble/13ab713ac044876c89b5 - Kirsteins
@Kirsteins 那看起来确实很像我要找的东西,但它似乎对我来说有点过度设计(没有明显的原因)。 - the_critic
1
即使使用了WeakSet,我还是会得到错误信息“不支持将协议用作符合协议的具体类型”。 - the_critic
所以真正寻找一种替代方法来完成它,因为协议不支持作为通用约束。 - the_critic
3个回答

2
使用Set来保存引用存在风险,因为Set最终可能需要使用元素的hashValue引用,当弱引用变成nil时,hashValue函数会崩溃。
我无法使用协议完成此操作,但我找到了一种通过返回函数元组的通用函数来实现类似功能的方法。
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({ $0.reference != nil }).map({$0.reference!}) 
   }

   func addObject(object:T)
   { 
      if getObjects().contains({ ($0 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()
}

这两个函数都是类型安全的,只接受/返回符合DataModelObserverProtocol协议的对象。


这种类型转换是在编译时处理的,仅告诉编译器我们知道变量是兼容类型。 - Alain T.
唯一的开销是为存储而额外占用了一些“单词”内存空间,并在函数调用中增加了一个间接引用。这对性能几乎没有任何实质性影响。 - Alain T.
我认为每个 as? 语句都会被编译成 if 指令。 - kelin

2

我也遇到了同样的问题,并想出了这个令人惊讶的简单解决方案。从一个基本的弱容器开始:

class WeakObserverBox {
    weak var unbox: Observer?

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

将您的存储声明为字典,并使用唯一的ObjectIdentifier作为键。接下来是一个方便的计算属性(该属性仅包含在调用时仍然存活的观察者):

var observerDict: [ObjectIdentifier: WeakObserverBox] = [:]
var observers: [Observer] {
    return observerDict.values.compactMap { $0.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)
    }
}

相当干净和安全的方法,我喜欢它。 - Sonastra

0

我知道有更好的方法来做这件事,但对于如此简单的概念来说,它似乎太复杂了,所以我选择了一种不同的方法:

我只是使用了 NSNotificationCenter 并重构了我的代码,使其不必具有紧密耦合的结构,因此,我成功地将信息抽象化,使得通知不需要向其观察者传递参数。由于通知只支持通过 userInfo 字典传递信息,我被迫围绕这个思考。

因此,正如我所提到的,我需要一种上下文解耦的方法来实现它。 这是一个非常抽象的答案,但我认为它可能有助于某人避免处理“功能障碍泛型”的问题。

要通知我的观察者,我只需注册它们:

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

无需参数即可发布通知:

NSNotificationCenter.defaultCenter().postNotification...

我之前在其他项目中已经使用过内置的NSNotificationCenter,并且我非常清楚它的内部工作原理,但最初我打算将信息传递给观察者,这就是为什么我想要创建一种类似于WeakSet的限制性方法。

再次强调,这在我的情况下起作用,因为我的观察者不一定需要传递可观察对象的具体信息,因为通常情况下观察者可以直接访问和获取(这是我的结构提供的,读者的情况可能不同)。很难解释清楚,以便任何人都能理解,但是我对建议使用WeakSet感到非常不鼓舞,所以我选择了这种方法。它更易读(尽管对第三方来说可能不太容易理解),并且对于我的用例来说似乎更合适。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接