iOS 中KVO是官方的观察者模式的实现, 今天来仔细研究一下相关的内容.
1. 系统的KVO实现
iOS 中系统的KVO的API是依赖NSObject的分类方法:
// 通知回调方法API
@interface NSObject(NSKeyValueObserving)
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
@end
// 注册/移除API
@interface NSObject(NSKeyValueObserverRegistration)
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end
复制代码
当Person类的对象person进行kvo监听属性(addObserver:xxxxx
)时, 会有如下实现过程:
- KVO依赖setter方法: KVO的本质会监听setter方法, 因此需要被观察的对象有成员变量, 以及setter方法, 就能通过KVO观察.
- **isa swizzling: **系统动态注册一个动态子类
NSKVONotifying_Person
, 后面用 newClass代替, 将person对象的isa指针指向动态newClass - newClass 重写关键方法 : 动态子类需要重写 setter, -class, dealloc, _isKVO 方法
- setter中, 处理 old, new value, 然后调用 originalClass 的 setter 方法
- -class中: 隐藏 “NSKVONotifying_Person
的实现, 直接返回
class_getSuperclass`, 也就 Person类 - dealloc: 中判断, 并确保isa swizzling 重写 person对象改回 Person
- _isKVO: 标记是否是KVOClass
- 当对象person被移除kvo监听(
removeObserver
)时, 会通过 isa swizzling 将 person对象指向原来的类 - 当对象person的监听全部被移除时 —
动态子类不会被注销
!!! 防止后续重复注册监听需要重复创建动态子类!!!
addObserver中的context作用: 防止keyPath 同名, 可以用环境变量区分
另外 setter 中如果直接使用
self->_xxx = xx
可以使用willChangeValueForKey:
和didChangeValueForKey:
手动触发KVO
2. 重写KVO
根据系统的KVO的逻辑, 我们可以根据系统的实现逻辑, 简单自定义一套逻辑重写KVO, 实现并不完美, 还有以下问题:
- 监听的对象keyPath的内容不够
- dealloc中无法自动释放
- 动态子类无法自动释放
@interface NSObject (LGKVO)
typedef void(^LGKVOBlock)(id observer,NSString *keyPath,id oldValue,id newValue);
- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(LGKVOBlock)block;
- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end
复制代码
#import "NSObject+LGKVO.h"
#import <objc/runtime.h>
#import <objc/message.h>
static NSString *const kLGKVOPrefix = @"LGKVONotifying_";
static NSString *const kLGKVOAssiociateKey = @"kLGKVO_AssiociateKey";
// 中间对象, 防止强引用 observer
@interface LGKVOInfo : NSObject
@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, copy) LGKVOBlock handleBlock;
@end
@implementation LGKVOInfo
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handleBlock:(LGKVOBlock)block{
self = [super init];
if (self) {
self.observer = observer;
self.keyPath = keyPath;
self.handleBlock = block;
}
return self;
}
@end
@implementation NSObject (LGKVO)
#pragma mark - add observer
- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath block:(LGKVOBlock)block{
//1. 判断 keyPath 是否是 KVC
[self judgeSetterMethodFromKeyPath:keyPath];
//2. 注册动态子类
Class newClass = [self createChildClassWithKeyPath:keyPath];
//3. isa swizzling
object_setClass(self, newClass);
//4. 使用关联对象将 observer 与 self 对象绑定!!!
LGKVOInfo *info = [[LGKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath handleBlock:block];
NSMutableArray *observersArr = objc_getAssociatedObject(self, (__bridge const void *)kLGKVOAssiociateKey);
if (!observersArr) {
observersArr = [NSMutableArray arrayWithCapacity:1];
[observersArr addObject:info];
objc_setAssociatedObject(self, (__bridge const void *)kLGKVOAssiociateKey, observersArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
}
// 必须提前强制退出, 否则有风险 -> 因为关联对象持有了外部的 observer, 这里是强引用
- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
if (observerArr.count <= 0) {
return;
}
for (LGKVOInfo *info in observerArr) {
if ([info.keyPath isEqualToString:keyPath]) {
[observerArr removeObject:info];
objc_setAssociatedObject(self, (__bridge const void *)kLGKVOAssiociateKey, observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
break;
}
}
// isa 指回给父类
if (observerArr.count<=0) {
Class superClass = [self class];
object_setClass(self, superClass);
}
}
// BOOL responder = [self respondsToSelector:setterSEL];
-(void)judgeSetterMethodFromKeyPath:(NSString *)keyPath {
// 1. 判断keyPath 构造的 setter 是否拥有实现
NSString *setterStr = setterForGetter(keyPath);
SEL setterSEL = NSSelectorFromString(setterStr);
Class cls = object_getClass(self);
Method setterMethod = class_getInstanceMethod(cls, setterSEL);
if (!setterMethod) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"老铁没有当前%@的setter",keyPath] userInfo:nil];
}
}
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
// 注册动态子类
// 1. 获取对象自己的Class
Class oldCls = object_getClass(self);
NSString *oldClsName = NSStringFromClass(oldCls);
NSString *newClsName = [NSString stringWithFormat:@"%@%@",kLGKVOPrefix,oldClsName];
Class newCls = NSClassFromString(newClsName);
if (newCls) {
return newCls;
}
// 2. 分配资源
newCls = objc_allocateClassPair(oldCls, newClsName.UTF8String, 0);
// 3. 注册 newCls 到 runtime 中
objc_registerClassPair(newCls);
{
// 3.1 添加 setter 方法
NSString *setterStr = setterForGetter(keyPath);
SEL setterSEL = NSSelectorFromString(setterStr);
Method method = class_getInstanceMethod(oldCls, setterSEL);
const char *types = method_getTypeEncoding(method);
class_addMethod(newCls, setterSEL, (IMP)lg_setter, types);
}
{
// 3.2 添加 -class 方法
SEL classSEL = NSSelectorFromString(@"class");
Method method = class_getInstanceMethod(oldCls, classSEL);
const char *types = method_getTypeEncoding(method);
class_addMethod(newCls, classSEL, (IMP)lg_class, types);
}
// { 自动释放
// // 3.3: newClass 添加dealloc -> 中间类创建一个 dealloc 方法!!
// SEL deallocSEL = NSSelectorFromString(@"dealloc");
// Method deallocMethod = class_getInstanceMethod([self class], deallocSEL);
// const char *deallocTypes = method_getTypeEncoding(deallocMethod);
// class_addMethod(newCls, deallocSEL, (IMP)lg_dealloc, deallocTypes);
// }
return newCls;
}
// dealloc 时, 将 isa 指针重置
//static void lg_dealloc(id self,SEL _cmd){
// Class superClass = [self class];
// object_setClass(self, superClass);
//}
static void lg_setter(id self, SEL _cmd, id newValue) {
NSLog(@"lg_setter self: %@, _cmd: %@, newValue: %@", self, NSStringFromSelector(_cmd), newValue);
void (*lg_msgSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper;
void (*lg_msgSend)(void *, SEL, id, id, id, id) = (void *)objc_msgSend;
// 4: 消息转发 : 转发给父类
// 改变父类的值 --- 可以强制类型转换
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
id oldValue = [self valueForKey:keyPath];
// void /* struct objc_super *super, SEL op, ... */
struct objc_super superStruct = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self)),
};
lg_msgSendSuper(&superStruct, _cmd, newValue);
// 1. 获取 observer, 调用 observerxxx方法
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
// 5: 信息数据回调
for (LGKVOInfo *info in observerArr) {
if ([info.keyPath isEqualToString:keyPath] && info.handleBlock) {
info.handleBlock(info.observer, keyPath, oldValue, newValue);
}
}
}
static Class lg_class(id self, SEL _cmd) {
return class_getSuperclass(object_getClass(self));
}
#pragma mark - 从get方法获取set方法的名称 key ===>>> setKey:
static NSString *setterForGetter(NSString *getter){
if (getter.length <= 0) { return nil;}
NSString *firstString = [[getter substringToIndex:1] uppercaseString];
NSString *leaveString = [getter substringFromIndex:1];
return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
}
#pragma mark - 从set方法获取getter方法的名称 set<Key>:===> key
static NSString *getterForSetter(NSString *setter){
if (setter.length <= 0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) { return nil;}
NSRange range = NSMakeRange(3, setter.length-4);
NSString *getter = [setter substringWithRange:range];
NSString *firstString = [[getter substringToIndex:1] lowercaseString];
return [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
}
@end
复制代码
开源方案 FBKVOController 对系统的KVO进行很好的改造, 后面进行分析.
另外 gnustep-base-1.28.0
中有GNUStep版本的KVO也可以参考以下
3. FBKVOController 利用中介者模式封装系统KVO
FBKVOController 中对需要观察某个对象的属性, 通常用以下API:
#pragma mark - lazy
- (FBKVOController *)kvoCtrl{
if (!_kvoCtrl) {
_kvoCtrl = [FBKVOController controllerWithObserver:self];
}
return _kvoCtrl;
}
// 调用方法
[self.kvoCtrl observe:self.person keyPath:@"name" options:(NSKeyValueObservingOptionNew) block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
NSLog(@"****%@****",change);
}];
复制代码
其中[FBKVOController controllerWithObserver:self]
会弱引用持有 self
指针, 也就是 observer
,
在FBKVOController内部将关键信息封装成FBKVOInfo
:
// create info
_FBKVOInfo *info = [[_FBKVOInfo alloc] initWithController:self keyPath:keyPath options:options block:block];
复制代码
此时两个关键内容, 分贝是KVOInfo 观察者以及回调信息
与 object 被观察者
, 然后交给全局的中介者单例_FBKVOSharedController
管理:
[[_FBKVOSharedController sharedController] observe:object info:info];
复制代码
使用单例_FBKVOSharedController
调用系统API addObserver:forKeyPath:options:context:
, 其中 observer就是 _FBKVOSharedController sharedInstance
!!! 被观察对象就是object
!!! 通过context配置成KVOInfo
来区分, 内部实现observeValueForKeyPath:...
方法集中处理!!!
在被观察对象dealloc时, KVO自动removeObserver:
的实现:
有一个FBKVOController
关联对象(或者手动创建的成员变量)与被观察者是绑定的. 因此当关联对象AssociatedObject的dealloc
调用时, 一定是被观察对象的dealloc
调用, 此时在关联对象的dealloc
中unobserve
观察的内容可以自动调用removeObserver:...
方法
有以下知识要点值得学习:
- 中介者模式
- 关联对象/成员属性释放时, 监听被观察者对象的dealloc释放