使用@propertyWrapper实现UserDefaults遇异常:观察者提前释放
问题分析与解决方案
嗨,这个问题其实是观察者实例没有被强引用导致提前销毁,我来一步步帮你解决:
核心原因
你在viewWillAppear里调用AppData.$layout.observe后,返回的DefaultsObservation实例没有被任何变量持有——它只是个临时对象,当viewWillAppear方法执行完毕,ARC就会立刻回收这个实例,触发deinit方法移除观察者,自然后续的layout属性变化就无法被监听了。
而你注释掉deinit里的移除代码后,虽然观察者实例被销毁了,但UserDefaults还保留着对它的KVO引用,这会导致野指针崩溃风险(当UserDefaults发送通知时,会尝试访问已经释放的对象),绝对不能这么做。
正确解决方案
只需要在ViewController中强持有观察者实例,就能避免它提前被销毁,同时保证deinit能在合适的时机执行(移除观察者,避免内存泄漏)。
步骤1:修改ViewController,添加持有属性
在你的ViewController里声明一个属性来保存观察者实例:
class YourViewController: UIViewController { // 用这个属性强持有观察者,避免提前销毁 private var layoutObservation: DefaultsObservation? override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) // 将observe返回的实例赋值给属性,保持强引用 layoutObservation = AppData.$layout.observe { old, new in print("Layout变化:旧值\(old ?? "空"),新值\(new ?? "空")") } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) // 可选:手动置空,提前触发deinit移除观察者(也可以等ViewController销毁时自动释放) layoutObservation = nil } }
步骤2:优化Storage的observe方法返回类型
把observe方法的返回值从NSObject改成DefaultsObservation,让类型更明确安全:
func observe(change: @escaping (T?, T?) -> Void) -> DefaultsObservation { return DefaultsObservation(key: key) { old, new in change(old as? T, new as? T) } }
为什么这样有效?
- 现在
DefaultsObservation实例被ViewController的layoutObservation属性强引用,只要ViewController存在,观察者就不会被销毁,监听就能正常工作。 - 当ViewController被销毁时,
layoutObservation会被自动释放,触发DefaultsObservation的deinit方法,正确移除UserDefaults的观察者,既不会有内存泄漏,也不会有野指针风险。
额外优化建议
如果你使用的是iOS 13+,可以考虑用Combine框架来替代KVO,代码会更简洁且更安全:
// 修改Storage的projectedValue返回一个Publisher var projectedValue: AnyPublisher<T, Never> { UserDefaults.standard.publisher(for: \.self[key], options: [.old, .new]) .map { $0 as? T ?? defaultValue } .eraseToAnyPublisher() } // 然后在ViewController里监听: private var cancellables = Set<AnyCancellable>() override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) AppData.$layout .sink { [weak self] newValue in // 处理变化 } .store(in: &cancellables) }
不过这是可选方案,如果你想继续用KVO的话,前面的解决方案已经完全能解决你的问题啦~
内容的提问来源于stack exchange,提问作者atalayasa
相关产品推荐
相关产品推荐

