iOS KVO底层原理探索

一,KVO (Key-Value Observing)

KVOObjective-C对观察者设计模式的一种实现,它提供一种机制,指定一个被观察对象(如A类),当对象中的某个属性发生变化的时候,对象就会接收到通知,并作出相应的处理。在MVC设计架构下的项目,KVO机制很适合实现mode模型和view视图之间的通讯。例如:代码中,在模型类A创建属性数据,在控制器中创建观察者,一旦属性数据发生改变就收到观察者收到通知,通过KVO再在控制器使用回调方法处理实现视图B的更新;

二,KVO底层实现原理

  • 1 KVO是基于runtime机制实现的

  • 2 当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter 方法。派生类在被重写的setter方法内实现真正的通知机制

  • 3 如果原类为Person,那么生成的派生类名为NSKVONotifying_Person

  • 4 每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将isa指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter方法

  • 5 键值观察通知依赖于NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey:;在一个被观察属性发生改变之前, willChangeValueForKey:一定会被调用,这就 会记录旧的值。而当改变发生后,didChangeValueForKey:会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。

  • 6 补充:KVO的这套实现机制中苹果还偷偷重写了class方法,让我们误认为还是使用的当前类,从而达到隐藏生成的派生类

三,KVO原理验证过程

3.1 环境准备

  • 1 我们创建一个项目,创建一个Person类,里边有一个属性name
@interface Person : NSObject
@property (copy, nonatomic) NSString *name;
@end
  • 2 在控制器中初始化该类的实例对象person,进行相关KVO常用三部曲
- (void)viewDidLoad {
    [super viewDidLoad];
    
    // Do any additional setup after loading the view.
    
    self.person = [[Person alloc]init];
    
    [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
    
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    self.person.name = [NSString stringWithFormat:@"测试+%@",@"1"];
    
}

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

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

以上就是相关的环境搭建,相信只要接触iOS开发的同学都知道怎么使用KVO,接下来就让我进一步验证相关原理的实现过程。

3.2 原理验证

  • 1 验证如何对一个类的属性进行setter方法的改变?

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

打印结果

2020-10-27 22:40:25.093093+0800 TheoryOfKVO[1154:48721] 改变&&&&change {
kind = 1;
new = "\U6d4b\U8bd5+1";
}

我们在以上内容打印的过程中能看到相关的打印内容,kind = 1我们进入代码的定义查看可知

typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
    NSKeyValueChangeSetting = 1,
    NSKeyValueChangeInsertion = 2,
    NSKeyValueChangeRemoval = 3,
    NSKeyValueChangeReplacement = 4,
};

NSKeyValueChangeSetting = 1, 也就是属性的setter方法,NSKeyValueChangeInsertion = 2,是集合类型的添加方法,NSKeyValueChangeRemoval = 3,是集合的移除操作,NSKeyValueChangeReplacement = 4,是可变集合类型的元素替换。

  • 2 如何验证派生类NSKVONotifying_Person的存在?
    我们进行断点调试;在程序刚刚初始化self.person的时候以及添加观察者的时候进行断点调试,看看类的信息变化,通过object_getClassName输出当前类信息
    初始化.png

    过掉第一个断点,进入第二个断点,再次打印类的信息,
    派生类的生存.png

从而就验证了底层会生存一个派生类,继承自Person

  • 3 如何验证通过willChangeValueForKeydidChangevlueForKey 来改变监听属性的值,从而达到修改的目的?

这一步其实很好验证的,我们在实现的类中重写+(BOOL)accessInstanceVariablesDirectly 使其返回值为NO,这样如果我们不手动实现以上的两个改变属性值得方法,我们是打印不出任何值得改变的,然而我们手动实现这两个方法时,

-(void)setName:(NSString *)name
{
    [self willChangeValueForKey:name];
    _name = [name copy];
    [self didChangeValueForKey:name];
    
}

+(BOOL)accessInstanceVariablesDirectly
{
    return NO;
}

此时控制台打印的内容和我们监听的一抹一样。这就是通过willChangeValueForKeydidChangevlueForKey改变属性值得过程;

  • 4 如何验证通知结束后isa指向返回原类,派生类NSKVONotifying_Person释放
    我们在程序该控制器销毁的时候打印相关的结果,断点调试
    图片.png

(lldb) po object_getClassName(self.person)
"Person"

以上就是相关的验证过程;

四,总结

以上就是本人通过学习KVO得出的相关结论和验证,至于自定义KVO的实现,其中用到了很多Runtime的API,还不是完全掌握,后续如果自己有更深的立即将会继续补充完整,希望是一个不断学习和提升的过程。有不足之处请多多指教。

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