KVOController源码阅读:一款好用的KVO管理库
系统KVO
- KVO 观察者模式的应用; 运用了isa混写技术。能监听属性的变化;响应式的代表。
- 释放是个问题,移除多次会闪退,内存泄漏导致dealloc没走,也会没释放。
- KVOController是为了解决这些问题。
- (void)bind:(BLMeetingItemModel *)model{@weakify(self);
[self.KVOController observe:model keyPath:FBKVOKeyPath(model.isOpenMicro) options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew block:^(id_Nullable observer, id_Nonnull object, NSDictionary * _Nonnull change) {
@strongify(self);
BOOL isOpenMicro = [change[@"new"] boolValue];
NSLog(@"%@-%@麦克风",model.nickName,isOpenMicro?@"开启":@"关闭");
[self.bottomView updateMicroStatus];
}];
[self.KVOController observe:model keyPath:FBKVOKeyPath(model.isOpenVideo) options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew block:^(id_Nullable observer, id_Nonnull object, NSDictionary * _Nonnull change) {
@strongify(self);
BOOL isOpenMicro = [change[@"new"] boolValue];
NSLog(@"%@-%@视频-%@",model.nickName,isOpenMicro?@"开启":@"关闭",NSStringFromClass(self.class));
[self loadVideoSource];
}];
[self.KVOController observe:model keyPath:FBKVOKeyPath(model.isHost) options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew block:^(id_Nullable observer, id_Nonnull object, NSDictionary * _Nonnull change) {
@strongify(self);
[self.bottomView updateView:model];
//更新主持人
}];
}- (void)unbind:(BLMeetingItemModel *)model{
[self.KVOController unobserve:model keyPath:FBKVOKeyPath(model.isOpenMicro)];
[self.KVOController unobserve:model keyPath:FBKVOKeyPath(model.isOpenVideo)];
[self.KVOController unobserve:model keyPath:FBKVOKeyPath(model.isHost)];
}
FBKVOKeyPath与FBKVOClassKeyPath
- keyPath直接使用字符,当修改属性时,编译时不会报错,而这两个宏能够在编译时检查出错误。
- FBKVOKeyPath针对一些简单场景,如上述model; 如果出现FBKVOKeyPath(self.lectureView.showType)就会出现异常,复杂情况可以采用FBKVOClassKeyPath(BLMeetingVCRotateControl, orientation)
//类名:NSObject (FBKVOController)
- (FBKVOController *)KVOController
{
id controller = objc_getAssociatedObject(self, NSObjectKVOControllerKey);
// lazily create the KVOController
if (nil == controller) {
controller = [FBKVOController controllerWithObserver:self];
self.KVOController = controller;
}return controller;
}- (void)setKVOController:(FBKVOController *)KVOController
{
objc_setAssociatedObject(self, NSObjectKVOControllerKey, KVOController, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
【KVOController源码阅读:一款好用的KVO管理库】FBKVOController关联对象存储的对象,方便使用。同时原对象释放,FBKVOController也会释放。
//FBKVOController
- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(FBKVONotificationBlock)block
{
NSAssert(0 != keyPath.length && NULL != block, @"missing required parameters observe:%@ keyPath:%@ block:%p", object, keyPath, block);
if (nil == object || 0 == keyPath.length || NULL == block) {
return;
}// create info
_FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];
// observe object with info
[self _observe:object info:info];
}- (void)_observe:(id)object info:(_FBKVOInfo *)info
{
// lock
pthread_mutex_lock(&_lock);
NSMutableSet *infos = [_objectInfosMap objectForKey:object];
// check for info existence
_FBKVOInfo *existingInfo = [infos member:info];
if (nil != existingInfo) {
// observation info already exists;
do not observe it again// unlock and return
pthread_mutex_unlock(&_lock);
return;
}// lazilly create set of infos
if (nil == infos) {
infos = [NSMutableSet set];
[_objectInfosMap setObject:infos forKey:object];
}// add info and oberve
[infos addObject:info];
// unlock prior to callout
pthread_mutex_unlock(&_lock);
[[_FBKVOSharedController sharedController] observe:object info:info];
}
- 每一个[observe:keyPath:options:block]生成一个_FBKVOInfo对象。_FBKVOInfo仅仅是一个model对象。
- 每个kvoController都有一个_objectInfosMap; 对应多个object,object是被观察者。
- 每个object都会生成一个NSMutableSet * infos,用来存放_FBKVOInfo对象。而生成的infos以object地址为key存储在_objectInfosMap。
- 如果_FBKVOInfo已存在,直接return返回。如不存在,则创建NSMutableSet * infos,infos 添加_FBKVOInfo * info,存储在_objectInfosMap中。
//_FBKVOSharedController
- (void)observe:(id)object info:(nullable _FBKVOInfo *)info
{
if (nil == info) {
return;
}// register info
pthread_mutex_lock(&_mutex);
[_infos addObject:info];
pthread_mutex_unlock(&_mutex);
// add observer
[object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];
if (info->_state == _FBKVOInfoStateInitial) {
info->_state = _FBKVOInfoStateObserving;
} else if (info->_state == _FBKVOInfoStateNotObserving) {
// this could happen when `NSKeyValueObservingOptionInitial` is one of the NSKeyValueObservingOptions,
// and the observer is unregistered within the callback block.
// at this time the object has been registered as an observer (in Foundation KVO),
// so we can safely unobserve it.
[object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
}
}- (void)observeValueForKeyPath:(nullable NSString *)keyPath
ofObject:(nullable id)object
change:(nullable NSDictionary *)change
context:(nullable void *)context
{
NSAssert(context, @"missing context keyPath:%@ object:%@ change:%@", keyPath, object, change);
_FBKVOInfo *info;
{
// lookup context in registered infos, taking out a strong reference only if it exists
pthread_mutex_lock(&_mutex);
info = [_infos member:(__bridge id)context];
pthread_mutex_unlock(&_mutex);
}if (nil != info) {// take strong reference to controller
FBKVOController *controller = info->_controller;
if (nil != controller) {// take strong reference to observer
id observer = controller.observer;
if (nil != observer) {// dispatch custom block or action, fall back to default action
if (info->_block) {
NSDictionary *changeWithKeyPath = change;
// add the keyPath to the change dictionary for clarity when mulitple keyPaths are being observed
if (keyPath) {
NSMutableDictionary *mChange = [NSMutableDictionary dictionaryWithObject:keyPath forKey:FBKVONotificationKeyPathKey];
[mChange addEntriesFromDictionary:change];
changeWithKeyPath = [mChange copy];
}
info->_block(observer, object, changeWithKeyPath);
} else if (info->_action) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[observer performSelector:info->_action withObject:change withObject:object];
#pragma clang diagnostic pop
} else {
[observer observeValueForKeyPath:keyPath ofObject:object change:change context:info->_context];
}
}
}
}
}
- _FBKVOSharedController是个单例类,真正的kvo实现都在这个类中实现。
- [observeValueForKeyPath:ofObject:change:context:]是kvo的回调,info = [_infos member:(__bridge id)context]; 找到info对象。
- 取出info对象中的FBKVOController *controller,观察者,block或action属性都在_FBKVOInfo *info对象中。对block和action进行回调。
//FBKVOController
- (void)dealloc
{
[self unobserveAll];
pthread_mutex_destroy(&_lock);
}- (void)_unobserveAll
{
// lock
pthread_mutex_lock(&_lock);
NSMapTable *objectInfoMaps = [_objectInfosMap copy];
// clear table and map
[_objectInfosMap removeAllObjects];
// unlock
pthread_mutex_unlock(&_lock);
_FBKVOSharedController *shareController = [_FBKVOSharedController sharedController];
for (id object in objectInfoMaps) {
// unobserve each registered object and infos
NSSet *infos = [objectInfoMaps objectForKey:object];
[shareController unobserve:object infos:infos];
}
}
//_FBKVOSharedController
- (void)unobserve:(id)object infos:(nullable NSSet<_FBKVOInfo *> *)infos
{
if (0 == infos.count) {
return;
}// unregister info
pthread_mutex_lock(&_mutex);
for (_FBKVOInfo *info in infos) {
[_infos removeObject:info];
}
pthread_mutex_unlock(&_mutex);
// remove observer
for (_FBKVOInfo *info in infos) {
if (info->_state == _FBKVOInfoStateObserving) {
[object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
}
info->_state = _FBKVOInfoStateNotObserving;
}
}
- FBKVOController是以关联对象的形式释放,原对象释放则FBKVOController释放,dealloc方法会执行。
- 在dealloc中调用了unobserveAll。
- 首先会[_objectInfosMap removeAllObjects],遍历objectInfoMaps移除_FBKVOSharedController的对象,移除系统的观察者。
- KVOController的[observe:keyPath:options:block]即使调用多次,系统也只添加一次kvo。即使_FBKVOInfo对象不同,keyPath相同,也算同一个观察者,在_FBKVOInfo中重写了isEqual方法。
- 使用系统kvo不用移除,不用担心释放问题,也不用担心移除多次操作闪退问题。如果原本观察者出现内存泄漏导致dealloc方法没有执行,需要读者自己检查原因了。
- 此库支持keyPaths,支持action和block回调,一个keyPath观察者一个block,比系统observeValueForKeyPath判断要清晰很多。
推荐阅读
- 考研英语阅读终极解决方案——阅读理解如何巧拿高分
- Ⅴ爱阅读,亲子互动——打卡第178天
- 上班后阅读开始变成一件奢侈的事
- 历史教学书籍
- 绘本讲师训练营【24期】14/21阅读原创《小黑鱼》
- 21天|21天|M&M《见识》04
- 绘本讲师训练营7期9/21阅读原创《蜗牛屋|绘本讲师训练营7期9/21阅读原创《蜗牛屋 》
- 桂妃研读社|桂妃研读社|D124|如何有效阅读一本书 Day1
- Android事件传递源码分析
- 4.23世界阅读日,樊登读书狂欢放送,听书中成长