【iOS基础】KVC / KVO详解

KVC(Key-value coding)

KVC是一种基于NSKeyValueCoding非正式协议的机制,能让我们直接使用一个或一串字符串标识符去访问、操作类的属性。KVO 就是基于 KVC 实现的关键技术之一。

KVC基本使用

KVC主要对三种类型进行操作,基础数据类型及常量、对象类型、集合类型。

- (nullable id)valueForKey:(NSString *)key;

- (void)setValue:(nullable id)value forKey:(NSString *)key;

//keyPath为属性的路径 比如xx.xx
- (nullable id)valueForKeyPath:(NSString *)keyPath;

- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;

//获取属性值时,如果属性不存在则执行该方法;
默认实现方式为抛出NSUnknownKeyException异常,可重写这个函数做错误处理
- (nullable id)valueForUndefinedKey:(NSString *)key;

//设置属性值,如果属性不存在则执行该方法,默认实现方式为抛出NSUnknownKeyException异常
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;

// 对非类对象属性设置nil时调用,默认抛出异常。
-(void)setNilValueForKey:(NSString *)key

KVC执行机制

  • setValue:forKey:搜索方式
    1、首先搜索setKey:方法。(key指成员变量名,首字母大写)
    2、如果上面的setter方法没找到,KVC机制会检查accessInstanceVariablesDirectly类方法有没有返回YES(默认返回YES)。如果返回YES,那么按 _key,_isKey,key,iskey的顺序搜索成员变量并赋值;如果返回NO,那么在这一步KVC会执行setValue:forUndefinedKey:方法。(如果开发者想让这个类禁用KVC里,那么重写accessInstanceVariablesDirectly类方法让其返回NO即可)
    3、如果没有找到成员变量,执行setValue:forUnderfinedKey:方法。

  • valueForKey:的搜索方式
    1、首先按getKey,key,isKey的顺序查找getter方法,找到直接调用。如果是BOOL、int等值类型,会做NSNumber的转换。
    2、如果上面的getter没找到,查找countOfKey、objectInKeyAtindex、KeyAtindexes格式的方法。如果countOfKey和另外两个方法中的一个找到,那么就会返回一个可以响应NSArray所有方法的代理集合的NSArray消息方法。
    3、还没找到,查找countOfKey、enumeratorOfKey、memberOfKey格式的方法。如果这三个方法都找到,那么就返回一个可以响应NSSet所有方法的代理集合。
    4、还是没找到,如果类方法accessInstanceVariablesDirectly返回YES。那么按 _key,_isKey,key,iskey的顺序搜索成员名。
    5、再没找到,调用valueForUndefinedKey方法。

KVC实现分析

KVC运用了isa-swizzing技术。isa-swizzing就是类型混合指针机制。KVC通过isa-swizzing实现其内部查找定位。isa指针指向维护分发表的对象的类,该分发表实际上包含了指向实现类中的方法的指针和其他数据。

每个类都有一张方法表,是一个hash表,值是函数指针IMP,SEL的名称就是查表时所用的键。
SEL数据类型:查找方法表时所用的键。定义成char*,实质上可以理解成int值。
IMP数据类型:他其实就是一个编译器内部实现时候的函数指针。当Objective-C编译器去处理实现一个方法的时候,就会指向一个IMP对象,这个对象是C语言表述的类型。

KVO(Key - Value - Observer)

KVO是Objective-C对观察者设计模式的一种实现,是一种基于NSKeyValueObserving非正式协议的机制,Cocoa通过这个协议为所有遵守协议的对象提供了一种自动化的属性观察能力。指定一个被观察对象(如A类),当对象中的某个属性发生变化的时候,对象就会接收到通知,并作出相应的处理。在 MVC 设计架构下的项目,KVO 机制很适合实现 mode 模型和 view 视图之间的通讯。

KVO实现原理

KVO 的实现依赖于 Objective-C 强大的 Runtime,Apple 使用了 isa 混写(isa-swizzling)来实现 KVO 。

  • 创建子类
    当观察某对象A时,系统会在运行期动态的创建一个名为:NSKVONotifying_A的派生类,该类继承自对象A的本类。
  • 重写Setter方法
    在派生类中重写基类中任何被观察属性的setter方法。派生类在被重写的 setter 方法中实现真正的通知机制。
  • 修改isa指针
    同时派生类还重写了 class 方法以“欺骗”外部调用者它就是起初的那个类。然后系统将这个对象的 isa 指针指向这个动态生成的派生类。

⚠️苹果为什么要用子类监听setter方法,而不用分类呢?原因当你用分类监听setter方法的时候,原来类中setter方法就不会走了,这样不好,所以苹果使用了子类监听setter方法。

KVO基本使用

1、注册观察者

通过addObserver:forKeyPath:options:context:方法注册观察者,观察者可以接收keypath属性的变化事件。

options参数是一些配置选项,用来指明通知发出的时机和通知响应方法observeValueForKeyPath:ofObject:change:context:的change字典中包含哪些值:
NSKeyValueObservingOptionNew:change字典中应该包含改变后的新值。
NSKeyValueObservingOptionOld:change字典中应该包含改变前的旧值。
NSKeyValueObservingOptionInitial:在注册观察者消息发出后立即发送通知一次,值改变的时候也会发送通知
NSKeyValueObservingOptionPrior:分别在值修改前后发送通知(即一次修改有两次触发)

⚠️在调用addObserver方法后,KVO并不会对观察者进行强引用,所以需要注意观察者的生命周期,在适当的时候将观察者移除,否则会导致观察者被释放带来的Crash

- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
2、监听回调

在观察者中实现observeValueForKeyPath:ofObject:change:context:方法,当keypath属性发生变化后,KVO会回调这个方法来通知观察者。

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context;
3、移除观察者

当观察者不需要监听时,可以调用removeObserver:forKeyPath:方法将观察者移除,注意在观察者消失之前移除否者会导致crash。

- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

KVO触发模式

自动监听

KVO默认自动监听。
使用 KVO 机制的前提是遵循 KVO 的属性设置方式来变更属性值。
例如是否执行了 setter 方法、或者是否使用了 KVC 赋值。
如果赋值没有通过 setter 方法或者 KVC,而是直接修改属性对应的成员变量,例如:仅调用 ```_name = @"newName",这时是不会触发 KVO 机制,更加不会调用回调方法的。
KVO无法监听对象的成员变量。

手动触发

1)重写automaticallyNotifiesObserversForKey类方法修改触发模式,返回YES表示自动,返回NO表示手动,可根据key值定制属性的触发模式。

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    if ([key isEqualToString:@"name"]) {
        return NO;
    }
    return [super automaticallyNotifiesObserversForKey:key];
}

2)在值改变前后分别调用:willChangeValueForKey:didChangeValueForKey:方法.
⚠️KVO触不触发与你的值改不改变没有关系,与willChangeValueForKey:didChangeValueForKey:方法有没有调用有关系。

KVO属性依赖

当一个属性与有限个属性关联时需要建立属性依赖。
比如:dog属性依赖于Dog对象下的name和age属性。
方法一:重写keyPathsForValuesAffectingValueForKey:方法

+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
    NSSet *keypaths = [super keyPathsForValuesAffectingValueForKey:key];
    if ([key isEqualToString:@"dog"]) {
        //Dog下面有两个属性:name,age
        keypaths = [[NSSet alloc] initWithObjects:@"_dog.name",@"_dog.age", nil];
    }
    return keypaths;
}

方法二:用以下的命名格式实现一个类方法keyPathsForValuesAffecting<Key>,<Key>是属性的名字(第一个字母要大写)。

+ (NSSet<NSString *> *)keyPathsForValuesAffectingDog
{
    return [NSSet setWithObjects:@"_dog.name",@"_dog.age", nil];
}

⚠️当你在category里增加了一个计算属性时,你不能重写keyPathsForValuesAffectingValueForKey:方法,因为你不能在category里重写方法.在这种情况下,你可以用keyPathsForValuesAffecting<Key>的类方法来实现这个机制。

比较

1.KVC 与 KVO 的不同?

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

2.KVO和、NSNotification(通知)和delegate的区别?

两者都是观察者模式。
KVO是被观察者直接发送消息给观察者,是对象间的交互,而通知则是观察者和被观察者通过通知中心对象之间进行交互,即消息由被观察者发送到通知中心对象,再由中心对象发给观察者,两者之间并不进行直接的交互。

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

delegate 一般是一对一,而这两个可以一对多。

参考

KVC官方文档
KVO官方文档
iOS开发技巧系列---详解KVC
KVO、Delegate、Notification 区别及相关使用场景

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

推荐阅读更多精彩内容