1.自定义KVO
1.上一篇博客了解了iOS 系统KVO的底层实现原理,那么这里进行自定义KVO,更好的理解原理和熟悉一些runtime的c方法的调用和功能,利用NSObject分类来实现
2.大体步骤如下
1.验证是否是属性,非属性不进行处理
2.保存观察者,分类多数时候用到关联对象
3.动态生成子类LGKVONotifying_本类名
,前缀没什么限制
3.1 申请类
3.2 注册类
3.3 添加一些方法,这里主要添加 setter class
3.4 isa指向子类LGKVONotifying_本类名
4.子类LGKVONotifying_本类名
的setter方法处理
4.1 给父类发送setter消息
4.2 给观察者发送回调消息
3.详细代码
3.1 注册观察者,外面调用者是Person类,observer是controller
- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGNSKeyValueObservingOptions)options {
// 1.验证是否存在setter方法:不让成员变量进来
[self judgeSetterMethodFromKeyPath:keyPath];
// 2.保存观察者信息,用于发送消息
// *****这里的知识点*****
// 2.1 分类里面使用关联对象进行保存比较多
// 2.2 LGKVOInfo数据模型里面是弱引用,关联对象是强引用,这样既没有对observer进行强引用,又达到了保存的目的
NSMutableArray * infoArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
if (!infoArray) {
infoArray = [NSMutableArray arrayWithCapacity:1];
}
LGKVOInfo * info = [[LGKVOInfo alloc] initWithObserver:observer forKeyPath:keyPath options:options];
[infoArray addObject:info];
// 关联对象保存 观察者信息数组
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), infoArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// 3.生成派生子类
// 新类名为 LGKVONotifying_Person
NSString * oldClassName = NSStringFromClass([self class]);
NSString * newClassName = [NSString stringWithFormat:@"%@%@", kLGKVOPrefix, oldClassName];
Class newClass = NSClassFromString(newClassName);
// 这个if只会进来一次
if (!newClass) {
// 3.1申请类 开辟新的类 size 大小为0,根据父类创建名称为newClassName的新类,开辟空间为0,objc_allocateClassPair申请子类
newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
// 3.2注册到内存
objc_registerClassPair(newClass);
// 3.3添加class方法 // 不写class setter方法的IMP会循环进入
SEL classSel = @selector(class);
Method classMethod = class_getInstanceMethod([self class], classSel);
const char * classType = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSel, (IMP)lg_class, classType);
// 3.4 isa 指向新的类 LGKVONotifying_本类名
object_setClass(self, newClass);
}
// 3.3添加setter方法,这里只模拟iOS系统的 setter和class方法
SEL setterSel = NSSelectorFromString(setterForGetter(keyPath));
Method method = class_getInstanceMethod([self class], setterSel);
const char * type = method_getTypeEncoding(method);
class_addMethod(newClass, setterSel, (IMP)lg_setter, type);
}
3.2 class方法IMP
// 子类LGKVONotifying_LGPerson class方法的IMP
Class lg_class(id self, SEL _cmd) { // 为了返回LGPerson
return class_getSuperclass(object_getClass(self));
}
3.3 setter方法IMP
// 子类LGKVONotifying_LGPerson setter方法的IMP
static void lg_setter(id self, SEL _cmd, id newValue) {
NSLog(@"LGKVONotifying_LGPerson setter IMP newValue == %@", newValue);
// 获取旧的值,设置新值之前获取旧值
NSString * keyPath = getterForSetter(NSStringFromSelector(_cmd));
id oldValue = [self valueForKeyPath:keyPath];
NSLog(@"oldValue == %@", oldValue);
// 1.给父类发送对应的setter方法
// 通用封装解决依赖性,发送消息到父类,不用关注到底是哪个类
void (*lg_msgSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper;
struct objc_super superStruct = {
.receiver = self,
.super_class = [self class],
};
lg_msgSendSuper(&superStruct, _cmd, newValue);
// 2.给observer发送回调消息
// 2.1 找到keyPath对应的observer
// 2.2 获取新值旧值发送给observer
NSMutableArray * mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
for (LGKVOInfo * info in mArray) {
NSMutableDictionary<NSKeyValueChangeKey,id> * change = [NSMutableDictionary dictionaryWithCapacity:1];
if ([info.keyPath isEqualToString:keyPath]) {
if (info.options & LGNSKeyValueObservingOptionNew) {
[change setObject:newValue forKey:NSKeyValueChangeNewKey];
} else {
if (oldValue) {
[change setObject:oldValue forKey:NSKeyValueChangeOldKey];
}
[change setObject:newValue forKey:NSKeyValueChangeNewKey];
}
id observer = info.observer;
if (observer && [observer respondsToSelector:@selector(lg_observeValueForKeyPath:ofObject:change:)]) {
[observer lg_observeValueForKeyPath:keyPath ofObject:info.observer change:change];
}
}
}
}
3.4移除观察者
// 移除观察者
- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
// 删除对应的observer
NSMutableArray * mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
for (LGKVOInfo * info in mArray) {
if ([info.keyPath isEqualToString:keyPath] && [info.observer isEqual:observer]) {
[mArray removeObject:info];
NSLog(@"removeObject mArray == %@ count == %lu", mArray, (unsigned long)[mArray count]);
break;
}
}
// 对应的observer没有观察的属性了,isa指向父类
BOOL isHadObserver = [self isKVOA:observer];
if (isHadObserver == NO) {
// 指回父类
Class superClass = [self class];
object_setClass(self, superClass);
}
}
// 判断该observer还有没有其他需要观察的属性
// 这个类似系统的isKVOA:方法,有几个属性同时观察,必须全部移除后isa指针才会指向父类
- (BOOL)isKVOA:(NSObject *)observer {
NSMutableArray * mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
BOOL isHadObserver = YES;
for (LGKVOInfo * info in mArray) {
if ([info.observer isEqual:observer]) {
isHadObserver = NO;
break;
}
}
NSLog(@"last mArray == %@ count == %lu", mArray, (unsigned long)[mArray count]);
// 关联对象保存 观察者信息数组
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return isHadObserver;
}
3.5其他辅助方法
// 验证是否存在setter方法,
- (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath {
Class superClass = object_getClass(self);
SEL setterSelector = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod(superClass, setterSelector);
if (!setterMethod) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"老铁没有当前%@的setter", keyPath] userInfo:nil];
}
}
// 从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];
}
// 从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];
}
3.6调用自定义KVO
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor whiteColor];
[self createButton];
self.person = [[Person alloc] init];
[self.person lg_addObserver:self forKeyPath:@"nickName" options:LGNSKeyValueObservingOptionOld];
[self.person lg_addObserver:self forKeyPath:@"name" options:LGNSKeyValueObservingOptionOld];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
count++;
self.person.nickName = [NSString stringWithFormat:@"%lu", (unsigned long)count];
}
- (void)lg_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change {
NSLog(@"change == %@", change);
}
- (void)createButton {
UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];
button.backgroundColor = [UIColor redColor];
button.frame = CGRectMake(100, 100, 100, 100);
[button setTitle:@"remove" forState:UIControlStateNormal];
[button addTarget:self action:@selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
- (void)buttonPressed:(UIButton *)button {
[self.person lg_removeObserver:self forKeyPath:@"nickName"];
[self.person lg_removeObserver:self forKeyPath:@"name"];
}
3.7输出
2021-12-17 17:51:24.740939+0800 KVO_18_Custom[92333:7137885] LGKVONotifying_LGPerson setter IMP newValue == 9
2021-12-17 17:51:24.741213+0800 KVO_18_Custom[92333:7137885] getNickName in Person
2021-12-17 17:51:24.741356+0800 KVO_18_Custom[92333:7137885] oldValue == 8
2021-12-17 17:51:24.741438+0800 KVO_18_Custom[92333:7137885] setNickName: in Person
2021-12-17 17:51:24.741637+0800 KVO_18_Custom[92333:7137885] change == {
new = 9;
old = 8;
}
2021-12-17 17:51:24.914475+0800 KVO_18_Custom[92333:7137885] LGKVONotifying_LGPerson setter IMP newValue == 10
2021-12-17 17:51:24.914663+0800 KVO_18_Custom[92333:7137885] getNickName in Person
2021-12-17 17:51:24.914760+0800 KVO_18_Custom[92333:7137885] oldValue == 9
2021-12-17 17:51:24.914813+0800 KVO_18_Custom[92333:7137885] setNickName: in Person
2021-12-17 17:51:24.914966+0800 KVO_18_Custom[92333:7137885] change == {
new = 10;
old = 9;
}
2021-12-17 17:51:25.107261+0800 KVO_18_Custom[92333:7137885] LGKVONotifying_LGPerson setter IMP newValue == 11
2021-12-17 17:51:25.107462+0800 KVO_18_Custom[92333:7137885] getNickName in Person
2021-12-17 17:51:25.107644+0800 KVO_18_Custom[92333:7137885] oldValue == 10
2021-12-17 17:51:25.107745+0800 KVO_18_Custom[92333:7137885] setNickName: in Person
2021-12-17 17:51:25.107955+0800 KVO_18_Custom[92333:7137885] change == {
new = 11;
old = 10;
}
4.一切看起来很完美,实则隐藏着大坑,当系统的KVO和自定义的KVO同时使用就会崩溃或者其他问题出现
4.1 混合调用
[self.person lg_addObserver:self forKeyPath:@"nickName" options:LGNSKeyValueObservingOptionOld];
[self.person lg_addObserver:self forKeyPath:@"name" options:LGNSKeyValueObservingOptionOld];
[self.person addObserver:self forKeyPath:@"nickName" options:NSKeyValueObservingOptionNew context:NULL];
4.2子类isa指向出现问题,而且会递归调用setter方法导致崩溃
iOS系统KVO和自定义KVO同时使用子类紊乱.jpeg
4.3解决方案
基于系统派生类自定义KVO,这种思路不需要创建自定义的派生类,代码实现上与自定义派生类KVO大同小异,先调用系统方法生成系统派生类,再修改系统派生类的setter方法IMP,IMP里面分别给父类和观察者发送消息
4.4基于系统派生类自定义KVO 具体代码,addObserver和removeObserver都是系统的,只改变setter的IMP
4.5注册KVO代码
- (void)addObserver:(NSObject*)observer forKeyPath:(NSString *)keyPath changedBlock:(_EasyKVOChangedBlock)block {
if (!observer || keyPath.length < 1) return;
/* 使用系统方法获得派生类 */
[self addObserver:observer forKeyPath:keyPath options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
/* 保存 block 信息 */
[_tipsMap(self, _cmd) setObject:[block copy] forKey:[NSString stringWithFormat:@"_%@_%@_block", NSStringFromClass(object_getClass(self)), keyPath]];
/* 改变setter方法 */
NSString *format = [keyPath stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[[keyPath substringToIndex:1] uppercaseString]];
NSLog(@"format == %@", format);
// NSString转SEL @selector
SEL setSel = NSSelectorFromString([NSString stringWithFormat:@"set%@:", format]);
Method setMethod = class_getInstanceMethod(object_getClass(self), setSel);
// 交换NSKVONotifying_Person ---> setter方法的IMP,使其指向自定义setter方法的IMP
// 也就是把系统的这个类的NSKVONotifying_Person setter方法IMP换成我们自己的IMP,这样系统的和自定义的KVO的setter方法全部指向了自定义的IMP
// 优点是只改了 IMP,其他dealloc class _isKVOA方法完全没有动
// 优点 addObserver removeObserver都是系统的方法,生命周期完全不用干涉
// 缺点是 1.向父类发送setter消息没有改变,但是,对observer发送消息系统的也走了我们自定义的方法或者block回调
// 如果addObserver系统的KVO那么,回调还是会走系统的,如果addObserver了自定义的KVO,那么都会走自定义的block,多少有点乱,一会这个回调一会那个回调
// 系统的和自定义的method交换
class_replaceMethod(object_getClass(self), setSel, (IMP)_setterFunction, method_getTypeEncoding(setMethod));
}
4.6自定义setter的IMP
void _setterFunction(id self, SEL _cmd, id newValue) {
NSString *setterName = NSStringFromSelector(_cmd);
if (setterName.length < 4) return;
NSString *format = [setterName substringWithRange:NSMakeRange(3, setterName.length -4)];
NSString *keyPath = [format stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:[[format substringToIndex:1] lowercaseString]];
if (keyPath.length < 1) return;
// 1.发送父类setter消息
// 获取旧的值
id oldValue = [self valueForKeyPath:keyPath];
if (![oldValue isEqual:newValue]) {
//调用父类setter
struct objc_super supercls = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};
void (* msgSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper;
msgSendSuper(&supercls, _cmd, newValue);
}
// 发送观察者消息
_EasyKVOChangedBlock block = (_EasyKVOChangedBlock)[_tipsMap(self, _cmd) objectForKey:[NSString stringWithFormat:@"_%@_%@_block", NSStringFromClass(object_getClass(self)), keyPath]];
if (block) block(newValue, oldValue);
}
4.7移除观察者
- (void)removeObserver:(NSObject *)observer blockForKeyPath:(NSString *)keyPath {
[self removeObserver:observer forKeyPath:keyPath];
NSString *blockKeyName = [NSString stringWithFormat:@"_%@_%@_block", @"NSKVONotifying_", keyPath];
NSMutableDictionary *tips = _tipsMap(self, _cmd);
[tips removeObjectForKey:blockKeyName];
}
4.8观察者信息保存
NSMutableDictionary *_tipsMap(id self, SEL _cmd) {
NSMutableDictionary * _tipsDic = objc_getAssociatedObject(self, &__EasyKVOTipsDic);
if (!_tipsDic) {
_tipsDic = [[NSMutableDictionary alloc] init];
objc_setAssociatedObject(self, &__EasyKVOTipsDic, _tipsDic, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return _tipsDic;
}
4.9总结,如果使用了这种自定义的KVO那么系统的KVO也会指向自定义的block回调,系统本身的回调不会执行,如果只使用系统的KVO则没有任何影响。
网友评论