自定义KVO

自定义KVO_第1张图片
自定义KVO.jpeg

导语:

如果对KVO原理不是很熟悉的,可以参考下另一篇文章《ios KVO原理探究》,主要是通过模拟KVO底层实现原理来写一个简单的KVO,以便加深对KVO的理解,主要是通过Rumtime来实现。

Demo源码见KVOCustom

NS_ASSUME_NONNULL_BEGIN

@interface KVOModel : NSObject
@property (nonatomic, strong) NSString* name;
@end

NS_ASSUME_NONNULL_END

有个KVOModel,需要观察name属性的变化 ,自己写一个KVO,就不能用苹果提供的KVO,用KVO最重要是注册方法addObserver:forKeyPath:options:context:,可以先查看下注册方法,看下方法的定义:

注册方法定义.png
可以看到其实就是在NSObject搞了一个分类 NSObject(NSKeyValueObserverRegistration),所以首先可以定义一个NSOject的分类,添加跟方法 addObserver:forKeyPath:options:context:类似的方法,然后在这个方法中作如下修改:
1. 创建一个子类
2. 重写setName方法
3. 修改isa指针
4. 将观察者保存到当前对象

创建一个子类

用runtime的objc_allocateClassPair方法给KVOModel创建一个子类,子类名为customKVO_KVOModel,并利用函数objc_registerClassPair注册这个子类,代码如下:

- (void)customKVO_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context
{
    // 创建一个类
    NSString* oldClassName = NSStringFromClass(self.class);
    NSString* newClassName = [@"customKVO_" stringByAppendingString:oldClassName];
    Class subClass = objc_allocateClassPair(self.class, newClassName.UTF8String, 0);
    // 注册类
    objc_registerClassPair(subClass);
} 

重写setName方法

利用runtime的class_addMethod方法重写setName方法,就是给子类customKVO_KVOModel添加函数setName。

// 重写setName方法
class_addMethod(subClass, @selector(setName:), (IMP)setName, "v@:@");

任何一个OC方法的调用,都会被系统转换为objc_msgSend函数处理,如下代码:

_model = [KVOModel alloc];
_model = [_model init];

其中在调用init方法的时候,系统会转换成如下代码:

_model = objc_msgSend(_model, @selector(init));

底层其实是一个init函数,有两个参数,一个是调用者,另一个就是方法编号,也就是说oc中任何一个没有参数的调用,都会有两个参数被传进去,oc函数默认有两个参数。要验证objc_msgSend需要改下编译环境,如下:

自定义KVO_第2张图片
配置编译环境.png
当然也可以在任何一个oc函数中直接用隐式参数self和_cmd,所以在重写方法的时候,重写方法setName的前两个参数为 id self, SEL _cmd,函数的声明如下:

void setName(id self, SEL _cmd, NSString* newName);

修改isa指针

用函数object_setClass可以做到更改KVOModel的isa指针,如下:

// 修改isa指针
object_setClass(self, subClass);

现在就可以通过调用自定义函数customKVO_addObserver:forKeyPath:options:context: 查看是否修改成功,

自定义KVO_第3张图片
查看结果.png

将观察者保存到当前对象

用函数objc_setAssociatedObject将观察者保存到当前对象,如下:

// 将观察者保存到当前对象
objc_setAssociatedObject(self, @"observer", observer, OBJC_ASSOCIATION_ASSIGN);

这边只能用OBJC_ASSOCIATION_ASSIGN,否则会造成循环引用,然后就可以在重写的setName方法中拿到observer对象,在重写setName方法中调用KVOModel类的setName方法,然后在通知observer,也就是调用observeValueForKeyPath:ofObject:change:context:方法,我们也可以在setName判断observer是否为空,就可以避免因为在dealloc没有remove观察导致crash。重写的setName代码如下:

void setName(id self, SEL _cmd, NSString* newName)
{
    // 调用父类的setName方法
    Class class = [self class];
    object_setClass(self, class_getSuperclass(class));
    objc_msgSend(self, @selector(setName:), newName);
    
    // 通知observer
    id observer = objc_getAssociatedObject(self, @"observer");
    if (observer)
    {
        objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:), @"name", self, @{@"customKVO_new:":newName, @"customKVO_kind":@1}, nil);
    }
    
    // self改回子类
    object_setClass(self, class);
}

结尾

模拟系统的KVO,只是简单的逻辑过程,苹果的KVO实现上有很多很多的判断,有很多很多的功能,但是内部的原理大致是这样。

你可能感兴趣的:(自定义KVO)