美文网首页
iOS-探究KV0本质

iOS-探究KV0本质

作者: 翀鹰精灵 | 来源:发表于2020-01-01 13:29 被阅读0次

先来看一下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<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"监听到%@的%@属性值改变了 - %@ - %@", object, keyPath, change, context);
}

- (void)touchesBegan:(NSSet<UITouch *> *)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] 监听到<BDFPerson: 0x6000008ca4a0>的age属性值改变了 - {
    kind = 1;
    new = 11;
    old = 1;
} - (null)
2019-12-30 15:45:09.058006+0800 KVODemo[19599:898383] 监听到<BDFPerson: 0x6000008ca4a0>的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本质

    先来看一下Objective-C中KVO的用法 打印结果如下: 这里我们发现,点击屏幕,调用 self.per...

  • iOS-性能优化深入探究

    iOS-性能优化深入探究 iOS-性能优化深入探究

  • iOS-isa指向图&类结构(上)

    一.isa & superclass的指向探究 靓仔们,我们在main.m中添加如下代码: 在iOS-对象的本质,...

  • iOS底层原理 - 探寻Runtime本质(三)

    1. 方法调用的本质 前两章分别对isa结构的本质、Class结构的本质做了探究,下面探究方法调用的本质。 转成C...

  • iOS-探究Runtime

    前言:本文探究iOS中Runtime相关内容,如有错误请留言指正。 Objective-C是一门动态性比较强的编程...

  • 探究+initialize本质

    记录于此! 1.代码层面验证2.阅读源码 一、代码 推导:1.只导入头文件,不会调用+initialize2.第一...

  • 探究Category本质

    细致的看了下Category的东西,记录一下。 Category用途:1.进行类扩展2.hook一个方法3.重写已...

  • 探究KVC本质

    细致的看了下KVC的东西,记录一下。 KVC:key-value-coding,键值编码。 KVC可以干什么?利用...

  • 探究KVO本质

    看了一些资料,对OC更加深入了解,记录总结一下。KVO:key-value-boserver,键-值-监听。主要是...

  • 探究block本质

    记录一下,分六个方面来探究block,一步步分析。 一.block底层结构写一个最简单的block: c++重写:...

网友评论

      本文标题:iOS-探究KV0本质

      本文链接:https://www.haomeiwen.com/subject/ypdmoctx.html