KVO 使用及原理
阿新 • • 發佈:2018-01-19
mar pan int 屬性 gist tcl string類 nta objects
KVO的基本原理大概是這樣的
當一個對象被觀察時, 系統會新建一個子類NSNotifying_A ,在子類中重寫了對象被觀察屬性的 set方法, 並且改變了該對象的 isa 指針的指向(指向了新建的子類) , 當屬性的值發生改變了, 會調用子類的set方法, 然後發出通知
一. KVO 的基本使用
給_person對象 添加觀察者self, 當person對象的name的值發生改變的時候, 會觸發observer方法
_person = [Person new]; p.name = @"oldName";
//添加觀察者
// [p addObserver:self forKeyPath:@"dog" options:NSKeyValueObservingOptionNew context:nil];
[_person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
// 所觀察的對象的keyPath 發生改變的時候, 會觸發 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ NSLog(@"%@",keyPath); NSLog(@"%@",change); }
二. 當keyPath 為對象時, 改對象有許多屬性, 怎麽辦?
在person類中,重寫這個方法, 設置需要觀察的屬性 , 註意:"_dog.level"
//返回一個容器, 裏面放的是NSString類型 + (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{ NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];//觀察dog對象中的所有屬性 if ([key isEqualToString:@"dog"]) { keyPaths = [keyPaths setByAddingObjectsFromArray:@[@"_dog.level",@"_dog.age"]]; } return keyPaths; }
三. 手動觸發KVO
系統默認該對象的所有屬性 都能被觀察到 ,重寫下面方法, 可以單獨設置某個屬性不能被觀察
//默認 yes, 默認自動觀察所有屬性 + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{ return YES; }
//返回NO, 則不能被默認觀察到name + (BOOL)automaticallyNotifiesObserversOfName{ return NO; } + (BOOL)automaticallyNotifiesObserversOfAge{ return YES; }
當 name 發生改變的時, 手動觸發, 發送通知
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ //手動發通知 //即將改變(發一次通知) [_person willChangeValueForKey:@"name"]; _person.name = @"dddd"; //已經改變(發一次通知),一共發了兩次通知 [_person didChangeValueForKey:@"name"];
}
四. 自定義KVO
根據kvo的原理, 可以自定義一個kvo, 建一個NSObject的分類, 添加方法
@interface NSObject (XSKVO) - (void)xs_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context; @end
通過runtime的方式, 動態創建一個類, 並給該類添加方法
#import "NSObject+XSKVO.h" #import <objc/runtime.h> @implementation NSObject (XSKVO) - (void)xs_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{ //1.新建一個類 NSString *className = [@"XSKVO" stringByAppendingString: NSStringFromClass([self class])]; Class newClass = objc_allocateClassPair([self class], className.UTF8String, 0); //註冊類 objc_registerClassPair(newClass); //2.修改 調用者類型 object_setClass(self, newClass); //3.給子類添加set方法(子類裏面沒有set方法的) //OC方法:方法編號SEL ,方法實現IMP class_addMethod(newClass, @selector(setName:), xssetName, ""); } /* 隱藏的參數: self 方法的調用者 _cmd 方法的編號 */ void xssetName(id self,SEL _cmd, NSString *newName){ NSLog(@"自定義的實現%@",newName); //方案一:通過消息機制 發送消息 -observeValueForKeyPath } @end
五. 其他
關於容器類(如:NSMutableArray)的觀察, 當通過addObject: 向數組中添加對象, 不會觸發KVO, 因為並沒有觸發set方法,
解決方法: 通過KVC 方法 - mutableArrayValueForKey:
KVO 使用及原理