OC之KVO自定义、自动销毁实现

和谐学习!不急不躁!!我是你们的老朋友小青龙~

前言

前面文章 探究分析了KVO的原理,本篇文章就原理内容进行一番KVO的自定义。首先结合原理梳理下我们自定义要做的事情:

  • 是否拥有set方法(KVO本质上是对set方法的观察

  • 动态生成一个子类,并添加set方法

  • 原类的实例变量的isa指向子类

  • set方法被调用,发起通知

  • remove之后,修改实例变量的isa指向原类

核心方法

添加观察模式

/// 添加观察模式:
- (void)ssj_addObserver:(id)observer forKeyPath:(NSString *)keyPath observerType:(ObserverType)observerType context:(NSString *)context{
    
    /// 先判断是否拥有set方法,(「成员变量」这种是不能被KVO观察到的,在KVO原理篇章我们已经测试过了)
    [self hasSetterMethod:keyPath];
    
    /// 记录这条观察数据
    [self addKVOInfo:observer keyPath:keyPath context:context observerType:observerType];
    
    /// 创建 SSJNSKVONotifying_xxx 子类
   Class subCls = [self ssj_createSubClassFromOriginClass:nil forKeyPath:keyPath];
    
    /// 实例变量的isa指向子类
    object_setClass(self, subCls);
}
复制代码

移除指定观察模式:

/// 移除指定观察模式
/// @param observer 观察者
/// @param keyPath 观察的key路径
- (void)ssj_removeObserver:(id)observer forKeyPath:(NSString *)keyPath{
    
    /// 1、从kVOInfos移除这条kVOInfo记录
    [self removeKVOInfo:observer keyPath:keyPath context:NULL];
    
    /// 2、实例变量的isa指向原类
    NSArray *kVOInfos = objc_getAssociatedObject(self, SSJKVOInfoKey);
    if(kVOInfos.count == 0){
        /// kVOInfos.count为空表示,当前已经移除了所有的观察模式,那么实例对象的「isa」就可以指向「原类」
        /// [self class] 获取的是原类
        object_setClass(self, [self class]);
    }
}
复制代码

通知回调(需要观察者实现):

/// 通知回调
/// 当值发生变化时,通知到这个方法
- (void)ssj_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    /// 需要观察者重写这个方法
}
复制代码

动态生成一个子类

- (Class)ssj_createSubClassFromOriginClass:(Class)originClass forKeyPath:(NSString *)keyPath{

    NSString *originClassName = NSStringFromClass([self class]);
    NSString *ssj_subClassName = [@"SSJNSKVONotifying_" stringByAppendingString:originClassName];
    const char *ssj_subClassName_char = ssj_subClassName.UTF8String;
    Class class_new = objc_getClass(ssj_subClassName_char);
    // 判断是否存在这个子类,不存在就创建
    if(!class_new){
        NSLog(@"开始创建SSJNSKVONotifying_子类~");
        /// 申请类
        class_new = objc_allocateClassPair([self class], ssj_subClassName_char, 0);
        /// 注册类
        objc_registerClassPair(class_new);
        /// 添加方法 (把「原类」的所有「实例方法」都加入到「SSJNSKVONotifying_子类」)
        SEL sel_class = NSSelectorFromString(@"class");
        Method md = class_getInstanceMethod([self class], sel_class);
        const char*type = method_getTypeEncoding(md);
        class_addMethod(class_new, sel_class, (IMP)ssj_class, type);
        
        /// 添加keypath对应setter方法
        if (keyPath.length > 0) {
            /// setKeypath
            NSString *method_setName = [NSString stringWithFormat:@"set%@:",[self smallToBigWithFirstCharFrom:keyPath]];
            SEL sel_keyPath = NSSelectorFromString(method_setName);
            Method md_keyPath = class_getInstanceMethod([self class], sel_keyPath);
            const char*type_keyPath = method_getTypeEncoding(md_keyPath);
            class_addMethod(class_new, sel_keyPath, (IMP)ssj_keypathMethod, type_keyPath);
        }
    }
    return class_new;
}
复制代码

keypath 的 setter方法

#pragma mark - setter方法
static void ssj_keypathMethod(id self,SEL _cmd,id value_new){
    /// 获取旧值
    NSString *old_key = getterForSetter(NSStringFromSelector(_cmd));
    NSString *old_Value = nil;
    if (old_key && old_key.length > 0) {
        old_Value = [self valueForKey:old_key];
    }
    ///开始调用父类的set:value:方法
    struct objc_super obj_super_class = {
       .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self)),
   };
   /// 将objc_msgSendSuper转换成对应的函数指针
   int (*ssj_msgSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper;
   /// 调用ssj_msgSendSuper方法,&操作符是c/c++取指针的操作符
    ssj_msgSendSuper(&obj_super_class, _cmd, value_new); // 这里的返回值类型根据实际调用方法可知,是一个int类型
    
    ///  打印新值、旧值
    NSLog(@"SSJNSKVONotifying_XXX 监听到set方法--旧值:%@ - 新值:%@",old_Value,value_new);
    
    /// changeDic包含了返回的值(新值、旧值等,具体看observerType)
    NSMutableDictionary *changeDic = [NSMutableDictionary new];
    
    id obser = nil;
    NSArray *kVOInfos = objc_getAssociatedObject(self, SSJKVOInfoKey);
    /// 通过objc_getAssociatedObject获取的数组是Array类型,这里转化成NSMutableArray类型
    NSMutableArray *kVOInfos_new = [kVOInfos mutableCopy];
    for (SSJKVOInfo *kVOInfo in kVOInfos) {
        /// 判断keyPath匹配
        BOOL keyPathCompare = [kVOInfo.keyPath isEqualToString:old_key];
        
        /// 匹配需要返回的数据有哪些
        if (kVOInfo.observerType == ObserverTypeOfNewAndOld) {
            if (old_Value) {
                changeDic = [@{
                    @"value_old":old_Value,
                    @"value_new":value_new,
                } mutableCopy];
            }else{
                changeDic = [@{
                    @"value_new":value_new,
                } mutableCopy];
            }
        }else if (kVOInfo.observerType == ObserverTypeOfNew) {
            changeDic = [@{
                @"value_new":value_new,
            } mutableCopy];
        }else if (kVOInfo.observerType == ObserverTypeOfOld) {
            changeDic = [@{
                @"value_old":old_Value,
            } mutableCopy];
        }
        /// 匹配成功,发起回调
        if (keyPathCompare) {
            obser = kVOInfo.observer;
            if(!obser) {
                ///说明observer被释放了,但是kVOInfos里还保存着这条数据,此处应该删除这条垃圾数据
                [kVOInfos_new removeObject:kVOInfo];
                continue;//忽略obser为空的情况
            }
            
            /// 给观察者发送回调
            SEL ssj_obser_sel = @selector(ssj_observeValueForKeyPath:ofObject:change:context:);
            objc_msgSend(obser,ssj_obser_sel,old_key,self,changeDic,kVOInfo.context);
        }
    }
    /// 重写赋值(此刻的kVOInfo已经去掉了obser是为nil的数据记录)
    objc_setAssociatedObject(self, SSJKVOInfoKey, kVOInfos_new, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
复制代码

辅助方法、常量字符串

常量字符串
static const void *SSJKVOInfoKey = &SSJKVOInfoKey;

static NSString const* SSJClassHead = @"SSJNSKVONotifying_";
复制代码
是否拥有set方法
/// 是否拥有set方法
- (void)hasSetterMethod:(NSString *)keyPath{
    /// 获得完整的方法名 setXyy
    NSString *method_setName = [NSString stringWithFormat:@"set%@:",[self smallToBigWithFirstCharFrom:keyPath]];
    SEL sel_class = NSSelectorFromString(method_setName);
    Method md = class_getInstanceMethod([self class], sel_class);
    NSString *showMsg = [NSString stringWithFormat:@"观察模式添加失败,因为%@不是属性",keyPath];
    NSAssert(md, showMsg);
}

复制代码
返回父类信息
/// 返回父类信息
Class ssj_class(id self,SEL _cmd){
    return class_getSuperclass(object_getClass(self));
}
复制代码
从set方法获取getter方法的名称
/// @"setName:" -> @"name"
static inline NSString *getterForSetter(NSString *setter){
    NSString *strBegin = [setter substringToIndex:3];
    NSString *strEnd = [setter substringFromIndex:setter.length-1];
    /// “set”开头,“:”结尾
    if ([strBegin isEqualToString:@"set"] && [strEnd isEqualToString:@":"]) {
        ///去掉"set"和":"
        NSString *string_return = [setter substringFromIndex:3];
        string_return = [string_return substringToIndex:string_return.length-1];
        /// 将第一个字符转为小写
        string_return = bigToSmallWithFirstCharFrom(string_return);
        return string_return;
    }
    return nil;
}
复制代码
第一个字母转化为大写
/// 将参数string第一个字母转化为大写
/// @param string 需要处理的字符
/// return 返回处理好的字符串
- (NSString *)smallToBigWithFirstCharFrom:(NSString *)string{
    if (string) {
        if (string.length == 1) {
            return string.uppercaseString;///转化大写
        }else if (string.length > 1) {
            NSString *firstChar = [string substringToIndex:1];
            NSString *atferDeal = firstChar.uppercaseString;///转化大写
            NSString *endStr = [string substringFromIndex:1];
            return [NSString stringWithFormat:@"%@%@",atferDeal,endStr];
        }else{
            return @"";
        }
    }
    NSLog(@"参数string为空,转化失败~");
    return nil;
}
复制代码
第一个字母转化为小写
/// 将参数string第一个字母转化为小写
/// @param string 需要处理的字符
/// return 返回处理好的字符串
static inline NSString *bigToSmallWithFirstCharFrom(NSString *string){
    if (string) {
        if (string.length == 1) {
            return string.lowercaseString;///转化小写
        }else if (string.length > 1) {
            NSString *firstChar = [string substringToIndex:1];
            NSString *atferDeal = firstChar.lowercaseString;///转化小写
            NSString *endStr = [string substringFromIndex:1];
            return [NSString stringWithFormat:@"%@%@",atferDeal,endStr];
        }else{
            return @"";
        }
    }
    NSLog(@"参数string为空,转化失败~");
    return nil;
}
复制代码
添加kVOInfos管理
/** kVOInfos是给NSObject拓展的属性,类型是NSMutableArray <SSJKVOInfo *>*,
(注意:通过objc_setAssociatedObject获取的类型是不可变NSArray)
    作用:记录被观察者的观察信息,「ssj_addObserver:」或「ssj_removeObserver:」以及「ssj_keypathMethod」都有可能让kVOInfos发生变化,具体看代码。
    由于「SSJKVOInfo」的「observer」属性是「weak」修饰的,它会跟着观察者的销毁而变成nil;
    「ssj_keypathMethod」作为「keypath」的set方法,在给观察者发消息之前,会先判断这个观察者是否存在,所以即便没有调用「ssj_removeObserver:」,也不会影响下一次使用。
 */

/// 手动添加kVOInfos - get方法
- (NSMutableArray<SSJKVOInfo *> *)kVOInfos{
    return objc_getAssociatedObject(self, SSJKVOInfoKey);
}

/// 手动添加kVOInfos - set方法
- (void)setKVOInfos:(NSMutableArray<SSJKVOInfo *> *)kVOInfos {
    objc_setAssociatedObject(self, SSJKVOInfoKey, kVOInfos, OBJC_ASSOCIATION_COPY_NONATOMIC);
}


/// 添加 - 新的kVOInfo
/// @param observer 观察者
/// @param keyPath 观察的key路径
/// @param context 上下文
- (BOOL)addKVOInfo:(id)observer keyPath:(NSString *)keyPath context:(NSString *)context observerType:(ObserverType)observerType{
    NSArray *kVOInfos = objc_getAssociatedObject(self, SSJKVOInfoKey);
    NSMutableArray *kVOInfos_new = [kVOInfos mutableCopy];
    for (SSJKVOInfo *kVOInfo in kVOInfos) {
        BOOL observerCompare = kVOInfo.observer == observer;
        BOOL keyPathCompare = [kVOInfo.keyPath isEqualToString:keyPath];
        if (observerCompare && keyPathCompare) {
            /// 说明已经存在这个监听
            return NO;
        }
    }
    
    SSJKVOInfo *kVOInfo_new = [SSJKVOInfo new];
    kVOInfo_new.observer = observer;
    kVOInfo_new.keyPath = keyPath;
    kVOInfo_new.context = context;
    kVOInfo_new.observerType = observerType;
    if (!kVOInfos_new) {
        kVOInfos_new = [NSMutableArray new];
    }
    [kVOInfos_new addObject:kVOInfo_new];
    self.kVOInfos = kVOInfos_new;
    return YES;
}


/// 移除 - 指定kVOInfo
/// @param observer 观察者
/// @param keyPath 观察的key路径
/// @param context 上下文,在这里基本上没啥哟过
- (void)removeKVOInfo:(id)observer keyPath:(NSString *)keyPath context:(nullable void *)context{
    NSMutableArray *kVOInfos_new = [self.kVOInfos mutableCopy];
    for (SSJKVOInfo *kVOInfo in self.kVOInfos) {
        BOOL observerCompare = kVOInfo.observer == observer;
        BOOL keyPathCompare = [kVOInfo.keyPath isEqualToString:keyPath];
        if (observerCompare && keyPathCompare) {
            /// 说明已经存在这个监听
            [kVOInfos_new removeObject:kVOInfo];
            continue;
        }
        /// 因为observer是weak修饰,如果观察者不存在了,那么observer就变成nil
        /// 这时候,这条SSJKVOInfo数据就变成脏数据,也需要清理
        if(!kVOInfo.observer){
            [kVOInfos_new removeObject:kVOInfo];
        }
    }
    self.kVOInfos = kVOInfos_new;
}
复制代码

如何使用

- (void)viewDidLoad {
    [super viewDidLoad];
    
    SSJPerson *person = [SSJPerson shareSingleSSJPerson];
    person.name = @"张三";
    [person ssj_addObserver:self forKeyPath:@"name" observerType:ObserverTypeOfNewAndOld context:@"SSJPerson_name_Context"];
    person.name = @"李四";
    NSString *str = person.name;
    NSLog(@"%s 查看当前name:%@",__func__,str);
}

/// 通知回调
- (void)ssj_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(NSString *)context{
    NSLog(@"回调~~~%@",change);
}
复制代码

运行效果:

image.png

此刻isa还是指向中间类:
image.png

remove之后isa指向原类:

image.png

优化

到目前,我们知道调用自定义KVO需要三步曲:

  1. ssj_addObserver:forKeyPath:observerType:context:

  2. ssj_observeValueForKeyPath:ofObject:change:context:

  3. ssj_removeObserver: forKeyPath:

那么,有没有办法自动管理ssj_removeObserver呢?如果是你,你会有什么好的方案呢?

没错,我想到了方法交换

通常来说,我们会把方法交换放在+ (void)load,这样显然是不行的,因为我们不需要拦截所有的「NSObject」类的dealloc方法。具体实现:

/// 添加观察模式
- (Class)ssj_createSubClassFromOriginClass:(Class)originClass forKeyPath:(NSString *)keyPath{
    ...
    /// 写在这里而不是写在load方法里,只对调用了ssj_addObserver的观察者起作用
        Method originalMethod = class_getInstanceMethod([self class], NSSelectorFromString(@"dealloc"));
        Method swizzlingMethod = class_getInstanceMethod([self class], @selector(ssj_dealloc));
        method_exchangeImplementations(originalMethod, swizzlingMethod);
}

- (void)ssj_dealloc{
    NSLog(@"%@的dealloc 我拦截到了~~~dealloc",NSStringFromClass([self class]));
    /// 实例变量的isa指向原类
    object_setClass(self, [self class]);
    /// 清除记录
    self.kVOInfos = [NSMutableArray new];
}
复制代码

运行:

image.png

看起来没啥问题,但经测试发现:

  • 观察者设置为ViewContor类(及派生类),无论观察者是否实现dealloc,这样都是可以的。

  • 当观察者设置为数据模型本身,并且没有实现dealloc方法的时候,就会找不到dealloc地址。

SSJPerson类观察自己weight属性的变化,并且没有实现dealloc方法:
image.png

代码进行修改:

/// 添加观察模式
- (Class)ssj_createSubClassFromOriginClass:(Class)originClass forKeyPath:(NSString *)keyPath{
    ...
    /// 为中间类添加dealloc方法,imp指向ssj_dealloc
    Method originalMethod = class_getInstanceMethod([self class], NSSelectorFromString(@"dealloc"));
    const char * types = method_getTypeEncoding(originalMethod);
    class_addMethod(class_new, NSSelectorFromString(@"dealloc"), (IMP)ssj_dealloc,types);
}

static void ssj_dealloc(id self,SEL _cmd){
    NSLog(@"%@的dealloc 我拦截到了~~~dealloc",NSStringFromClass([self class]));
    /// 实例变量的isa指向原类
    object_setClass(self, [self class]);
    /// 清除记录
    objc_setAssociatedObject(self, SSJKVOInfoKey, [NSMutableArray new], OBJC_ASSOCIATION_COPY_NONATOMIC);
}
复制代码

运行:

image.png
至此,完成了自动销毁的功能。

代码

链接:pan.baidu.com/s/1HtdFa240…

密码:547z

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享