KVC(Key Value Coding)键值编码,乍一听感觉很高大上,其实简单的说起来就是一个赋值的语句,那为什么会有这个操作呢,用 '.' 语法不是更简单吗,理解上是没错的,但在点语法出现之前,我们的程序员前辈们都是通过这种赋值方法的,并且在很多情况下,KVC赋值看似麻烦,实际上是比 ‘.’ 语法简更加精炼的。
其实在之前我们也用过键值编码的例子了,是在字典的赋值上,如
NSMutableDictionary * dic = [NSMutableDictionary dictionary]; //通过键值编码(KVC)赋值 [dic setValue:@"RunIntoLove" forKey:@"me"];
下面会用代码来解释: (当然例子还是之前的自定义类 Cricle )
首先我们为类定义几个属性
// // Cricle.h // KVC (键值编码) KVO (键值观察) 博客 // // Created by YueWen on 15/9/14. // Copyright (c) 2015年 YueWen. All rights reserved. // #import <Foundation/Foundation.h> #define PI 3.14 //定义π的值 @interface Cricle : NSObject @property(nonatomic,strong)NSString * name;//圆的名字 @property(nonatomic,assign) double radius;//半径 @property(nonatomic,assign) double circumference;//周长 @property(nonatomic,assign) double area;//面积
然后自定义一个初始化方法
/** * 自定义的便利初始化方法 * * @param name 圆的名称 * @param circumference 圆的半径 * * @return */ -(instancetype)initWithName:(NSString *)name withRadius:(double)radius;
实现一下
-(instancetype)initWithName:(NSString *)name withRadius:(double)radius { if (self = [super init]) { /*用KVC赋值*/ [self setValue:name forKey:@"name"]; /*用点语法赋值*/ self.radius = radius;//这里如果用KVC会报错,就是说 基础数据类型 是不能用 KVC的 self.circumference = 2 * PI * radius;//为周长赋值 self.area = PI * radius * radius;//为面积赋值 } return self; }
//创建一个字典,只是为了举例子,没有实际作用 NSDictionary * dict = @{@"name":@"RunIntoLove",@"radius":@6,@"circumference":@8,@"area":@6.28};
-(instancetype)initWithDict:(NSDictionary *)dict { if (self = [super init]) { //通过字典赋值 [self setValuesForKeysWithDictionary:dict]; } return self; }
那么我们来在实际代码中来瞅瞅吧
// // main.m // KVC (键值编码) KVO (键值观察)博客 // // Created by YueWen on 15/9/14. // Copyright (c) 2015年 YueWen. All rights reserved. // #import <Foundation/Foundation.h> #import "Cricle.h" int main(int argc, const char * argv[]) { //创建一个Cricle对象,因为面积与周长我们是在初始化方法里面自己算出来的,所以不需要赋值 Cricle * cricle = [[Cricle alloc]initWithName:@"circle1" withRadius:2]; /** * 打印结果是:2015-09-14 15:54:43.015 KVC 博客[934:35883] name = circle1, radius = 2, circumference = 12.56 , area = 12.56 */ NSLog(@"name = %@, radius = %g, circumference = %g , area = %g",cricle.name,cricle.radius,cricle.circumference,cricle.area); //如果我们再修改圆的半径值 cricle.radius = 3; /** * 我们再来打印一下 * 打印结果是:2015-09-14 15:56:26.082 KVC 博客[949:36615] next : name = circle1, radius = 3, circumference = 12.56 , area = 12.56 */ NSLog(@"next : name = %@, radius = %g, circumference = %g , area = %g",cricle.name,cricle.radius,cricle.circumference,cricle.area); //我们看到改变的只是半径的值而已,但是周长和面积没有变 //解决方法有两种 //1、在radius的set方法中,设置周长和面积的值,当然这个问题比较简单,所以相信我们会觉得这种方法会好,但是遇到复杂的问题的时候,这种方法就不适合了 //2、KVO(键值观察) return 0; }
-(instancetype)initWithName:(NSString *)name withRadius:(double)radius { //因为我们要用到KVO(键值观察) 所以不需要在这里给周长赋值了,首先我们需要添加一个观察 //注意 一定要在init之前添加观察,不然是观察不到的 /** * 解释一下参数 * addObserver:就是表示监听谁的(这里我们监听自己的) * keyPath:什么属性(半径属性) * options:监听的是什么,这是一个枚举,我们可以点进开发文档中观看,这里我们监听它的新值 * context:就是一个类似的全局指针 */ [self addObserver:self forKeyPath:@"radius" options:NSKeyValueObservingOptionNew context:@"radius"]; if (self = [super init]) { /*用KVC赋值*/ [self setValue:name forKey:@"name"]; /*用点语法赋值*/ self.radius = radius; } return self; }
接着就是我们需要来实现它的回调方法(当 我们监听的 radius 发生变化时会自动调用该方法)
/** * 实现KVO(键值观察)的回调方法 * * @param keyPath 观察的键值 * @param object 观察的键值是谁的 * @param change 存储变化的字典 * @param context 全局的指针 */ -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { //首先我们获取文本的信息 NSString * myContext = (__bridge NSString *)context;//__bridge的作用就是 类型转换 ,不修改内存的管理权限 这里牵扯到ARC中 NSObject与CFObject(Core Foundation Object)转换的关系 以及内存管理权的知识,不做过多的解释 //如果这个接收的信息是 radius变化 发来的 if ([myContext isEqualToString:@"radius"]) { //首先我们需要获取一下这个新值,返回的类型是一个NSNumber类型的 NSNumber * number = change[@"new"]; //转换成double double r = [number doubleValue]; //为周长赋值 _circumference = 2 * PI * r; } }
//2、KVO(键值观察) //创建一个Cricle对象,因为面积与周长我们是在初始化方法里面自己算出来的,所以不需要赋值 Cricle * cricle = [[Cricle alloc] initWithName:@"cricle1" withRadius:2]; //打印一下信息 /** * 打印结果是:2015-09-14 17:06:20.400 KVC 博客[1324:62564] name = cricle1, radius = 2, circumference = 12.56 */ NSLog(@"name = %@, radius = %g, circumference = %g",cricle.name,cricle.radius,cricle.circumference); //这时修改一下半径值 cricle.radius = 3; /** * 打印结果是:2015-09-14 17:07:21.328 KVC 博客[1336:63034] next : name = cricle1, radius = 3, circumference = 18.84 * 可以看出已经自己发生了变化 */ NSLog(@"next : name = %@, radius = %g, circumference = %g",cricle.name,cricle.radius,cricle.circumference);
用KVO的时候,不要忘记在最后的时候移除观察,不然会引起概率性崩溃
-(void)dealloc { //注册完键值观察,在这个方法里一定要记得移除观察,不然会发生概率性崩溃(这种崩溃很坑,所以一定要避免) [self removeObserver:self forKeyPath:@"radius"]; }