OC底层学习-KVO的本质

1.什么是KVO?

KVO的全称是Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变

1.1 KVO的本质分析

创建一个简单的KVO监听实例:

@interface GYPerson : NSObject
/** <#descrption#> */
@property (nonatomic, assign) int age;
@end
@implementation GYPerson

@end

//在viewcontroller里面监听
@interface ViewController ()

/** <#descrption#> */
@property (nonatomic, strong) GYPerson *person1;

/** <#descrption#> */
@property (nonatomic, strong) GYPerson *person2;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.person1 = [[GYPerson alloc] init];
    self.person2 = [[GYPerson alloc] init];
    
    self.person1.age = 1;
    self.person2.age = 2;
    
    //使用KVO来监听属性 -->  person1的name属性被当前控制器监听了,只要发发生改变就会通知回调方法
    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 = 11;
    self.person2.age = 22;
}

- (void)dealloc {
    [self.person1 removeObserver:self forKeyPath:@"age" context:nil];
}

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


@end

当我们点击屏幕的时候,KVO的监听方法执行了一次,为什么person1和person2 都调用了age属性的set方法,调用的结果不一样了.我们猜测程序可能对person1和person2两个对象做了什么事情,那么我们来查看下person1,person2的isa指针
在这里插入图片描述
由此可见被KVO监听的对象,会创建一个NSKVONotifying_GYPerson类(是使用Runtime动态创建的一个类,是GYPerson的子类。

然后person1的isa指正实际上是指向新创建的NSKVONotifying_GYPerson这个类, 而没有被KVO监听的person2的isa指针还是 指向person2的class对象,单是person1却是改变了,isa指针并没有指向原先的class对象而是指向NSKVONotifying_GYPerson的class对象。

我们都知道instance对象调用对象方法时,首先是通过instance的isa指针,找到class对象,但是现在person1的class对象发生了改变,person2的class对象没有发生变化,当person1的setAge()方法时,实际是调用NSKVONotifying_GYPerson中的setAge()方法,而不是原先class对象中的setAge()。而person2是调用原先class对象的setAge(),这也就是结果不同的原因。

  • 那NSKVONotifying_GYPerson中的setAge()中做了什么事情了?

    1. 实际是调用Core Fountion 中的_NSSetIntValueAndNotif(_NSSet*ValueAndNotify),
    2. _NSsetIntValueAndNotify方法内部的操作相当于
      • 调用 willChangeValueForKey()方法
      • 调用父类的 属性setter方法实现
      • 调用 didChangeValueForKey()方法,然后在这个方法里面通知监听器
  • 未使用KVO监听的对象
    在这里插入图片描述

  • 使用KVO监听的对象
    在这里插入图片描述

  • 总结 :

    1. 一个属性能否被KVO监听,看这个属性是否具有set方法
    2. KVO的本质把被监听属性的set方法的实现给换掉。

1.2 KVO的本质验证

1.2.1 class对象验证

 NSLog(@"KVO监听之前person1-==%@  person2=== %@", object_getClass(self.person1),object_getClass(self.person2));
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [self.person1 addObserver:self forKeyPath:@"age" options:options context:nil];
    NSLog(@"KVO监听之后person1-==%@  person2=== %@", object_getClass(self.person1),object_getClass(self.person2));

输出结果:
在这里插入图片描述
由此可见 一旦添加监听,类对象就不一样了

1.2.2 方法验证

我们可以获取KVO监听前后的方法实现地址

  • - (IMP)methodForSelector:(SEL)aSelector:传入一个方法,返回这个方法的具体实现(IMP:方法的具体实现)
NSLog(@"KVO监听之前person1-==%p  person2=== %p", [self.person1 methodForSelector:@selector(setAge:)],[self.person2 methodForSelector:@selector(setAge:)]);
    NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [self.person1 addObserver:self forKeyPath:@"age" options:options context:nil];
    NSLog(@"KVO监听之后person1-==%p  person2=== %p", [self.person1 methodForSelector:@selector(setAge:)],[self.person2 methodForSelector:@selector(setAge:)]);

在这里插入图片描述

结论:由上述结果可知,在KVO监听之前,两个对象的setAge()实现是一样的,单是在KVO监听之后,person1的方法具体实现发生变化,但是person2的方法实现还是跟以前一样

接下来我们可以使用LLDB调试来打印下方法的实现
在这里插入图片描述

结论:由此可见KVO监听前后的方法的具体实现是不一样的

1.2.3 验证NSKVONotifying_GYPerson中的方法

首先创建一个打印class对象方法列表的方法

- (void)printMethodNamesOfClass:(Class)cls {
    //1.你传递一个class兑现,我打印出对象中的方法列表
    //首先我们获取方法列表
    unsigned int count;
    Method *methodList = class_copyMethodList(cls, &count);
    NSMutableString *nameStr = [[NSMutableString alloc] init];
    //指针实际可以当做一个数组来用,遍历数组
    for (int i = 0; i < count; i++) {
        //获取方法
        Method method = methodList[i];
        [nameStr appendFormat:@"%@, ",NSStringFromSelector(method_getName(method))];
    }

    //释放内存
    free(methodList);
    
    NSLog(@"%s ----- %@",class_getName(cls),nameStr);                                    
}

//直接调用打印
 NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
    [self.person1 addObserver:self forKeyPath:@"age" options:options context:nil];
 
 /**
 通过instance的对象获取的class方法,是获取原来对象原来的class对象(也就是GYPerson的class对象),但是 经过KVO知乎person1对象的isa指正被程序修改了指向了NSKVONotifying_GYPerson类对象。 所以我们在KVO之后使用`object_getClass()`获取真正的person1的isa指针指向的class对象,而通过class方法是无法获取person1的isa指针指向的真正的class对象
 */
//    [self printMethodNamesOfClass:[self.person1 class]]; 
//    [self printMethodNamesOfClass:[self.person2 class]];
    [self printMethodNamesOfClass:object_getClass(self.person1)];
    [self printMethodNamesOfClass:object_getClass(self.person2)];

打印结果:
在这里插入图片描述

由此我们可以看到NSKVONotifying_GYPerson对象中存在的方法,确实有:setAge:、class、dealloc、_isKVOA 几个方法

为什么NSKVONotifying_GYPerson中没有get方法,因为父类中已经有get方法,KVO只是重写了set方法.

2.面试题

2.1 IOS用什么方式实现一个对象的KVO?(KVO的本质是什么?)

  1. 利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类
  2. 当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数
    1. 调用 willChangeValueForKey()方法
    2. 调用父类原来属性的setter方法实现
    3. 调用 didChangeValueForKey()方法,然后在这个方法里面通知监听器
      • 内部会触发监听器(Oberser)的监听方法:observeValueForKeyPath:ofObject:change:context:

2.2 如何手动触发KVO?

  • 手动调用监听对象的willChangeValueForKey()方法和didChangeValueForKey()方法就可以达到手动触发KVO。

2.3 直接修改成员变量会触发KVO么?

  • 不会触发KVO(因为KVO的本质上把监听属性的set方法实现给换掉,但是直接修改成员变量是不会触发set方法,所以是不会触发KVO的)。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值