KVO和KVC

一、KVC:键值编码

  KVC的操作方法由NSKeyValueCoding协议提供,而NSObject实现了这个协议

  基本作用:动态操作变量值

       通过key/value字符串的方式访问变量,而不是直接使用变量名。

  1、使用方法

     1)获取值:valueForKey

        NSString *n = [object valueForKey:@"name"]

     2)设定值:

     [object setValue:@"Daniel" forKey:@"name"]

     ps:可针对的数据类型:对象基本数据类型均可

  2、键路径:key path

     访问属性的属性时使用

     访问方式:valueForKeyPath

[person valueForKeyPath:@"address.city"]

  3、使一个类完全支持kvc

     关键:1)传入的基本数据类型为nil时,重写默认方法,防止直接抛出异常

        2)使用的key不存在时(传入,获取),重写默认方法,防止直接抛出异常

    1)PersonInfo.h

#import <Foundation/Foundation.h>

@interface PersonInfo : NSObject
{
    NSString *name;
    float height;
    
}
//支持kvc,不用 @property 和 @synthesize的属性
-(NSString *)name;
-(void)setName:(NSString *)name;
-(float)height;
-(void)setHeight:(float)height;

//防止“基本数据类型”、“struct类型”传入nil后,默认方法直接抛异常
//当对非类对象属性设置nil时,调用,默认抛出异常
-(void)setNilValueForKey:(NSString *)key;//传入

//防止使用未知的key时,默认方法直接抛异常
-(void)setValue:(id)value forUndefinedKey:(NSString *)key;//传入
-(id)valueForUndefinedKey:(NSString *)key;//获取

@end

    2)PersonInfo.m

#import "PersonInfo.h"

@implementation PersonInfo

-(NSString *)name{
    return name;
}

-(float)height{
    return height;
}

- (void)setNilValueForKey:(NSString *)key//传入
{
    NSLog(@"传入nil值");
    if ([key isEqualToString:@"height"]){
        [self setValue:@"99.00" forKey:key];
    } else
        [super setNilValueForKey:key];
}

-(void)setValue:(id)value forUndefinedKey:(NSString *)key//传入
{
    NSLog(@"未定义的key,无法设置key-value");
}
-(id)valueForUndefinedKey:(NSString *)key;//获取
{
    NSLog(@"未知的key");
    return @"未知的key";
}

@end

    3)调用:   

   PersonInfo *personInfo=[[PersonInfo alloc]init];
    [personInfo setValue:nil forKey:@"height"];//kvc的方式设置某个属性为nil
    [personInfo setValue:@1.78 forKey:@"heightsss"];//kvc的方式设置不存在的key
    [personInfo valueForKey:@"heisght"];//kvc的方式获取不存在的key
    
    //正常存入,获取
    NSLog(@"personInfo.height:%@",[personInfo valueForKey:@"height"]);
    [personInfo setValue:@1.88 forKey:@"height"];
    NSLog(@"personInfo.height新值:%@",[personInfo valueForKey:@"height"]);

    4)运行结果:

    

  4、实用场景:

      根据集合中存储的“属性名”,通过字符串拼接动态获取“ui元素名”,实现动态操作ui元素的功能

      实现:略

      参考:http://blog.jobbole.com/69731/   

  5、补充:对于3个方法比较通用的实现

- (void)setNilValueForKey:(NSString *)key//传入
{
    NSLog(@"当前key为:%@,传入nil值",key);
    [super setNilValueForKey:key];
}

-(void)setValue:(id)value forUndefinedKey:(NSString *)key//传入
{
    NSLog(@"未定义的key,无法设置key-value");
}

-(id)valueForUndefinedKey:(NSString *)key;//获取
{
    NSLog(@"未定义的key,无法获取");
    return @"未定义的key,无法获取";
}

二、KVO:键值监听

  重要特点:使用时“添加”和“移除”必须成对出现

  1、基本概念: 1)一种观察者模式

         2)需要实现NSKeyValueObServing协议(NSObject中已实现)

  2、使用方法:1)“被监听对象”通过addObserver: forKeyPath: options: context:注册监听

          2)实现监听回调方法:observeValueForKeyPath: ofObject: change: context:

          3)覆盖dealloc方法,移除监听:removeObserver: forKeyPath: context:

  3、线程安全性:1)值的改变和监听发生在同一个线程上

           2)如果要从“子线程”中改变属性值,需要保证:

            所有的监听者(通常是操作UI对象)都用线程安全的方式处理KVO通知

            ps:performSelectorOnMainThread??

      4、代码实现:

    1)被监听者

      StockInfo.h:

#import <Foundation/Foundation.h>

@interface StockInfo : NSObject

@property(nonatomic,copy)NSString *stockName;
@property(nonatomic,assign)float stockPrice;

@end

      StockInfo.m:

#import "StockInfo.h"

@implementation StockInfo

@synthesize stockName=_stockName;
@synthesize stockPrice=_stockPrice;

@end

    2)监听者(操作UI的对象)

       UI_Observer.h:

#import <UIKit/UIKit.h>

@interface UI_Observer : UIViewController

@end

       UI_Observer.m:

#import "UI_Observer.h"
#import "StockInfo.h"

@interface UI_Observer ()

@property(nonatomic,strong)StockInfo *stockInfo;

@property(nonatomic,strong)UILabel *lblPrice;
@property(nonatomic,strong)UIButton *btnChangePrice;

@end

@implementation UI_Observer

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.view.backgroundColor=[UIColor yellowColor];
    
    //为stockInfo添加观察者
    self.stockInfo=[[StockInfo alloc]init];
    //self.stockInfo.stockName=@"stockOO1";
    //self.stockInfo.stockPrice=@"17.2";
    [self.stockInfo setValue:@"stockOO1" forKey:@"stockName"];
    [self.stockInfo setValue:@"10.0" forKey:@"stockPrice"];
    [self.stockInfo addObserver:self forKeyPath:@"stockPrice" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:NULL];
    
    //初始化界面元素
    self.lblPrice = [[UILabel alloc]initWithFrame:CGRectMake(self.view.frame.size.width/2-50, 100, 100, 30 )];
    self.lblPrice.textColor = [UIColor redColor];
    self.lblPrice.text = [[self.stockInfo valueForKey:@"stockPrice"] stringValue];//必须加上stringValue的类型强转
    [self.view addSubview:self.lblPrice];
    
    self.btnChangePrice = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    self.btnChangePrice.frame = CGRectMake(self.view.frame.size.width/2-50, 300, 100, 30);
    self.btnChangePrice.backgroundColor=[UIColor whiteColor];
    [self.btnChangePrice setTitle:@"改变价格" forState:UIControlStateNormal];
    [self.btnChangePrice addTarget:self action:@selector(btnChangePriceClick) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:self.btnChangePrice];
}

-(void)btnChangePriceClick{
    //self.stockInfo.stockPrice=@"29.9";
    //值改变后,回调方法被自动触发
    [self.stockInfo setValue:@"29.9" forKey:@"stockPrice"];
}

#pragma mark 监听的回调方法:observeValueForKeyPath,价格变化后通知ui改变
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    if([keyPath isEqualToString:@"stockPrice"]){//这里只处理stockPrice属性
        NSLog(@"keyPath: %@",keyPath);
        NSLog(@"object: %@",object);
        NSLog(@"更新前的旧值: %.2f",[[change objectForKey:@"old"] floatValue]);
        NSLog(@"更新后的新值: %.2f",[[change objectForKey:@"new"] floatValue]);
        NSLog(@"context: %@",context);
        self.lblPrice.text = [[self.stockInfo valueForKey:@"stockPrice"] stringValue];//必须加上stringValue的类型强转
    }
}

//重写销毁方法
-(void)dealloc{
    [self.stockInfo removeObserver:self forKeyPath:@"stockPrice"];//移除监听
    //[super dealloc];//启用了ARC,此处不需要调用
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

    3)运行结果:

        

      点击按钮:

      

     

     深入理解:http://blog.jobbole.com/69731/  

     参考:http://www.cnblogs.com/kenshincui/p/3871178.html  

        http://www.cnblogs.com/kenshincui/p/3871178.html

        http://blog.csdn.net/messageloop3/article/details/8634798

posted @ 2016-01-04 20:16  edisonfeng  阅读(424)  评论(0)    收藏  举报