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:的方法时,底层的执行机制如下:

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

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

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

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

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

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

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

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

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

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

  • 如果还没有找到,再检查类方法+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默认行为),那么和先前的设值一样,会按_,_is,,is的顺序搜索成员变量名,这里不推荐这么做,因为这样直接访问实例变量破坏了封装性,使代码更脆弱。如果重写了类方法+ (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: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 *)dictionaryWithValuesForKeys:(NSArray *)keys;
//  setValuesForKeysWithDictionary    是用来修改Model中对应key的属性。下面直接用代码会更直观一点
- (void)setValuesForKeysWithDictionary:(NSDictionary *)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.通知发出后,不能从观察者获得任何的反馈信息。

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

你可能感兴趣的:(KVO与KVC)