KVO

作者: 紫荆秋雪_文 | 来源:发表于2018-07-01 14:01 被阅读2次

一、KVO介绍

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

二、KVO实例

通过一个简单的例子来演示一下KVO,首先创建一个RevanPerson类,并且定义一个age属性,我们通过KVO来监听age值的改变

  • 测试代码
/*************** RevanPerson.h ***************/
#import <Foundation/Foundation.h>

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

@end

/*************** RevanPerson.m ***************/
#import "RevanPerson.h"

@implementation RevanPerson

@end

/************* KVO测试代码 ***************/
#import "ViewController.h"
#import "RevanPerson.h"

@interface ViewController ()
@property (nonatomic, strong) RevanPerson *revanP1;

@end

@implementation ViewController

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

- (void)viewDidLoad {
    [super viewDidLoad];
    //revanP1
    self.revanP1 = [[RevanPerson alloc] init];
    self.revanP1.age = 11;
    //策略
    NSKeyValueObservingOptions options =  NSKeyValueObservingOptionNew |
    NSKeyValueObservingOptionOld;
    //给self.revanP1添加一个监听者:self
    [self.revanP1 addObserver:self forKeyPath:@"age" options:options context:nil];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.revanP1.age = 12;
}

#pragma mark - KVO的监听方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"\nkeyPath=%@\nobject=%@\nchange=%@\n"
          ,keyPath
          ,object
          ,change);
}

@end

当点击屏幕会触发touchesBegan:withEvent:方法来修改age的大小,由于age值的改变会触发KVO的
监听方法observeValueForKeyPath:ofObject:change:
输出如下:
keyPath=age
object=<RevanPerson: 0x60400000fa20>
change={
    kind = 1;
    new = 12;//age改变后的新值
    old = 11;  //age改变前的旧值
}

三、KVO原理分析

1、为了分析KVO原理,重新创建一个revanP2实例,但是不给revanP2实例添加KVO

  • 测试源码
/*************** RevanPerson.h ***************/
#import <Foundation/Foundation.h>

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

@end
/*************** RevanPerson.m ***************/
#import "RevanPerson.h"

@implementation RevanPerson

@end

/************* KVO测试代码 ***************/
#import "ViewController.h"
#import "RevanPerson.h"

@interface ViewController ()
@property (nonatomic, strong) RevanPerson *revanP1;
@property (nonatomic, strong) RevanPerson *revanP2;
@end

@implementation ViewController

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

- (void)viewDidLoad {
    [super viewDidLoad];
    //revanP1
    self.revanP1 = [[RevanPerson alloc] init];
    self.revanP1.age = 11;
    
    
    self.revanP2 = [[RevanPerson alloc] init];
    self.revanP2.age = 21;
    //策略
    NSKeyValueObservingOptions options =  NSKeyValueObservingOptionNew |
    NSKeyValueObservingOptionOld;
    //给self.revanP1添加一个监听者:self
    [self.revanP1 addObserver:self forKeyPath:@"age" options:options context:nil];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.revanP1.age = 12;
    self.revanP2.age = 22;
}

#pragma mark - KVO的监听方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"\nkeyPath=%@\nobject=%@\nchange=%@\n"
          ,keyPath
          ,object
          ,change);
}

打印输出:只有revanP1的输出
keyPath=age
object=<RevanPerson: 0x600000014cb0>
change={
    kind = 1;
    new = 12;
    old = 11;
}

在触发touchesBegan:withEvent:方法时会给revanP1实例对象和revanP2实例对象的age赋值,会触发age的setAge:方法。Objective-C对象的分类中说过,对象方法信息是存储在class对象中的,类方法信息是存储在meta-class对象中的。所以我们先查看一下revanP1、revanP2的class对象

  • 测试源码
/************* KVO测试代码 ***************/
#import "ViewController.h"
#import "RevanPerson.h"
#import <objc/runtime.h>

@interface ViewController ()
@property (nonatomic, strong) RevanPerson *revanP1;
@property (nonatomic, strong) RevanPerson *revanP2;
@end

@implementation ViewController

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

- (void)viewDidLoad {
    [super viewDidLoad];
    //revanP1
    self.revanP1 = [[RevanPerson alloc] init];
    self.revanP1.age = 11;
    
    
    self.revanP2 = [[RevanPerson alloc] init];
    self.revanP2.age = 21;
    
    Class revanP1_class = object_getClass(self.revanP1);
    Class revanP2_class = object_getClass(self.revanP2);
    NSLog(@"\nrevanP1KVO之前的class对象:%p-%@", revanP1_class, revanP1_class);
    NSLog(@"\nrevanP2的class对象:%p-%@", revanP2_class, revanP2_class);
    //策略
    NSKeyValueObservingOptions options =  NSKeyValueObservingOptionNew |
    NSKeyValueObservingOptionOld;
    //给self.revanP1添加一个监听者:self
    [self.revanP1 addObserver:self forKeyPath:@"age" options:options context:nil];
    Class revanP1_class_kvo = object_getClass(self.revanP1);
    NSLog(@"\nrevanP1KVO之后的class对象:%p-%@", revanP1_class_kvo, revanP1_class_kvo);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.revanP1.age = 12;
    self.revanP2.age = 22;
}

#pragma mark - KVO的监听方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"\nkeyPath=%@\nobject=%@\nchange=%@\n"
          ,keyPath
          ,object
          ,change);
}

@end

打印输出:
revanP1KVO之前的class对象:0x10b0dc0b8-RevanPerson
revanP2的class对象:0x10b0dc0b8-RevanPerson
revanP1KVO之后的class对象:0x604000116140-NSKVONotifying_RevanPerson
  • 在revanP1添加KVO之前,revanP1实例对象和revanP2实例对象的class对象都是是RevanPerson,在revanP1添加了KVO之后revan_P1实例对象的class对象是NSKVONotifying_RevanPerson。
  • 既然revanP1对象的class对象是NSKVONotifying_RevanPerson,那为什么在给revanP1对象的age重新赋值的时候还可以改变age的值呢?NSKVONotifying_RevanPerson中有age这个属性?我们猜测NSKVONotifying_RevanPerson的父类是RevanPerson类
/************* KVO测试代码 ***************/
#import "ViewController.h"
#import "RevanPerson.h"
#import <objc/runtime.h>

@interface ViewController ()
@property (nonatomic, strong) RevanPerson *revanP1;
@property (nonatomic, strong) RevanPerson *revanP2;
@end

@implementation ViewController

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

- (void)viewDidLoad {
    [super viewDidLoad];
    //revanP1
    self.revanP1 = [[RevanPerson alloc] init];
    self.revanP1.age = 11;
    
    
    self.revanP2 = [[RevanPerson alloc] init];
    self.revanP2.age = 21;
    
    Class revanP1_class = object_getClass(self.revanP1);
    Class revanP2_class = object_getClass(self.revanP2);
    NSLog(@"\nrevanP1KVO之前的class对象:%p-%@", revanP1_class, revanP1_class);
    NSLog(@"\nrevanP2的class对象:%p-%@", revanP2_class, revanP2_class);
    //策略
    NSKeyValueObservingOptions options =  NSKeyValueObservingOptionNew |
    NSKeyValueObservingOptionOld;
    //给self.revanP1添加一个监听者:self
    [self.revanP1 addObserver:self forKeyPath:@"age" options:options context:nil];
    Class revanP1_class_kvo = object_getClass(self.revanP1);
    NSLog(@"\nrevanP1KVO之后的class对象:%p-%@", revanP1_class_kvo, revanP1_class_kvo);
    Class revanP1_superclass_kvo = [revanP1_class_kvo superclass];
    NSLog(@"\nrevanP1KVO之后的superclass对象:%p-%@", revanP1_superclass_kvo, revanP1_superclass_kvo);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.revanP1.age = 12;
    self.revanP2.age = 22;
}

#pragma mark - KVO的监听方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"\nkeyPath=%@\nobject=%@\nchange=%@\n"
          ,keyPath
          ,object
          ,change);
}

@end

打印输出:
revanP1KVO之前的class对象:0x1074dd0e0-RevanPerson
revanP2的class对象:0x1074dd0e0-RevanPerson
revanP1KVO之后的class对象:0x60000011c170-NSKVONotifying_RevanPerson
revanP1KVO之后的superclass对象:0x1074dd0e0-RevanPerson

  • 通过代码测试后我们发现NSKVONotifying_RevanPerson这个类是RevanPerson类的子类。

2、NSKVONotifying_RevanPerson分析

在revanP1添加KVO的时候,系统会通过runtime动态的创建一个NSKVONotifying_RevanPerson类,并且把revanP1的isa指针指向NSKVONotifying_RevanPerson的class对象。当在给age赋值的时候其实是调用了age的setAge:方法,revanP2会直接调用RevanPerson中的setAge:方法,假设NSKVONotifying_RevanPerson类中没有重写setAge:方法,那么给revanP1.age赋值和给revanP2.age赋值的效果是一样的了,但是实际的情况是没有添加KVO的revanP2在给age赋值的时候是不会触发observeValueForKeyPath:ofObject:change:监听方法,所以猜测NSKVONotifying_RevanPerson类中重写了age的setAge:方法

/************* KVO测试代码 ***************/
#import "ViewController.h"
#import "RevanPerson.h"
#import <objc/runtime.h>

@interface ViewController ()
@property (nonatomic, strong) RevanPerson *revanP1;
@property (nonatomic, strong) RevanPerson *revanP2;
@end

@implementation ViewController

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

- (void)viewDidLoad {
    [super viewDidLoad];
    //revanP1
    self.revanP1 = [[RevanPerson alloc] init];
    self.revanP1.age = 11;
    
    
    self.revanP2 = [[RevanPerson alloc] init];
    self.revanP2.age = 21;
    
    Class revanP1_class = object_getClass(self.revanP1);
    Class revanP2_class = object_getClass(self.revanP2);
    IMP revanP1_imp = [self.revanP1 methodForSelector:@selector(setAge:)];
    IMP revanP2_imp = [self.revanP1 methodForSelector:@selector(setAge:)];

//    NSLog(@"\nrevanP1KVO之前的class对象:%p-%@", revanP1_class, revanP1_class);
//    NSLog(@"\nrevanP2的class对象:%p-%@", revanP2_class, revanP2_class);
    NSLog(@"\nrevanP1KVO之前setAge:地址:%p", revanP1_imp);
    NSLog(@"\nrevanP2的setAge:地址:%p", revanP2_imp);
    
    
    //策略
    NSKeyValueObservingOptions options =  NSKeyValueObservingOptionNew |
    NSKeyValueObservingOptionOld;
    //给self.revanP1添加一个监听者:self
    [self.revanP1 addObserver:self forKeyPath:@"age" options:options context:nil];
    
    
    Class revanP1_class_kvo = object_getClass(self.revanP1);
    Class revanP1_superclass_kvo = [revanP1_class_kvo superclass];
    IMP revanP1_imp_kvo = [self.revanP1 methodForSelector:@selector(setAge:)];
    
//    NSLog(@"\nrevanP1KVO之后的class对象:%p-%@", revanP1_class_kvo, revanP1_class_kvo);
//    NSLog(@"\nrevanP1KVO之后的superclass对象:%p-%@", revanP1_superclass_kvo, revanP1_superclass_kvo);
    NSLog(@"\nrevanP1KVO之后setAge:地址:%p", revanP1_imp_kvo);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.revanP1.age = 12;
    self.revanP2.age = 22;
}

#pragma mark - KVO的监听方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"\nkeyPath=%@\nobject=%@\nchange=%@\n"
          ,keyPath
          ,object
          ,change);
}

@end
打印输出:
revanP1KVO之前setAge:地址:0x1021317c0
revanP2的setAge:地址:0x1021317c0
revanP1KVO之后setAge:地址:0x102477bf4
  • 在revanP1添加KVO之前,revanP1和revanP2在给age赋值的时候调用的方法是同一个方法。在revanP1添加KVO之后,revanP1在给age赋值时调用了另一个方法。这就说明在NSKVONotifying_RevanPerson中重写了父类的setAge:方法
  • 查看revanP1和revanP2在给age赋值的时候各自调用了什么方法。revanP2调用了setAge:方法而revanP1调用了Foundation中的_NSSetLongLongValueAndNotify函数 查看调用的方法.png
  • 查看对象方法
/*************** RevanPerson.h ***************/
#import <Foundation/Foundation.h>

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

@end

/*************** RevanPerson.m ***************/
#import "RevanPerson.h"

@implementation RevanPerson

@end

/************* KVO测试代码 ***************/
#import "ViewController.h"
#import "RevanPerson.h"
#import <objc/runtime.h>

@interface ViewController ()
@property (nonatomic, strong) RevanPerson *revanP1;
@property (nonatomic, strong) RevanPerson *revanP2;
@end

@implementation ViewController

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

- (void)viewDidLoad {
    [super viewDidLoad];
    //revanP1
    self.revanP1 = [[RevanPerson alloc] init];
    self.revanP1.age = 11;
    
    self.revanP2 = [[RevanPerson alloc] init];
    self.revanP2.age = 21;
    
    Class revanP1_class = object_getClass(self.revanP1);
    Class revanP2_class = object_getClass(self.revanP2);
    NSLog(@"KVO之前revanP1,class对象中对象方法信息");
    [self methodNamesOfClass:revanP1_class];
    NSLog(@"KVO之前revanP2,class对象中对象方法信息");
    [self methodNamesOfClass:revanP2_class];
    
    //策略
    NSKeyValueObservingOptions options =  NSKeyValueObservingOptionNew |
    NSKeyValueObservingOptionOld;
    //给self.revanP1添加一个监听者:self
    [self.revanP1 addObserver:self forKeyPath:@"age" options:options context:nil];
    
    Class revanP1_class_kvo = object_getClass(self.revanP1);
    NSLog(@"KVO之后revanP1,class对象中对象方法信息");
    [self methodNamesOfClass:revanP1_class_kvo];
    NSLog(@"KVO之后revanP2,class对象中对象方法信息");
    [self methodNamesOfClass:revanP2_class];

}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.revanP1.age = 12;
    self.revanP2.age = 22;
}

#pragma mark - KVO的监听方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"\nkeyPath=%@\nobject=%@\nchange=%@\n"
          ,keyPath
          ,object
          ,change);
}

- (void)methodNamesOfClass:(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);
}

@end

打印输出:
KVO之前revanP1,class对象中对象方法信息:
RevanPerson setAge:, age,
KVO之前revanP2,class对象中对象方法信息:
RevanPerson setAge:, age,
KVO之后revanP1,class对象中对象方法信息:
NSKVONotifying_RevanPerson setAge:, class, dealloc, _isKVOA,
KVO之后revanP2,class对象中对象方法信息:
RevanPerson setAge:, age,
  • revanP2没有添加KVO RevanPersoninstance对象.png
  • revanP1添加KVO KVO底层原理.png

四、小结

  • KVO本质
    • 利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指针指向这个全新的子类
    • 当修改instance对象的属性时,全新的子类会重写属性的set方法,会调用Foundation的_NSSetXXXValueAndNotify函数
      • willChangeValueForKey:
      • 父类原来的setter方法
      • didChangeValueForKey:
      • 内部会触发监听器的监听方法observeValueForKeyPath:ofObject:change:context:
  • 如何手动触发KVO
    • 手动调用willChangeValueForKey:和didChangeValueForKey:
  • 一个属性能不能使用KVO来监听,取决于是否可以重写该属性的set方法

相关文章

  • iOS原理篇(一): KVO实现原理

    KVO实现原理 什么是 KVO KVO 基本使用 KVO 的本质 总结 一 、 什么是KVO KVO(Key-Va...

  • 04. KVO使用,原理,本质

    问题 KVO日常使用 KVO原理(KVO本质是什么) 如何手动触发KVO 直接修改成员变量会触发KVO吗 KVO图...

  • 20.iOS底层学习之KVO 原理

    本篇提纲1、KVO简介;2、KVO的使用;3、KVO的一些细节;4、KVO的底层原理; KVO简介 KVO全称Ke...

  • 深入理解KVO

    iOS | KVO | Objective-C KVO的本质是什么,如何手动触发KVO? 1.什么是KVO KVO...

  • OC语法:KVO的底层实现

    一、KVO是什么二、怎么使用KVO三、KVO的底层实现四、KVO常见面试题 一、KVO是什么 KVO全称Key-V...

  • KVO基本使用

    分三部分解释KVO一.KVO基本使用二.KVO原理解析三.自定义实现KVO 一、KVO基本使用 使用KVO,能够非...

  • KVO 解析

    KVO解析(一) —— 基本了解KVO解析(二) —— 一个简单的KVO实现KVO解析(三) —— KVO合规性K...

  • KVO

    目录 1. KVO的使用1.1 KVO基本使用方法1.2 KVO手动触发模式1.3 KVO属性依赖1.4 KVO容...

  • OC语言之KVO与KVC

    KVO 什么是KVO? KVO 是 Key-value observing(键值观察)的缩写。 KVO是Objec...

  • 可能碰到的iOS笔试面试题(7)--KVO-KVC

    KVC-KVO KVC的底层实现? KVO的底层实现? 什么是KVO和KVC? KVO的缺陷? KVO是一个对象能...

网友评论

      本文标题:KVO

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