鍵值觀察(Key-value observing,或簡稱 KVO)容許對象觀察另外一個對象的屬性。該屬性值改變時,會通知觀察對象。它瞭解新值以及舊值;若是觀察的屬性爲對多的關係(例如數組),它也要了解哪一個包含的對象發生了改變。KVO 有助於使應用程序變得更內聚,保持模型、控制器和視圖層中的對象與改變同步。html
與 NSNotificationCenter
通知類似,多個 KVO 觀察者能夠觀察單一屬性。此外,KVO 更動態,由於它容許對象觀察任意屬性,而不需任何新的 API,例如通知名稱。KVO 是一個輕量級點對點通訊機制,不容許觀察全部實例的特定屬性。數組
----以上解釋來着官方文檔----架構
Key-Value Observing機制的概述app
Key-Value Observing (簡寫爲KVO):當指定的對象的屬性被修改了,容許對象接受到通知的機制。每次指定的被觀察對象的屬性被修改的時候,KVO都會自動的去通知相應的觀察者。框架
KVO的優勢函數
當有屬性改變,KVO會提供自動的消息通知。這樣的架構有不少好處。首先,開發人員不須要本身去實現這樣的方案:每次屬性改變了就發送消息通知。這是KVO機制提供的最大的優勢。由於這個方案已經被明肯定義,得到框架級支持,能夠方便地採用。開發人員不須要添加任何代碼,不須要設計本身的觀察者模型,直接能夠在工程裏使用。其次,KVO的架構很是的強大,能夠很容易的支持多個觀察者觀察同一個屬性,以及相關的值。測試
KVO如何工做atom
須要三個步驟來創建一個屬性的觀察員。理解這三個步驟就能夠知道KVO如何設計工做的。spa
(1)首先,構思一下以下實現KVO是否有必要。好比,一個對象,當另外一個對象的特定屬性改變的時候,須要被通知到。設計
例如,PersonObject但願可以覺察到BankObject對象的accountBalance屬性的任何變化。
(2)那麼PersonObject必須發送一個「addObserver:forKeyPath:options:context:」消息,註冊成爲BankObject的accountBalance屬性的觀察者。
(說明:「addObserver:forKeyPath:options:context:」方法在指定對象實例之間創建了一個鏈接。注意,這個鏈接不是兩個類之間創建的,而是兩個對象實例之間創建的。)
(3)爲了可以響應消息,觀察者必須實現「observeValueForKeyPath:ofObject:change:context:」方法。這個方法實現如何響應變化的消息。在這個方法裏面咱們能夠跟本身的狀況,去實現應對被觀察對象屬性變更的相應邏輯。
(4)假如遵循KVO規則的話,當被觀察的屬性改變的話,方法「observeValueForKeyPath:ofObject:change:context:」會自動被調用。
KVO是Cocoa的一個重要機制,他提供了觀察某一屬性變化的方法,極大的簡化了代碼。這種觀察-被觀察模型適用於這樣的狀況,比方說根據A(數據類)的某個屬性值變化,B(view類)中的某個屬性作出相應變化。對於推崇MVC的cocoa而言,kvo應用的地方很是普遍。
適用kvo時,一般遵循以下流程:
1 註冊:
-(void)addObserver:(NSObject *)anObserver forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void*)context
keyPath就是要觀察的屬性值,options給你觀察鍵值變化的選擇,而context方便傳輸你須要的數據(注意這是一個void型)
2 實現變化方法:
-(void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void*)context
change裏存儲了一些變化的數據,好比變化前的數據,變化後的數據;若是註冊時context不爲空,這裏context就能接收到。
是否是很簡單?kvo的邏輯很是清晰,實現步驟簡單。
說了這麼多,你們都要躍躍欲試了吧。但是,在此以前,咱們還須要瞭解KVC機制。其實,知道了kvo的邏輯只是幫助你理解而已,要真正掌握的,不在於kvo的實現步驟是什麼,而在於KVC,由於只有符合KVC標準的對象才能使用kvo(強烈推薦要使用kvo的人先理解KVC)。
KVC--Key - value coding 機制
KVC是一種間接訪問對象屬性(用字符串表徵)的機制,而不是直接調用對象的accessor方法或是直接訪問成員對象。
key就是肯定對象某個值的字符串,它一般和accessor方法或是變量同名,而且必須以小寫字母開頭。
Key path就是以「.」分隔的key,property can be object whick also contains property。好比咱們能夠person這樣的key,也能夠有key.gender這樣的key path。
(setValue:forKey,valueForKey:)、(setValue:forKeyPath,valueForKeyPath:)
獲取屬性值時能夠經過valueForKey:的方法,設置屬性值用setValue:forKey:。與此同時,KVC還對未定義的屬性值定義了 valueForUndefinedKey:,你能夠重載以獲取你要的實現(補充下,KVC定義載NSKeyValueCoding的非正式協議裏)。
在O-C 2.0引入了property,咱們也能夠經過 .(點)運算符來訪問屬性。下面直接看個例子:
注意KVC中的value都必須是對象。
下面舉個例子解釋一下KVC:
1.先定義兩數據模型,下面的這兩個model是沒法直接訪問屬性的。
@interface BookData : NSObject { NSString * bookName; float price; AuthorData * author; } @end @implementation BookData @end
@interface AuthorData : NSObject { NSString * name; } @end @implementation AuthorData @end
使用KVC
AuthorData * author1 = [[AuthorData alloc] init]; [author1 setValue:@"tom" forKey:@"name"]; BookData * book1 = [[BookData alloc] init]; [book1 setValue:@"english" forKey:@"bookName"]; [book1 setValue:@"20.0" forKey:@"price"]; [book1 setValue:author1 forKey:@"author"]; NSLog(@"value=%@",[book1 valueForKey:@"bookName"]); NSLog(@"price=%f",[[book1 valueForKey:@"price"] floatValue]); NSLog(@"author=%@",[book1 valueForKeyPath:@"author.name"]);
下面舉一個例子解釋一下 KVO
Person.h
#import <Foundation/Foundation.h> @interface Person : NSObject @property(nonatomic,retain)NSString *name; @property(nonatomic,assign)NSInteger age; -(void)changeName; @end
Person.m
#import "Person.h" @implementation Person @synthesize name,age;//屬性name將被監視 -(void)changeName { name=@"changeName directly"; } @end
PersonMonitor.h
#import <Foundation/Foundation.h> @interface PersonMonitor : NSObject @end
PersonMonitor.m
#import "PersonMonitor.h" @implementation PersonMonitor //觀察者須要實現的方法 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if([keyPath isEqual:@"name"]) { NSLog(@"change happen,old:%@ ,new:%@;context = %@",[change objectForKey:NSKeyValueChangeOldKey],[change objectForKey:NSKeyValueChangeNewKey],context); } } @end
main.m
#import <Foundation/Foundation.h> #import "Person.h" #import "PersonMonitor.h" int main(int argc, const char * argv[]) { @autoreleasepool { //測試代碼 //監視對象 Person *p =[[Person alloc] init]; //觀察者對象 PersonMonitor *pm= [[PersonMonitor alloc]init]; //Observer KVO收到通知指定的關鍵路徑相對於接收器。 /* *pm 對象註冊KVO通知。觀察者必須實現鍵值觀察方法observeValueForKeyPath。 *forKeyPath 關鍵路徑,相對於接收器,用於觀察的屬性。這個值不能爲零。 *options NSKeyValueObservingOptions的組合值,指定包含在觀察通知的內容。 *context 任意的數據傳遞給anObserver在observeValueForKeyPath。 */ [p addObserver:pm forKeyPath:@"name" options:(NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld) context:@"hello world"]; //測試前的數據 NSLog(@"p.name is %@",p.name); //經過setvalue 的方法,PersonMonitor的監視將被調用 [p setValue:@"name kvc" forKey:@"name"]; //查看設置後的值 NSLog(@"p.name is %@",[p valueForKey:@"name"]); //效果和經過setValue 是一致的 p.name=@"name change by .name"; //經過person本身的函數來更改 name [p changeName]; //最後一次修改是直接修改,因此無法產生通知。只有當咱們調用KVC去訪問key值的時候KVO纔會起做用 NSLog(@"p.name is %@",p.name); //刪除觀察者 // [p removeObserver:pm forKeyPath:@"name"]; } return 0; }
終端輸出結果:
2013-07-19 11:04:17.946 KVC_KVOTest[1187:303] p.name is (null)
2013-07-19 11:04:17.948 KVC_KVOTest[1187:303] change happen,old:<null> ,new:name kvc;context = hello world
2013-07-19 11:04:17.949 KVC_KVOTest[1187:303] p.name is name kvc
2013-07-19 11:04:17.949 KVC_KVOTest[1187:303] change happen,old:name kvc ,new:name change by .name;context = hello world
2013-07-19 11:04:17.950 KVC_KVOTest[1187:303] p.name is changeName directly
剛接觸KVO KVC 因此只是懂了個大概,之後仍是要經過實踐應用才更深刻的理解。
註明:以上內容基本上參考了其餘博友的文章和例子,這裏整合了。