KVO(Key-Value-Observer)也就是觀察者模式,是蘋果提供的一套事件通知機制。容許對象監聽另外一個對象特定屬性的改變,並在改變時接收到事件,通常繼承自NSObject
的對象都默認支持KVO
。函數
KVO和NSNotificationCenter都是iOS中觀察者模式的一種實現。區別在於:
一、相對於被觀察者和觀察者之間的關係,KVO是一對一的,而不一對多的。也就是kvo監聽到被觀察屬性值改變時只會通知到觀察者,是一對一的關係。而通知模式則是在被觀察值改變的時候發送全局通知,任何對象均可以接聽到這個通知,是一個一對多的關係;
二、KVO對被監聽對象無侵入性,不須要修改其內部代碼便可實現監聽。而通知須要在被監聽對象改變的時候添加發送通知代碼。
二、使用atom
一、spa
//1.註冊觀察者 /* - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context; observer:觀察者 也就是被觀察對象發生改變時通知的接收者 keyPath:被觀察的屬性名 好比咱們這裏是age屬性 options:參數 這裏通常選擇NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld 也就是在回調方法裏會受到被觀察屬性的舊值和新值,默認爲只接收新值。若是想在註冊觀察者後,當即接收一次回調,則能夠加入NSKeyValueObservingOptionInitial枚舉。 context:這個參數能夠傳入任意類型的對象,這個值會傳遞到接收消息回調的代碼中,是KVO中的一種傳值方式。 */ [self.per1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
二、代理
//2.實現通知回調方法 當被觀察對象的屬性值發生變化時 就會回調這個方法 change字典中存放KVO屬性相關的值,根據options時傳入的枚舉來返回。 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ NSLog(@"%@---%@----%@---%@",keyPath,object,change,context); }
三、指針
//3.移除監聽 [self.per1 removeObserver:self forKeyPath:@"age"];
KVO
的addObserver
和removeObserver
須要是成對的,若是重複remove
則會致使NSRangeException
類型的Crash
,若是忘記remove
則會在觀察者釋放後再次接收到KVO
回調時Crash
。code
蘋果官方推薦的方式是,在init
的時候進行addObserver
,在dealloc
時removeObserver
,這樣能夠保證add
和remove
是成對出現的,是一種比較理想的使用方式。server
調用KVO
屬性對象時,不只能夠經過點語法和set
語法進行調用,KVO
兼容不少種調用方式:(關於KVC的實現原理接下來會講到)對象
// 1.經過屬性的點語法間接調用 self.per1.age = 123;
//2. 直接調用set方法 [self.per1 setAge:123];
// 3.使用KVC的setValue:forKeyPath:方法 [self.per1 setValue:@123 forKeyPath:@"age"]; //4. 使用KVC的setValue:forKey:方法 [self.per1 setValue:@123 forKey:@"age"]; // 5.經過mutableArrayValueForKey:方法獲取到代理對象,並使用代理對象進行操做
若是直接修改對象的成員變量是不會觸發KVO的:blog
//PersonClass.h文件 #import <Foundation/Foundation.h> @interface PersonClass : NSObject{
@public; NSInteger _age;//成員變量 } //屬性 @property (nonatomic, assign) NSInteger age; @end
直接修改爲員變量,咱們發現沒有觸發KVO繼承
self.person1 -> _age = 234;
上面全是監聽一些基礎的數據類型 當被觀察屬性是一個複雜對象時,好比如今person對象有一個屬性animal,那麼kvo會如何監聽呢?
#import <Foundation/Foundation.h> @class AnimalClass; @interface PersonClass : NSObject @property (nonatomic, assign) NSInteger age; @property (nonatomic, strong) AnimalClass *animal; @end
AnimalClass類中有一個name屬性
@interface AnimalClass : NSObject @property (nonatomic, copy) NSString *name; @end
當咱們對animal這個屬性進行監聽時,發現當對animal的屬性值(name)修改時 kvo並不會監聽到, 而當給person對象從新賦值一個新的animalClass對象時會被監聽到
//會監聽到改變 由於person1的animal屬性是個指針 存儲的是animal類型的一個地址值 當從新賦值一個alloc出來的新animalClass對象時 animal的地址值發生了改變 會調用person1的setAnimal方法 AnimalClass *ani2 = [[AnimalClass alloc]init]; ani2.name = @"cat"; self.person1.animal = ani2; //不會被kvo監聽到 由於修改animal的name屬性 根本沒有調用person1的setAnimal方法 只是調用了animal的setName方法 self.person1.animal.name = @"cat";
而當咱們對person1.animal對象的name屬性進行監聽時 是能夠監聽到 self.person1.animal.name = @"cat";這種值改動的
[self.person1.animal addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
因此kvo可否監聽到變化 要看這個被監聽對象存儲的是什麼?其實是否發生了改變?
咱們在經過runtime函數object_getclass分別打印person1在添加kvo先後的類對象分別是是PersonClass和NSKVONotifying_PersonClass;
也就是在person1對象註冊了kvo之後,其類對象發生了改變
咱們在改變age值的時候 其實是調用了setAge方法 而實例對象調用方法是根絕isa指針找到類對象的對象方法列表找到對應的方法進行調用,因此kvo的本質其實是重寫了被觀察屬性值的set方法
NSKVONotifying_PersonClass類對象set方法的具體實現:(_NSSet*ValueAndNotify的內部實現)
didChangeValueForKey:內部會調用observer的observeValueForKeyPath:ofObject:change:context:方法
KVO
是經過isa-swizzling
技術實現的(這句話是整個KVO
實現的重點)。在運行時利用RuntimeAPI動態生成一個根據原類建立的中間類(命名規則是NSKVONotifying_xxx
的格式),這個中間類是原類的子類,並動態修改當前對象的isa
指向中間類。
首先重寫set方法。在set方法裏分別調用willChangeValueForKey->set的賦值操做->didChangeValueForKey 其中didChangeValueForKey在內部視線中會調用觀察者的回調方法 返回被觀察對象的相關參數
而且將class
方法重寫,返回原類的Class(PersonClass類)
。這是由於蘋果不想暴露kvo的內部實現,建議在開發中不該該依賴isa
指針,而是經過class
實例方法來獲取對象類型。
_isKVOA
方法,這個方法能夠當作使用了KVO
的一個標記,系統可能也是這麼用的。若是咱們想判斷當前類是不是KVO
動態生成的類,就能夠從方法列表中搜索這個方法。
KVO
在屬性發生改變時的調用是自動的,若是在被觀察屬性值沒有改變的狀況下手動調用kvo 那麼須要時候調用willChangeValueForKey和didChangeValueForKey兩個方法(兩個方法必須都進行調用 系統在執行didChangeValueForKey方法前會檢測willChangeValueForKey是否被調用了)
[self.person1 willChangeValueForKey:@"age"]; [self.person1 didChangeValueForKey:@"age"];
手動觸發的前提是這個對象已經添加了kvo 若是沒有添加的話kvo是沒法知道觀察者是誰的 也就是不會回調觀察者的- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{}這個回調方法的