前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >KVO详解(二)

KVO详解(二)

作者头像
拉维
发布2021-03-25 14:50:04
6470
发布2021-03-25 14:50:04
举报
文章被收录于专栏:iOS小生活iOS小生活

自定义KVO

上篇文章中我介绍了KVO的简单用法以及KVO的实现细节,为了加深对KVO的理解,我决定从头到尾介绍一下如何自定义KVO。

点进KVO的API,我发现所有的KVO的API都是通过类目的形式实现的:

因此,自定义KVO的第一步,就是创建一个NSObject的分类:

然后在分类中去增加一个自定义的添加观察者的方法,在该方法中,大致是做三件事情:

  1. 验证观察的keyPath是否有对应的setter方法,有的话才会进行下一步
  2. 创建一个中间类
  3. 修改实例对象的isa指针的指向

代码如下:

验证是否存在setter的代码如下:

下面来着重看一下中间子类是如何创建的:

首先会判断内存中是否已经存在了该动态子类,如果存在的话就直接返回,如果没有存在才会新建。

新建子类的时候,先申请开辟内存,然后注册类,然后给类添加class方法和属性的setter方法,也就是所谓的重写class方法和setter方法。

class方法的复写还好说,就是让其返回其父类也就是最开始的那个原类即可。

setter方法如何复写呢?setter中做了哪些事情呢?我们接下来就分析一下。

我现在想看一下当被KVO观测的属性值改变的时候,原来的setter方法里面做了哪些事情:

然后我在调试框中使用watchpoint来观测_name的变化:

我们看到,new value是空值,此时打开汇编调试,然后点击下一步:

此时新值已被设置,说明name的setter已经走了,此时再看堆栈:

找到了两个非常熟悉的方法名:willChangeValueForKey:和didChangeValueForKey:,这说明这两个方法在被KVO监测的属性的Setter中被调用了,我们之前也讲过,这两个方法是真正触发KVO的源头。而且我上篇文章也猜测过,在中间子类里面重写setter的时候会加上willChangeValueForKey:和didChangeValueForKey:,这里也验证了这个猜测。

而且通过查看堆栈信息我们也可以看到,最终还会调到LVPerson的setName,这说明在中间子类的setter中会调用父类的setter

基于上面的分析,我就写出了下面的?norman_setter:

上面提到,我需要在对应的属性值改变的时候,向所有监听该改变的观察者们去发送消息,以通知其对该改变作出响应

所以现在就有这样的一个问题,我如何找到这些观察者呢?

答案是我通过一个数组来保存这些观察者。

那么在什么时机进行保存呢?

答案是在添加观察者的时候就进行保存。

那么在保存的时候,我是保存哪些内容呢?不会是只保存观察者这一项吧?答案是,在添加KVO观察者的时候,会将本次观察的keyPath、观察的类型options、观察者等信息都封装进一个信息Model中,然后将这个Model存进一个数组里面

保存KVO观察信息的数据模型的定义如下:

代码语言:javascript
复制
typedef NS_OPTIONS(NSUInteger, NormanKeyValueObservingOptions) {
    NormanKeyValueObservingOptionNew = 0x01,
    NormanKeyValueObservingOptionOld = 0x02,
};

@interface NormanKVOInfo : NSObject

@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, assign) NormanKeyValueObservingOptions options;

- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NormanKeyValueObservingOptions)options;

@end



@implementation NormanKVOInfo

- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NormanKeyValueObservingOptions)options {
    if (self = [super init]) {
        self.observer = observer;
        self.keyPath  = keyPath;
        self.options  = options;
    }

    return self;
}

@end

到这里,我们已经说完了添加观察者和KVO的响应,但是移除KVO观察者还没有说。

那么KVO观察者在移除的时候需要做什么事情呢?我们前面提到,当一个对象在被KVO监测之后,其isa指针会指向一个新的中间子类,因此,在移除KVO观测的时候,我们就需要将isa给指回来:

到这里,我已经将添加观察者、KVO监测响应、移除观察者都说完了,接下来将完整代码罗列出来。

承载KVO信息的NormanKVOInfo类:

代码语言:javascript
复制
typedef NS_OPTIONS(NSUInteger, NormanKeyValueObservingOptions) {
    NormanKeyValueObservingOptionNew = 0x01,
    NormanKeyValueObservingOptionOld = 0x02,
};

@interface NormanKVOInfo : NSObject

@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, assign) NormanKeyValueObservingOptions options;

- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NormanKeyValueObservingOptions)options;

@end


@implementation NormanKVOInfo

- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NormanKeyValueObservingOptions)options {
    if (self = [super init]) {
        self.observer = observer;
        self.keyPath  = keyPath;
        self.options  = options;
    }

    return self;
}

@end

KVO的实现——NSObject+NormanKVO类目:

代码语言:javascript
复制
@interface NSObject (NormanKVO)

// 添加KVO观察者
- (void)norman_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NormanKeyValueObservingOptions)options context:(nullable void *)context;
// KVO监测响应
- (void)norman_observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
// 移除KVO观察者
- (void)norman_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

@end

其实现如下:

代码语言:javascript
复制
#import "NSObject+NormanKVO.h"
#import <objc/message.h>

static NSString *const kNormanKVOPrefix = @"NormanKVONotifying_";
static NSString *const kNormanKVOAssiociateKey = @"kNormanKVOAssiociateKey";

@implementation NSObject (NormanKVO)

- (void)norman_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NormanKeyValueObservingOptions)options context:(void *)context {
    // 1,验证是否存在setter(保证实例变量不会进来)
    [self judgeSetterMethodFromKeyPath:keyPath];
    // 2,动态生成子类
    Class newClass = [self createChildClassWithKeyPath:keyPath];
    // 3,修改isa指向(isa-swizzling)
    object_setClass(self, newClass);
    // 4,保存观察者信息
    [self saveKVOInfoWithObserver:observer keyPath:keyPath options:options];
}

- (void)norman_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {

}

- (void)norman_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {

    NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kNormanKVOAssiociateKey));
    if (observerArr.count<=0) {
        return;
    }

    for (NormanKVOInfo *info in observerArr) {
        if ([info.keyPath isEqualToString:keyPath]) {
            [observerArr removeObject:info];
            objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kNormanKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            break;
        }
    }

    if (observerArr.count<=0) {
        // 将isa指针指回中间子类的父类
        Class superClass = [self class];
        object_setClass(self, superClass);
    }
}

#pragma mark - Private Methods

#pragma mark -- 验证是否存在setter方法

// 验证是否存在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];
    }
}

// 从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];
}

// 从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];
}

#pragma mark -- 动态生成子类

- (Class)createChildClassWithKeyPath:(NSString *)keyPath {
    NSString *oldClassName = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@"%@%@", kNormanKVOPrefix, oldClassName];

    Class newClass = NSClassFromString(newClassName);
    // 如果该中间子类不存在,则创建生成新类并注册
    if (!newClass) {
        /**
         * 如果该中间子类在内存中不存在,则创建生成新类,需要传如下三个参数:
         * 参数一:父类
         * 参数二:类的名字
         * 参数三:新类的开辟的额外空间
         */
        // 2.1 : 申请类
        newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
        // 2.2 : 注册类
        objc_registerClassPair(newClass);
        // 2.3.1 : 给新类添加class方法 : 新类中的class指向的是新类的父类,也就是最初的原类
        SEL classSEL = NSSelectorFromString(@"class");
        Method classMethod = class_getInstanceMethod([self class], classSEL);
        const char *classTypes = method_getTypeEncoding(classMethod);
        class_addMethod(newClass, classSEL, (IMP)norman_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)norman_setter, setterTypes);

    return newClass;
}

Class norman_class(id self,SEL _cmd) {
    return class_getSuperclass(object_getClass(self));
}

static void norman_setter(id self, SEL _cmd, id newValue) {
    // <1>,将setter消息转发给父类
    void (*norman_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)),
    };
    norman_msgSendSuper(&superStruct, _cmd, newValue);

    // <2>,值发生变化之后,进行相关KVO的处理
    // <2.1> : 拿到观察者
    NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kNormanKVOAssiociateKey));
    NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
    id oldValue       = [self valueForKey:keyPath];
    for (NormanKVOInfo *info in observerArr) {
        if ([info.keyPath isEqualToString:keyPath]) {
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                NSMutableDictionary<NSKeyValueChangeKey,id> *change = [NSMutableDictionary dictionaryWithCapacity:1];
                // 对新、旧值进行处理
                if (info.options & NormanKeyValueObservingOptionNew) {
                    [change setObject:newValue forKey:NSKeyValueChangeNewKey];
                }
                if (info.options & NormanKeyValueObservingOptionOld) {
                    [change setObject:@"" forKey:NSKeyValueChangeOldKey];
                    if (oldValue) {
                        [change setObject:oldValue forKey:NSKeyValueChangeOldKey];
                    }
                }
                // <2.2> : 将属性值变化的消息发送给观察者,以使观察者进行响应
                SEL observerSEL = @selector(norman_observeValueForKeyPath:ofObject:change:context:);
                objc_msgSend(info.observer, observerSEL, keyPath, self, change, NULL);
            });
        }
    }
}

#pragma mark -- 保存观察者信息

- (void)saveKVOInfoWithObserver:(NSObject *)observer keyPath:(NSString *)keyPath options:(NormanKeyValueObservingOptions)options {
    // 4.1 : 获取存储观察者信息的数组
    NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kNormanKVOAssiociateKey));
    if (!observerArr) {
        // 4.2 : 如果存储观察者信息的数组不存在,就新建一个
        observerArr = [NSMutableArray arrayWithCapacity:1];
    }
    // 4.3 : 将观察者信息封装成一个数据模型,并存放在数组中
    NormanKVOInfo *info = [[NormanKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath options:options];
    [observerArr addObject:info];
    objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kNormanKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

这里再完整总结一下自定义KVO的整个流程。

<一>添加观察者

1,首先,要检查一下传入的keyPath是否有对应的setter,如果没有的话,说明是成员变量,此时无法KVO,报出异常;如果有setter,那么就进入下一步。

2,动态生成中间子类

(1)首先看一下内存中是否有该子类,如果没有的话,就创建类并注册到内存。并且复写class方法使其指向动态子类的父类;如果中间子类已经存在则进入下一步。

(2)复写当前监测属性的setter方法,在复写的setter中做两件事情:①将setter消息转发到父类,调用父类的setter;②通过遍历对照keypath,找到当前变化的所有监听者,并给这些监听者发送一个监听到变化的消息,使之在外界对变化进行响应

3,isa-swizzling

将当前被KVO监听的实例对象的isa指针修改为指向中间子类

4,存储这条KVO信息

将当前的KVO信息封装成一个数据model,并保存到专门的数组中

<二>响应KVO

提供给外界一个接口,使之可以在KVO监听到变化的时候对变化进行处理

<三>移除观察者

里面做的最重要的一件事就是将实例对象的isa指针的指向给改回来

自定义KVO优化——函数式编程

现在各位想一下,我们实现一个KVO的监听,需要做哪些步骤呢?

首先你得添加观察者,添加完观察者你还需要去复写专门的方法来响应观测到的变化,监听完之后还得移除观察者。

哇,好麻烦啊~能不能优化一下呢?答案是能优化。那么怎么优化呢?这就要说到函数式编程的思想了。

关于函数式编程,我在之前的文章中有提到过:

1,Block 的高级使用

2,Swift进阶六——函数和闭包

函数式编程会将封装粒度降低到函数级别,它会将函数作为封装的基本单元,并且会减少中间不可变因素的产生

在OC中,函数式编程可以通过Block来体现,因此,我们可以通过Block的形式将KVO的添加观察者和响应变化合二为一。

响应变化的Block的定义放在KVO信息模型中:

在添加KVO观察者的时候传一个handleBlock进来,然后将其保存在KVO信息Model中:

在对应的属性值发生变化的时候,回调该handle:

这样一改造之后,在外界使用的时候,KVO观察者的添加和KVO变化的监听就可以一起写了,而不必分开写了:

一个循环引用的小问题

接下来说一个小点:

在保存KVO信息的模型中,关于观察者observer属性的声明使用的是weak关键字,各位知道是为什么吗?这是因为如果不使用weak将会导致循环引用。

现在来分析:

kvoInfo持有了observer,保存kvo信息的数组持有了本次观察的kvoInfo,而self(即被观察对象)又持有了保存kvo信息的数组,这就相当于是被观察对象持有了观察者。在本例中,被观察对象是person,观察者是viewController,很显然,person是vc中的属性,所以观察者又持有了被观察对象,这就导致循环引用了。为了避免循环引用,那么就打断其中一条腿,所以在保存KVO信息的模型中,关于观察者observer属性的声明使用的是weak关键字

KVO的自动移除

首先我们需要考虑的点是,什么时候去移除KVO的观察?

实际上,移除KVO的时间点应该是被观察对象销毁的时候

那我就会想到,既然这样,我在NSObject+NormanKVO中复写dealloc方法好了:

这样做是有问题的,为什么呢?因为这是在NSObject的分类中直接覆写的dealloc,那么所有的直接或者间接继承自NSObject的类的实例销毁的时候都会走到这里,而我只需要在被KVO观测的对象销毁的时候走,所以这么写是不可以的。

然后我就想,既然不能直接复写系统的dealloc,那么我就方法交换呗。此时一定要注意交换的时机,不能在+load方法里面交换,因为这样的话,就又回到上面的问题了,所有的直接或者间接继承自NSObject的类的实例销毁的时候都会走到这里。那么我应该在什么时机去交换呢?

我应该在创建中间子类的时候,对该子类的dealloc方法进行方法交换。

此时,时机是没有问题了,但是这样写是有问题的。你想想,我中间子类没有实现dealloc,那么我是不是要到中间子类的父类也就是最初始原类中去查找dealloc?如果最初始原类中也没有复写dealloc,那么就会一层层网上找,一直找到NSObject中,然后将NSObject中的dealloc方法与中间子类中的自定义norman_dealloc方法进行交换,此时影响的就是所有的NSObject及其子类的实例对象了,那肯定是不可取的!

不知道大家还有没有印象,在上篇文章中,我在打印系统的KVO产生的中间类的方法列表的时候,是有4个方法,如下:

其中一个方法就是dealloc,这说明在中间子类中dealloc也被覆写了。

因此,基于上面的这些分析,我们应该可以猜测KVO观察自动移除的实际和地方了:

在中间子类创建的时候复写dealloc方法,然后在复写的dealloc中做移除观察者相关的操作(最重要的一步就是将isa重新指回来)

代码修改如下:

这样的话,每当被监测对象销毁的时候,都会自动回调到norman_dealloc中,进而进行KVO监测的自动销毁。此时就不需要我们程序员手动在相关的dealloc中去移除观察者了!

最终代码如下。

记录KVO监测信息的模型NormanKVOInfo:

代码语言:javascript
复制
typedef NS_OPTIONS(NSUInteger, NormanKeyValueObservingOptions) {
    NormanKeyValueObservingOptionNew = 0x01,
    NormanKeyValueObservingOptionOld = 0x02,
};

typedef void(^NormanKVOHandleBlock)(id observer, NSString *keyPath, id oldValue, id newValue);

@interface NormanKVOInfo : NSObject

@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, assign) NormanKeyValueObservingOptions options;
@property (nonatomic, copy) NormanKVOHandleBlock handleBlock;

- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NormanKeyValueObservingOptions)options handleBolck:(NormanKVOHandleBlock)handleBlock;

@end



@implementation NormanKVOInfo

- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NormanKeyValueObservingOptions)options handleBolck:(NormanKVOHandleBlock)handleBlock {
    if (self = [super init]) {
        self.observer = observer;
        self.keyPath  = keyPath;
        self.options  = options;
        self.handleBlock = handleBlock;
    }
    
    return self;
}

@end

KVO类目的实现:

代码语言:javascript
复制
@interface NSObject (NormanKVO)

// 添加KVO观察者
- (void)norman_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NormanKeyValueObservingOptions)options context:(nullable void *)context handle:(NormanKVOHandleBlock)handle;

@end



static NSString *const kNormanKVOPrefix = @"NormanKVONotifying_";
static NSString *const kNormanKVOAssiociateKey = @"kNormanKVOAssiociateKey";

@implementation NSObject (NormanKVO)

- (void)norman_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NormanKeyValueObservingOptions)options context:(void *)context handle:(NormanKVOHandleBlock)handle {
    // 1,验证是否存在setter(保证实例变量不会进来)
    [self judgeSetterMethodFromKeyPath:keyPath];
    // 2,动态生成子类
    Class newClass = [self createChildClassWithKeyPath:keyPath];
    // 3,修改isa指向(isa-swizzling)
    object_setClass(self, newClass);
    // 4,保存观察者信息
    [self saveKVOInfoWithObserver:observer keyPath:keyPath options:options handleBlock:handle];
}

#pragma mark - Private Methods

#pragma mark -- 验证是否存在setter方法

// 验证是否存在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];
    }
}

// 从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];
}

// 从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];
}

#pragma mark -- 动态生成子类

- (Class)createChildClassWithKeyPath:(NSString *)keyPath {
    NSString *oldClassName = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@"%@%@", kNormanKVOPrefix, oldClassName];
    
    Class newClass = NSClassFromString(newClassName);
    // 如果该中间子类不存在,则创建生成新类并注册
    if (!newClass) {
        /**
         * 如果该中间子类在内存中不存在,则创建生成新类,需要传如下三个参数:
         * 参数一:父类
         * 参数二:类的名字
         * 参数三:新类的开辟的额外空间
         */
        // 2.1 : 申请类
        newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
        // 2.2 : 注册类
        objc_registerClassPair(newClass);
        // 2.3.1 : 给新类添加class方法 : 新类中的class指向的是新类的父类,也就是最初的原类
        SEL classSEL = NSSelectorFromString(@"class");
        Method classMethod = class_getInstanceMethod([self class], classSEL);
        const char *classTypes = method_getTypeEncoding(classMethod);
        class_addMethod(newClass, classSEL, (IMP)norman_class, classTypes);
        // 2.3.2 : 给新类添加dealloc方法
        SEL deallocSEL = NSSelectorFromString(@"dealloc");
        Method deallocMethod = class_getInstanceMethod([self class], deallocSEL);
        const char *deallocTypes = method_getTypeEncoding(deallocMethod);
        class_addMethod(newClass, deallocSEL, (IMP)norman_dealloc, deallocTypes);
    }
    
    // 2.3.3 : 给新类添加setter方法
    SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
    Method setterMethod = class_getInstanceMethod([self class], setterSEL);
    const char *setterTypes = method_getTypeEncoding(setterMethod);
    class_addMethod(newClass, setterSEL, (IMP)norman_setter, setterTypes);
    
    return newClass;
}

Class norman_class(id self,SEL _cmd) {
    return class_getSuperclass(object_getClass(self));
}

static void norman_dealloc(id self,SEL _cmd) {
    
    NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kNormanKVOAssiociateKey));
    if (observerArr.count<=0) {
        return;
    }
    
    for (NormanKVOInfo *info in observerArr) {
        [observerArr removeObject:info];
        objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kNormanKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        break;
    }

    if (observerArr.count<=0) {
        // 将isa指针指回中间子类的父类
        Class superClass = [self class];
        object_setClass(self, superClass);
    }
}


static void norman_setter(id self, SEL _cmd, id newValue) {
    // <1>,将setter消息转发给父类
    void (*norman_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)),
    };
    norman_msgSendSuper(&superStruct, _cmd, newValue);
    
    // <2>,值发生变化之后,进行相关KVO的处理
    // <2.1> : 拿到观察者
    NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kNormanKVOAssiociateKey));
    NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
    id oldValue       = [self valueForKey:keyPath];
    for (NormanKVOInfo *info in observerArr) {
        if ([info.keyPath isEqualToString:keyPath]) {
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                NSMutableDictionary<NSKeyValueChangeKey,id> *change = [NSMutableDictionary dictionaryWithCapacity:1];
                // 对新、旧值进行处理
                if (info.options & NormanKeyValueObservingOptionNew) {
                    [change setObject:newValue forKey:NSKeyValueChangeNewKey];
                }
                if (info.options & NormanKeyValueObservingOptionOld) {
                    [change setObject:@"" forKey:NSKeyValueChangeOldKey];
                    if (oldValue) {
                        [change setObject:oldValue forKey:NSKeyValueChangeOldKey];
                    }
                }
                // <2.2> : 将属性值变化的消息发送给观察者,以使观察者进行响应
                !info.handleBlock ?: info.handleBlock(info.observer, info.keyPath, oldValue, newValue);
            });
        }
    }
}

#pragma mark -- 保存观察者信息

- (void)saveKVOInfoWithObserver:(NSObject *)observer keyPath:(NSString *)keyPath options:(NormanKeyValueObservingOptions)options handleBlock:(NormanKVOHandleBlock)handleBlock {
    // 4.1 : 获取存储观察者信息的数组
    NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kNormanKVOAssiociateKey));
    if (!observerArr) {
        // 4.2 : 如果存储观察者信息的数组不存在,就新建一个
        observerArr = [NSMutableArray arrayWithCapacity:1];
    }
    // 4.3 : 将观察者信息封装成一个数据模型,并存放在数组中
    NormanKVOInfo *info = [[NormanKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath options:options handleBolck:handleBlock];
    [observerArr addObject:info];
    objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kNormanKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

结语

我们使用了两篇文章的篇幅来介绍了KVO的使用、KVO的原理以及KVO的自定义,相信到这里诸位对KVO已经有了一个相对比较深入的了解了。

虽然我在上面列出了自定义KVO的所有代码,但是实际上这个自定义KVO还是非常简陋的,甚至可以说是漏洞百出的,不过架子是对的,整个关于KVO自定义的思路是没有任何问题的。

这里给大家推荐一个脸书开源的KVO工具——FBKVOController,如果大家觉得苹果官方提供的KVO太难用的话,可以使用FBKVOController,好用滴很~而且实现也很完善,没啥硬伤~

我们知道,KVO 在Foundation框架下,而Foundation的源码是没有开源的,所以我们找不到KVO的源码。但是我们退而求其次,我们可以在GNUStep的源码(http://www.gnustep.org/resources/downloads.php)中去探寻。

下载了相关源码之后打开工程全局搜索“addobserver”即可。

关于到底是什么,以及其与苹果的关系,大家可以自行百度。

以上。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-03-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 iOS小生活 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档