12、OC KVC(键值编码)与KVO(键值监听)

一、键值编码 Key-Value-Coding 

KVC是通过字符串的形式操作对象的属性,这个字符串既可以是常量也可以是变量,使用KVC可以使得编码更加简洁。

KVC支持类对象和内建基本数据类型。

  获取值

valueForKey:,传入NSString属性的名字。

valueForKeyPath:,传入NSString属性的路径,xx.xx形式。

valueForUndefinedKey它的默认实现是抛出异常,可以重写这个函数做错误处理。

  修改值

setValue:forKey:

setValue:forKeyPath:

setValue:forUndefinedKey:

setNilValueForKey:当对非类对象属性设置nil时,调用,默认抛出异常。

  一对多关系成员的情况

mutableArrayValueForKey:有序一对多关系成员  NSArray

mutableSetValueForKey:无序一对多关系成员  NSSet


在获取值或者修改值的时候可以使用valueForKey和setValue:forKey,都是通过NSString对象来指定被操作属性的,通过forKey传入属性名。而寻找属性名也是有顺序的,
setValue:forKey和valueforKey寻找key的方式按如下优先级进行:
1.首先会优先调用set方法,如果有自定义的setter方法,首先会调用自定义的set方法,没有自定义的会找@property合成的。
2.如果没有1中提到的setter方法,则KVC会搜索该类的(_成员变量,例如_name)成员变量,无论该成员变量在类接口部分还是在类实现部分,也不会考虑那个访问控制符。
3.如果如果没有setter,也没有(_成员变量),则会搜索该类名为(成员变量,name)的成员变量,无论该成员变量在类接口部分还是在类实现部分,也不会考虑那个访问控制符。

我们在使用setValue:forKey和valueforKey会遇到key不存在,或者为值赋值为nil的情况,那么我们需要重写 setValue:forUndefinedKey、 valueForUndefinedKey 、setNilValueForKey等方法。

下面是KVC对上面所讲内容的测试。
#import <Foundation/Foundation.h>
@interface Person : NSObject{
    //声明成员变量,并没有指定setter和getter
    NSDate* _birth;
}
//使用@property定义的属性
@property(nonatomic,copy) NSString* name;
//修改setter方法
@property(nonatomic,copy) NSString* sex;
//sex的setter方法
-(void)setSex:(NSString *)sex;

//重写setValue:forUndefinedKey,如果setter方法无果,则会调用这个方法,处理不存在的key
-(void)setValue:(id)value forUndefinedKey:(NSString *)key;
//重写valueForUndefinedKey,如果getter方法无果,则会调用此方法
-(id)valueForUndefinedKey:(NSString *)key;
//重写setNilValueForKey,处理nil值
-(void)setNilValueForKey:(NSString *)key;
@end

#import "Person.h"
@implementation Person{
    //类实现部分的属性,无setter无getter
    int age;
}
-(void)setSex:(NSString *)sex{
    self->_sex = [NSString stringWithFormat:@"--%@--",sex];
}

-(void)setValue:(id)value forUndefinedKey:(NSString *)key{
    NSLog(@"值--%@--不存在",key);
}
-(id)valueForUndefinedKey:(NSString *)key{
    NSLog(@"你要获取的值%@并不存在",key);
    return nil;
}

-(void)setNilValueForKey:(NSString *)key{
    NSLog(@"为%@赋值nil",key);
    //如果name的属性值赋为nil则修改name的值
    if ([key isEqualToString:@"age"]) {
        NSLog(@"要为age赋值为nil,执行默认赋值0");
        self->_name = 0;
    }else{
        //回掉父类的setNilValueForKey
        [super setNilValueForKey:key];
    }
}
@end


#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //创建Person对象
        Person* person = [[Person alloc]init];
       
        //使用KVC为@property声明的属性赋值
        [person setValue:@"小明" forKey:@"name"];
        //使用kvc获取name属性值
        NSLog(@"name: %@",[person valueForKey:@"name"]);
       
        //使用KVC为@property声明并自定义setter方法的属性赋值
        [person setValue:@"男" forKey:@"sex"];
        //使用kvc获取sex属性值
        NSLog(@"sex: %@",[person valueForKey:@"sex"]);
       
        //使用KVC为没有定义setter和getter方法的成员变量赋值
        [person setValue:[[NSDate alloc]init] forKey:@"birth"];
        //使用kvc获取birth属性值
        NSLog(@"birth: %@",[person valueForKey:@"birth"]);
       
        //使用KVC为在实现部分定义的属性age赋值
        [person setValue:[NSNumber numberWithInt:22] forKey:@"age"];
        //使用kvc获取age属性值
        NSLog(@"age: %@",[person valueForKey:@"age"]);
       
        //处理不存在的key
        [person setValue:@"小兰" forKey:@"grilFriend"];
        [person valueForKey:@"grilFriend"];
       
        //处理nil值
        [person setValue:nil forKey:@"age"];
    }
    return 0;
}

输出结果:
2014-12-07 17:49:25.442 Blog_02_OC[2033:108207] name: 小明
2014-12-07 17:49:25.443 Blog_02_OC[2033:108207] sex: --
--
2014-12-07 17:49:25.448 Blog_02_OC[2033:108207] birth: 2014-12-07 09:49:25 +0000
2014-12-07 17:49:25.448 Blog_02_OC[2033:108207] age: 22
2014-12-07 17:49:25.448 Blog_02_OC[2033:108207]
--grilFriend-- 不存在
2014-12-07 17:49:25.449 Blog_02_OC[2033:108207]
你要获取的值 grilFriend 并不存在
2014-12-07 17:49:25.449 Blog_02_OC[2033:108207]
age 赋值 nil
2014-12-07 17:49:25.449 Blog_02_OC[2033:108207] 要为age赋值为nil,执行默认赋值0



二、键值监听Key-Value-Observer
KVO机制NSKeyValueObserving协议提供支持,有以下几种方法:
addObserver:forKeyPath:options:context:注册一个监听器用于监听指定的Key路径。
removeObserver:forKeyPath:为key路径删除指定的监听器。
removeObserver:forKeyPath:context:为Key路径删除指定的监听器。
key所指定的值改变后会回掉监听器自身的监听方法,监听方法如下:
observerValueForKeyPath:ofObject:change:context。重写该方法就可以得到修改的数据。

下面是用一个例子来看下键值监听的用途,我们首先定义一个Books类,这个类里面有两个属性,分别是书名和数目,我们再定义一个BookManger类,这个类会使用到Book,可以把BookManger类当作是一个视图,而这个视图会监听数据Book的变化,数据变化相应的界面也会变化,这个在IOS开发中会比较常用,我们使用main函数模拟值的变化。


Books类
#import <Foundation/Foundation.h>
@interface Books : NSObject
@property (nonatomic,copy)NSString* name;
@property (nonatomic,assign)int num;
@end

#import "Books.h"
@implementation Books
@end

BookManger类
#import <Foundation/Foundation.h>
@class Books;
@interface BookManger : NSObject
@property(nonatomic,weak) Books* myBook;
-(void)printBooks;
@end

#import "BookManger.h"
#import "Books.h"
@implementation BookManger
@synthesize myBook=_myBook;
-(void)printBooks{
    NSLog(@"书名%@,数量%d",_myBook.name,_myBook.num);
}
//自定义myBook的setter方法
-(void)setMyBook:(Books *)myBook{
    self->_myBook = myBook;
    //为myBook的name属性添加监听
    [self.myBook addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    //为myBook的num属性添加监听
    [self.myBook addObserver:self forKeyPath:@"num" options:NSKeyValueObservingOptionNew context:nil];
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    NSLog(@"被修改的keyPath为:%@",keyPath);
    NSLog(@"被修改的对象为:%@",object);
    NSLog(@"被修改的属性值为:%@",change);
    NSLog(@"被修改的上下文为:%@",context);
}

-(void)dealloc{
    [self.myBook removeObserver:self forKeyPath:@"name"];
    [self.myBook removeObserver:self forKeyPath:@"num"];
}
@end


main函数
#import <Foundation/Foundation.h>
#import "Books.h"
#import "BookManger.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Books* book = [[Books alloc]init];
        book.name = @"OC开发";
        book.num = 10;
        BookManger* bookManger = [[BookManger alloc]init];
        bookManger.myBook = book;
        //打印初始的书信息
        [bookManger printBooks];
        //修改book的name属性
        book.name = @"IOS学习";
        //修改book的num属性
        book.num = 20;
    }
}


运行程序,可以看到已经成功的监听到了值的变化:
2014-12-07 22:11:39.923 Blog_02_OC[2951:166400] 书名 OC 开发,数量 10
2014-12-07 22:11:39.924 Blog_02_OC[2951:166400]
被修改的 keyPath :name
2014-12-07 22:11:39.924 Blog_02_OC[2951:166400]
被修改的对象为 :<Books: 0x100304990>
2014-12-07 22:11:39.924 Blog_02_OC[2951:166400]
被修改的属性值为 :IOS 学习
2014-12-07 22:11:39.924 Blog_02_OC[2951:166400]
被修改的上下文为 :(null)
2014-12-07 22:11:39.925 Blog_02_OC[2951:166400]
被修改的 keyPath :num
2014-12-07 22:11:39.925 Blog_02_OC[2951:166400]
被修改的对象为 :<Books: 0x100304990>
2014-12-07 22:11:39.925 Blog_02_OC[2951:166400]
被修改的属性值为 :20
2014-12-07 22:11:39.925 Blog_02_OC[2951:166400] 被修改的上下文为:(null)



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值