KVO与KVC

最近发现之前看的东西没一会就忘记了,所以写来好一些,也建议大家吧学到了记录下来这样加深印象,也可以帮助到别人。
首先附上参考链接1参考链接2参考链接3--KVC详解

1.KVC--键值编码(Key-value coding):

在某种程度上跟map(C++的函数,所以多学吧。。。)的关系匪浅。它提供了一种使用字符串而不是访问器方法去访问一个对象实例变量的机制。

实现方法:

KVC运用了一个isa-swizzling(isa指针混写)技术,任何对象都有isa指针。KVC主要通过isa-swizzling,来实现其内部查找定位的:
(1).实例方法调用时,通过对象的 isa 在类中获取方法的实现
(2).类方法调用时,通过类的 isa 在元类中获取方法的实现

实现原理:

1.当调用setValue: forKey:@"name:的方法时,底层的执行机制如下:

注意,这里的<key>是指成员变量名,首字母大清写要符合KVC的全名规则
  • 程序优先调用set<Key>:属性值方法,代码通过setter方法完成设置。

  • 如果没有找到setName:方法,KVC机制会检查+ (BOOL)accessInstanceVariablesDirectly方法有没有返回YES,默认该方法会返回YES,如果你重写了该方法让其返回NO的话,那么在这一步KVC会执行setValue:forUNdefinedKey:方法,不过一般开发者不会这么做。所以KVC机制会搜索该类里面有没有名为<key>的成员变量,无论该变量是在类接口部分定义,还是在类实现部分定义,也无论用了什么样的访问修饰符,只在存在以<key>命名的变量,KVC都可以对该成员变量赋值。

  • 如果该类即没有set<Key>:方法,也没有_<key>成员变量,KVC机制会搜索_is<Key>的成员变量

  • 和上面一样,如果该类即没有set<Key>:方法,也没有_<key>和_is<Key>成员变量,KVC机制再会继续搜索<key>和is<Key>的成员变量。再给它们赋值。

  • 如果上面列出的方法或者成员变量都不存在,系统将会执行该对象的setValue:forUNdefinedKey:方法,默认是抛出异常。

如果开发者想让这个类禁用KVC里,那么重写+ (BOOL)accessInstanceVariablesDirectly
方法让其返回NO即可,这样的话如果KVC没有找到set<Key>:属性名时,会直接用setValue:forUNdefinedKey:方法。

2.当调用ValueforKey:@”name“的代码时,KVC对key的搜索方式不同于setValue:属性值 forKey:@”name“,其搜索方式如下

  • 首先按get<Key>,<key>,is<Key>的顺序方法查找getter方法,找到的话会直接调用。如果是BOOL或者int等值类型, 会做NSNumber转换

  • 如果上面的getter没有找到,KVC则会查找countOf<Key>,objectIn<Key>AtIndex,<Key>AtIndex格式的方法。如果countOf<Key>和另外两个方法中的要个被找到,那么就会返回一个可以响应NSArray所的方法的代理集合(它是NSKeyValueArray,是NSArray的子类),调用这个代理集合的方法,或者说给这个代理集合发送NSArray的方法,就会以countOf<Key>,objectIn<Key>AtIndex,<Key>AtIndex这几个方法组合的形式调用。还有一个可选的get<Ket>:range:方法。所以你想重新定义KVC的一些功能,你可以添加这些方法,需要注意的是你的方法名要符合KVC的标准命名方法,包括方法签名。

  • 如果上面的方法没有找到,那么会查找countOf<Key>,enumeratorOf<Key>,memberOf<Key>格式的方法。如果这三个方法都找到,那么就返回一个可以响应NSSet所的方法的代理集合,以送给这个代理集合消息方法,就会以countOf<Key>,enumeratorOf<Key>,memberOf<Key>组合的形式调用。

  • 如果还没有找到,再检查类方法+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默认行为),那么和先前的设值一样,会按_<key>,_is<Key>,<key>,is<Key>的顺序搜索成员变量名,这里不推荐这么做,因为这样直接访问实例变量破坏了封装性,使代码更脆弱。如果重写了类方法+ (BOOL)accessInstanceVariablesDirectly返回NO的话,那么会直接调用valueForUndefinedKey:

  • 还没有找到的话,调用valueForUndefinedKey:

KVC相关技术:

1.KVC的主要方法:
- (id)valueForKey:(NSString *)key;  
- (void)setValue:(id)value forKey:(NSString *)key;  
- (id)valueForKeyPath:(NSString *)keyPath;  
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;  

这里要说一下keyPath:它是一个被点操作符隔开的用于访问对象的指定属性的字符串序列。(就像平时使用的的属性)

[people1 setValue:@"USA" forKeyPath:@"address.country"];
country1 = people1.address.country;
country2 = [people1 valueForKeyPath:@"address.country"];
2.点语法和KVC

在实现了访问器方法的类中,使用点语法和KVC访问对象其实差别不大,二者可以任意混用。但是没有访问起方法的类中,点语法无法使用,这时KVC就有优势了。)

3.一对多关系(To-Many)中的集合访问器方法

一对一应该很容易理解,一对多呢就是当你的对象里又多个属性呢(例子:Person类中的name属性,每个人只有一个名字。但也有一对多的关系,比如Person中有一个friendsName属性,这是个集合(在Objective-C中可以是NSArray,NSSet等),保存的是一个人的所有朋友的名字。)
当操作一对多的属性中的内容时,我们有两种选择:
①间接操作
先通过KVC方法取到集合属性,然后通过集合属性操作集合中的元素。比较习惯用这个方法。
即通过键值对(key-value)取出这个实例,然后取属性
②直接操作
苹果为我们提供了一些方法模板,我们可以以规定的格式实现这些方法来达到访问集合属性中元素的目的。

- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
4、键值验证(Key-Value Validation)

VC提供了验证Key对应的Value是否可用的方法,KVC是不会自动调用键值验证方法的,就是说我们需要手动验证。
当开发者需要验证能不能用KVC设定某个值时,可以调用validateValue: forKey:这个方法来验证,如果这个类的开发者实现了-(BOOL)validate<Key>:error:这个方法,那么KVC就会直接调用这个方法来返回,如果没有,就直接返回YES.

#注意:

KVC在设值时不会主动去做验证,所以即使你在类里面写了验证方法,但是KVC因为不会去主动验证,需要开发者手动去验证。
但是有些技术,比如CoreData会自动调用。

4、KVC的使用
  • KVC支持数值和结构体型属性
    KVC可以自动的将数值或结构体型的数据打包或解包成NSNumber或NSValue对象,以达到适配的目的。当然还有很多其他的,这里就举几个例子
+ (NSNumber *)numberWithFloat:(float)value;  
+ (NSNumber *)numberWithDouble:(double)value;  
+ (NSNumber *)numberWithBool:(BOOL)value;  

这是将NSValue主要用于处理结构体型的数据

// 这是将基本类型转为NSValue,其中,result是基本数据的值,int是我们要转化的基本数据类型
NSValue *value = [NSValue valueWithBytes:&result objCType:@encode(int)];  
// NSValue转化成基本数据类型,value是一个NSValue类型的对象,result是一个已知的类型的基本数据类型。
// 经过这样的转化,NSValue中保存的数值就放到了result中了。
[value getValue:&result];  
// 这里只列举一些,相信其他的你也可以自己试出来
+ (NSValue *)valueWithCGPoint:(CGPoint)point;  
+ (NSValue *)valueWithCGSize:(CGSize)size;  
+ (NSValue *)valueWithCGRect:(CGRect)rect;  
  • KVC和字典
    当对NSDictionary对象使用KVC时,valueForKey:的表现行为和objectForKey:一样。所以使用valueForKeyPath:用来访问多层嵌套的字典是比较方便的。
// dictionaryWithValuesForKeys:     是指输入一组key,返回这组key对应的属性,再组成一个字典。
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
//  setValuesForKeysWithDictionary    是用来修改Model中对应key的属性。下面直接用代码会更直观一点
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
  • 用KVC来访问和修改私有变量
    对于自定义类里的私有属性,我们可以直接利用kvc访问这个属性。

  • KVC Collection Operators(集合操作)
    集合操作:是一个特殊的Key Path,它是一个集合/数组通过调用valueForKeyPath:可允许一个集合中的对象属性根据集合操作符做相应的操作。
    注意: 只能是这个方法,如果传给了valueForKey:方法保证你程序崩溃。

集合操作符是一个以@开头特殊的字符串.所有的集合操作,除了@count,其他都需要有右边的keyPath(一般为属性名),目前还不支持自定义集合操作符。


集合操作符分为三种:
1.简单的集合操作 返回NSString、NSNumber、NSDate
2.对象操作符 返回NSArray
3.数组或集合操作符 返回NSArray、NSSet
集合操作简单例子
①Simple Collection Operators(简单的操作符)
简单集合运算符共有@avg,@count,@max,@min,@sum5种

②Object Operator (对象操作符)
@distinctUnionOfObjects 返回一个由操作符右边的key path所指定的对象属性组成的数组,不对数组去重
@unionOfObjects 返回一个由操作符右边的key path所指定的对象属性组成的数组,并对数组去重

③Array and Set Operators(数组和集合操作符)
@distinctUnionOfArrays和@unionOfArrays: 返回NSArray,distinct版本会对数组取重
@distinctUnionOfSets: 返回一个NSSet对象,因为Sets中的元素本身就是唯一的,所以没有对应的@unionOfSets运算符。


2.KVO--键值观察Key-value observing)

提供了一种当其它对象属性被修改的时候能通知当前对象的机制。再MVC大行其道的Cocoa中,KVO机制很适合实现model和controller类之间的通讯。---它是基于KVC实现的。

KVO实现原理:

当某个类的对象第一次被添加观察的时候,
1.系统就会在运行期动态地创建该类的一个派生类(继承自原本的类),在这个派生类中重写基类中任何被观察属性的 setter 方法。派生类在被重写的 setter 方法实现真正的通知机制,就如前面手动实现键值观察那样。这么做是基于设置属性会调用 setter 方法,而通过重写就获得了 KVO 需要的通知机制。
2.同时派生类还重写了 class 方法以“欺骗”外部调用者它就是起初的那个类。然后系统将这个对象的 isa 指针指向这个新诞生的派生类,因此这个对象就成为该派生类的对象了,因而在该对象上对 setter 的调用就会调用重写的 setter,从而激活键值通知机制。此外,派生类还重写了 dealloc 方法来释放资源。
我的理解就是,在你添加观察的时候呢,系统就创建一个派生类,然后派生类重写setter,class等方法,然后把指向原本的类指针指向派生类。由于重写了class类,这样你就以为是原来的类了。重写seeter方法呢,就是为了实现观察对象的改变


// 添加一下这两个方法
// 实现kvo的关键--使用KVO,只要有will/didChangeValueForKey:方法就可以了。
- (void)willChangeValueForKey:(NSString *)key  
- (void)didChangeValueForKey:(NSString *)key 

// 其中,didChangeValueForKey:方法负责调用:
- (void)observeValueForKeyPath:(NSString *)keyPath  
                      ofObject:(id)object  
                        change:(NSDictionary *)change  
                       context:(void *)context  

// 如下
- (void)setNow:(NSDate *)aDate {
    [self willChangeValueForKey:@"now"]; 
    _now = aDate;
    [self didChangeValueForKey:@"now"];
}

这样就实现了KVO。在派生类重写呢也可以节省内存消耗。自己手动实现这个呢,可以帮助理解吧可以学习学习。

这里补充一下KVO、NSNotification、delegate的区别:

KVC:

优势:
1.能够提供一种简单的方法实现两个对象间的同步。例如:model和view之间同步;
2.能够对非我们创建的对象,即内部对象的状态改变作出响应,而且不需要改变内部对象(SKD对象)的实现;
3.能够提供观察的属性的最新值以及先前值;
4.用key paths来观察属性,因此也可以观察嵌套对象;
5.完成了对观察对象的抽象,因为不需要额外的代码来允许观察值能够被观察
缺点:
1.我们观察的属性必须使用strings来定义。因此在编译器不会出现警告以及检查;
2.对属性重构将导致我们的观察代码不再可用;
3.复杂的“IF”语句要求对象正在观察多个值。这是因为所有的观察代码通过一个方法来指向;
4.当释放观察者时不需要移除观察者。

delegate:

就是你有一些事自己不想做或者不能做交给别人做(当然只能交给一个人,不然会不知道谁该做什么),设置协议,代理并告诉代理人什么时候做事。然后让代理人声明跟你签约并且做事。
优势:
1.非常严格的语法。所有将听到的事件必须是在delegate协议中有清晰的定义。
2.如果delegate中的一个方法没有实现那么就会出现编译警告/错误
3.协议必须在controller的作用域范围内定义
4.在一个应用中的控制流程是可跟踪的并且是可识别的;
5.在一个控制器中可以定义定义多个不同的协议,每个协议有不同的delegates
6.没有第三方对象要求保持/监视通信过程。
7.能够接收调用的协议方法的返回值。这意味着delegate能够提供反馈信息给controller
缺点:
1.需要定义很多代码:1.协议定义;2.controller的delegate属性;3.在delegate本身中实现delegate方法定义
2.在释放代理对象时,需要小心的将delegate改为nil。一旦设定失败,那么调用释放对象的方法将会出现内存crash
3.在一个controller中有多个delegate对象,并且delegate是遵守同一个协议,但还是很难告诉多个对象同一个事件,不过有可能。

NSNotification:

通过Notification Center这个单例对象,在事件发生时通知一些对象,让对象做出相应反应。
优势:
1.不需要编写多少代码,实现比较简单;
2.对于一个发出的通知,多个对象能够做出反应,简单实现1对多的方式,较之于 Delegate 可以实现更大的跨度的通信机制;
3.能够传递参数(object和userInfo),object和userInfo可以携带发送通知时传递的信息。
缺点:
1.在编译期间不会检查通知是否能够被观察者正确的处理;
2.在释放通知的观察者时,需要在通知中心移除观察者;
3.在调试的时候,通知传递的过程很难控制和跟踪;
4.发送通知和接收通知时需要提前知道通知名称,如果通知名称不一致,会出现不同步的情况;
5.通知发出后,不能从观察者获得任何的反馈信息。

大致就这样吧,这次写了好久。。。主要我也在理解学习哈哈。

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

推荐阅读更多精彩内容

  • 本文讲述了使用Cocoa框架中的KVC和KVO,实现观察者模式 KVC 键/值编码中的基本调用包括-valueFo...
    voQuan阅读 329评论 0 1
  • KVO与KVC简单理解 ///////////////////////////////////////我是该死的分...
    李某lkb阅读 379评论 0 1
  • KVO与KVC是观察者模式在iOS中的一种实现 KVO 一、KVO的介绍 KVO就是观察者模式,说白了就是你关心的...
    天净沙阅读 430评论 0 2
  • KVO与KVC的区别: KVO是指键-值-观察者模式,键值监听,监听一个对象属性值的改变,KVO是基于KVC的。K...
    41c48b8df394阅读 711评论 0 1
  • 先聊聊 KVO 与 KVC 的区别吧:KVO是指键-值-观察者模式, 键值监听, 监听一个对象属性值的改变。KVO...
    smile丽语阅读 311评论 1 3