iOS 开发 Objective-C

iOS 底层 day04 KVO

2020-08-25  本文已影响0人  望穿秋水小作坊

一、基础 KVO 的日常使用

一般情况,分如下三个步骤:

// 1. 进行监听
[self.person1 addObserver:self forKeyPath:@"height" options:3 context:@"123"];

// 2. 添加监听回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"是监听到了%@实例对象的%@值,改变了%@,context:%@", object, keyPath, change, context);
}
// 3. 销毁监听
- (void)dealloc {
    [self.person1 removeObserver:self forKeyPath:@"height"];
}

二、探求 KVO 的本质,并从代码层面上求证

1. 先说结论,如下图
KVO 原理图
2. 代码观察被 KOV 实例对象的 isa 指针
- (void)viewDidLoad {
    [super viewDidLoad];
    
    SPPerson *person1 = [[SPPerson alloc] init];
    person1.height = 1;
    SPPerson *person2 = [[SPPerson alloc] init];
    person2.height = 2;
    NSLog(@"请在此处打断点1");
    [person1 addObserver:self forKeyPath:@"height" options:3 context:@"123"];
    NSLog(@"请在此处打断点2");
}
(lldb) p person1->isa
(Class) $0 = SPPerson
(lldb) p person2->isa
(Class) $1 = SPPerson
2020-08-25 15:42:17.983376+0800 Demo-KVO[9298:415101] 请在此处打断点1
(lldb) p person1->isa
(Class) $2 = NSKVONotifying_SPPerson
(lldb) p person2->isa
(Class) $3 = SPPerson
3. 代码观察NSKVONotifying_SPPerson类对象方法列表
- (void)viewDidLoad {
    [super viewDidLoad];
    
    SPPerson *person1 = [[SPPerson alloc] init];
    person1.height = 1;
    NSLog(@"KVO 之前 %@ 的方法列表:", object_getClass(person1));
    [self printClassMethods:object_getClass(person1)];
    [person1 addObserver:self forKeyPath:@"height" options:3 context:@"123"];
    NSLog(@"KVO 之后  %@ 的方法列表:", object_getClass(person1));
    [self printClassMethods:object_getClass(person1)];
    
}
- (void)printClassMethods:(Class) cls {
    unsigned int count;
    Method *methodList = class_copyMethodList(cls, &count);
    NSMutableString *methodNames = [[NSMutableString alloc] init];
    for (int i = 0 ; i < count; i++) {
        NSString *name = NSStringFromSelector(method_getName(methodList[i]));
        [methodNames appendString:name];
        [methodNames appendString:@"  ,  "];
    }
    NSLog(@"%@", methodNames);
}
Demo-KVO[10337:501248] KVO 之前 SPPerson 的方法列表:
Demo-KVO[10337:501248] height  ,  setHeight:  ,  weight  ,  setWeight:  ,
Demo-KVO[10337:501248] KVO 之后  NSKVONotifying_SPPerson 的方法列表:
Demo-KVO[10337:501248] setHeight:  ,  class  ,  dealloc  ,  _isKVOA  ,

4. 代码观察被 KOV 实例对象的 set 方法
- (void)viewDidLoad {
    [super viewDidLoad];
    
    SPPerson *person1 = [[SPPerson alloc] init];
    person1.height = 1;
    NSLog(@"%p",[person1 methodForSelector:@selector(setHeight:)]);
    [person1 addObserver:self forKeyPath:@"height" options:3 context:@"123"];
    NSLog(@"%p",[person1 methodForSelector:@selector(setHeight:)]);
}
Demo-KVO[9473:425751] 0x1010eaf60
Demo-KVO[9473:425751] 0x7fff258f10eb
(lldb) p (IMP)0x1010eaf60
(IMP) $1 = 0x00000001010eaf60 (Demo-KVO`-[SPPerson setHeight:] at SPPerson.h:15)
(lldb) p (IMP)0x7fff258f10eb
(IMP) $2 = 0x00007fff258f10eb (Foundation`_NSSetIntValueAndNotify)
5. _NSSetIntValueAndNotify 是一个 C 语言方法,因为 Foundation 框架并不开源,所以我们只能从侧面猜测它的内部实现。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self.person1 willChangeValueForKey:@"height"];
    [self.person1 didChangeValueForKey:@"height"];
}
2020-08-25 16:48:15.816554+0800 Demo-KVO[10176:485066] 是监听到了<SPPerson: 0x600003eb0230>实例对象的height值,改变了{
    kind = 1;
    new = 1;
    old = 1;
},context:123
- (void)setHeight:(int)height {
    [self willChangeValueForKey:@"height"];
    [super setHeight:value];
    [self didChangeValueForKey:@"height"];
}

三、两道经典面试题

1. iOS 用什么方式实现对一个对象的 KVO?(KVO 的本质是什么?)
2. 如何手动触发 KVO?
上一篇 下一篇

猜你喜欢

热点阅读