1.KVC
除了一般的赋值和取值的方法,我们还可以用Key-Value-Coding(KVC)键值编码来访问你要存取的类的属性。
A.Student类没有提供set方法,用kvc可以达到存取的目的。
@interface Student : NSObject
{
NSString *name;
}
@end
[student setValue:@"张三" forKey:@"name"];
NSString *name = [student valueForKey:@"name"];
B.如果访问这个类里中的属性中的属性呢?那就用到了键路径.
@interface Course : NSObject
{
NSString *CourseName;
}
@end
@interface Student : NSObject
{
NSString *name;
Course *course;
}
@end
Student *student = [[Student alloc]init ];
[student setValue:@"张三" forKey:@"name"];
NSString *name = [student valueForKey:@"name"];
NSLog(@"学生姓名:%@",name);
Course *course = [[Course alloc]init];
[course setValue:@"语文课" forKey:@"CourseName"];
[student setValue:course forKey:@"course"];
NSString *courseName = [student valueForKeyPath:@"course.CourseName"];
NSLog(@"课程名称:%@", courseName);
//也可以这样存值,存取时如果KeyPath不存在那么会崩溃。
[student setValue:@"数学课" forKeyPath:@"course.CourseName"];
courseName = [student valueForKeyPath:@"course.CourseName"];
C.自动封装基本数据类型
@interface Student : NSObject
{
NSString *name;
Course *course;
NSInteger point;
}
[student setValue:@"88" forKeyPath:@"point"];
[NSString *point = [student valueForKey:@"point"];
类里面的point字段类型是NSInteger的但是set方法给的是NSString仍然可以正常存取。
D.操作集合
{
NSString *name;
Course *course;
NSInteger point;
NSArray *otherStudent;
}
Student *student = [[[Student alloc]init ]autorelease];
[student setValue:@"张三" forKey:@"name"];
NSString *name = [student valueForKey:@"name"];
NSLog(@"学生姓名:%@",name);
[student setValue:@"88" forKey:@"point"];
NSString *point = [student valueForKey:@"point"];
NSLog(@"分数:%@", point);
Student *student1 = [[[Student alloc]init]autorelease];
Student *student2 = [[[Student alloc]init]autorelease];
Student *student3 = [[[Student alloc]init]autorelease];
[student1 setValue:@"65" forKey:@"point"];
[student2 setValue:@"77" forKey:@"point"];
[student3 setValue:@"99" forKey:@"point"];
NSArray *array = [NSArray arrayWithObjects:student1,student2,student3,nil];
[student setValue:array forKey:@"otherStudent"];
NSLog(@"其他学生的成绩%@", [student valueForKeyPath:@"otherStudent.point"]);
NSLog(@"共%@个学生", [student valueForKeyPath:@"otherStudent.@count"]);
NSLog(@"最高成绩:%@", [student valueForKeyPath:@"otherStudent.@max.point"]);
NSLog(@"最低成绩:%@", [student valueForKeyPath:@"otherStudent.@min.point"]);
NSLog(@"平均成绩:%@", [student valueForKeyPath:@"otherStudent.@avg.point"]);
}
有一些方法可以对集合进行相应操作。
2.KVO
key-value observing (KVO)
观察 model 对象的变化
在 Cocoa 的模型-视图-控制器 (Model-view-controller)架构里,控制器负责让视图和模型同步。这一共有两步:当 model 对象改变的时候,视图应该随之改变以反映模型的变化;当用户和控制器交互的时候,模型也应该做出相应的改变。
.h文件 globalObject
@interface globalObject : NSObject {
NSString * theNum;
}
+(globalObject *)sharedInstance;
//@property (nonatomic, assign) NSNumber theNum;
- (void)setTheNum1 :(NSNumber *)num;
- (void)setTheNum :(NSNumber *)num;
@end
.m 文件 globalObject
+(globalObject *)sharedInstance
{
staticglobalObject *sharedglobalObjectInstance =nil;
staticdispatch_once_t predicate;
dispatch_once(&predicate, ^{
sharedglobalObjectInstance = [[globalObjectalloc]init];
});
return sharedglobalObjectInstance;
}
- (void)setTheNum1 :(NSNumber *)num{
[self willChangeValueForKey:@"theNum"];
theNum = [numstringValue];
[self didChangeValueForKey:@"theNum"];
}
- (void)setTheNum :(NSNumber *)num{
theNum = [numstringValue];
}
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key {
if ([keyisEqualToString:@"theNum"]) {
returnNO;
}
return [superautomaticallyNotifiesObserversForKey:key];
}
XXXcontroller.m
- (void)viewDidLoad {
[superviewDidLoad];
[[globalObjectsharedInstance]addObserver:selfforKeyPath:@"theNum"options:NSKeyValueObservingOptionNewcontext:nil];
// Do any additional setup after loading the view from its nib.
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPathisEqualToString:@"theNum"]) {
id num = [changeobjectForKey:@"new"];
NSInteger n = [((NSNumber *)num)integerValue];
NSLog(@"%@----%ld",@"observeValueForKeyPath", n);
}
}
- (IBAction)doSomething:(id)sender {
staticNSInteger i =0;
[[globalObject sharedInstance] setTheNum1:[[NSNumber alloc] initWithInteger:i++]];
[[globalObject sharedInstance] setTheNum:[[NSNumber alloc] initWithInteger:i++]];
[[globalObject sharedInstance]setValue:[[NSNumber alloc]initWithInteger:i++] forKey:@"theNum"];
NSLog(@"-----%@",[[globalObject sharedInstance] valueForKey:(@"theNum")]);
}
使用KVO时遇到的问题
1.手动KVO
要使用手动KVO时要重写automaticallyNotifiesObserversForKey这个方法,见上面代码,实现这个方法后,KVC方式或是属性seter,无法出发KVO,我们可以自己写个方法如setTheNum1手动发送消息实现KVO ,两个方法如下
willChangeValueForKey:@"theNum"];
didChangeValueForKey:@"theNum"];
现在只有这个方法可以触发KVO.
2.KVO的注意点。
A.使用KVO的时候要注意线程安全,要保证收到通知后的方法 observeValueForKeyPath 在正确的线程执行,我觉得可以把这个通知的响应dispatch到具体线程执行。
B.如果类成员变量已经声明为属性,那么self.xxx、[self setXxx] 、可以触发KVO;_xxx = 无法触发KVO。
如果类成员变量没有声明为属性,那么[self.setXxx]仍然可以触发KVO。
3.KVO的本质。
当某个类的对象第一次被观察时, 系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的 setter 方法。
派生类在被重写的 setter 方法实现真正的通知机制 ,就如前面手动实现键值观察那样。这么做是基于设置属性会调用 setter 方法,而通过重写就获得了 KVO 需要的通知机制。当然前提是要通过遵循 KVO 的属性设置方式来变更属性值,如果仅是直接修改属性对应的成员变量,是无法实现 KVO 的。
同时派生类还重写了 class 方法以“欺骗”外部调用者它就是起初的那个类。然后系统将这个对象的 isa 指针指向这个新诞生的派生类,因此这个对象就成为该派生类的对象了,因而在该对象上对 setter 的调用就会调用重写的 setter,从而激活键值通知机制。此外,派生类还重写了 dealloc 方法来释放资源。
- (void)showSelfMsg {
NSLog(@"self->isa:%@", object_getClass([globalObject sharedInstance]) );
NSLog(@"self class:%@",[[globalObject sharedInstance] class]);
NSLog(@"self className:%s",object_getClassName([globalObject sharedInstance]));
NSLog(@"self class-name:%s",object_getClassName([[globalObject sharedInstance] class]) );
}
在addObserver前后调用globalObject的showSelfMsg方法打印log如下。
2016-05-16 16:19:14.192 test[15938:949754] self->isa:globalObject
2016-05-16 16:19:14.193 test[15938:949754] self class:globalObject
2016-05-16 16:19:14.193 test[15938:949754] self className:globalObject
2016-05-16 16:19:14.193 test[15938:949754] self class-name:globalObject
2016-05-16 16:19:18.964 test[15938:949754] self->isa:NSKVONotifying_globalObject
2016-05-16 16:19:18.964 test[15938:949754] self class:globalObject //重写了 class 方法以“欺骗”外部调用者它就是起初的那个类
2016-05-16 16:19:18.964 test[15938:949754] self className:NSKVONotifying_globalObject
2016-05-16 16:19:18.965 test[15938:949754] self class-name:globalObject//重写了 class 方法以“欺骗”外部调用者它就是起初的那个类
3.KVO的优缺点。
优点:
1.提供一个简单地方法来实现两个对象的同步
2.能够提供观察的属性的最新值和先前值
3.用keypaths 来观察属性,因此也可以观察嵌套对象
缺点:
1.观察的属性必须使用string来定义,因此编译器不会出现警告和检查
2.对属性的重构将导致观察不可用
3.复杂的“if”语句要求对象正在观察多个值,这是因为所有的观察都通过一个方法来指向
4.容易造成崩溃,比如多次删除kaypah监听,删除不存在监听,没实现监听方法,规避崩溃可以参考:
https://blog.csdn.net/klabcxy36897/article/details/51680423
暂时先总结这么多。如有问题麻烦大家指正。