KVO的底层原理

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

基本使用方法

//
//  WKPerson.h
//  lllmmnn
//
//  Created by wukai on 2019/1/4.
//  Copyright © 2019年 诸葛小亮. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface WKPerson : NSObject

@property (nonatomic, assign) NSInteger age;

@end

//
//  WKPerson.m
//  lllmmnn
//
//  Created by wukai on 2019/1/4.
//  Copyright © 2019年 诸葛小亮. All rights reserved.
//

#import "WKPerson.h"

@implementation WKPerson

- (void)setAge:(NSInteger)age{
    
    _age = age;
}

@end

self.person1 = [[WKPerson alloc] init];
self.person1.age = 1;
    
//content 代表文本 现在监听
[person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
 person1.age = 10;

//建值更改后 会自动进入 conten传入123 这里的content就是123
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    
    NSLog(@"%@",change);
    
}

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

  • 为啥更改了键值之后就可以立马收到通知
  • 我们来深入的看看
#import "ViewController.h"

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

    self.person1.age = 10;
    NSLog(@"更改之后");
}

@end
#import "WKPerson.h"

@implementation WKPerson

- (void)setAge:(NSInteger)age{
    
    _age = age;
}

@end

2019-01-05 11:43:36.779705+0800 lllmmnn[3027:195001] {
    kind = 1;
    new = 10;
    old = 1;
}
2019-01-05 11:46:38.422867+0800 lllmmnn[3027:195001] 更改之后

结论是 执行完的结果是 set里面之后,立即通知发生更改,我们来看看更改了什么

 self.person1 = [[WKPerson alloc] init];
 self.person1.age = 1;
    
self.person2 = [[WKPerson alloc] init];
self.person2.age = 2;

NSLog(@"KVO之前 person1 = %@",object_getClass(_person1));
NSLog(@"KVO之前 person2 = %@",object_getClass(_person2) );

[_person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];

NSLog(@"KVO之后 person1 = %@",object_getClass(_person1));
NSLog(@"KVO之后 person2 = %@",object_getClass(_person2));
NSLog(@"KVO之后的父类 person2 = %@",[_person2 superclass]);
NSLog(@"KVO之后的父类 person1 = %@",[object_getClass(_person1) superclass]);

2019-01-05 11:43:24.794017+0800 lllmmnn[3027:195001] KVO之前 person1 = WKPerson
2019-01-05 11:43:24.794119+0800 lllmmnn[3027:195001] KVO之前 person2 = WKPerson
2019-01-05 11:43:33.617426+0800 lllmmnn[3027:195001] KVO之后 person1 = NSKVONotifying_WKPerson
2019-01-05 11:43:34.346046+0800 lllmmnn[3027:195001] KVO之后 person2 = WKPerson
2019-01-05 11:54:37.120260+0800 lllmmnn[3181:214328] KVO之后的父类 person2 = NSObject
2019-01-05 11:54:37.120365+0800 lllmmnn[3181:214328] KVO之后的父类 person1 = WKPerson


由此可以说明 当KVO键值监听之后,系统会通过runtime生成一个衍生类NSKVONotifying_WKPerson 继承于WKPerson

接下来我们来看看 setAge里面的实现

   
    NSLog(@"KVO之前person1=%p",[_person1 methodForSelector:@selector(setAge:)]);
    NSLog(@"KVO之前person2=%p",[_person2 methodForSelector:@selector(setAge:)]);
   
  [_person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
    NSLog(@"KVO之后person1=%p",[_person1 methodForSelector:@selector(setAge:)]);
    NSLog(@"KVO之后person2=%p",[_person2 methodForSelector:@selector(setAge:)]);
   

2019-01-05 13:52:52.379255+0800 lllmmnn[4611:339139] KVO之前person1=0x101dcc430
2019-01-05 13:52:52.379450+0800 lllmmnn[4611:339139] KVO之前person2=0x101dcc430
2019-01-05 13:52:54.873704+0800 lllmmnn[4611:339139] KVO之后person1=0x102112bf4
2019-01-05 13:52:54.873833+0800 lllmmnn[4611:339139] KVO之后person2=0x101dcc430

我们发现KVO之后person1的内存地址发生了变化
(lldb) p (IMP)0x101dcc430
(IMP) $0 = 0x0000000101dcc430 (lllmmnn`-[WKPerson setAge:] at WKPerson.m:13)
(lldb) p (IMP)0x102112bf4
(IMP) $1 = 0x0000000102112bf4 (Foundation`_NSSetLongLongValueAndNotify)

  • LLDB调试之后我们发现没有KVO的setAge方法 内部实现就是[WKPerson setAge:]
  • 调用了KVO的 setAge内部实现是调用了Foundation框架里面的 _NSSetLongLongValueAndNotify 这个方法
  • 由此我们可以得出 KVO 是在setAge方法里面调用了_NSSetLongLongValueAndNotify这个方法
#pragma mark - 获取类的所有方法
- (NSArray *)getClassMethodsWithClass:(Class)cls {
    
    NSMutableArray *mutArr = [NSMutableArray array];
    
    unsigned int outCount;
    
    /** 第一个参数:要获取哪个类的方法
     * 第二个参数:获取到该类的方法的数量
     */
    Method *methodList = class_copyMethodList(cls, &outCount);
    
    // 遍历所有的方法
    for (int i = 0; i<outCount; i++) {
        SEL name = method_getName(methodList[i]);
        [mutArr addObject:NSStringFromSelector(name)];
    }
    return [NSArray arrayWithArray:mutArr];
}

  NSArray *arry = [self getClassMethodsWithClass:object_getClass(_person1)];
    NSLog(@"%@",arry);

2019-01-05 13:58:24.512361+0800 lllmmnn[4611:339139] (
    "setAge:",
    class,
    dealloc,
    "_isKVOA"
)

推荐阅读更多精彩内容