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

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



你可能感兴趣的:(编码,Objective-C,KVO,KVC)