KVO 、category实现原理

一、KVO原理

1. KVO 简介

KVO 是 Objective-C 对观察者设计模式的一种实现。KVO 提供一种机制,指定一个被观察对象,当对象某个属性发生改变时,对象会获得通知,并做出相应处理;

在 MVC 设计架构的项目,KVO机制很适合实现 model 模型和 View 视图之间的通讯。

2. 实现原理

KVO 的实现依赖于 Objective-C 强大的 Runtime。
当观察某对象α时,KVO 机制动态创建一个对象α当前类的子类,并为这个新的子类重写被观察属性 keyPath 的 setter 方法。setter 方法随后负责通知观察对象属性的改变状况。

3. 深入剖析:

Apple 使用了 isa-swizzling 来实现 KVO。当观察对象α时,KVO 机制动态创建一个新的名为:NSKVONotifying_α的新类,该类继承自对象α的本类,且 KVO 为 NSKVONotifying_α重写观察属性的 setter 方法,setter 方法会负责在调用原setter 方法之前和之后,通知所有观察对象属性值的更改情况。

NSKVONotifying_α类剖析:在这个过程,被观察对象的 isa 指针从原来的α类,被 KVO 机制修改为指向系统新创建的子类 NSKVONotifying_α类,来实现当前类属性值改变的监听。

所以当我们从应用层面上看来,完全没有意识到有新的类出现,这是系统对 KVO 的底层实现过程,此时如果我们创建一个新的名为“NSKVONotifying_α”的类,就会发现系统运行到注册 KVO 的那段代码时程序就会崩溃。因为系统在注册监听的时候创建了名为 NSKVONotifying_α的中间类,并指向这个中间类。

(isa 指针的作用: 每个对象都有 isa 指针,指向该对象的类,它告诉 Runtime 系统这个对象的类是什么。所以对象注册为观察者时,isa 指针指向新子类,那么这个被观察的对象就神奇地变成新子类的对象(或实例)了。)因而在该对象上对 setter 的调用就会调用已重写的 setter,从而激活键值通知机制。

子类 setter 方法剖析:KVO 的键值观察通知依赖于 NSOjbect 的两个方法:willChangeValueForKey:和 didChangeValueForKey:,在存取数值的前后分别调用2个方法:
被观察属性发生改变之前,willChangeValueForKey:被调用,通知系统该 keyPath 的属性值即将变更;当改变发生后,didChangeValueForKey:被调用,通知系统该 keyPath 的属性值已经变更;之后,observeValueForKey:ofObject:change:context:也会被调用。且重写观察属性的 setter 方法这种继承方式的注入是在运行时而不是编译时实现的。

KVO 为子类的观察者属性重写调用存取方法的工作原理在代码中相当于:

-  (void)setName:(NSString *)newName{
    [self willChangeValueForKey:@"name"];
    [super setValue:newName forKey:@"name"];
    [self didiChangeValueForKey:@"name"];
}

4.特点:

观察者观察的是属性,只有遵循 KVO 变更属性值的方式才会执行 KVO 的回调方法,例如是否执行了 setter 方法、或者是否使用了 KVC 赋值。如果赋值没有通过 setter 方法或者 KVC,而是直接修改属性对应的成员变量,例如:仅调用_name = @"newName",这事是不会触发 KVO 机制,更加不会调用回调方法的。所以使用 KVO 机制的前提是遵循 KVO 的属性设置方式来变更属性值。

5.步骤

  • 1.注册观察者,实施监听;
  • 2.在回调方法中处理属性发生的变化;
  • 3.移除观察者

6. 实现方法

A. 注册观察者:

//第一个参数 observer:观察者(这里观察 self.myKVO 对象的属性变化)
//第二个参数 keyPath: 被观察的属性名称(这里 self.myKVO 中 num属性值的改变)
//第三个参数 options:观察属性的新值、旧值等的一些配置(枚举值,可以根据需要设置,例如这里可以使用两项)
//第四个参数 context: 上下文,可以为 kvo 的回调方法传值(例如设定为一个放置数据的字典)
[self.myKVO addObserver:self forKeyPath:@"num" options: NSKeyValueObservingOptionOld|NSKeyValueObservingOpitonNew context:nil];

B.属性(keyPath)的值发生变化时,收到通知,调用以下方法:

//keyPath:属性名称
//object: 被观察的对象
//change:变化前后的值都存储在 change 字典中
//context: 注册观察者时,context 传过来的值
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *) change context:(void *)context{
}

7.拓展

1.KVC 和 KVO 的不同
KVC(键值编码),即 Key-Value Coding,一个非正式的 Protocol,使用字符串(键)访问一个对象实例变量的机制。而不是通过调用 Setter、Getter 方法等显式的存取方法去访问。KVO(键值监听),即 Key-Value Observing,它提供一种机制,当指定的对象的属性被修改后,对象就会接受到通知,前提是执行了 setter 方法、或者使用了 KVC 赋值。

2.和 notification(通知)的区别?
notification 比 KVO 多了发送通知的一步。
两者都是一对多,但是对象之间直接的交互,notification 明显很多,需要 notificationCenter 来作为中间交互。而 KVO 如我们介绍的,设置观察者->处理属性变化,

notification 的优点是监听不局限与属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,例如键盘、前后台等系统通知的使用也更更显灵活方便。

  1. 与 delegate 的不同

和 delegate 一样,KVO 和 NSNotification 的作用都是类与类之间的通信。但是与 delegate 不同的是:
这两个都是负责发送接收通知,剩下的事情由系统处理,所以不同返回值;而 delegate 则需要通信的对象通过变量(代理)联系;
delegate 一般是一对一,而这两个可以一对多。

二、Category实现原理

1. 简介

category 是 Objective-C 2.0 之后添加的语言特性。主要作用是为已经存在的类添加方法。还有以下两种用法:

可以把类的实现分开在几个不同的文件里面。1)可以减少单个文件的体积, 2)可以把不同的功能组织到不同的 category 里 3)可以由多个开发者共同完成一个类 4)可以按需加载想要的 category 等等

声明私有方法。

2. 比较 category 和 extension

extension 在编译期决定,它是类的一部分,在编译期和头文件里的@interface 以及实现文件里的@implement 一起形成一个完整的类,它伴随类的产生而产生,消亡而消亡。extension 一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加 extension,所以无法为系统的类添加 extension。

但是 category 实在运行期决定的,就 category 和 extension 的区别来看,extension 可以添加实例变量,而 category 无法添加实例变量(因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来说是灾难性的)。

3. category 原理

所有 OC 类和对象,在 Runtime 层都是用 struct 表示的,category 也不例外,在 runtime 层,category 用结构体 category_t (在 objc-runtime-new.h 中可以找到此定义),它包括了:
1)类的名字(name)
2)类(cls)
3)category 中所有给类添加的实例方法的列表(instanceMethods)

  1. category 中所有添加的类方法的列表(classMethods)
    5) category 实现的所有协议的列表(Protocols)
    6) category 中添加的所有属性(instanceProperties)
typedef  struct category_t {
        const  char  * name;
        classref_t  cls;
        struct  method_list_t  *instanceMethods;
        struct  method_list_t  * classMethods;
        struct  protocol_list_t  * protocols;
        struct  property_list_t  * instanceProperties;
} category_t;

从 category 的定义也可以看出 category 的可为(可以添加实例方法,类方法,甚至可以实现协议,添加属性)和不可为(无法添加实例变量)。
MyClass.h:

#import  <Foundation/Foundation.h>
@interface MyClass:NSObject
- (void)printName;
@end

@interface MyClass(MyAddition)
@property(nonatomic,copy) NSString *name;
- (void)printName;
@end

MyClass.m

#import"MyClass.h"
@implementation MyClass
- (void)printName{
    NSLog(@"%@",@"MyClass");
}
@end

@implementation MyClass(MyAddition)
- (void)printName{
    NSLog(@"%@",@"MyAddition");
}

4. category 和关联对象

在 category 里是无法为 category 添加实例变量的。但是我们很多时候需要在 category 中添加和对象关联的值,这个时候可以求助关联对象来实现。

MyClass + Category.h:

#import  “MyClass.h”
@interface MyClass(Category1)
@property(nonatomic,copy) NSString *name;
@end

MyClass+Category.m:

#import "MyClass+Category.h"
#import <objc/runtime.h>

@implementation MyClass(Category)

- (void)setName:(NSString *)name{
    objc_setAssociatedObject(self, "name", name, OBJC_ASSOCIATION_COPY);
}

-(NSString *)name{

    NSString *nameObject = objc_getAssociatedObject(self, "name");
    return nameObject;

}


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

推荐阅读更多精彩内容