KVC/KVO的理解

iOS开发基础.png

KVC

KVC实现了基于KEY访问对象属性的一套查找规则,可以直接操作对象的属性,变量即使设置为私有的也一样访问。

要操作一个对象首先要把它找出来,KVC底层机制实现了其一步步的查找规则。了解它的处理机制更有利于设计我们的类,方法实现等操作。

获取值操作的查找方式

通过valueForKey:方法获取值

  • 1、搜索实例的访问器方法,调用方法名为get<Key>, <key>, is<Key>, or _<key>的方法。如果找到了就返回 -> 第5步,找不到继续下一步。

  • 2、继续查找是否含有NSArray类的方法:countOf<Key> , objectIn<Key>AtIndex: , <key>AtIndexes:方法

//必须实现
countOf<Key> 

//必须实现其中一个
objectIn<Key>AtIndex:
<key>AtIndexes:

如果存在,创建一个集合代理对象,这个对象就像和NSArray一样来使用,返回该对象。否则下一步。

  • 3、查找是否全部包含以下NSSet类的方法
countOf<Key>, 
enumeratorOf<Key>, 
and memberOf<Key>:

如果存在,创建一个集合代理对象,就像和NSSet一样来使用,返回该对象。否则下一步。

  • 4、没有找到可以访问的方法;

判断是否可以直接访问实例变量,

 + (BOOL)accessInstanceVariablesDirectly

如果返回true,按照以下顺序查找:

_<key>, _is<Key>, <key>, is<Key>

找到了执行第5步 , 没找到执行第6步。

  • 5、找到了,结果返回分为三种情况:
    如果值是一个对象,直接返回。
    如果是NSNumber支持的数值类型,包装成NSNumber对象,返回。
    如果不是NSNumber支持的数值类型,包装成NSValue对象,返回。
  • 6、没找到,默认调用valueForUndefinedKey: 抛出异常
    我们可重写该方法实现,至此整个查找流程结束。
设置操作的查找方式

通过setValue:forKey:方法设置操作

  • 1、依次查找set<Key>: 、 _set<Key>方法名,找到后直接调用赋值然后完成返回,否则下一步2。
  • 2、没有找到可访问的方法:

判断是否可以直接访问实例变量,

 + (BOOL)accessInstanceVariablesDirectly

如果返回true,按照以下顺序查找:

_<key>, _is<Key>, <key>, is<Key>

如果找到指定变量名的实例变量直接赋值然后完成返回。否则下一步3

  • 3、调用setValue:forUndefinedKey:抛出异常。

数组的查找方法为 mutableArrayValueForKey:,其查找过程类似。

支持集合操作
  • @avg 平均值

  • @count 个数

  • @max 最大值

  • @min 最小值

  • @sum 和

//////集合操作
NSArray * arr = @[@10 , @13 , @5 , @20 , @30];

NSInteger count =  [[arr valueForKeyPath:@"@count"] integerValue];
NSLog(@"count:%ld" , (long)count);

NSInteger min =  [[arr valueForKeyPath:@"@min.integerValue"] integerValue];
NSLog(@"min:%ld" , (long)min);

NSInteger max =  [[arr valueForKeyPath:@"@max.integerValue"] integerValue];
NSLog(@"max:%ld" , (long)max);

NSInteger sum =  [[arr valueForKeyPath:@"@sum.integerValue"] integerValue];
NSLog(@"sum:%ld" , (long)sum);

NSInteger avg =  [[arr valueForKeyPath:@"@avg.integerValue"] integerValue];
NSLog(@"avg:%ld" , (long)avg);
    
当对子类继承自父类的属性使用KVC时,查找顺序如下:

子类的get方法->父类的get方法->accessInstanceVariablesDirectly是否返回YES-->子类的成员变量>父类的成员变量。此外还支持路径查询。

KVC优缺点:

优点:可以根据对象属性名直接访问,不管是否设置为私有都可取到。

缺点:解析key字符串,一步步的查找难免不费时间。


KVO

KVO是观察者模式的一个实现,利用runtime的机制,当对一个对象进行观察时,会在运行时创建一个该对象的子类,这个子类一般以NSKVONotifying_xxx(xxx为父类的名字)命名,子类中会重写所有被观察属性的set方法,除了创建子类,还会将该对象的isa指针指向这个子类,当被观察的对象属性修改时,通过isa找到子类,在通过子类的方法列表找到对应的set方法,set方法是被重写过得,里面实现了相关的通知。

引用网络一个图说明其流程:

kvo实现原理.png
  • 使用isa-swizzling技术 ,改变对象所属的类。

  • 当值改变时会调用 willChangeValueForKey:、didChangeValueForKey: 。在一个被观察属性改变之前,调用 willChangeValueForKey: 记录旧的值。在属性值改变之后调用 didChangeValueForKey:。

  • KVO的触发分为自动触发模式和手动触发模式2种。通常我们使用的都是自动通知,注册观察者之后,当条件触发的时候会自动调用-(void)observeValueForKeyPath.

  • 通过kvc设置值时会自动触发kvo观察者事件。

  • 我们可根据上面思路手动触发观察者事件, 然后重写setter方法、通知操作。

手动模拟KVO,此时要禁止自动触发操作:
//官方demo
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
 
    BOOL automatic = NO;
    if ([theKey isEqualToString:@"balance"]) {
        automatic = NO;
    }
    else {
        automatic = [super automaticallyNotifiesObserversForKey:theKey];
    }
    return automatic;
}

修改setter方法:

- (void)setBalance:(double)theBalance {
    [self willChangeValueForKey:@"balance"];
    _balance = theBalance;
    [self didChangeValueForKey:@"balance"];
}

//一次修改多个值
- (void)setBalance:(double)theBalance {
    [self willChangeValueForKey:@"balance"];
    [self willChangeValueForKey:@"itemChanged"];
    _balance = theBalance;
    _itemChanged = _itemChanged+1;
    [self didChangeValueForKey:@"itemChanged"];
    [self didChangeValueForKey:@"balance"];
}

willChangeValueForKey:告诉观察者值将要改变;
didChangeValueForKey:告诉观察者值已经改变;这两个方法必须成对出现。

KVO优缺点:

优点:

  • 借助系统框架可以很方便检测对象属性值变化实现简的单数据同步,支持路径观察。
  • 支持一对多。
  • 用key paths来观察属性,因此也可以观察嵌套对象。
  • 能够提供观察的属性的最新值以及先前值。
  • 完成了对观察对象的抽象,因为不需要额外的代码来允许观察值能够被观察。

缺点:

  • 观察的属性必须使用strings来定义。因此在编译器不会出现警告以及检查。
  • 对属性重构将导致我们的观察代码不再可用。

参考

Key-Value Coding Programming Guide
Key-Value Observing Programming Guide
好好看看 KVC && KVO-https://juejin.im/post/5c52acabe51d4551c75ffa5b

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

推荐阅读更多精彩内容