您的位置:首页 > 移动开发 > IOS开发

kvc与kvo

2013-10-13 22:13 399 查看
kvc:key value coding,键值编码。

通过键值对的形势访问对象的属性:

//读取属性
- (id)valueForKey:(NSString *)key;
//设置属性
- (void)setValue:(id)value forKey:(NSString *)key;

使用实例:使用xcode创建新的project,选择osx,application中的commond line tool
,选择type为Foundation;创建一个Person类。

Person.h

#import <Foundation/Foundation.h>

@interface Person : NSObject
{
NSString *name;
}
@end

Person.m
#import "Person.h"

@implementation Person
@end

没有设置property所以无法直接获取Person对象的name属性,这时可以通过kvc的方式访问对象。

#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[])
{

@autoreleasepool {
// insert code here...
Person *person = [[Person alloc] init];
NSLog(@"%@",[person valueForKey:@"name"]);
[person setValue:@"xiaoxiao" forKey:@"name"];
NSLog(@"%@",[person valueForKey:@"name"]);
}
return 0;
}

打印输出

2013-10-13 22:07:27.211 KVCKVO[4323:303] (null)
2013-10-13 22:07:27.213 KVCKVO[4323:303] xiaoxiao
在kvc中,valueForKey方法先尝试在person实例上找getName方法,如果找到就调用。如果没有找到,则查找实例是否有name变量或者_name变量。如果还没找到,会抛出异常:


2013-10-13 20:45:50.888 KVCKVO[3984:303] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Person 0x10010a5b0> valueForUndefinedKey:]: this class is not key value coding-compliant for the key name1.'
这里可以在Person类中添加方法来避免抛出异常

- (id)valueForUndefinedKey:(NSString *)key
- (void)setValue:(id)value forUndefinedKey:(NSString *)key

重新为Person类添加一个属性School,

School.h
#import <Foundation/Foundation.h>

@interface School : NSObject
{
NSString *schoolName;
}
@end

Person.h
#import <Foundation/Foundation.h>
#import "School.h"
@interface Person : NSObject
{
NSString *name;
School *school;
}
@end

School.m和Person.m依然什么都不写

#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[])
{

@autoreleasepool {
// insert code here...
Person *person = [[Person alloc] init];
NSLog(@"%@",[person valueForKey:@"name"]);
[person setValue:@"misery" forKey:@"name"];
NSLog(@"%@",[person valueForKey:@"name"]);

School *school = [[School alloc] init];
[person setValue:school forKey:@"school"];
NSLog(@"%@",[person valueForKeyPath:@"school.schoolName"]);
[person setValue:@"whut" forKeyPath:@"school.schoolName"];
NSLog(@"%@",[person valueForKeyPath:@"school.schoolName"]);
}
return 0;
}


以上为kvc的基本用法。
kvo:Key Value Observing,直译为:基于键值的观察。
依然沿用上面的例子,在ios开发中,person对象可能与多个界面属性相关,这时可以通过注册观察者的方式来观察person类的属性,一旦属性发生变化,界面就可以作出响应。
这里我们为Person类添加一个属性,int age,新建一个observer类来当作观察者观察这个属性。
Observer.h

#import <Foundation/Foundation.h>

@interface Observer : NSObject

@end

Observer.m

#import "Observer.h"
@implementation Observer
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:@"age"]) {
NSLog(@"age changed");
NSLog(@"old value:%@",[change objectForKey:@"old"]);
NSLog(@"new value:%@",[change objectForKey:@"new"]);
}
else
{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
@end

main.m

#import <Foundation/Foundation.h>
#import "Person.h"
#import "Observer.h"
int main(int argc, const char * argv[])
{

@autoreleasepool {
// insert code here...
Person *person = [[Person alloc] init];
NSLog(@"%@",[person valueForKey:@"name"]);
[person setValue:@"misery" forKey:@"name"];
NSLog(@"%@",[person valueForKey:@"name"]);

School *school = [[School alloc] init];
[person setValue:school forKey:@"school"];
NSLog(@"%@",[person valueForKeyPath:@"school.schoolName"]);
[person setValue:@"whut" forKeyPath:@"school.schoolName"];
NSLog(@"%@",[person valueForKeyPath:@"school.schoolName"]);

Observer *observer = [[Observer alloc] init];
//注册kvo
[person addObserver:observer forKeyPath:@"age" options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:(__bridge void *)([Person class])];
[person setValue:[NSNumber numberWithInteger:25] forKey:@"age"];
//移出kvo
[person removeObserver:observer forKeyPath:@"age"];
}
return 0;
}

输出结果为

2013-10-13 21:19:58.270 KVCKVO[4150:303] age changed
2013-10-13 21:20:01.214 KVCKVO[4150:303] old value:0
2013-10-13 21:20:01.215 KVCKVO[4150:303] new value:25
注意根据具体情况来调用,比如observer释放的时候
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

来移出kvo,避免内存泄漏。

手动键值观察:
还是上面的例子:
Person.h
#import <Foundation/Foundation.h>
#import "School.h"
@interface Person : NSObject
{
NSString *name;
School *school;
int age;
}
- (int)age;
- (void)setAge:(int)age;
@end

Person.m

#import "Person.h"

@implementation Person
- (int)age
{
return age;
}
- (void)setAge:(int)theAge
{
[self willChangeValueForKey:@"age"];
age = theAge;
[self didChangeValueForKey:@"age"];
}
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{ if ([key isEqualToString:@"age"]) { return NO; } else { return [super automaticallyNotifiesObserversForKey:key]; }
4000
}@end
[person setAge:30];

得到输出:

2013-10-13 21:40:22.931 KVCKVO[4221:303] age changed
2013-10-13 21:40:22.932 KVCKVO[4221:303] old value:0
2013-10-13 21:40:22.932 KVCKVO[4221:303] new value:25
2013-10-13 21:40:22.933 KVCKVO[4221:303] age changed
2013-10-13 21:40:22.934 KVCKVO[4221:303] old value:25
2013-10-13 21:40:22.934 KVCKVO[4221:303] new value:30
注意需要实现函数

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key

在对应的key来返回no,否则调用super的改方法。

自动键值观察:
使用@property来使用属性。
键值观察依赖键:
有时候观察者依赖于被观察者的多个属性,这时可以使用键值观察依赖。
这里需要新建一个实现依赖键:

targetWrapper.h
#import <Foundation/Foundation.h>
#import "Person.h"
@interface targetWrapper : NSObject
{
Person *tperson;
}
@property(nonatomic,assign) NSString *information;
- (id)init:(Person  *)theperson;
@end


targetWrapper.m

#import "targetWrapper.h"

@implementation targetWrapper
- (id)init:(Person *)aperson
{
self = [super init];
if (self) {
tperson = aperson;
}
return self;
}
- (id)information
{
return [NSString stringWithFormat:@"%d^%@",[tperson age],[tperson valueForKeyPath:@"school.schoolName"]];
}
- (void)setInformation:(NSString *)theInformation
{
NSArray * array = [theInformation componentsSeparatedByString:@"^"];
[tperson setAge:[[array objectAtIndex:0] intValue]];
[tperson setValue:[array objectAtIndex:1] forKeyPath:@"school.schoolName"];
}
+ (NSSet *)keyPathsForValuesAffectingInformation
{
return [NSSet setWithObjects:@"tperson.age",@"tperson.school",nil];
}
@end

main.m添加代码

//依赖
targetWrapper *tw = [[targetWrapper alloc] init:person];
[tw addObserver:observer forKeyPath:@"information" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:(__bridge void *)([targetWrapper class])];

[person setAge:45];
School *school2 = [[School alloc] init];
[school2 setValue:@"hust" forKey:@"schoolName"];
[person setValue:school2 forKeyPath:@"school"];
[tw removeObserver:observer forKeyPath:@"information"];


输出结果为:

2013-10-13 22:07:27.219 KVCKVO[4323:303] information changed
2013-10-13 22:07:27.220 KVCKVO[4323:303] old value:30^whut
2013-10-13 22:07:27.221 KVCKVO[4323:303] new value:45^whut
2013-10-13 22:07:27.221 KVCKVO[4323:303] information changed
2013-10-13 22:07:27.222 KVCKVO[4323:303] old value:45^whut
2013-10-13 22:07:27.222 KVCKVO[4323:303] new value:45^hust
要实现 keyPathsForValuesAffectingInformation  或 keyPathsForValuesAffectingValueForKey:
方法来告诉系统 information 属性依赖于哪些其他属性,这两个方法都返回一个key-path 的集合
在这里,information
属性依赖于 person 的 age 和 school 属性,person 的 age/school 属性任一发生变化,information 的观察者都会得到通知。


kvo实现机理

键值观察用处很多,Core Binding 背后的实现就有它的身影,那键值观察背后的实现又如何呢?想一想在上面的自动实现方式中,我们并不需要在被观察对象 Target 中添加额外的代码,就能获得键值观察的功能,这很好很强大,这是怎么做到的呢?答案就是 Objective C 强大的 runtime 动态能力,下面我们一起来窥探下其内部实现过程。

当某个类的对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的 setter 方法。

派生类在被重写的 setter 方法实现真正的通知机制,就如前面手动实现键值观察那样。这么做是基于设置属性会调用 setter 方法,而通过重写就获得了 KVO 需要的通知机制。当然前提是要通过遵循 KVO 的属性设置方式来变更属性值,如果仅是直接修改属性对应的成员变量,是无法实现 KVO 的。

同时派生类还重写了 class 方法以“欺骗”外部调用者它就是起初的那个类。然后系统将这个对象的 isa 指针指向这个新诞生的派生类,因此这个对象就成为该派生类的对象了,因而在该对象上对 setter 的调用就会调用重写的 setter,从而激活键值通知机制。此外,派生类还重写了 dealloc 方法来释放资源。
引用
http://www.cnblogs.com/kesalin/archive/2012/11/17/KVO.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ios开发 kvo kvc