iOS底层探索之KVO—自定义KVO
Posted 卡卡西Sensei
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS底层探索之KVO—自定义KVO相关的知识,希望对你有一定的参考价值。
回顾
在前两篇博客中,已经介绍了KVO
的相关操作,还有KVO
的底层逻辑是通过动态生成子类,重写父类的方法实现的,那么我们如何自定义一个KVO
呢?
iOS底层探索之KVO(一)—KVO简介
iOS底层探索之KVO(二)—KVO原理分析
1. 前期分析
系统的KVO
是在NSObject
的上面拓展了一些能力,如下图所示:
系统的
KVO
使用的三部曲是:
- 添加监听
addObserver
- 监听回调
observeValueForKeyPath
- 移除监听
removeObserver
我们也仿照系统的API
自定义一个KVO
,如下:
@interface NSObject (JP_KVO)
- (void)jp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)jp_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context;
- (void)jp_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end
我们自定义KVO
是对属性进行观察,那么成员变量是没有setter
方法的,所以第一步就得过滤掉成员变量。如何动态生成子类,改变isa的指向,保存观察者,步骤大致如下:
- 验证是否存在
setter
方法 : 不让成员变量(实例变量)进来 - 动态生成子类
- 改变子类的isa的指向
- 保存我们的观察者
- (void)jp_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
// 1: 验证是否存在setter方法 : 不让实例进来
[self judgeSetterMethodFromKeyPath:keyPath];
// 2: 动态生成子类
Class newClass = [self createChildClassWithKeyPath:keyPath];
// 3: isa的指向
object_setClass(self, newClass);
// 4: 保存观察者
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(KJPKVOAssiociateKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
2. 验证是否存在setter方法
- judgeSetterMethodFromKeyPath:keyPath
- (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath{
Class superClass = object_getClass(self);
SEL setterSeletor = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod(superClass, setterSeletor);
if (!setterMethod) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"不好意思没有当前%@的setter",keyPath] userInfo:nil];
}
}
判断是否有
setter
方法,没有就抛出异常信息
3. 动态生成子类
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@"%@%@",kjpKVOPrefix,oldClassName];
Class newClass = NSClassFromString(newClassName);
// 防止重复创建生成新类
if (newClass) return newClass;
/**
* 如果内存不存在,创建生成
* 参数一: 父类
* 参数二: 新类的名字
* 参数三: 新类的开辟的额外空间
*/
// 2.1 : 申请类
newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
// 2.2 : 注册类
objc_registerClassPair(newClass);
// 2.3.1 : 添加class : class的指向是JPPerson
SEL classSEL = NSSelectorFromString(@"class");
Method classMethod = class_getInstanceMethod([self class], classSEL);
const char *classTypes = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSEL, (IMP)jp_class, classTypes);
// 2.3.2 : 添加setter
SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod([self class], setterSEL);
const char *setterTypes = method_getTypeEncoding(setterMethod);
class_addMethod(newClass, setterSEL, (IMP)jp_setter, setterTypes);
return newClass;
}
- JP_class 返回父类信息
Class JP_class(id self,SEL _cmd){
return class_getSuperclass(object_getClass(self));
}
- 判断是否已经创建了子类,没有就创建
- 申请类
- 注册类
newClass
不存在则调用objc_allocateClassPair
创建kvo
子类,并且重写- class
方法- 添加
setter
- 返回
newClass
4. 创建setter
创建setter
方法主要是分两部分:调用父类方法以及发送通知
static void jp_setter(id self,SEL _cmd,id newValue){
// 4: 消息转发 : 转发给父类
// 改变父类的值 --- 可以强制类型转换
void (*jp_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
// void /* struct objc_super *super, SEL op, ... */
struct objc_super superStruct = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self)),
};
//objc_msgSendSuper(&superStruct,_cmd,newValue)
jp_msgSendSuper(&superStruct,_cmd,newValue);
// 既然观察到了,下一步不就是回调 -- 让我们的观察者调用
// - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
// 1: 拿到观察者
id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kJPKVOAssiociateKey));
// 2: 消息发送给观察者
SEL observerSEL = @selector(observeValueForKeyPath:ofObject:change:context:);
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
objc_msgSend(observer,observerSEL,keyPath,self,@{keyPath:newValue},NULL);
}
-
调用父类方法可以通过
objc_msgSendSuper
实现,调用父类的setter
(也可以通过performSelector
调用 -
通知观察者
keypath
可以通过_cmd
转换获取,object
是self
,change
也可以获取到,context
可以先不传。那么核心就是observer
的获取。 -
通过关联对象的方式,把
observer
进行存储 -
拿到观察者了,就把消息发送给观察者,进行信息的回调处理
-
getterForSetter
#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];
}
更多内容持续更新
🌹 喜欢就点个赞吧👍🌹
🌹 觉得有收获的,可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我😁🌹
🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹
以上是关于iOS底层探索之KVO—自定义KVO的主要内容,如果未能解决你的问题,请参考以下文章