KVC 和 KVO(三)

ppsheep發表於2019-02-22

歡迎大家關注我的公眾號,我會定期分享一些我在專案中遇到問題的解決辦法和一些iOS實用的技巧,現階段主要是整理出一些基礎的知識記錄下來

文章也會同步更新到我的部落格:
ppsheep.com

KVO和執行緒

KVO 行為是同步的 並且發生與所觀察的值發生變化的同樣的執行緒上。這聽起來有點拗口,簡單點說,就是監聽行為發生的執行緒和所觀察的值發生變化的執行緒,肯定是同一個執行緒,這樣我們使用的時候就需要注意了:

當我們試圖從其他執行緒改變屬性值的時候我們應當十分小心,除非能確定所有的觀察者都用執行緒安全的方法處理 KVO 通知

通常來說,我們不推薦把 KVO 和多執行緒混起來。如果我們要用多個佇列和執行緒,我們不應該在它們互相之間用 KVO。

KVC

最簡單的 KVC 能讓我們通過以下的形式訪問屬性:

@property (nonatomic, copy) NSString *name;複製程式碼

取值:

NSString *n = [object valueForKey:@"name"]複製程式碼

設定:

[object setValue:@"Daniel" forKey:@"name"]複製程式碼

這個不僅可以訪問作為物件屬性,而且也能訪問一些標量(例如 int 和 CGFloat)和 struct(例如 CGRect)。Foundation 框架會為我們自動封裝它們。舉例來說,如果有以下屬性:

@property (nonatomic) CGFloat height;複製程式碼

我們可以這樣設定:

[object setValue:@(20) forKey:@"height"]複製程式碼

鍵路徑 (key path)

KVC 同樣允許我們通過關係來訪問物件。假設 person 物件有屬性 address,address 有屬性 city,我們可以這樣通過 person 來訪問 city:

[person valueForKeyPath:@"address.city"]複製程式碼

值得注意的是這裡我們呼叫 -valueForKeyPath: 而不是 -valueForKey:

Key-Value Coding Without @property

不需要 @property 的 KVC

我們可以實現一個支援 KVC 而不用 @property 和 @synthesize 或是自動 synthesize 的屬性。最直接的方式是新增 – 和 -set: 方法。例如我們想要 name ,我們這樣做:

- (NSString *)name;
- (void)setName:(NSString *)name;複製程式碼

這完全等於 @property 的實現方式。

但是當標量和 struct 的值被傳入 nil 的時候尤其需要注意。假設我們要 height 屬性支援 KVC 我們寫了以下的方法:

- (CGFloat)height;
- (void)setHeight:(CGFloat)height;複製程式碼

然後我們這樣呼叫:

[object setValue:nil forKey:@"height"]複製程式碼

這會丟擲一個 exception。要正確的處理 nil,我們要像這樣 override -setNilValueForKey:

- (void)setNilValueForKey:(NSString *)key
{
    if ([key isEqualToString:@"height"]) {
        [self setValue:@0 forKey:key];
    } else
        [super setNilValueForKey:key];
}複製程式碼

集合的操作

一個常常被忽視的 KVC 特性是它對集合操作的支援。舉個例子,我們可以這樣來獲得一個陣列中最大的值:

NSArray *a = @[@4, @84, @2];
NSLog(@"max = %@", [a valueForKeyPath:@"@max.self"]);複製程式碼

或者說,我們有一個 Transaction 物件的陣列,物件有屬性 amount 的話,我們可以這樣獲得最大的 amount:

NSArray *a = @[transaction1, transaction2, transaction3];
NSLog(@"max = %@", [a valueForKeyPath:@"@max.amount"]);複製程式碼

當我們呼叫 [a valueForKeyPath:@”@max.amount”] 的時候,它會在陣列 a 的每個元素中呼叫 -valueForKey:@”amount” 然後返回最大的那個。

常見的 KVO 錯誤

首先,KVO 相容是 API 的一部分。如果類的所有者不保證某個屬性相容 KVO,我們就不能保證 KVO 正常工作。蘋果文件裡有 KVO 相容屬性的文件。例如,NSProgress 類的大多數屬性都是相容 KVO 的。

當做出改變以後,有些人試著放空的 -willChange 和 -didChange 方法來強制 KVO 的觸發。KVO 通知雖然會生效,但是這樣做破壞了有依賴於 NSKeyValueObservingOld 選項的觀察者。詳細來說,這影響了 KVO 對觀察鍵路徑 (key path) 的原生支援。KVO 在觀察鍵路徑 (key path) 時依賴於 NSKeyValueObservingOld 屬性。

我們也要指出有些集合是不能被觀察的。KVO 旨在觀察關係 (relationship) 而不是集合。我們不能觀察 NSArray,我們只能觀察一個物件的屬性——而這個屬性有可能是 NSArray。舉例說,如果我們有一個 ContactList 物件,我們可以觀察它的 contacts 屬性。但是我們不能向要觀察物件的 -addObserver:forKeyPath:… 傳入一個 NSArray。

相似地,觀察 self 不是永遠都生效的。而且這不是一個好的設計。

除錯 KVO

你可以在 lldb 裡檢視一個被觀察物件的所有觀察資訊。

(lldb) po [observedObject observationInfo]複製程式碼

這會列印出有關誰觀察誰之類的很多資訊。

這個資訊的格式不是公開的,我們不能讓任何東西依賴它,因為蘋果隨時都可以改變它。不過這是一個很強大的排錯工具。

參考:

www.objccn.io/issue-7-3/