iOS 探索KVO四(自定义KVO进阶)

前言

前几篇KVO的文章介绍了一些KVO的使用方式和KVO的实现原理和自定义KVO,但是有几个问题没有解决,例如:如果监听多个属性,上篇文章中的自定义KVO就不能解决了,自动销毁等,本篇文章需要在上篇文章的基础上进行阅读;

KVO监听多属性

多属性监听KVO,Github地址

首先引入思路

  1. 引入一个类来保存观察者

  2. 在保存观察者的时候,需要使用数组或字典进行保存

  3. 在获取观察者的时候,需要从数组或字典中获取观察者并发送对应消息

  4. 销毁观察者,需要从数组中取出进行销毁,数组中没有元素后,才能进行指针修改

要监听多个属性,我们就需要对监听的属性进行保存,就需要引入一个集合,这里我们需要先定义一个类XZKVOInfo,用于保存需要监听的值,这里需要保存的值,其实就是observer,keyPath 和监听值的方式,这里定义了2种

typedef NS_OPTIONS(NSUInteger, XZKeyValueObservingOptions) {

    XZKeyValueObservingOptionNew = 0x01,
    XZKeyValueObservingOptionOld = 0x02,
};
@interface XZKVOInfo : NSObject
//观察者
@property (nonatomic, weak) NSObject  *observer;
//观察键值
@property (nonatomic, copy) NSString    *keyPath;
//需要观察的状态值
@property (nonatomic, assign) XZKeyValueObservingOptions options;
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(XZKeyValueObservingOptions)options;

@end
@implementation XZKVOInfo
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(XZKeyValueObservingOptions)options
{
    self = [super init];
    if (self) {
        self.observer = observer;
        self.keyPath  = keyPath;
        self.options  = options;
    }
    return self;
}

@end

基于上篇文章,因为只保存了一个观察者,所以这里需要将保存的观察者使用数组或者字典方式进行保存观察者,我这里使用数组进行保存,如果要使用自定,直接使用keyPath为key,info为value就可以代替

- (void)xz_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 指向 isa_swizzling
    object_setClass(self, newClass);
    //4.保存观察者
    XZKVOInfo *info = [[XZKVOInfo alloc]initWitObserver:observer forKeyPath:keyPath options:options];
    //收集观察者
    NSMutableArray *observerArray = objc_getAssociatedObject(self,(__bridge const void * _Nonnull)(kXZKVOAssiociateKey));
    if (!observerArray) {
        observerArray = [NSMutableArray array];
    }
    [observerArray addObject:info];
    //这里就不保存观察者了,直接将Array进行保存就行了
    objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kXZKVOAssiociateKey), observerArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

再获取observer的地方做相应调整

static void xz_setter(id self,SEL _cmd,id newValue){
    NSLog(@"来了:%@",newValue);
    //获取当前监听的key
    NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
    //先获取当前的旧值
    NSString *oldValue = [self valueForKey:keyPath];

   //4:消息转发: 转发给父类
    //改变父类的值---可以强制类型转换
    void (*xz_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
    /**
     newvalue 这里修改了子类的的值,父类值是没有改变的
     使用 objc_msgSendSuper给父类的setter方法发送消息修改值
    */
    // 回调给外界
    
    struct objc_super superStruct = {
        .receiver       = self,
        .super_class    = [self class]
    };
    //4.1转发给父类
    xz_msgSendSuper(&superStruct,_cmd,newValue);

    //4.2 获取观察者观察者
    NSMutableArray *observerArray =objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kXZKVOAssiociateKey));
    for (XZKVOInfo *info in observerArray) {
 if ([info.keyPath isEqualToString:keyPath]) {
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                
                //4.3消息发送观察者
                NSMutableDictionary *change = [NSMutableDictionary dictionary];
                //对新旧值进行处理
                if (info.options & XZKeyValueObservingOptionNew) {
                    [change setObject:newValue forKey:NSKeyValueChangeNewKey];
                }
                if (info.options & XZKeyValueObservingOptionOld) {
                    [change setObject:@"" forKey:NSKeyValueChangeOldKey];
                    if (oldValue) {
                        [change setObject:oldValue forKey:NSKeyValueChangeOldKey];
                    }
                }
                SEL observerSel = @selector(observeValueForKeyPath:ofObject:change:context:);
                objc_msgSend(info.observer,observerSel,keyPath,self,change,NULL);
            });
            
        }  
     }
}

还有一处就是,我们在添加setter方法时,因为之前只添加一个属性,所以只要有类了,就可以直接返回,但是这里需要添加多个观察属性,这里就需要在判断时,如果有类了,就只添加setter方法

- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
    
    // 2.1 判断是否有了
    NSString *oldClassName = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@"%@%@",kXZKVOPrefix,oldClassName];// XZKVONotifying_XZPerson
    Class newClass = NSClassFromString(newClassName);
    if (newClass) {
        // 2.3.2 添加setter方法 setNickname
        SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
        Method setterMethod = class_getClassMethod([self class], setterSEL);
        const char *setterType = method_getTypeEncoding(setterMethod);
        class_addMethod(newClass, setterSEL, (IMP)xz_setter, setterType);
        return newClass;
    }
    /**
     * 如果内存不存在,创建生成
     * 参数一: 父类
     * 参数二: 新类的名字
     * 参数三: 新类的开辟的额外空间
     */

    // 2.1 申请类
    newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
    // 2.2 注册类
    objc_registerClassPair(newClass);
    // 2.3.1 添加class方法
    SEL classSEL = NSSelectorFromString(@"class");
    Method classMethod = class_getClassMethod([self class], @selector(class));
    const char *classType = method_getTypeEncoding(classMethod);
    class_addMethod(newClass, classSEL, (IMP)xz_class, classType);
    // 2.3.2 添加setter方法 setNickname
    SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
    Method setterMethod = class_getClassMethod([self class], setterSEL);
    const char *setterType = method_getTypeEncoding(setterMethod);
    class_addMethod(newClass, setterSEL, (IMP)xz_setter, setterType);
    
    return newClass;
}

最后需要调整的就是,remove的时候需要从数组中进行移除

- (void)xz_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
{
    NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kXZKVOAssiociateKey));
       if (observerArr.count<=0) {
           return;
       }
    for (XZKVOInfo *info in observerArr) {
        if ([info.keyPath isEqualToString:keyPath]) {
            [observerArr removeObject:info];
            objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kXZKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            break;
        }
    }
    //将指针指回来
    if (observerArr.count <= 0) {
        Class superclass = [self class];
        object_setClass(self, superclass);
    }
}

这样就可以监听多属性KVO自定义完成

KVO链式变成

KVO链式编程,Demo地址

为了方便起见,我们自定义了KVO,但是对于多个属性监听的话,还是需要在观察对象后,进行多次判断,所以考虑使用链式变成,直接返回到回调方法中;

思路:

  1. 调用添加时引入block,并在info中进行保存,

  2. 回调时不使用消息转发,直接调用block

  3. observer修饰符需要修改为weak;因为(person)self-持有->array-持有->observer(VC)-(外部持有)->Person

KVOInfo中添加block

NS_ASSUME_NONNULL_BEGIN
typedef NS_OPTIONS(NSUInteger, XZKeyValueObservingOptions) {

    XZKeyValueObservingOptionNew = 0x01,
    XZKeyValueObservingOptionOld = 0x02,
};
typedef void(^XZKVOBlock)(id observer,NSString *keyPath,id oldValue, id newValue);

@interface XZKVOInfo : NSObject
//保存函数回调
@property (nonatomic, copy) XZKVOBlock kvoBlock;

//观察者
@property (nonatomic, weak) NSObject *observer;
//观察键值
@property (nonatomic, copy) NSString *keyPath;
//需要观察的状态值
@property (nonatomic, assign) XZKeyValueObservingOptions options;
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(XZKeyValueObservingOptions)options andBlock:(XZKVOBlock)block;

@end

NS_ASSUME_NONNULL_END
@implementation XZKVOInfo
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(XZKeyValueObservingOptions)options andBlock:(XZKVOBlock)block
{
    self = [super init];
    if (self) {
        self.observer = observer;
        self.keyPath  = keyPath;
        self.options  = options;
        self.kvoBlock = block;
    }
    return self;
}

@end

KVOAdd方法中添加block

- (void)xz_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context handle:(XZKVOBlock)handle
{

    // 1: 验证setter
    [self judgeSetterMethodFromKeyPath:keyPath];
    // 2: 动态生成子类
    Class newClass = [self createChildClassWithKeyPath:keyPath];
    // 3: isa 指向 isa_swizzling
    object_setClass(self, newClass);
    //4.保存观察者
    XZKVOInfo *info = [[XZKVOInfo alloc]initWitObserver:observer forKeyPath:keyPath options:options andBlock:handle];
    //收集观察者
    NSMutableArray *observerArray = objc_getAssociatedObject(self,(__bridge const void * _Nonnull)(kXZKVOAssiociateKey));
    if (!observerArray) {
        observerArray = [NSMutableArray array];
    }
    [observerArray addObject:info];
    //这里就不保存观察者了,直接将Array进行保存就行了
    objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kXZKVOAssiociateKey), observerArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

回调方法修改

static void xz_setter(id self,SEL _cmd,id newValue){
    NSLog(@"来了:%@",newValue);
    //获取当前监听的key
    NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
    //先获取当前的旧值
    NSString *oldValue = [self valueForKey:keyPath];

   //4:消息转发: 转发给父类
    //改变父类的值---可以强制类型转换
    void (*xz_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
    /**
     newvalue 这里修改了子类的的值,父类值是没有改变的
     使用 objc_msgSendSuper给父类的setter方法发送消息修改值
    */
    // 回调给外界
    
    struct objc_super superStruct = {
        .receiver       = self,
        .super_class    = [self class]
    };
    //4.1转发给父类
    xz_msgSendSuper(&superStruct,_cmd,newValue);

    //4.2 获取观察者观察者
    NSMutableArray *observerArray =objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kXZKVOAssiociateKey));
    for (XZKVOInfo *info in observerArray) {
        if ([info.keyPath isEqualToString:keyPath]) {
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                //4.3消息发送观察者
                info.kvoBlock(info.observer, info.keyPath, oldValue, newValue);
            });
            
        }
    }
}

这样就实现了KVO的链式变成,在调用的时候更加方便

    [self.person xz_addObserver:self forKeyPath:@"nickName" options:(NSKeyValueObservingOptionOld) context:NULL handle:^(id  _Nonnull observer, NSString * _Nonnull keyPath, id  _Nonnull oldValue, id  _Nonnull newValue) {
        NSLog(@"nickName: oldvalue-- %@ newValue---%@",oldValue,newValue);
    }];

但是这样调用是简单了,但是还是需要写KVO的remove方法 ,接下来我们来看如何自动销毁

KVO自动销毁

KVO自动销毁,Demo地址

思路导引:

  1. 这里KVO肯定还是需要有isa指回父类的,只是将这个方法需要定义到KVO内部

  2. 这里需要等到当前的Person销毁后,就直接进行remove,但是不能直接添加delloc方法(因为当前是NSObject的分类中,肯定会有很多地方都有调用delloc)

  3. 所以这里我们对只能对自己本类的delloc的时候需要添加,所以我们直接在创建类的时候给当前类添加自己的delloc方法进行处理

这里需要修改的代码不是很多,在添加类的时候添加delloc方法:

#pragma mark -
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
    
    // 2.1 判断是否有了
    NSString *oldClassName = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@"%@%@",kXZKVOPrefix,oldClassName];// XZKVONotifying_XZPerson
    Class newClass = NSClassFromString(newClassName);
    if (newClass) {
        // 2.3.2 添加setter方法 setNickname
        SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
        Method setterMethod = class_getClassMethod([self class], setterSEL);
        const char *setterType = method_getTypeEncoding(setterMethod);
        class_addMethod(newClass, setterSEL, (IMP)xz_setter, setterType);
        return newClass;
    }
    /**
     * 如果内存不存在,创建生成
     * 参数一: 父类
     * 参数二: 新类的名字
     * 参数三: 新类的开辟的额外空间
     */

    // 2.1 申请类
    newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
    // 2.2 注册类
    objc_registerClassPair(newClass);
    // 2.3.1 添加class方法
    SEL classSEL = NSSelectorFromString(@"class");
    Method classMethod = class_getClassMethod([self class], @selector(class));
    const char *classType = method_getTypeEncoding(classMethod);
    class_addMethod(newClass, classSEL, (IMP)xz_class, classType);
    // 2.3.1 添加dealloc方法
    SEL delalocSEL = NSSelectorFromString(@"dealloc");
    Method deallocMethod = class_getClassMethod([self class], delalocSEL);
    const char *deallocType = method_getTypeEncoding(deallocMethod);
    class_addMethod(newClass, delalocSEL, (IMP)xz_dealloc, deallocType);

    // 2.3.2 添加setter方法 setNickname
    SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
    Method setterMethod = class_getClassMethod([self class], setterSEL);
    const char *setterType = method_getTypeEncoding(setterMethod);
    class_addMethod(newClass, setterSEL, (IMP)xz_setter, setterType);
    
    return newClass;
}

在自己添加的xz_delloc中进行isa的指回就行了

static void xz_dealloc(id self,SEL _cmd){
    NSLog(@"xz_dealloc来了");
    NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kXZKVOAssiociateKey));
    [observerArr removeAllObjects];
    objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kXZKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    //将指针指回来
    if (observerArr.count <= 0) {
        Class superclass = [self class];
        object_setClass(self, superclass);
    }
}

这样在调用KVO的时候就可以直接使用,一行代码解决所有问题:

    [self.person xz_addObserver:self forKeyPath:@"nickName" options:(NSKeyValueObservingOptionOld) context:NULL handle:^(id  _Nonnull observer, NSString * _Nonnull keyPath, id  _Nonnull oldValue, id  _Nonnull newValue) {
        NSLog(@"nickName: oldvalue-- %@ newValue---%@",oldValue,newValue);
    }];

总结

这篇文章对KVO进行了再次封装,实现了多属性监听,链式编程,和自动释放功能,这个在自己使用KVO的时候能够有所帮助

希望对大家有用处,欢迎大家点赞+评论,关注我的CSDN,我会定期做一些技术分享!未完待续。。。

原创文章 88 获赞 21 访问量 2万+

猜你喜欢

转载自blog.csdn.net/ZhaiAlan/article/details/105944530