iOS底层原理探索 之 重写KVO|8月更文挑战

写在前面: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的。 记录我的不断探索之旅,希望能有帮助到各位读者朋友。
复制代码

目录如下:

  1. iOS 底层原理探索 之 alloc
  2. iOS 底层原理探索 之 结构体内存对齐
  3. iOS 底层原理探索 之 对象的本质 & isa的底层实现
  4. iOS 底层原理探索 之 isa - 类的底层原理结构(上)
  5. iOS 底层原理探索 之 isa - 类的底层原理结构(中)
  6. iOS 底层原理探索 之 isa - 类的底层原理结构(下)
  7. iOS 底层原理探索 之 Runtime运行时&方法的本质
  8. iOS 底层原理探索 之 objc_msgSend
  9. iOS 底层原理探索 之 Runtime运行时慢速查找流程
  10. iOS 底层原理探索 之 动态方法决议
  11. iOS 底层原理探索 之 消息转发流程
  12. iOS 底层原理探索 之 应用程序加载原理dyld (上)
  13. iOS 底层原理探索 之 应用程序加载原理dyld (下)
  14. iOS 底层原理探索 之 类的加载
  15. iOS 底层原理探索 之 分类的加载
  16. iOS 底层原理探索 之 关联对象
  17. iOS底层原理探索 之 魔法师KVC
  18. iOS底层原理探索 之 KVO原理|8月更文挑战

以上内容的总结专栏


细枝末节整理


前言

上一篇KVO原理的探索,我们探索并总结了KVO的整体流程,总体而言还是比较简单的,那么今天,我们就顺着KVO流程,手写一个 KVO。

KVO流程

在自定义KVO之前,我们把上一篇最后整理的KVO流程拿过来,然后,跟着苹果的流程,我们来自定义一下实现。

KVO流程.001.jpeg

自定义 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 中去,以实现完美的闭环。(动态生成的子类并不会被移除)

猜你喜欢

转载自juejin.im/post/6991573899926306829