一、简介
KVC/KVO是观察者模式的一种实现,在Cocoa中是以被万物之源NSObject类实现的NSKeyValueCoding/NSKeyValueObserving非正式协议的形式被定义为基础框架的一部分。从协议的角度来说,KVC/KVO本质上是定义了一套让我们去遵守和实现的方法。
当然,KVC/KVO实现的根本是Objective-C的动态性和runtime,这在后文的原理部分会有详述。
另外,KVC/KVO机制离不开访问器方法的实现.
1、KVC简介
全称是Key-value coding,翻译成键值编码。顾名思义,在某种程度上跟map的关系匪浅。它提供了一种使用字符串而不是访问器方法去访问一个对象实例变量的机制。
2、KVO简介
全称是Key-value observing,翻译成键值观察。提供了一种当其它对象属性被修改的时候能通知当前对象的机制。再MVC大行其道的Cocoa中,KVO机制很适合实现model和controller类之间的通讯。
KVC定义了一种按名称访问对象属性的机制,支持这种访问的主要方法是:
- (id)valueForKey:(NSString *)key; - (void)setValue:(id)value forKey:(NSString *)key; - (id)valueForKeyPath:(NSString *)keyPath; - (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
前边两个方法用到的Key较容易理解,就是要访问的属性名称对应的字符串。
后面两个方法用到的KeyPath是一个被点操作符隔开的用于访问对象的指定属性的字符串序列。比如KeyPath address.street将会访问消息接收对象所包含的address属性中包含的一个street属性。其实KeyPath说白了就是我们平时使用点操作访问某个对象的属性时所写的那个字符串。
如果要修改对象的属性值
1.一般情况下是直接利用对象属性的set方法来修改:
Student *stu = [[Student alloc] init]; // set方法的两种书写格式 [stu setAge:10]; stu.age = 10;
2.但是如果不知道对象类型呢?那么就可以运用KVC键值编码(Key Value Coding) 间接的修改对象属性
KVC实现方式是:使用字符串来描述对象需要修改的属性。
KVC的基本调用包括: valueForKey: 和 setValue:ForKey: 是以字符串的方式向对象发送消息
KVC一般用法:
/******************************** 创建Book.h文件只是为了说明forKeyPath的用法,不用实现 *********************************/ #import <Foundation/Foundation.h> @interface Book : NSObject @property (nonatomic,assign) double price; // 书的价格 @end /******************************** Student.h文件 *********************************/ #import <Foundation/Foundation.h> @class Book; @interface Student : NSObject @property(nonatomic,assign) int age; // 学生年龄 @property (nonatomic,copy) NSString *name; // 学生姓名 @property (nonatomic,retain) Book *book; // 学生拥有书 // 测试方法 - (void)test; @end /******************************** Student.m文件 *********************************/ #import "Student.h" #import "Book.h" @implementation Student - (void)test { Student *stu = [[Student alloc] init]; // 1.为基本数据类型单次赋值 [stu setValue:@"John" forKey:@"name"]; NSString *str = [stu valueForKey:@"name"]; // str = John // 2.为对象类型单次赋值 // setValue 要参数为id类型 因此要先将基本数据类型包装成对象类型 [stu setValue:@10 forKey:@"age"]; // 根据key值去取出对象后在转换成为基本数据类型 int age = [[stu valueForKey:@"age"] intValue]; // 10 NSLog(@"%@ %d",str,age); // John 10 // 3.批次的为基本数据类型和对象类型赋值 [stu setValuesForKeysWithDictionary:@{@"age":@20,@"name":@"Jim"}]; // 根据key取出所有的值存入字典 NSDictionary *dic = [stu dictionaryWithValuesForKeys:@[@"name",@"age"]]; NSLog(@"%@",dic); // age = 20;name = Jim // 4.间接的为Book对象的price属性赋值 stu.book = [[Book alloc] init]; // 创建stu.book对象 // 第一种方式:直接赋值 [stu.book setPrice:20.00]; // price = 20.00 // 第二种方式:通过键值来赋值 [stu.book setValue:@30.00 forKey:@"price"]; // price = 30.00 // 第三种方式:通过健路径来赋值 [stu setValue:@40 forKeyPath:@"book.price"]; // price = 40.00 // 键值和键路径就相当于文件名和文件路径名,那么键值路径是包含了键值的,因此可以使用键值的时候可以用键路径来代替 那么的第二种方式也可以写为 [stu.book setValue:@50 forKeyPath:@"price"]; // price = 50.00 NSLog(@"%.2f",stu.book.price); // 测试输出 } @end 另外:KVC还提供了操作数组的方法和一些计算的参数
2.KVO (Key Value Observing) 键值观察机制,主要用来监听对象属性的变化
实现方式:添加监听器
举例:Teacher类要监听Student类中的name属性值的变化
/***************************** Student.h文件 *************************************/ #import <Foundation/Foundation.h> @interface Student : NSObject @property (nonatomic,copy) NSString *name; // 声明监听的属性 // 测试监听的方法 - (void)test; @end
/***************************** Student.h文件 *************************************/ #import "Student.h" #import "Teacher.h" @implementation Student - (void)test { Student *stu = [[Student alloc] init]; // 利用KVC为name属性赋值 [stu setValue:@"章三" forKey:@"name"]; // 实现Teacher类监听Student属性name的变化 // 1.创建监听对象 Teacher *teacher = [[Teacher alloc] init]; // addOberver是NSObject分类方法 那么任何对象都可以添加监听方法 // 2.对student类添加监听对象teacher options参数:监听新值还是旧值 // forKeyPath:键路径 context:上下文用于动画中 [stu addObserver:teacher forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil]; // 这里只监听了新值 当然也可以一起监听 option: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld // 修改了Student类name属性的值 就会通知监听器 Teacher对象 然后调用Teacher类中的 stu.name = @"李斯"; // 监听对象属性的值改变后 执行监听对象的方法 } @end
/***************************** Teacher.h文件 *************************************/ #import <Foundation/Foundation.h> @interface Teacher : NSObject @end
/***************************** Teacher.m文件 *************************************/ #import "Teacher.h" #import "Student.h" @implementation Teacher // 一旦监听到Student类中name属性值发生变化就会调用监听器Teacher类中的这个方法 通过参数来说明 // keyPath = @"name"; object就是Student类 change:改变后传到的值是新值还是旧值 还是新旧值都有对应的是监听器中的option参数 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { NSLog(@"keyPath:%@",keyPath); // keyPath:name NSLog(@"objcet:%@",object); // object:Student NSLog(@"change:%@",change); // \U674e\U65af 汉字被转义 } @end
/***************************** mian.m文件 *************************************/ #import <Foundation/Foundation.h> #import "Student.h" int main(int argc, const char * argv[]) { @autoreleasepool { // 测试 Student *stu = [[Student alloc] init]; [stu test]; } return 0; }