KVO详解-本质分析


一、KVO基本用法

//例如有一个Person,里面有个属性age
self.person = [[Peerson alloc]init];
self.person.age = 10;

//给person对象添加一个kvo监听
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person addObserver:self forKeyPath:@"age" options:options context:nil];
//给当前的控制器添加一个person的监听对象用用来监听age

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.person.age = 20;//当屏幕点击的时候,改变page
}
//当监听到age发生改变的时候,会调用下面这个方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    // object 就是self.person对象
    // keyPath就是 age
    // change 就是 {"old":10,"new":20,"kind":1}
}
//注意移除
- (void)dealloc{
	[self.person removeObserver:self forKeyPath:@"age"];
}

二、KVO本质分析

1.为何监听后会变化

self.person1 = [[Peerson alloc]init];
self.person1.age = 1;

self.person2 = [[Peerson alloc]init];
self.person2.age = 2;

//给person1添加一个监听
 NSKeyValueObservingOptions  options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:nil];

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//    self.person1.age = 20;
//    self.person2.age = 30;
    [self.person1 setAge:20];
    
    [self.person2 setAge:30];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"监听到%@的%@属性值发生了改变%@", object, keyPath, change);
}
/*
打印:
监听到<MJPerson: 0x600000a388f0>的age属性值发生了改变{
    kind = 1;
    new = 20;
    old = 1;
}
*/

为什么上面代码能监听到person1的改变而监听不到person2改变????

2.分析发生变化

此时在上述代码中打印一下person1的isa和person2的isa

/*
 p self.person1.isa
(Class) $0 = NSKVONotifying_MJPerson
  Fix-it applied, fixed expression was: 
    self.person1->isa
 ------------------------   
 p self.person2.isa
(Class) $1 = MJPerson
  Fix-it applied, fixed expression was: 
    self.person2->isa 
 */

我们会发现 self.person1.isa = NSKVONotifying_MJPerson,
self.person2.isa = MJPerson
我们知道,实例对象指针指向的位置,就是类对象,说明self.person1的类对象是NSKVONotifying_MJPerson已经不是原来的MJPerson
NSKVONotifying_MJPerson这个类对象其实,是runtime动态创建出来的,并且它是继承MJPerson类的
用关系图表示:
请添加图片描述
而NSKVONotifying_MJPerson类对象中,肯定有isa,superclass,setAge(其它的暂时不用看)
当调用self.person1.age = 20的时候,

  1. 会先创建NSKVONotifying_MJPerson类继承MJPerson
  2. 里面会有一个setAge方法,
  3. setAge方法中调用了Foundation中的_NSSetIntValueNotify方法
    大概用伪代码来实现一下
//  ----------------NSKVONotifying_MJPerson.h文件-----------------
#import "MJPerson.h"

NS_ASSUME_NONNULL_BEGIN

@interface NSKVONotifying_MJPerson : MJPerson

@end

NS_ASSUME_NONNULL_END

//  ----------------NSKVONotifying_MJPerson.m文件-----------------

#import "NSKVONotifying_MJPerson.h"

@implementation NSKVONotifying_MJPerson
- (void)setAge:(int)age{
    //肯定会调用Foundation中的这个方法
    _NSSetIntValueNotify();
}


//因为Foundation代码没有开源,只能猜测NSSetIntValueNotify方法的实现
void NSSetIntValueNotify(){
    
    [self willChangeValueForKey:@"age"];
    
    [super setAge:age];//调用原来的set方法
    
    [self didChangeValueForKey:@"age"];
    
}

- (void)didChangeValueForKey:(NSString *)key{
    //通知监听器,某某属性发生了改变
    [observer observerValueForKeyPath:key ofObject:self change:nil context:nil];
}


@end

而self.person2的指针指向MJPerson类对象,直接从MJPerson类对象中找到setAge方法实现就可以了.
用图看一下就是这样:
请添加图片描述

三、KVO本质的验证

看看添加之前和添加之后,它们的类对象有哪些变化

#import <objc/runtime.h>

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    self.person1 = [[MJPerson alloc]init];
    self.person1.age = 1;
    self.person1.height = 11;
    
    self.person2 = [[MJPerson alloc]init];
    self.person2.age = 2;
    self.person2.height = 22;
  //
    NSLog(@"person1添加kvo监听之前类对象 %@ - %@", object_getClass(self.person1),
          object_getClass(self.person2));
    //给person1的age添加一个监听
    NSKeyValueObservingOptions  options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [self.person1 addObserver:self forKeyPath:@"age" options:options context:nil];
    
    NSLog(@"person1添加kvo监听之后类对象 %@ - %@", object_getClass(self.person1),
          object_getClass(self.person2));

/*
打印:
1651740686.076586 person1添加kvo监听之前类对象 MJPerson - MJPerson
1651740686.076676 person1添加kvo监听之后类对象 NSKVONotifying_MJPerson - MJPerson
*/
    
}

添加之前类对象是MJPerson,执行的是MJPerson中setAge方法
添加以后person1类对象是NSKVONotifying_MJPerson,那么究竟执行的NSKVONotifying_MJPerson中哪个方法

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    self.person1 = [[MJPerson alloc]init];
    self.person1.age = 1;
    self.person1.height = 11;
    
    self.person2 = [[MJPerson alloc]init];
    self.person2.age = 2;
    self.person2.height = 22;
    
    //methodForSelector这个可以看到执行方法的内存地址
    NSLog(@"person1添加kvo监听之前执行的方法 %p -%p", [self.person1 methodForSelector:@selector(setAge:)],
          [self.person2 methodForSelector:@selector(setAge:)]);
    
    
    //给person1的age添加一个监听
    NSKeyValueObservingOptions  options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [self.person1 addObserver:self forKeyPath:@"age" options:options context:nil];
    

    NSLog(@"person1添加kvo监听之后执行的方法 %p -%p", [self.person1 methodForSelector:@selector(setAge:)],
          [self.person2 methodForSelector:@selector(setAge:)]);
  /*
  打印:
  1651741040.976810 person1添加kvo监听之前执行的方法 0x1061d8da0 -0x1061d8da0
  1651741040.976951 person1添加kvo监听之后执行的方法 0x7fff207b1cfb -0x1061d8da0
  */
}

可以看到:添加kvo之前person1和person2都是执行的同一个方法0x1061d8da0,
添加kvo以后person1执行的是0x7fff207b1cfb这个方法
那么如何查看0x1061d8da0对应的具体方法???
通过p (IMP)0x1061d8da0转换,如图
请添加图片描述

NSKVONotifying_MJPerson的isa指针指向自己的元类,meta-NSKVONotifying_MJPerson

四、窥探KVO本质执行顺序

至于判断Foundation中有没有NSSetIntValueAndNotify方法,只能通过反编译查看。这里不做详细介绍
前面提到说_NSSetIntValueNotify中先调用willChangeValueForKey在调用setAge:age在调用didChangeValueForKey,然后didChangeValueForKey中调用observerValueForKeyPath:key,方法进行验证一下

- (void)setAge:(int)age{
    //肯定会调用Foundation中的这个方法
    _NSSetIntValueNotify();
}


//因为Foundation代码没有开源,只能猜测NSSetIntValueNotify方法的实现
void NSSetIntValueNotify(){
    
    [self willChangeValueForKey:@"age"];
    
    [super setAge:age];//调用原来的set方法
    
    [self didChangeValueForKey:@"age"];
    
}

- (void)didChangeValueForKey:(NSString *)key{
    //通知监听器,某某属性发生了改变
    [observer observerValueForKeyPath:key ofObject:self change:nil context:nil];
}

我们在person中重新写 willChangeValueForKey这些方法,看看执行顺序如图
请添加图片描述

五、获取NSKVONotifying_MJPerson中类方法

- (void)printMethodNamesOfClass:(Class)cls{
    unsigned int count;
    // 获得方法数组
    Method *methodList = class_copyMethodList(cls, &count);
    // 存储方法名
    NSMutableString *methodNames = [NSMutableString string];
    // 遍历所有的方法
    for (int i = 0; i < count; i++) {
        // 获得方法
        Method method = methodList[i];
        // 获得方法名
        NSString *methodName = NSStringFromSelector(method_getName(method));
        // 拼接方法名
        [methodNames appendString:methodName];
        [methodNames appendString:@", "];
    }
    // 释放
    free(methodList);
    // 打印方法名
    NSLog(@"%@ %@", cls, methodNames);
}
- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.person1 = [[MJPerson alloc] init];
    self.person1.age = 1;
    
    self.person2 = [[MJPerson alloc] init];
    self.person2.age = 2;
    
    // 给person1对象添加KVO监听
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
    
    [self printMethodNamesOfClass:object_getClass(self.person1)];
    [self printMethodNamesOfClass:object_getClass(self.person2)];
}
/*
	打印:
	Interview01[17722:182442] NSKVONotifying_MJPerson setAge:, class, dealloc, _isKVOA,
    Interview01[17722:182442] MJPerson age, setAge:,
*/

从打印结果就可以看出NSKVONotifying_MJPerson中包含了setAge:, class, dealloc, _isKVOA这些方法
如果给peson1添加kvo那么它的指针其实已经指向了NSKVONotifying_MJPerson,调用的方法也是NSKVONotifying_MJPerson中的setAge

class:重写了父类的class,当你用[self.person1 class]获取类对象的时候还是MJPerson,而不是 NSKVONotifying_MJPerson,为了就是让你不知道它的存在,蒙蔽你
dealloc:方法中做了一些销毁,收尾工作,比如动态创建的类,需要销毁
_isKVOA: 应该是返回YES

下面附上一张面试题:
请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值