KVO和KVC分別是Key-Value Observing和Key-Value Coding的簡稱。想到這個話題是因為現在我寫的tableView的更新是每隔數秒的輪詢,在考慮是不是需要用個類似觸發更新的機制。這自然就想到了觀察者模式。
搜尋Objective-c的觀察者模式,發現在http://stackoverflow.com/questions/165790/whats-the-nicest-way-to-do-observer-observable-in-objective-c-iphone-version 中提到有兩種方式可以實現:NSNotificationCenter和KVO。那個回答裡形容Notification是較為古老而重型的。。。雖然我也稍微研究過,不過還是作為下次的話題吧。
1. KVC
咦,不是要說KVO嗎?為什麼先要說KVC?Mac Developer Library說KVC是KVO的準備知識。好吧,先來看看KVC。
KVC是一種非直接存取對象屬性的機制。什麼是直接存取?get和set。
用省略過頭的話來解釋,KVC就是通過key來擷取value。
有點像Dictionary?嗯,從某種意義上來說是,但遠遠不止。
在Objective-C下,也有一個字典類NSDictionary,可以通過objectForKey方法來擷取某個key所對應的value。但KVC可以在所有類中使用valueForKey這個方法,來擷取某個property(key)的值(value)。
來看看代碼範例。例如有這麼一個類:
@interface TestClass : NSObject@property NSInteger integerProperty;@property TestClass *linkedInstance;@end
可以使用傳統的set來賦值,例如:
TestClass *anotherInstance = [[TestClassalloc] init]; myInstance.linkedInstance = anotherInstance; myInstance.integerProperty = 2;
不過也可以用kvc來賦值:
TestClass *anotherInstance = [[TestClass alloc] init]; myInstance.linkedInstance = anotherInstance; [myInstance setValue:@2forKeyPath:@"linkedInstance.integerProperty"];
從這個角度上來說,KVC類似於.NET中反射的部分功能。
如上所述,要通過KVC取值,可以通過valueForKey。
由於是語言層級的功能,所以KVC能實現的功能挺強大。例如:
根據index來擷取一個或數個:objectAtIndex, objectsAtIndexes, count, -get<Key>:range:
增刪改:insertObject, insertObjects,removeObjectAtIndex, replaceObjectAtIndex,
2. KVO
KVO的作用是允許某個對象得知其他對象的某個屬性發生了改動。在MVC的model和controller層之間的互動中應用得特別廣泛。
實現KVO需要三步:
1) 明確通知對象和被通知對象
例如:如果存款發生了變更,比如被提取了5000元,那麼賬戶所有人就需要被告知。這個情境中,通知對象就是銀行賬戶,被通知對象就是賬戶所有人
2) 被通知對象在通知對象中,註冊對某個屬性的觀察者
文法為:addObserver:forKeyPath:options:context:
對於上面那個例子,所有者就需要在賬戶執行個體裡註冊一個觀察者。
代碼範例:
[account addObserver:inspector
forKeyPath:@"openingBalance"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:NULL];
其中option的參數表示需要得知這個屬性更改前和更改後的值。
另外,可以添加,自然也可以用removeObserver移除。
3) 被通知對象(觀察者)實現observeValueForKeyPath方法
這個方法是用來實現當被通知後,觀察者會做些啥反應
對於上面那個例子,如果觀察者判斷自己有沒有進行過提款或購物操作,然後決定是不是警示?
代碼範例:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqual:@"openingBalance"]) { [openingBalanceInspectorField setObjectValue: [change objectForKey:NSKeyValueChangeNewKey]]; } /* Be sure to call the superclass's implementation *if it implements it*. NSObject does not implement the method. */ [superobserveValueForKeyPath:keyPath ofObject:object change:change context:context];}
參考資料:
Key-Value Coding Programming Guide
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/KeyValueCoding/Articles/KeyValueCoding.html%23//apple_ref/doc/uid/10000107-SW1
Key-Value Observing Programming Guide
http://developer.apple.com/library/mac/#documentation/cocoa/conceptual/KeyValueObserving/KeyValueObserving.html%23//apple_ref/doc/uid/10000177-BCICJDHA