iOS自动移除KVO观察者的实现方法

2020-01-21 03:07:20丽君

尝试 unsafe_unretained

通过上面操作, 我们知道self在被释放之前, 会先释放其持有的关联属性, self并未完全释放, 可在临时对象中target却成了nil. 同时self还是有效的, 那如何保持不为nil呢?

我们看看OC中的两个修饰符weak与unsafe_unretained:

weak: 持有者不会对目标进行retain, 当目标销毁时, 持有者的实例变量会被置空 unsafe_unretained: 持有者不会对目标进行retain, 当目标释放后, 持有者的实例变量还会依然指向之前的内存空间(野指针)

由上, unsafe_unretained很好的解决了我们的问题. 于是我做了如下修改:


@interface SJObserverHelper : NSObject
@property (nonatomic, unsafe_unretained) id target;
@property (nonatomic, unsafe_unretained) id observer;
@property (nonatomic, strong) NSString *keyPath;
@end

再次运行程序, 还行, 观察者移除了.

最终实现

还存在的问题

目前, 我们只是实现了, 如何在self释放的时候, 移除自己身上的Observer.

但如果Observer提前释放了呢?

而添加关联属性, 两者还不能同时持有临时对象, 否则临时对象也不会及时的释放.

好吧, 既然一个不行, 那就各自关联一个:


- (void)addObserver {
 .....  
 SJObserverHelper *helper_obj = [SJObserverHelper new];
 SJObserverHelper *sub_obj = [SJObserverHelper new];
 sub_obj.target = helper_obj.target = _xiaoM;
 sub_obj.observer = helper_obj.observer = _observer;
 sub_obj.keyPath = helper_obj.keyPath = keyPath;
 const char *helpeKey = [NSString stringWithFormat:@"%zd", [_observer hash]].UTF8String;
 // 关联
 objc_setAssociatedObject(_xiaoM, helpeKey, helper_obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
 // 关联
 objc_setAssociatedObject(_observer, helpeKey, sub_obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

如上, 仔细想想, 存在一个很明显的问题, 两个关联属性释放的同时, 进行了两次观察移除的操作. 为避免这个问题, 我又做了如下修改:


@interface SJObserverHelper : NSObject
@property (nonatomic, unsafe_unretained) id target;
@property (nonatomic, unsafe_unretained) id observer;
@property (nonatomic, strong) NSString *keyPath;
@property (nonatomic, weak) SJObserverHelper *factor; // 1. 新增一个 weak 变量
@end

@implementation SJObserverHelper
- (void)dealloc {
 if ( _factor ) {
  [_target removeObserver:_observer forKeyPath:_keyPath];
 }
}
@end

- (void)addObserver {
 ..... 
 SJObserverHelper *helper_obj = [SJObserverHelper new];
 SJObserverHelper *sub_obj = [SJObserverHelper new];
 sub_obj.target = helper_obj.target = _xiaoM;
 sub_obj.observer = helper_obj.observer = _observer;
 sub_obj.keyPath = helper_obj.keyPath = keyPath;
 // 2. 互相 weak 引用
 helper_obj.factor = sub_obj; 
 sub_obj.factor = helper_obj;
 const char *helpeKey = [NSString stringWithFormat:@"%zd", [_observer hash]].UTF8String;
 // 关联
 objc_setAssociatedObject(_xiaoM, helpeKey, helper_obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
 // 关联
 objc_setAssociatedObject(_observer, helpeKey, sub_obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}