Swift 5如何对对象执行Selector 或如何实现存储协议类型的Set
方案1:使用类型擦除实现Set存储协议实例(更推荐,无基类要求)
你之前的Set<CustomObserver>写法不可行的原因是:CustomObserver继承了Hashable,而Hashable带有Self约束,这类带Self约束的协议不能直接作为泛型参数的类型使用,只能作为泛型约束。你不需要给所有观察者新增统一基类,只需要实现一个通用的类型擦除包装器即可:
protocol CustomObserver: Hashable { func receiveNotification(userInfo: [AnyHashable : Any]?) } struct AnyCustomObserver: CustomObserver { private let _receive: ([AnyHashable : Any]?) -> Void private let _hash: (inout Hasher) -> Void private let _equals: (Any) -> Bool init<T: CustomObserver>(_ observer: T) { _receive = observer.receiveNotification _hash = { hasher in observer.hash(into: &hasher) } _equals = { other in guard let other = other as? T else { return false } return observer == other } } func receiveNotification(userInfo: [AnyHashable : Any]?) { _receive(userInfo) } func hash(into hasher: inout Hasher) { _hash(&hasher) } static func == (lhs: AnyCustomObserver, rhs: AnyCustomObserver) -> Bool { lhs._equals(rhs) } }
修改后的存储结构如下:
private var notificationsMap: [String: Set<AnyCustomObserver>] = [:]
后续添加观察者时,把遵守CustomObserver协议的实例包进AnyCustomObserver再存入Set即可。观察者本身不需要继承任何基类,只要遵守CustomObserver协议就能正常使用。
方案2:修复当前Selector方案的报错
报错原因是AnyHashable是一个包装结构体,本身没有performSelector方法,你需要先取出它包装的原始值,转成AnyObject(类类型)再调用方法,同时建议加响应判断避免崩溃:
func post(name: String, object: Any?, userInfo: [AnyHashable : Any]?) { if let obsDict = notificationsMap[name] { for (observerWrapper, selector) in obsDict { // 取出AnyHashable包装的原始对象,转成类实例 guard let observer = observerWrapper.base as? AnyObject, observer.responds(to: selector) else { continue } // 如需传递userInfo可以把withObject参数换成userInfo observer.performSelector(onMainThread: selector, withObject: userInfo, waitUntilDone: true) } } }
注意这个方案依赖OC runtime,只能用于继承自NSObject的类实例,没有编译时类型校验,容易出现未识别选择器的崩溃,不如方案1安全。
内容的提问来源于stack exchange,提问作者Mark S
相关产品推荐
相关产品推荐

