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

KVC

鍵值編碼的基本概念

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

設置和訪問

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

示例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 來獲取對象的值時许溅,如果對象的實例變量為基本數據類型(char瓤鼻、intfloat贤重、BOOL)時茬祷,我們需要使用 NSNumber 對象對數據進行封裝并蝗。

key路徑

  • 除了通過鍵設置值外祭犯,鍵/值編碼還支持指定路徑,像文件系統(tǒng)一樣滚停,用“點”號隔開键畴。
  • 使用 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"]);

實現(xiàn)簡單的運算

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 不同,鍵-值觀察中并沒有所謂的中心對象來為所有觀察者提供變化通知蛇更。取而代之地瞻赶,當有變化發(fā)生時,通知被直接發(fā)送至處于觀察狀態(tài)的對象派任。NSObject 提供這種基礎的鍵-值觀察實現(xiàn)方法砸逊。
  3. 你可以觀察任意對象屬性,包括簡單屬性掌逛,對一或是對多關系师逸。對多關系的觀察者將會被告知發(fā)生變化的類型-也就是任意發(fā)生變化的對象。
  4. 鍵-值觀察為所有對象提供自動觀察兼容性豆混。你可以通過禁用自動觀察通知并實現(xiàn)手動通知來篩選通知篓像。

注冊觀察者

為了正確接收屬性的變更通知,觀察對象必須首先發(fā)送一個addObserver:forKeyPath:options:context:消息至被觀察對象皿伺,用以傳送觀察對象和需要觀察的屬性的關鍵路徑员辩,以便與其注冊。選項參數指定了發(fā)送變更通知時提供給觀察者的信息鸵鸥。 使用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 何時發(fā)生變化,都要通知我它改變的新值以及改變之前的舊值
[logger addObserver:observer
         forKeyPath:@"lastTime"
            options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
            context:nil];

[[NSRunLoop mainRunLoop] run];

接受變更后通知

當被觀察對象的一個被觀察的屬性發(fā)生變動時宰翅,觀察者會收到 observeValueForKeyPath:ofObject:change:context: 消息弃甥。所有觀察者都必須實現(xiàn)這一方法。觸發(fā)觀察通知的對象和鍵路徑汁讼、包含變更細節(jié)的字典淆攻,以及觀察者注冊時提交的上下文指針均被提交給觀察者,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 會隨通知一起發(fā)送。context 可以用來回答:“這真的是我需要的通知嗎?”(你可以理解為 context 可以作為此觀察事件的唯一標識符)唱较。例如扎唾,你的父類可能使用 KVO,如果覆蓋了 observeValueForKeyPath:ofObject:change:context: 方法南缓,如何知道該將哪條消息轉發(fā)給父類的實現(xiàn)胸遇?可以創(chuàng)建一個單獨的指針,在開始觀察的時候將它作為 context汉形,每次收到通知的時候將它和 context 進行對比纸镊。靜態(tài)變量的地址可以很好地工作。因此概疆,如果子類化某個使用了 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 {
        // 這是我的快遞凯旭,處理變化
    }
}

顯式觸發(fā)通知

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

- (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];

但是系統(tǒng)不知道當 _lastTime 屬性發(fā)生變化時差凹,_lastTimeString 也會隨之發(fā)生變化。為了修復這個問題侧馅,你可以通過實現(xiàn)一個類方法顯式的告訴系統(tǒng)危尿,_lastTime 會影響 _lastTimeString

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

注意這個方法的名字,它是 keyPathsForValuesAffecting 加上首字母大寫的鍵的名字馁痴。另外谊娇,沒有必要在類的頭文件中聲明這個方法,系統(tǒng)會在運行時找到它罗晕。

移除觀察者身份

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

[child removeObserver:self forKeyPath:@"key"];
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末小渊,一起剝皮案震驚了整個濱河市法褥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌酬屉,老刑警劉巖半等,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件揍愁,死亡現(xiàn)場離奇詭異,居然都是意外死亡杀饵,警方通過查閱死者的電腦和手機莽囤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凹髓,“玉大人烁登,你說我怎么就攤上這事∥狄ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵锨络,是天一觀的道長赌躺。 經常有香客問我,道長羡儿,這世上最難降的妖魔是什么礼患? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮掠归,結果婚禮上缅叠,老公的妹妹穿的比我還像新娘。我一直安慰自己虏冻,他們只是感情好肤粱,可當我...
    茶點故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布典奉。 她就那樣靜靜地躺著化借,像睡著了一般。 火紅的嫁衣襯著肌膚如雪搀玖。 梳的紋絲不亂的頭發(fā)上蛮穿,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天庶骄,我揣著相機與錄音,去河邊找鬼践磅。 笑死单刁,一個胖子當著我的面吹牛,可吹牛的內容都是我干的府适。 我是一名探鬼主播羔飞,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼细溅!你這毒婦竟也來了褥傍?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤喇聊,失蹤者是張志新(化名)和其女友劉穎恍风,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡朋贬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年凯楔,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锦募。...
    茶點故事閱讀 38,673評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡摆屯,死狀恐怖,靈堂內的尸體忽然破棺而出糠亩,到底是詐尸還是另有隱情虐骑,我是刑警寧澤,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布赎线,位于F島的核電站廷没,受9級特大地震影響,放射性物質發(fā)生泄漏垂寥。R本人自食惡果不足惜颠黎,卻給世界環(huán)境...
    茶點故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望滞项。 院中可真熱鬧狭归,春花似錦、人聲如沸文判。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽律杠。三九已至潭流,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間柜去,已是汗流浹背灰嫉。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留嗓奢,地道東北人讼撒。 一個月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像股耽,于是被迫代替她去往敵國和親根盒。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,562評論 2 349

推薦閱讀更多精彩內容

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