《Objective-C 編程》35.KVC、KVO

KVC

鍵值編碼的基本概念

  1. KVCKeyValue Coding 的簡稱,它是一種可以直接通過字符串的名字(key)來訪問屬性的機制。使用該機制不需要調用存取方法和變量實例就可訪問對象屬性。本質上講,鍵-值編碼定義了你的程序存取方法需要實現的樣式及方法簽名。
  2. 在應用程序中實現鍵-值編碼兼容性是一項重要的設計原則。存取方法可以加強合適的數據封裝,而鍵-值編碼方法在多數情況下可簡化程序代碼。
  3. 鍵-值編碼方法在 Objective-C 非標準協議(類目)NSKeyValueCoding中被聲明,默認的實現方法由 NSObject 提供,所以凡是繼承自 NSObject 的類都具備 KVC 功能。
  4. 鍵-值編碼支持帶有對象值的屬性,同時也支持純數值類型和結構。非對象參數和返回類型會被識別并自動封裝/解封。

設置和訪問

鍵/值編碼中的基本調用包括 -valueForKey:-setValue:forkey: 這兩個方法,它們以字符串的形式向對象發送消息,字符串是我們關注屬性的關鍵。

示例1:

BNRAppliance *a = [[BNRAppliance alloc] init];

[a setProductName:@"Washing Machine"];
// 使用 KVC 重寫以上代碼
[a setValue:@"Washing Machine" forKey:@"productName"];
        
[a setVoltage:240];
// 使用 KVC 重寫以上代碼
[a setValue:[NSNumber numberWithInt:240] forKey:@"voltage"];
        
 NSLog(@"a is %@",a);
 // 使用 KVC 重寫以上代碼
 NSLog(@"the product name is %@",[a valueForKey:@"productName"]);

示例2:

Person *jack = [[Person alloc] init];
NSMutableString *name = [[NSMutableString alloc] initWithFormat:@"jack"];
[jack setValue:name forKey:@"name"];
NSLog(@"jack name : %@", [jack valueForKey:@"name"]);
  • 使用 KVC,編譯器會查找是否存在 settergetter 方法,如果不存在,它將在內部查找名為 _keykey 的實例變量。通過 KVC,可以獲取不存在getter方法的對象值,無需通過對象指針直接訪問。也就是說,KVC 能夠在沒有存取方法的情況下直接存取實例變量
  • KVC 只對對象類型有效。當我們通過 setValue:forKey: 設置對象的值,或通過 valueForKey 來獲取對象的值時,如果對象的實例變量為基本數據類型(charintfloatBOOL)時,我們需要使用 NSNumber 對象對數據進行封裝

key路徑

  • 除了通過鍵設置值外,鍵/值編碼還支持指定路徑,像文件系統一樣,用“點”號隔開。
  • 使用 key path 可以一次性遍歷復雜的對象表。
  • 注意順序,第一個想要遍歷的對象放在第一個。

??????你可以這樣理解:KVC 支持類似于「鏈式語法」的特性!

示例:
BNRDepartment. manager 指向 BNREmployee. emergencyContact 指向 BNRPerson. phoneNumber

BNRDepartment *sales = ...;
BNREmployee *sickEmployee = [sales valueForKey:@"manager"];
BNRPerson *personToCall = [sickEmployee valueForKey:@"emergencyContact"];
[personToCall setValue:@"555-606-0842" forKey:@"phoneNumber"];

// 使用 Key路徑 重寫以上代碼
BNRDepartment *sales = ...;
[personToCall setValue:@"555-606-0842"
            forKeyPath:@"manager.emergencyContact.phoneNumber"];

一對多的關系

如果向 NSArray 請求一個鍵值,它實際上會查詢數組中的每個對象來查找這個鍵值,然后將查詢結果打包到另一個數組中并返回給你。

NSArray *booksArray = [NSArray arrayWithObjects:book1, book2, nil];
[book1 release];
[book2 release];
[book setValue:booksArray forKey:@"relativeBooks"];
NSLog(@"books 2: %@", [book valueForKeyPath:@"relativeBooks.price"]);

實現簡單的運算

NSString *count = [book valueForKeyPath:@"relativeBooks.@count"];
NSLog(@"count : %@", count);
NSString *sum = [book valueForKeyPath:@"relativeBooks.@sum._price"];
NSLog(@"sum : %@", sum);
NSString *avg = [book valueForKeyPath:@"relativeBooks.@avg._price"];
NSLog(@"avg : %@", avg);
NSString *min = [book valueForKeyPath:@"relativeBooks.@min._price"];
NSLog(@"min : %@", min);
NSString *max = [book valueForKeyPath:@"relativeBooks.@max._price"];
NSLog(@"max : %@", max);

KVO

鍵值觀察的基本概念

  1. Key Value Observing,直譯為:基于鍵值的觀察者。它提供一種機制,當指定的對象的屬性被修改后,則對象就會接受到通知。簡單的說,就是每次指定的被觀察對象的屬性被修改后,KVO 就會自動通知相應的觀察者。
  2. NSNotification 不同,鍵-值觀察中并沒有所謂的中心對象來為所有觀察者提供變化通知。取而代之地,當有變化發生時,通知被直接發送至處于觀察狀態的對象。NSObject 提供這種基礎的鍵-值觀察實現方法。
  3. 你可以觀察任意對象屬性,包括簡單屬性,對一或是對多關系。對多關系的觀察者將會被告知發生變化的類型-也就是任意發生變化的對象。
  4. 鍵-值觀察為所有對象提供自動觀察兼容性。你可以通過禁用自動觀察通知并實現手動通知來篩選通知。

注冊觀察者

為了正確接收屬性的變更通知,觀察對象必須首先發送一個addObserver:forKeyPath:options:context:消息至被觀察對象,用以傳送觀察對象和需要觀察的屬性的關鍵路徑,以便與其注冊。選項參數指定了發送變更通知時提供給觀察者的信息。 使用NSKeyValueObservingOptionOld 選項可以將初始對象值以變更字典中的一個項的形式提供給觀察者。指定NSKeyValueObservingOptionNew 選項可以將新的值以一個項的形式添加至變更字典。你可以使用逐位“|”這兩個常量來指定接收上述兩種類型的值。

BNRLogger *logger = [[BNRLogger alloc] init];
__unused NSTimer *timer =
            [NSTimer scheduledTimerWithTimeInterval:2.0
                                             target:logger
                                           selector:@selector(updateLastTime:)
                                           userInfo:nil
                                            repeats:YES];

BNRObserver *observer = [[BNRObserver alloc] init];
// 讓 BNRObserver 實例觀察 BNRLogger 的 lastTime 屬性!
// 無論 lastTime 何時發生變化,都要通知我它改變的新值以及改變之前的舊值
[logger addObserver:observer
         forKeyPath:@"lastTime"
            options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
            context:nil];

[[NSRunLoop mainRunLoop] run];

接受變更后通知

當被觀察對象的一個被觀察的屬性發生變動時,觀察者會收到 observeValueForKeyPath:ofObject:change:context: 消息。所有觀察者都必須實現這一方法。觸發觀察通知的對象和鍵路徑、包含變更細節的字典,以及觀察者注冊時提交的上下文指針均被提交給觀察者,context 可以為任意類型參數。

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary<NSKeyValueChangeKey,id> *)change
                       context:(void *)context {
    NSString *oldValue = [change objectForKey:NSKeyValueChangeOldKey];
    NSString *newValue = [change objectForKey:NSKeyValueChangeNewKey];
    NSLog(@"Observed:%@ of %@ was changed from %@ to %@",
          keyPath,object,oldValue,newValue);
} 

注意,當在代碼中將某個對象注冊為觀察者時,你需要傳遞指針作為 context。當接收變化的通知時,context 會隨通知一起發送。context 可以用來回答:“這真的是我需要的通知嗎?”(你可以理解為 context 可以作為此觀察事件的唯一標識符)。例如,你的父類可能使用 KVO,如果覆蓋了 observeValueForKeyPath:ofObject:change:context: 方法,如何知道該將哪條消息轉發給父類的實現?可以創建一個單獨的指針,在開始觀察的時候將它作為 context,每次收到通知的時候將它和 context 進行對比。靜態變量的地址可以很好地工作。因此,如果子類化某個使用了 KVO 的類時,可以編寫如下代碼:

static int contextForKVO;

[petOwner addObserver:self
           forKeyPath:@"fido"
              options:NSKeyValueObservingOptionNew
              context:&contextForKVO];
...

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary<NSKeyValueChangeKey,id> *)change
                       context:(void *)context
{
    // 判斷這是不是我的快遞?
    if (context !=&contextForKVO) {
        // 不,這是我爹(父類)的快遞
        [super observeValueForKeyPath:keyPath
                             ofObject:object
                               change:change
                              context:context];
    } else {
        // 這是我的快遞,處理變化
    }
}

顯式觸發通知

如果使用存取方法來設置屬性,那么系統會自動通知觀察者。但如果處于某些原因,你選擇不使用存取方法呢?比如,直接存取實例變量。這時可以通過 willChangeValueForKey:didChangeValueForKey: 方法通知系統某個屬性的值即將/已經發生變化。

- (void)updateLastTime:(NSTimer *)timer {
    NSDate *now = [NSDate date];
    [self willChangeValueForKey:@"lastTime"];
    _lastTime = now;
    [self didChangeValueForKey:@"lastTime"];
}

獨立的屬性

如果你不想觀察 _lastTime 而想觀察 _lastTimeString,即:

BNRObserver *observer = [[BNRObserver alloc] init];
[logger addObserver:observer
         forKeyPath:@"lastTimeString"
            options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
            context:nil];

但是系統不知道當 _lastTime 屬性發生變化時,_lastTimeString 也會隨之發生變化。為了修復這個問題,你可以通過實現一個類方法顯式的告訴系統,_lastTime 會影響 _lastTimeString

+ (NSSet *)keyPathsForValuesAffectingLastTimeString {
    return [NSSet setWithObject:@"lastTime"];
}

注意這個方法的名字,它是 keyPathsForValuesAffecting 加上首字母大寫的鍵的名字。另外,沒有必要在類的頭文件中聲明這個方法,系統會在運行時找到它。

移除觀察者身份

你可以發送一條指定觀察方對象和鍵路徑的 removeObserver:forKeyPath: 消息至被觀察的對象,來移除一個鍵-值觀察者(當我們達到目的時)。

[child removeObserver:self forKeyPath:@"key"];
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容

  • 本文轉自:Objective-C中的KVC和KVO. KVC KVO2.1. Registering for Ke...
    0o凍僵的企鵝o0閱讀 423評論 0 3
  • 在iOS開發過程中,我們經常會聽到或者用到KVO,KVC,NSNotificationCenter等,但是很多時候...
    dullgrass閱讀 7,091評論 14 133
  • 一. KVO是鍵值觀察,是Objective-C對觀察者模式的實現,每次當被觀察者對象的某個屬性值發生改變時,注冊...
    魂一飛閱讀 203評論 0 0
  • 一、KVC ====基本概念 它是一種可以直接通過字符串類型的屬性名(key)來訪問某個類屬性的機制。而不是通過調...
    我真的真的是文藝青年閱讀 602評論 2 5
  • 在iOS開發過程中,我們經常會聽到或者用到KVO/KVC,但是對于什么是KVO和KVC,我們可能沒有那么了解。下面...
    問題餓閱讀 469評論 1 0