您的位置:首页 > 移动开发 > Objective-C

Objective-C学习-KVC(键值编码)和KVO(键值观察)

2015-09-14 08:51 429 查看

KVC(键值编码)

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;
}


从上面来看,KVC貌似没有点语法简答,那么我再举个例子,我想通过字典来创建这个类呢,字典也有个要求,key值要和类的属性名相同,如下

//创建一个字典,只是为了举例子,没有实际作用
NSDictionary * dict = @{@"name":@"RunIntoLove",@"radius":@6,@"circumference":@8,@"area":@6.28};


如果用点语法,我们是需要一个一个的赋值的,个数少还好,如果个数是上百上千,那么会显得很麻烦,这时KVC就显得很重要了

-(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;
}


KVO(键值观察)

KVO(Key Value Observe)键值观察,第一反应,和KVC很像,会不会和KVC的关系很密切呢,只有通过该属性的setter方法或者Key-Path(KVC)来设置值的他的值的时候才会被KVO监听到(原理在runtime中有很清楚的解释,有兴趣可以搜一下),作用是什么呢,就是通过观察一个属性的值,如果值发生变化,我们就需要来处理一些事情,打个比方:

比如上面的例子中,圆的半径发生了变化,但是周长和面积理应也该发生变化,但是实际上没有发生变化,如果用KVO来讲,就是说当检测到radius发生变化的时候,那么是需要重新计算周长和面积的,多说无益,我们来用代码来看吧

首先要用到观察,我们要添加一个监听,在init方法中我们里实现(为了简化,我们把之前的面积属性去掉了,用法和周长其实是一样的)

-(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"];
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: