iOS-探究KV0本质

先来看一下Objective-C中KVO的用法


#import "ViewController.h"
#import "BDFPerson.h"

@interface ViewController ()
@property (strong, nonatomic) BDFPerson *person;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.person = [[BDFPerson alloc]init];
    self.person.age = 1;
    self.person.height = 11;
    // 给person1对象添加KVO监听
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [self.person addObserver:self forKeyPath:@"age" options:options context:nil];
    [self.person addObserver:self forKeyPath:@"height" options:options context:nil];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    self.person.age = 2;
    self.person.height = 22;
}


- (void)dealloc {
    [self.person removeObserver:self forKeyPath:@"age"];
    [self.person removeObserver:self forKeyPath:@"height"];
}

@end

打印结果如下:

2019-12-30 15:45:09.057647+0800 KVODemo[19599:898383] 监听到的age属性值改变了 - {
    kind = 1;
    new = 11;
    old = 1;
} - (null)
2019-12-30 15:45:09.058006+0800 KVODemo[19599:898383] 监听到的height属性值改变了 - {
    kind = 1;
    new = 22;
    old = 2;
} - (null)

这里我们发现,点击屏幕,调用 self.person.age = 2; self.person.height = 22;方法, addObserver这个监听器可以在observeValueForKeyPath代理方法中监听到值的变化,那么这是为什么呢?

这里我们来分析一下:
  • 首先,代码
    self.person.age = 2;
    self.person.height = 22;

等同于

    [self.person setAge:2];
    [self.person setAge:22];

那么也就是说,他们都是调用set方法,set方法是相同的,所以没有差异,差异可能就会存在 person 对象本身。
我们来看下对象 person 的 isa

打印结果如下:

(lldb) po self.person.isa
NSKVONotifying_BDFPerson

  Fix-it applied, fixed expression was: 
    self.person1->isa

这里我们发现 person的isaNSKVONotifying对象。然而正常的alloc init 的示例对象,它的isa是类对象。

下面这个是没有添加监听,正常创建的对象的isa.

(lldb) po self.person.isa
BDFPerson

  Fix-it applied, fixed expression was: 
    self.person2->isa
通过对比我们发现,添加了KVO监听后的isa对象是NSKVONotifying_BDFPerson,但是这个类并不是我们创建的,它是怎么来的呢?既然不是我们创建的,我们可以猜测,它肯定是通过OC的Runtime机制,在程序动态运行的时候添加的。

NSKVONotifying_BDFPerson类窥探方法:
1.在添加监听前后,我们可以通过object_getClass(self.person)方法,来查看控制台输出变化。

   NSLog(@"----personal1添加KVO监听之前 - %@ ",
         object_getClass(self.person1));
   // 给person1对象添加KVO监听
   NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
   [self.person1 addObserver:self forKeyPath:@"age" options:options context:nil];
   [self.person1 addObserver:self forKeyPath:@"height" options:options context:nil];
   NSLog(@"----personal1添加KVO监听之后 - %@ ",
         object_getClass(self.person),

输出结果如下:

2020-01-01 11:27:34.124882+0800 KVODemo[3029:42894] ----personal1添加KVO监听之前 - BDFPerson
2020-01-01 11:27:46.480695+0800 KVODemo[3029:42894] ----personal1添加KVO监听之后 - NSKVONotifying_BDFPerson

2.通过打印IMP指针的内存地址,来查看方法。 (命令:po (IMP)内存地址)

   NSLog(@"----person1添加KVO监听之前 - %p",
         [self.person1 methodForSelector:@selector(setAge:)]);
   // 给person1对象添加KVO监听
   NSKeyValueObservingOptions options = >NSKeyValueObservingOptionNew | >NSKeyValueObservingOptionOld;
   [self.person1 addObserver:self forKeyPath:@"age" options:options context:nil];
   [self.person1 addObserver:self forKeyPath:@"height" options:options context:nil];
   
   NSLog(@"----person1添加KVO监听之后 - %p %p",
        [self.person1 methodForSelector:@selector(setAge:)]);

person1的setAge:方法的内存地址输出结果如下:

2020-01-01 11:38:31.687585+0800 KVODemo[3584:69032] ----person1添加KVO监听之前 - 0x1011f2160
2020-01-01 11:38:44.173528+0800 KVODemo[3584:69032] ----person1添加KVO监听之后 - 0x7fff257023ea

进入LLDB断点调试模式,通过po输出IMP结果如下:

(lldb) po (IMP)0x1011f2160
(KVODemo`-[BDFPerson setAge:] at BDFPerson.m:12)

(lldb) po (IMP)0x7fff257023ea
(Foundation`_NSSetIntValueAndNotify)

系统在程序运行时,动态给我们添加的NSKVONotifying_BDFPerson这个类,它其实是我们BDFPerson的一个子类。所以,如果是NSKVONotifying_BDFPerson类对象,会执行子类自己里面的setAge:方法,子类里面的这个方法和父类BDFPerson里面的setAge:是不一样的。子类里面的setAge:方法会执行Foundation框架里面的_NSSetIntValueAndNotify方法。

_NSSetIntValueAndNotify函数里面的逻辑如下:

-(void)setAge:(int)age {
[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey:@"age"];
}

-(void)didChangeValueForKey:(NSString *)key {
// 通知监听器
[observe observeValueForKeyPath:key ofObject:self change:nil context:nil];
}

所以,本质就是KVO的isa不一样,所以执行的set方法的实现也就不一样。

总结:如果是正常创建的对象person1 ,它的isa是Person它本身。
如果是添加了监听的对象person2,它的isa是 NSKVONotifying_

1.正常创建的Person类 调用setAge:方法。
2.添加监听的Peson类,实际上是NSKVONotifying_类,它里面的set方法如下:
-(void)setAge:(int)age {
[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey:@"age"];
}

所以,添加监听的对象和正常创建的对了,set方法内部实现的原理不一样。所以也就能解释为何添加了监听的对象能够监听值的变化问题了。

你可能感兴趣的:(iOS-探究KV0本质)