iOS 底层学习20

前言

iOS 底层第20天的学习。今天分享的内容是 KVO 一些你从未涉及到的东西。

What is KVO ?

  • KVO : Key-Value Observing 是一种机制,当其他对象的特定属性发送变化时会通知某个对象

KVO 如何使用

  • 使用 KVO,首先你必须确定被观察的对象是否支持。如果你的对象继承 NSObject 且通过常规的方式创建属性。你的对象和属性会自动支持 KVO,也可以手动支持。
  • 其次,你需要把观察者 Person 注册到被观察者的 Account 上。Person 发送一个 addObserver:forKeyPath:options:context: 消息给 Account
  • 为了能够接受到来自 Account 的消息,Person 观察者需要实现一个接口observeValueForKeyPath:ofObject:change:contextAccount将会发送一个消息给Person在任何时间点 keyPath 发生变化。Person 也能根据通知的变化采取相应的措施。
  • 最后,当不再需要通知时,至少在观察者 deallocated之前通过 Person 对象移除册从Account 发送消息 removeObserver:forKeyPath:
  • 小结:kvo三部曲
    • 第一步:receive observer
    • 第二步:observed property of an object changes
    • 第三步:remove observer

第一步:receive observer

 [self.xk_student addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];

第二步: observed property of an object changes

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"change %@",change);
}

第三步:remove observer

- (void)dealloc{
    [self.xk_student removeObserver:self forKeyPath:@"name"];
}
  • name 进行赋值
  self.xk_student.name = @"emiya";
  • 打印输出👇

addObserver 参数分析

Options
  • 可传入4个类型,分别是👇
功能
NSKeyValueObservingOptionNew 作为变更信息的一部分发送新值
NSKeyValueObservingOptionOld 作为变更信息的一部分发送旧值
NSKeyValueObservingOptionInitial 在注册时发送一个初始更新
NSKeyValueObservingOptionPrior 在变更前后分别发送变更,而不只在变更后发送一次
Context
  • 先来看一下官方的解释 👇

大致的意思就是: context pointer 在更改通知中传递回观察者的任意数据,您可以指定NULLkey path 来确定更改通知的来源,但是这种方法可能会导致对象出现问题,因为其超类出于不同的原因也会观察同一个 key path
一种更安全、更可扩展的方法是使用 Context来确保您收到的通知是针对您的观察者而不是超类的。

  • 写个🌰 看一下
    self.xk_student  = [XKStudent new];
    self.xku_student = [XKUniversityStudent new];
   // XKUniversityStudent 继承 XKStudent
  // 给 xk_student  和 xku_student 同事注册对 name 的监听
    [self.xk_student addObserver:self forKeyPath:@"name"
                         options:(NSKeyValueObservingOptionNew) context:XKStudentContext];
  
    [self.xku_student addObserver:self forKeyPath:@"name"
                         options:(NSKeyValueObservingOptionNew) context:XKUniversityStudentContext];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    if(context == XKStudentContext) {
        NSLog(@"\n change %@ \n XKStudentContext",change);
    }else if (context == XKUniversityStudentContext){
        NSLog(@"\n change %@ \n XKUniversityStudentContext",change);
    }
}
  • 给 name 赋值
    self.xk_student.name = @"emiya";
    self.xku_student.name = @"un emiya";
  • 打印输出结果
  • 小结: Context 就是一个标识,为了就是当 key path 相同时用来区分子类分类监听同一个属性

automatic for Observers

  • 我们可以设置 automaticallyNotifiesObserversForKey 返回 NO
@implementation XKStudent
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
   return NO;
}
  • 设置 No以后对属性的监听就失效了,你也可以用 key 进行判断,对某一个属性进行设置。

mutableArray for Observe

  • 我们再试一下如何对可变数组对象进行监听
    // 可变数组观察,先观察再变值
    self.xk_student.dataArray = [NSMutableArray array];
    [self.xk_student addObserver:self forKeyPath:@"dataArray"
                         options:(NSKeyValueObservingOptionNew) context:NULL];
   [[self.xk_student mutableArrayValueForKey:@"dataArray"] addObject:@"数组元素1"];
  • 打印输出
  • 这里有个坑点就是必须调用 mutableArrayValueForKey:@"dataArray"],不能使用 self.xk_student.dataArray

KVO 底层原理

  • KVO 底层实现详情

根据官方文档 👇

  • KVO的实现使用了一个叫做 isa-swizzing 的技术。而 isa-swizzing 可以理解为 isa 指向的替换 (如不清楚 isa 的可以看下这篇文章
  • isa指针指向一个维护分派表的对象类。这个分派表本质上包含指向类实现的方法的指针以及其他数据。
  • 当一个观察者为一个对象的属性注册时,被观察对象的 isa 指针被修改,指向一个中间类而不是真正的类。因此,isa 指针的值不一定反映实例的实际类。
  • 你决不能依赖isa指针来确定类的成员。相反,你应该使用class方法来确定对象实例的类。
  • 说真心话技术这种官方文档翻译过来真心好难理解,因此还是需要用技术的方式去验证。
  • 进入lldb 进行调试
  • 由输出可知 当 进行了 addObserverisa -> NSKVONotifying_XKStudent

这个 NSKVONotifying_XKStudent 到底是什么呢? NSKVONotifying_XKStudentXKStudent 有何关系呢?

  • 继续lldb 进行调试
  • 由👆可知 XKStudentNSKVONotifying_XKStudent父子关系
  • 继续调试分析 NSKVONotifying_XKStudent 里面到底有什么呢?
  • lldb 分析👇
  • 根据输出结果可知 NSKVONotifying_XKStudent4Method
    • setName:
    • class
    • dealloc
    • _isKOVA

那这4Method里面到底做了什么的?

  • _isKOVA 其实就是一个标识。判断是不是 KVO 生成的。

  • class 进行分析,输出👇

你是否有疑问 isa -> NSKVONotifying_XKStudent ,为何不输出 NSKVONotifying_XKStudent。 其实 class 已经做了处理,对外部让我们开发人员看到的永远都是 XKStudent。可以简单理解为:做了一次掩饰

  • dealloc 字面理解就是销毁。
    当我们在 addObserver 的时 isa -> NSKVONotifying_XKStudent。那何时把isa 指回去呢?
    dealloc 进行分析,输出👇

由输出可知, 当 removeObserver 的时 , isa -> XKStudent,故能推导出其实在 dealloc 时候又把 isa 指回了 XKStudent

  • setName: 从字面理解就是 setter方法

疑问1:假设 KVOsetter 可以进行监听观察,那它是否也会对 成员变量 进行观察呢?

开始进行验证

// 添加成员变量
@interface XKStudent : NSObject{
    @public
    NSString *age;
}
// 添加观察
 [self.xk_student addObserver:self forKeyPath:@"name"
                         options:(NSKeyValueObservingOptionNew) context:NULL];
[self.xk_student addObserver:self forKeyPath:@"age"
                         options:(NSKeyValueObservingOptionNew) context:NULL];
// 进行赋值
self.xk_student.name = @"emiya";
self.xk_student->age = @"20";
// 监听
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    NSLog(@"\n change %@",change);
}

打印输出👇

 change {
    kind = 1;
    new = emiya;
}

从输出结果可知:KVO 只会对属性 setter 进行监听,无法对成员变量进行监听。

疑问2:已知 isa -> NSKVONotifying_XKStudent ,那么 setName: 肯定是 NSKVONotifying_XKStudent 对其进行了赋值操作。那如果 isa -> XKStudent 那么 name是否已经有值了?

开始进行验证

由输出可知:在 isa -> XKStudent 后,name 已经有了 。

疑问3:可以肯定是在 KVO_XKStudent 这一级进行了 name 的赋值,那在 XKStudent 里的 name 是何时进行赋值的呢?

开始在 lldb 里进行符号断点

watchpoint  set variable self->_xk_student->_name

输出👇

bt 打印堆栈

由👆可知
-> 1.touche'sBegin 进行 KVO_XKStudent.setName:
-> 2.Foundation:_NSSetObjectValueAndNotify
-> 3.Foundation:-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:]
->4.Foundation:-[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:maybeNewValuesDict:usingBlock:]

进入 4.Foundation:-[NSObject(NSKeyValueObservingPrivate) 查看汇编

在最后 using block 把值返回

进入 2.Foundation:_NSSetObjectValueAndNotify 查看汇编

_implicitObservationInfo 会发送一个通知 ,这个通知就会来到 NSKeyValueDidChange
最后来到 _observeValueForKeyPath:ofObject:

  • 小结:解释动态子类 KVO_XKStudent setter 到底做了什么。
    其实就是调用了父类 Student setter 方法,然后在 valueDidChange 后发起一个通知说明 已经赋值完毕了,最后来到 observeValueForKeyPath

KVO 底层原理流程总结

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容