一、运用键值观察KVO
关键是运用一下的几个函数:
(1)注册和取消观察;
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
(2)处理消息变更;
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
(3)手动观察键值必须使用的两个category方法;
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;
下面解释下手动观察键值。
二、手动观察键值
关键实现这两个category方法:- (void)willChangeValueForKey:(NSString *)key;- (void)didChangeValueForKey:(NSString *)key;
首先,需要手动实现属性的 setter 方法,并在设置操作的前后分别调用 willChangeValueForKey: 和 didChangeValueForKey方法,这两个方法用于通知系统该 key 的属性值即将和已经变更了;
其次,要实现类方法 automaticallyNotifiesObserversForKey,并在其中设置对该 key 不自动发送通知(返回 NO 即可)。这里要注意,对其它非手动实现的 key,要转交给 super 来处理。
//+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { // // if ([key isEqualToString:@"age"]) { // // return NO; // } // // else if ([key isEqualToString:@"number"]) { // // return YES; // } // // else if ([key isEqualToString:@"testst"]) { // // return YES; // } // // // return [super automaticallyNotifiesObserversForKey:key]; //} - (NSString *)number { return theNumber; } - (void)setNumber:(NSString *)number { //手动调用KVO [self willChangeValueForKey:@"number"]; theNumber = number; //手动调用KVO [self didChangeValueForKey:@"number"]; } //- (void)willChangeValueForKey:(NSString *)key { // // if (![key isEqualToString:@"number"]) { // // NSLog(@"old value -- %@",[self valueForKey:@"number"]); // } //} // // // //- (void)didChangeValueForKey:(NSString *)key { // // if (![key isEqualToString:@"number"]) { // // NSLog(@"new value -- %@",[self valueForKey:@"number"]); // } //}
重写了- (void)willChangeValueForKey:(NSString *)key;- (void)didChangeValueForKey:(NSString *)key;就不会再执行- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;不管automaticallyNotifiesObserversForKey返回的是不是YES(并不是很理解为什么会这样)。
看一个例子:
[user addObserver:self forKeyPath:@"number" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"test"]; //[others addObserver:self forKeyPath:@"delegate" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:(__bridge void * _Nullable)([OtherObject class])]; user.number = @"1000002";
model中手动调用KVO;[self willChangeValueForKey:@"number"]; [self didChangeValueForKey:@"number"];
(1)若都调用,则会观察两遍observeValueForKeyPath:ofObject:change:context:
2015-12-10 10:07:55.907 Mannyi1[1408:326320] old value is -- 3423
2015-12-10 10:07:55.908 Mannyi1[1408:326320] new value is -- 1000002
2015-12-10 10:07:55.908 Mannyi1[1408:326320] old value is -- 3423
2015-12-10 10:07:55.908 Mannyi1[1408:326320] new value is -- 1000002
(2)调用一个函数或者都不调用,则只观察一遍observeValueForKeyPath:ofObject:change:context:
2015-12-10 10:05:25.469 Mannyi1[1344:314649] old value is -- 3423
2015-12-10 10:05:25.469 Mannyi1[1344:314649] new value is -- 1000002
三、键值依赖键观察
有时候一个属性的值依赖于另一对象中的一个或多个属性,如果这些属性中任一属性的值发生变更,被依赖的属性值也应当为其变更进行标记。因此,object 引入了依赖键。
(一)observeValueForKeyPath:ofObject:change:context:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context { if ([keyPath isEqualToString:@"delegate"]) { //NSLog(@"chage value -- %@",change[NSKeyValueChangeNewKey]); NSLog(@" old information is %@", [change objectForKey:@"old"]); NSLog(@" new information is %@", [change objectForKey:@"new"]); } else if ([keyPath isEqualToString:@"number"]) { NSLog(@" old value is -- %@",change[NSKeyValueChangeOldKey]); NSLog(@" new value is -- %@",change[NSKeyValueChangeNewKey]); } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } }
这个处理变更通知的函数内,可以处理多个keyPath。
(二)实现依赖
(1)被依赖的Model(number、name)属性。
#import <Foundation/Foundation.h> @interface TestObject : NSObject { // NSString *testst; } //- (void)setAge:(int)theAge; @property (nonatomic,copy) NSString *number; @property (nonatomic,copy) NSString *name; @end #import "TestObject.h" @interface TestObject () { // int age; // NSString *theNumber; } //- (int)age; @end @implementation TestObject @synthesize number; @synthesize name; - (instancetype)init { self = [super init]; if (self) { number = @"3423"; name = @"zhang"; } return self; } @end
(2)实现依赖属性
#import <Foundation/Foundation.h> @class TestObject; @interface OtherObject : NSObject { // @private // // TestObject *_obj; } @property (nonatomic,copy) NSString *delegate; @property (nonatomic,strong) TestObject *obj; - (instancetype)initWithObj:(TestObject *)obj; @end #import "TestObject.h" #import "OtherObject.h" @implementation OtherObject - (instancetype)initWithObj:(TestObject *)obj { self = [super init]; if (self) { self.obj = obj; } return self; } - (NSString *)delegate { return [[NSString alloc] initWithFormat:@"%@-%@",self.obj.number,self.obj.name]; } - (void)setDelegate:(NSString *)delegate { NSArray * array = [delegate componentsSeparatedByString:@"-"]; [self.obj setNumber:[array objectAtIndex:0]]; [self.obj setName:[array objectAtIndex:1]]; } //这个返回的是依赖其他对象的属性名,返回数组。 + (NSSet *)keyPathsForValuesAffectingDelegate { NSSet * keyPaths = [NSSet setWithObjects:@"obj.number", @"obj.name", nil]; return keyPaths; } @end
这一步关键的是重写+ (NSSet *)keyPathsForValuesAffectingDelegate,返回依赖keyPath(被依赖类对象.属性名)数组。
(3)实例
user = [[TestObject alloc] init]; others = [[OtherObject alloc] initWithObj:user]; [others addObserver:self forKeyPath:@"delegate" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:(__bridge void * _Nullable)([OtherObject class])]; user.number = @"1000002"; user.name = @"1000003"; [others removeObserver:self forKeyPath:@"delegate" context:(__bridge void * _Nullable)([OtherObject class])];
得到的结果:
old information is 3423-zhang
2015-12-08 15:31:15.885 Mannyi1[3618:1549002] new information is 1000002-zhang
2015-12-08 15:31:17.255 Mannyi1[3618:1549002] old information is 1000002-zhang
2015-12-08 15:31:17.255 Mannyi1[3618:1549002] new information is 1000002-1000003