写在前面: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的。 记录我的不断探索之旅,希望能有帮助到各位读者朋友。
复制代码
目录如下:
- iOS 底层原理探索 之 alloc
- iOS 底层原理探索 之 结构体内存对齐
- iOS 底层原理探索 之 对象的本质 & isa的底层实现
- iOS 底层原理探索 之 isa - 类的底层原理结构(上)
- iOS 底层原理探索 之 isa - 类的底层原理结构(中)
- iOS 底层原理探索 之 isa - 类的底层原理结构(下)
- iOS 底层原理探索 之 Runtime运行时&方法的本质
- iOS 底层原理探索 之 objc_msgSend
- iOS 底层原理探索 之 Runtime运行时慢速查找流程
- iOS 底层原理探索 之 动态方法决议
- iOS 底层原理探索 之 消息转发流程
- iOS 底层原理探索 之 应用程序加载原理dyld (上)
- iOS 底层原理探索 之 应用程序加载原理dyld (下)
- iOS 底层原理探索 之 类的加载
- iOS 底层原理探索 之 分类的加载
- iOS 底层原理探索 之 关联对象
- iOS底层原理探索 之 魔法师KVC
- iOS底层原理探索 之 KVO原理|8月更文挑战
以上内容的总结专栏
细枝末节整理
前言
上一篇KVO原理的探索,我们探索并总结了KVO的整体流程,总体而言还是比较简单的,那么今天,我们就顺着KVO流程,手写一个 KVO。
KVO流程
在自定义KVO之前,我们把上一篇最后整理的KVO流程拿过来,然后,跟着苹果的流程,我们来自定义一下实现。
自定义 KVO
addObserver:self forKeyPath:@property options:NSKeyValueObservingOptionNew context:NULL
- 验证是否存在
setter
方法 : 不让实例进来
- (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];
}
}
复制代码
- 动态生成子类
SMKVONotifying_Obj
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@"%@%@",kSMKVOPrefix,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的指向是SMPerson
SEL classSEL = NSSelectorFromString(@"class");
Method classMethod = class_getInstanceMethod([self class], classSEL);
const char *classTypes = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSEL, (IMP)sm_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)sm_setter, setterTypes);
return newClass;
}
复制代码
- 将
isa
的指向 :SMKVONotifying_Obj
object_setClass(self, newClass);
复制代码
- 保存观察者 (通过关联对象)
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kSMKVOAssiociateKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
复制代码
经过这样四个步骤,我们就假里假气的实现了苹果在 addObserver
时所做的处理。
注意 重写的四个方法
重写的四个方法最关键的是 属性的setter
方法, 这里我们先不做实现,因为接下来的步骤,改变属性的值的时候,就是其实现的内容,我们留在下面实现 setProperty
这也就是流程图中,重写的方法和第二部中改变属性值的时候用虚线链接的原因。
.property = newValue
新值最为参数拿到后是需要转发给父类的,因为父类对应的属性也需要切修改新值;
接着要将属性值的修改回调给属性观察者,也就是苹果的observeValueForKeyPath
方法中,让观察者可以知道属性的值已发生变化,并且在回调中处理相关业务逻辑。
static void sm_setter(id self,SEL _cmd,id newValue){
NSLog(@"来了:%@",newValue);
// 消息转发 : 转发给父类
// 改变父类的值 --- 可以强制类型转换
void (*sm_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)
sm_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)(kSMKVOAssiociateKey));
// 2: 消息发送给观察者
SEL observerSEL = @selector(observeValueForKeyPath:ofObject:change:context:);
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
objc_msgSend(observer,observerSEL,keyPath,self,@{keyPath:newValue},NULL);
}
复制代码
removeObserver:self forKeyPath:@property
最后在移除观察者的时候,要将观察者的 isa
指回到原来的父类:
- (void)sm_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
// 指回给父类
Class superClass = [self class];
object_setClass(self, superClass);
}
复制代码
补充
class 方法 和 getter 与 setter 方法的互转。
static NSString *const kSMKVOPrefix = @"SMKVONotifying_";
static NSString *const kSMKVOAssiociateKey = @"kSMKVO_AssiociateKey";
Class sm_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];
}
复制代码
总结
通过上面三步:
- 添加属性的观察时,排出实例变量,动态的生成子类,将观察者的isa指向生成的子类 SMKVONotifying_Objc,并重写 四个方法;
- 重点是 setProperty 方法,以实现 将消息发送给父类对父类的实例变量也修改,并且回调到 观察者 change 到方法中去,通知观察者属性的变化。
- 最后,在移除观察的时候,将isa指回到 Objc 中去,以实现完美的闭环。(动态生成的子类并不会被移除)