概述
在Java和C#中都提供了反射的功能,既根据字符串能动态的创建对象并修改对象的属性。OC内置了这些功能,使得我们在操作的时候更方便。
键值编码KVC
kvc是Key for Value的缩写,KVC的操作方法由NSKeyValueCoding协议提供,而NSObject就实现了这个协议,也就是说ObjC中几乎所有的对象都支持KVC操作,常用的KVC操作方法如下:
1>动态设置: setValue:属性值 forKey:属性名(用于简单路径)、setValue:属性值 forKeyPath:属性路径(用于复合路径,例如Person有一个Account类型的属性,那么person.account就是一个复合属性)
2>动态读取: valueForKey:属性名 、valueForKeyPath:属性名(用于复合路径)
例子如下:
Book.h
1 #import <Foundation/Foundation.h> 2 3 @interface Book : NSObject 4 @property (nonatomic,copy) NSString* bookName; 5 @property (nonatomic,assign)float price; 6 @end
Book.m
1 #import "Book.h" 2 3 @implementation Book 4 5 @end
Student.h
1 #import <Foundation/Foundation.h> 2 #import "Book.h" 3 @interface Student : NSObject 4 5 #pragma mark - 属性 6 @property (nonatomic,assign)int age; 7 @property (nonatomic,copy) NSString *name; 8 @property (nonatomic,retain) Book *book; 9 -(void)show; 10 @end
Student.m
1 #import "Student.h" 2 3 @implementation Student 4 -(void)show{ 5 NSLog(@"name=%@ and age=%d",_name,_age); 6 } 7 @end
main.m
1 #import <Foundation/Foundation.h> 2 #import "Student.h" 3 int main(int argc, const char * argv[]) 4 { 5 6 @autoreleasepool { 7 Student *stu1=[[Student alloc]init]; 8 [stu1 setValue:@"lisi" forKey:@"name"]; 9 [stu1 setValue:@14 forKey:@"age"]; 10 [stu1 show]; 11 12 NSLog(@"stu1 name=%@,age=%@",[stu1 valueForKey:@"name"],[stu1 valueForKey:@"age"]); 13 14 Book *book1=[[Book alloc]init]; 15 stu1.book=book1; 16 [stu1 setValue:@"99.8" forKeyPath:@"book.price"]; 17 [stu1 setValue:@"mathBook" forKeyPath:@"book.bookName"]; 18 NSLog(@"stu1 bookname=%@ price=%@",[stu1 valueForKeyPath:@"book.bookName"],[stu1 valueForKeyPath:@"book.price"]); 19 20 } 21 return 0; 22 }
KVC是如何对属性进行读取的呢?假如要读取属性a
- 如果是动态设置属性,则优先考虑调用setA方法,如果没有该方法则优先考虑搜索成员变量_a,如果仍然不存在则搜索成员变量a,如果最后仍然没搜索到则会调用这个类的setValue:forUndefinedKey:方法(注意搜索过程中不管这些方法、成员变量是私有的还是公共的都能正确设置);
- 如果是动态读取属性,则优先考虑调用a方法(属性a的get方法),如果没有搜索到则会优先搜索成员变量_a,如果仍然不存在则搜索成员变量a,如果最后仍然没搜索到则会调用这个类的valueforUndefinedKey:方法(注意搜索过程中不管这些方法、成员变量是私有的还是公共的都能正确读取);
键值监听KVO
在C#或者Java图形界面编程的时候,我们改变控件的某个属性,一般都能动态的反应到UI的变化上。这实际上利用的观察者模式,利用这种模式很容易实现数据模型和界面操作的分离。OC默认内置了这项功能叫KVO。在OC中要实现KVO则必须实现NSKeyValueObServing协议,不过NSObject已经实现了该协议,因此几乎所有的ObjC对象都可以使用KVO。
在OC中使用KVO操作常用的方法如下:
1>注册监听: addObserver: forKeyPath: options: context:
2>回调监听: observeValueForKeyPath: ofObject: change: context:
3>删除监听: removeObserver: forKeyPath、removeObserver: forKeyPath: context:
KVO的使用步骤也比较简单:
1>通过addObserver: forKeyPath: options: context:为被监听对象(它通常是数据模型)注册监听器
2>重写监听器的observeValueForKeyPath: ofObject: change: context:方法
上面的例子中,假如我们希望在Book价格改变的时候通知Student,我们可以这样写:
Student.h:
1 #import <Foundation/Foundation.h> 2 #import "Book.h" 3 @interface Student : NSObject 4 5 #pragma mark - 属性 6 @property (nonatomic,assign)int age; 7 @property (nonatomic,copy) NSString *name; 8 @property (nonatomic,retain) Book *book; 9 -(void)show; 10 @end
Student.m:
1 #import "Student.h" 2 3 @implementation Student 4 -(void)show{ 5 NSLog(@"name=%@ and age=%d",_name,_age); 6 } 7 8 -(void)setBook:(Book *)book{ 9 _book=book; 10 //添加对Book的监听 11 [self.book addObserver:self forKeyPath:@"price" options:NSKeyValueObservingOptionNew context:nil]; 12 } 13 14 -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{ 15 //Book价格变化会调用这个函数 16 if([keyPath isEqualToString:@"price"]){ 17 NSLog(@"keyPath=%@,Obj=%@,newValue=%f,context=%@",keyPath,object,[[change objectForKey:@"new"]floatValue],context); 18 } 19 } 20 -(void)dealloc{ 21 //移除监听 22 [self.book removeObserver:self forKeyPath:@"price"]; 23 [super dealloc]; 24 } 25 @end
main.m:
1 #import <Foundation/Foundation.h> 2 #import "Student.h" 3 int main(int argc, const char * argv[]) 4 { 5 6 @autoreleasepool { 7 Student *stu1=[[Student alloc]init]; 8 [stu1 setValue:@"lisi" forKey:@"name"]; 9 [stu1 setValue:@14 forKey:@"age"]; 10 [stu1 show]; 11 12 NSLog(@"stu1 name=%@,age=%@",[stu1 valueForKey:@"name"],[stu1 valueForKey:@"age"]); 13 14 Book *book1=[[Book alloc]init]; 15 stu1.book=book1; 16 [stu1 setValue:@"99.8" forKeyPath:@"book.price"]; 17 [stu1 setValue:@"mathBook" forKeyPath:@"book.bookName"]; 18 NSLog(@"stu1 bookname=%@ price=%@",[stu1 valueForKeyPath:@"book.bookName"],[stu1 valueForKeyPath:@"book.price"]); 19 20 } 21 return 0; 22 }
结果输出:
1 2015-05-04 11:54:54.557 first[2697:303] name=lisi and age=14 2 2015-05-04 11:54:54.559 first[2697:303] stu1 name=lisi,age=14 3 2015-05-04 11:54:54.560 first[2697:303] keyPath=price,Obj=<Book: 0x1024004a0>,newValue=99.800003,context=(null) 4 2015-05-04 11:54:54.560 first[2697:303] stu1 bookname=mathBook price=99.8
我们给Book的price属性注册了监听,并添加了监听回调方法,这样在我们改变Book的price属性时,就会调用我们的回调方法打印出相关的信息。通过这个例子我们可以看出,KVO的本质是:
1>在要监听的成员中的get/set方法中注册回调函数
2>重写这个回调函数
这样在改变这个属性的时候就会自动调用我们的回调函数,实现了观察者模式。