Swift中用RunLoop实现弱引用对象nil监测是否正确?求替代方案
问题解答
原RunLoop用法不符合需求,原因如下:
- RunLoop实例无法持续运行:你手动创建的
RunLoop实例没有绑定到任何线程,调用run(mode: .default, before: .now)会因超时时间为当前时间而立即退出,后续的perform闭包根本不会执行。 - 一次性执行无法满足持续监测:就算RunLoop能正常运行,
perform的闭包也只会执行一次,无法在弱引用对象被释放时及时触发清理操作。 - 代码存在笔误:
self?.state.listeners应为self?.listeners,否则会访问不存在的state属性。
更合适的方案
方案1:给WeakContainer添加销毁回调(精准触发,推荐)
如果可以修改WeakContainer的实现,给它添加对象销毁时的回调,能在监听对象被释放的瞬间就移除对应条目:
// 修改WeakContainer class WeakContainer<T: AnyObject> { weak var value: T? private var onDeinit: (() -> Void)? init(value: T, onDeinit: (() -> Void)? = nil) { self.value = value self.onDeinit = onDeinit } deinit { onDeinit?() } } // 修改Process类 class Process<S> { typealias Handler = (S) -> () typealias Listener = (WeakContainer<AnyObject>, Handler) var listeners: [Listener] = [] func addListener(_ object: AnyObject, handler: @escaping Handler) { let container = WeakContainer(value: object) { [weak self] in // 监听对象被释放时,移除对应listener self?.listeners.removeAll { $0.0 === container } } listeners.append((container, handler)) } }
方案2:触发回调前自动清理(无需修改WeakContainer)
如果不能修改WeakContainer,可以在每次通知监听者的时机(比如更新状态时),先清理无效条目:
class Process<S> { typealias Handler = (S) -> () typealias Listener = (WeakContainer<AnyObject>, Handler) var listeners: [Listener] = [] func updateState(_ newState: S) { // 先移除已释放对象的监听 listeners.removeAll { $0.0.value == nil } // 通知所有有效监听 listeners.forEach { $0.1(newState) } } }
方案3:用NSMapTable自动管理(最简洁)
直接使用Foundation提供的NSMapTable,它支持弱引用key,当key对应的对象被释放时会自动移除对应条目,无需手动管理:
class Process<S> { typealias Handler = (S) -> () // 弱引用key(监听对象),强引用handler private let listeners = NSMapTable<AnyObject, Handler>.weakToStrongObjects() func addListener(_ object: AnyObject, handler: @escaping Handler) { listeners.setObject(handler, forKey: object) } func updateState(_ newState: S) { // 遍历所有有效handler并触发 guard let enumerator = listeners.objectEnumerator() else { return } while let handler = enumerator.nextObject() as? Handler { handler(newState) } } }
内容的提问来源于stack exchange,提问作者Ihor M.
相关产品推荐
相关产品推荐

