前面介绍KVO是讲了系统KVO实现的大体逻辑,分为下面几步
1.创建子类,将该对象的isa指向新创建的子类
2.重写class,指向父类(即当前对象的class)
3.重写指定键值的set的imp方法,实现监听回调
4.释放时,该对象指向当前类(即释放类的父类),避免不必要的麻烦
下面模仿系统实现自动销毁的自定义KVO
大体逻辑核心代码
注册子类方法
//注册新的子类
Class newCls = objc_allocateClassPair(cls, [NSString stringWithFormat:@"LSKVONotifying_%@", NSStringFromClass(cls)].UTF8String, 0);
objc_registerClassPair(newCls);
子类重写dealloc
//子类重写dealloc方法实现
SEL deallocSel = NSSelectorFromString(@"dealloc");
Method deallocMethod = class_getInstanceMethod(cls, deallocSel);
class_addMethod(newCls, deallocSel, (IMP)ls_dealloc, method_getTypeEncoding(deallocMethod));
子类重写class
//子类重写class方法实现
SEL classSel = NSSelectorFromString(@"class");
Method classMethod = class_getInstanceMethod(cls, classSel);
class_addMethod(newCls, classSel, (IMP)ls_class, method_getTypeEncoding(classMethod));
子类重写setter
//子类重写setter方法实现
SEL setterSel = NSSelectorFromString(setter);
Method setterMethod = class_getInstanceMethod(cls, setterSel);
class_addMethod(newCls, setterSel, (IMP)ls_setter, method_getTypeEncoding(setterMethod));
设置对象的类为新的类
//将但前对象所属类指向其子类
object_setClass(observed, newCls);
基础方法就这么多,下面实现一个能用的吧
自定义KVO
如果理解了前面FBKVOController的观察者之间的对应关系,相信很快理解自定义KVO之间的对应关系,即一个被观察者对应多个观察者
_LSKVOInfo
通过上面可以得出,每个键值和对应block对应一个载体,其为回调的一个基本数据结构,我们定义为LSKVOInfo
其数据结构如下所示:
{
@public
__weak id _observer; //观察者
NSString *_keyPath; //监听键值
CallBack _block; //回调block
SEL _sel; //回调sel
}
_LSClassInfo
这个类负责处理被观察者的核心逻辑实现
{
@public
Class _cls; //创建的新类
Class _superCls; //原始类
NSMutableDictionary<NSString *, __LSClassKeyPathInfo *> *_keyPathMap;//每次添加一对setter和getter键值,均指向__LSClassKeyPathInfo对象
NSHashTable<id> *_hashTable; //该类作为被观察者对象的的弱引用集合,用于判断是否已经设置class了
dispatch_semaphore_t _semaphore;
}
1.创建类
类只需创建一个,因此当我们创建一个观察者时,如果没创建该观察类,则创建该观察类,并且标记,方便下一次判断和使用;如果发现已经创建类,则直接获取当前类的class,不在进行创建,
2.重写dealloc、重写class
其和创建类一样,只需要重写一次,因此重写过程只需要在创建类的时候重写即可
创建类、dealloc、class实现如下所示:
- (void)_observerClass:(Class)cls info:(_LSKVOInfo *)info {
//注册新的子类
Class newCls = objc_allocateClassPair(cls, [NSString stringWithFormat:@"LSKVONotifying_%@", NSStringFromClass(cls)].UTF8String, 0);
objc_registerClassPair(newCls);
//子类重写dealloc方法实现
SEL deallocSel = NSSelectorFromString(@"dealloc");
Method deallocMethod = class_getInstanceMethod(cls, deallocSel);
class_addMethod(newCls, deallocSel, (IMP)ls_dealloc, method_getTypeEncoding(deallocMethod));
//子类重写class方法实现
SEL classSel = NSSelectorFromString(@"class");
Method classMethod = class_getInstanceMethod(cls, classSel);
class_addMethod(newCls, classSel, (IMP)ls_class, method_getTypeEncoding(classMethod));
_superCls = cls;
_cls = newCls;
}
#pragma mark --重写class方法,注意这里的self代表着什么(调用者对象,该类的应用对象为observed,即self为observed)
static Class ls_class(id self, SEL _cmd){
return class_getSuperclass(object_getClass(self));
}
#pragma mark --重写dealloc方法
static void ls_dealloc(id self, SEL _cmd) {
//class设置回去,后面获取父类也可以[self class],可以避免不必要的麻烦
object_setClass(self, class_getSuperclass(object_getClass(self)));
}
3.重写setter方法
和创建类一样,当前键值的setter方法未重写时,重写并保存记录;如果重写,则不再重新实现setter方法,保存相关的后面在介绍
//重写setter并保存
- (void)addSetterAndSave:(_LSKVOInfo *)info {
NSString *setter = [NSString stringWithFormat:@"set%@%@:",[[info->_keyPath substringToIndex:1] uppercaseString], [info->_keyPath substringFromIndex:1]];
__LSClassKeyPathInfo *keyPathInfo = [__LSClassKeyPathInfo alloc];
keyPathInfo->_setter = setter;
keyPathInfo->_getter = info->_keyPath;
//没有加入key的情况加入key
SEL sel = NSSelectorFromString(setter);
const char *encoding = method_getTypeEncoding(class_getInstanceMethod(_superCls, sel));
IMP imp = getTypeEncodingImp(encoding, &(keyPathInfo->_type));
if (!imp) return;
class_addMethod(_cls, sel, imp, encoding);
//保存对应的键值对,方便获取
[_keyPathMap setObject:keyPathInfo forKey:info->_keyPath];
[_keyPathMap setObject:keyPathInfo forKey:setter];
}
4.给当前对象设置新的类,并且坐上标记,如果已经该对象已经设置了类,则不再设置, 设置setter方法也同理
- (void)updateInfo:(_LSKVOInfo *)info observer:(id)observed {
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
//设置对象的类为新的类
if (![_hashTable containsObject:observed]) {
object_setClass(observed, _cls);
[_hashTable addObject:observed]; //加入集合中
}
//如果没有实现该键值的setter方法,则实现
__LSClassKeyPathInfo *keyPathInfo = [_keyPathMap objectForKey:info->_keyPath];
if (!keyPathInfo) {
//子类重写setter方法实现
addSetterAndSave(self, info);
}
//设置classInfo基本信息
//获取对象对应的mapTable,对象对应的mapTable不一定存在
NSMapTable *mapTable = objc_getAssociatedObject(observed, &kKVOAssociatedMapTableKey); //获取当前对象对应的
if (!mapTable) {
//初始化mapTable
mapTable = [[NSMapTable alloc] initWithKeyOptions:(NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality) valueOptions:(NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality) capacity:0];
//设置回调
NSMutableDictionary *infos = [NSMutableDictionary dictionary];
[infos setObject:info forKey:info->_keyPath];
//根据观察者加入mapTable
[mapTable setObject:infos forKey:info->_observer];
//加入关联
objc_setAssociatedObject(observed, &kKVOAssociatedMapTableKey, mapTable, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}else {
NSMutableDictionary *infos = [mapTable objectForKey:info->_observer];
if (!infos) {
infos = [NSMutableDictionary dictionary];
//根据观察者加入mapTable
[mapTable setObject:infos forKey:info->_observer];
}
[infos setObject:info forKey:info->_keyPath];
}
//给对象添加该类的关联
if (!objc_getAssociatedObject(observed, &kKVOAssociatedClassKey)) {
objc_setAssociatedObject(observed, &kKVOAssociatedClassKey, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
dispatch_semaphore_signal(_semaphore);
}
集合以及关联介绍:
看了前面除了基本操作,会发现多了很多集合类,下面介绍一下其功能:
_hashTable: 保存该类作为被观察者的弱引用集合,用于判断是否已经设置class了,由于NSHashTable弱引用类型,因此当对象释放时,自动会从集合中移除
_keyPathMap:每次添加一对setter和getter键值,均指向__LSClassKeyPathInfo对象
__LSClassKeyPathInfo: 保存着 setter、getter、setter方法参数的encodingTypes(用于重写setter方法)
mapTable:其为被观察者对象对应的,以每一个观察者为键值key,以keyPath对应的回调集合为value(方便直接获取),以Associated的方式关联到被观察者对象中
_classInfo: 其作为该类的类方便的信息(保存着类似于元类的一些处理方式),其也以Associated的方式被关联到每一个被观察者对象中
setter方法重写
看了上面重写setter方法的的实现后,会疑问,为什么要获取方法的encodingType,其主要是为了判断setter方法传递参数的类型,毕竟自己调用seter方法的时候,可能会有基本数据类型,例如:int long,也可能为NSObject类型,也可能为结构体等,这些是不可能使用id接收到的,因此要通过此类型来实现对应的imp
void addSetterAndSave(_LSClassInfo *classInfo, _LSKVOInfo *info) {
NSString *setter = [NSString stringWithFormat:@"set%@%@:",[[info->_keyPath substringToIndex:1] uppercaseString], [info->_keyPath substringFromIndex:1]];
__LSClassKeyPathInfo *keyPathInfo = [__LSClassKeyPathInfo alloc];
keyPathInfo->_setter = setter;
keyPathInfo->_getter = info->_keyPath;
//没有加入key的情况加入key
SEL sel = NSSelectorFromString(setter);
const char *encoding = method_getTypeEncoding(class_getInstanceMethod(classInfo->_superCls, sel));
IMP imp = getTypeEncodingImp(encoding, &(keyPathInfo->_type));
if (!imp) return;
class_addMethod(classInfo->_cls, sel, imp, encoding);
//保存对应的键值对,方便获取
[classInfo->_keyPathMap setObject:keyPathInfo forKey:info->_keyPath];
[classInfo->_keyPathMap setObject:keyPathInfo forKey:setter];
}
获取参数类型:
以对象为例,获取的setter的encodingTyes如下所示:
v24@0:8@16 v--void 24代表大小,以此类推
其他的setter方法一直到@和前面的都一样,均为返回值void、参数id self、参数SEL _cmd,最后一个才是我们传递的方法参数,因此可以通过该方法获取,其类型对照表如下所示:
通过上面就可以获取到类型了,然后重写setter方法即可
重写setter方法的过程,注意新旧值的包装,例如:数字转化成NSNumber, 结构体转化成NSValue等,block由于不能转化,通过KVC获取到对象了类型
回调
直接遍历map集合进行遍历即可
void _ls_responseSetter(id self, id value, id oldValue, NSString *keyPath) {
//响应当前对象添加的对应键值的所有监听
NSMapTable *mapTable = objc_getAssociatedObject(self, &kKVOAssociatedMapTableKey);
for (id observer in mapTable) {
if (observer) {
NSDictionary *infos = [mapTable objectForKey:observer];
if (infos) {
_LSKVOInfo *info = [infos objectForKey:keyPath];
if (info) {
if (info->_block)
info->_block(observer, value, oldValue);
else if (info->_sel)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
//这个由于不存在循环引用问题,则就不返回observer了
[observer performSelector:info->_sel withObject:value withObject:oldValue];
#pragma clang diagnostic pop
}
}
}
}
}
_KVOControllerClassManager
看名字即可察觉到,其为所有类的管理对象,为一个单例,其保存这所有的被观察类,根据类是否已经创建,执行更新还是创建方法,并避免一些创建安全问题
{
@public
NSMutableDictionary<NSString *, _LSClassInfo *> *_classInfoMap; //类的集合
dispatch_semaphore_t _semaphore;
}
//添加新的观察, initialResponse设置回调时,是否默认回调一次
- (void)observer:(id)observed info:(_LSKVOInfo *)info initialResponse:(BOOL)initialResponse {
NSString *clsName = NSStringFromClass([observed class]);
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
_LSClassInfo *classInfo = [_classInfoMap objectForKey:clsName];
if (classInfo) {
dispatch_semaphore_signal(_semaphore);
//根据对象更新classInfo的回调信息
[classInfo updateInfo:info observer:observed];
}else {
classInfo = [[_LSClassInfo alloc] init];
[_classInfoMap setObject:classInfo forKey:clsName];
//添加新类
[classInfo _observerClass:object_getClass(observed) info:info];
dispatch_semaphore_signal(_semaphore);
//根据对象设置classInfo的回调信息
[classInfo setInfo:info observer:observed];
}
if (initialResponse) {
[classInfo responseWithInfo:info observer:observed];
}
}
NSObject (LSKVOController)
为用户使用类,有如下使用方式,可参考代码注释或者remind.md测试文件
其可以监听单个、多个keyPath
也可以监听某一个键值的指定子键值和所有子键值
支持监听后立即回调一次
/*
注意: 只有属性才支持监听,即有setter和getter方法,想获取旧值必须有getter方法
不支持的监听类型,包括自定义struct结构体,union联合体, c类型数组[]
id对象返回的还是对象
NSNumber、Class、SEL、Pointer(指针类型)、char *返回的均为NSValue类型
*/
/// 添加block监听
/// @param observer 观察者
/// @param keyPath 被观察的属性,会因为没有setter和getter方法而报错
/// @param callback 回调block(id observer, id newValue, id oldValue) 观察者、新值、旧值
/// @param initialResponse 是否默认回调一次
- (void)ls_addObserver:(id)observer keyPath:(NSString * _Nonnull)keyPath callBack:(CallBack)callback initialResponse:(BOOL)initialResponse;
//默认不回调
- (void)ls_addObserver:(id)observer keyPath:(NSString * _Nonnull)keyPath callBack:(CallBack)callback;
/// 给多个属性添加block监听
/// @param observer 观察者
/// @param keyPaths 被观察的属性集合,会因为没有setter和getter方法而报错
/// @param callback 回调block(id observer, id newValue, id oldValue) 观察者、新值、旧值
/// @param initialResponse 是否默认回调一次
- (void)ls_addObserver:(id)observer keyPaths:(NSArray<NSString *> * _Nonnull)keyPaths callBack:(CallBack)callback initialResponse:(BOOL)initialResponse;
//默认不回调
- (void)ls_addObserver:(id)observer keyPaths:(NSArray<NSString *> * _Nonnull)keyPaths callBack:(CallBack)callback;
/// 给指定键值属性添加子属性监听block,当属性的某个子属性更改时,则回调当前属性(指定键值属性的子属性不支持监听时,改变不会回调)
/// @param observer 观察者
/// @param keyPath 被观察的属性集合,会因为没有setter和getter方法而报错(子属性也必须有setter和getter方法)
/// @param callback 回调block(id observer, id newValue, id oldValue) 观察者、新值、旧值
/// @param initialResponse 是否默认回调一次
- (void)ls_addSubObserver:(id)observer keyPath:(NSString * _Nonnull)keyPath callBack:(SubCallBack)callback initialResponse:(BOOL)initialResponse;
//默认不回调
- (void)ls_addSubObserver:(id)observer keyPath:(NSString * _Nonnull)keyPath callBack:(SubCallBack)callback;
/// 给属性添加子属性监听block,指定键值的子属性更改时,则回调当前属性
/// @param observer 观察者
/// @param keyPath 观察的属性键值
/// @param subkeyPaths 被观察的属性的响应子属性白名单集合,会因为没有setter和getter方法而报错(子属性也必须有setter和getter方法)
/// @param callback 回调block(id observer, id newValue, id oldValue) 观察者、新值、旧值
/// @param initialResponse 是否默认回调一次
- (void)ls_addSubObserver:(id)observer keyPath:(NSString * _Nonnull)keyPath subKeyPaths:(NSArray<NSString *> *)subkeyPaths callBack:(SubCallBack)callback initialResponse:(BOOL)initialResponse;
//默认不回调
- (void)ls_addSubObserver:(id)observer keyPath:(NSString * _Nonnull)keyPath subKeyPaths:(NSArray<NSString *> *)subkeyPaths callBack:(SubCallBack)callback;
/// 添加SEL监听
/// @param observer 观察者
/// @param keyPath 被观察的键值,会因为没有setter和getter方法而报错
/// @param sel 回调SEL 两个参数 observer、newValue 新值、旧值
/// @param initialResponse 是否默认回调一次
- (void)ls_addObserver:(id)observer keyPath:(NSString * _Nonnull)keyPath selector:(nonnull SEL)sel initialResponse:(BOOL)initialResponse;
//默认不回调
- (void)ls_addObserver:(id)observer keyPath:(NSString * _Nonnull)keyPath selector:(nonnull SEL)sel;
/// 添加SEL监听
/// @param observer 观察者
/// @param keyPaths 被观察的键值集合,会因为没有setter和getter方法而报错
/// @param sel 回调SEL 两个参数 observer、newValue 新值、旧值
/// @param initialResponse 是否默认回调一次
- (void)ls_addObserver:(id)observer keyPaths:(NSArray<NSString *> * _Nonnull)keyPaths selector:(nonnull SEL)sel initialResponse:(BOOL)initialResponse;
//默认不回调
- (void)ls_addObserver:(id)observer keyPaths:(NSArray<NSString *> * _Nonnull)keyPaths selector:(nonnull SEL)sel;
最后
如果发现问题欢迎提出讨论哈