探究KVO本质

看了一些资料,对OC更加深入了解,记录总结一下。
KVO:key-value-boserver,键-值-监听。主要是用来监听对象属性的变化。

一、KVO 的 简单用法

首先我们先写一个常规的KVO:
创建一个Person

#import 

@interface Person : NSObject
@property (nonatomic,assign) NSInteger  age;
@end

@implementation Person
@end

创建一个Person 对象p,在touchesBegan中更改p.age的值

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    p = [[Person alloc] init];
    p.age = 10;
        
    [p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:nil];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    NSLog(@"observeValueForKeyPath:ofObject:change:context");
}

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    p.age = 20;
}

然后点击view,就会触发observeValueForKeyPath监听方法

二、对象pisa指向

我们都知道instanceisa 指向 类对象类对象isa 指向元类对象

所以我们利用函数 object_getClass看看p对象的isa指向,发现:

添加监听前是isa指向是:Person
添加监听后是isa指向是:NSKVONotifying_Person

 Class cls = object_getClass(p);//得到p的类对象
 NSLog(@"%@", cls);

打印:Person

说明对象在添加KVO 的时候,利用runtime 动态生成了一个NSKVONotifying_开头的类,而且该类是被监听类的子类

如果我们自己手动创建一个NSKVONotifying_Person,会怎么样?
控制台会有这么一个打印:

KVO failed to allocate class pair for name NSKVONotifying_Person,
automatic key-value observing will not work for this class

所以综上所述和验证,可以得到结论:
在对象添加监听时,runtime会动态生成一个NSKVONotifying_开头的类,并且该类继承自instance对象所属类

三、KVO的内部实现

从另一个方向验证:我们可以看下setAge:的IMP的实现

NSLog(@"1--%p",[p methodForSelector:@selector(setAge:)]);
[p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:nil];
NSLog(@"2--%p",[p methodForSelector:@selector(setAge:)]);

打印如下:

2018-08-08 13:31:54.659785+0800 KVO[47390:1066243] 1--0x10724a610
2018-08-08 13:31:54.660092+0800 KVO[47390:1066243] 2--0x1076a6bf4

说明方法实现不是在一个类里也再一次证明:添加监听后,runtime 创建了新的类

我们利用断点进行调试

image.png

监听之前:[Person setAge:]
监听之后:Foundation _NSSetLongLongValueAndNotify
说明添加监听之后生成的NSKVONotifying_Person对象重写了setAge :这个方法。

_NSSetLongLongValueAndNotify是Foundation 框架的内部C函数,具体源码需要通过反编译工具去看,不展开讲了。

伪代码:

_NSSetLongLongValueAndNotify(){
    [self willChangeValueForKey:@"age"];
    [super setAge:xx];
    [self didChangeValueForKey:@"age"];
}
- (void)didChangeValueForKey:(NSString *)key{
    // 通知监听器
    [oberser observeValueForKeyPath:key ofObject:self change:nil context:nil];
}

调用didChangeValueForKey内部会调用observeraddObserver:forKeyPath:options:context:方法

我们可以验证一下上述结论,由于我们不能重写NSKVONotifying_Person的类的方法,所以我们利用继承关系来验证

@implementation Person
- (void)setAge:(NSInteger)age{
    NSLog(@"setAge:");
}
- (void)willChangeValueForKey:(NSString *)key{
    [super willChangeValueForKey:key];
    NSLog(@"willChangeValueForKey:");
}
- (void)didChangeValueForKey:(NSString *)key{
    NSLog(@"didChangeValueForKey: -- start");
    [super didChangeValueForKey:key];
    NSLog(@"didChangeValueForKey: -- end");
}
@end

打印如下:

1.willChangeValueForKey:
2.setAge:
3.didChangeValueForKey: -- start
4.observeValueForKeyPath:ofObject:change:context
5.didChangeValueForKey: -- end

所以按照这个执行顺序,我们可以验证我们上述伪代码。

四、KVO的一些细节

打印出一个类的对象方法

- (void)methodNameOfClass:(Class)cls{
    unsigned int count;
    
    Method *methodList = class_copyMethodList(cls, &count);
    NSMutableArray *arr = [NSMutableArray array];
    for (int i = 0 ;  i < count; i++) {
        Method method = methodList[I];
        [arr addObject:NSStringFromSelector(method_getName(method))];
    }
    NSLog(@"%@",arr);
    free(methodList);
}

调用methodNameOfClass

[self methodNameOfClass:object_getClass(p)];

打印如下:

(
    "setAge:",
    class,
    dealloc,
    "_isKVOA"
)

除了setAge :方法还有class,dealloc,_isKVOA
class:返回类对象
dealloc:销毁等一系列操作
_isKVOA:是否是KVO对象,return YES
值得考究的是class这个函数,我们调用一下这个函数

[p class]

打印如下:

Person

为什么是Person
因为重写了class函数,并将Person类返回,苹果并不想让我们知道添加KVO监听,会动态生成一个新的派生类

五、如何手动调用KVO

很简单

[被监听对象 willChangeValueForKey:key];
[被监听对象 didChangeValueForKey:key];

如果不写willChangeValueForKey,不会触发监听方法,因为didChangeValueForKey内部会判断willChangeValueForKey之前是否被调用过

总结

1 添加KVO时,会利用runtime动态生成一个NSKVONotifying_开头的类。

2 NSKVONotifying_类继承自被监听的类,也就是instance对象的isa指针指向NSKVONotifying_类,且NSKVONotifying_superClass 指向被监听的类。

3 NSKVONotifying_类重写了class方法并返回被监听的类

4 KVO 的本质是重写了set方法,一个对象属性能否被监听,主要是看该属性有没有set方法

探究KVO本质_第1张图片
image.png

你可能感兴趣的:(探究KVO本质)