KVC和KVO總結(jié)

KVC

全稱Key-Value Coding(鍵值編碼)医寿,是一個(gè)基于NSKeyValueCoding協(xié)議實(shí)現(xiàn)的機(jī)制斑响,可通過key對對象的屬性進(jìn)行存取操作,而不是調(diào)用明確的存取方法与殃。

常見的API:

// setter方法
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKey:(NSString *)key;
// getter方法
- (id)valueForKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key;
key和keyPath的區(qū)別:

keyPath的功能要更強(qiáng)大母赵,keyPath可以對屬性進(jìn)行深層的訪問,key則是直接找到對應(yīng)的value授药。舉個(gè)例子:

NSDictionary *dic = @{@"array":@[@"1",@"2",@"1"],
                      @"dictionary":@{@"name":@"Lily",@"sexy":@"female"}};    
NSString *name = [dic valueForKeyPath:@"dictionary.name"];    
NSString *name1 = [[dic valueForKey:@"dictionary"] valueForKey:@"name"];
NSLog(@"%@ -- %@",name,name1);; // Lily -- Lily

除此之外keyPath還有一些便捷的用法士嚎,參見那些不為人知的KVC;

KVC還可以批量賦值和取值,

- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;

使用KVC的setValuesForKeysWithDictionary方法可以進(jìn)行字典轉(zhuǎn)模型呜魄;

setValue:ForKey:的實(shí)現(xiàn)原理

調(diào)用setValue:ForKey:,會按照setKey:莱衩、_setKey:的順序查找方法實(shí)現(xiàn)爵嗅,找到后傳遞參數(shù),調(diào)用方法笨蚁;沒找到方法睹晒,查看+accessInstanceVariablesDirectly方法(即是否直接訪問對象成員變量)的返回值。返回NO括细,調(diào)用setValue:forUndefinedKey:并拋出異常NSUnKnownKeyException伪很;返回YES,按照_key奋单、_isKey锉试、key、isKey順序查找成員變量找到成員變量直接賦值览濒,如果沒有找到對應(yīng)的成員變量調(diào)用setValue:forUndefinedKey:并拋出異常NSUnKnownKeyException呆盖;

valueForKey:的實(shí)現(xiàn)原理

調(diào)用valueForKey:會按照getKey、key贷笛、isKey应又、_key順序查找方法,找到直接調(diào)用乏苦;如果沒有找到方法株扛,查看+accessInstanceVariablesDirectly(即是否直接訪問對象成員變量)方法的返回值。返回NO汇荐,調(diào)用valueForUndefinedKey:并拋出異常NSUnKnownKeyException席里;返回YES,則按照_key拢驾、_isKey奖磁、key、isKey順序查找成員變量繁疤,找到成員變量直接取值咖为,如果沒有找到成員變量調(diào)用valueForUndefinedKey:并拋出異常NSUnKnownKeyException;

異常處理

根據(jù)上述setValue:ForKey:和valueForKey:的實(shí)現(xiàn)原理稠腊,如果沒有找到對應(yīng)的key或keyPath躁染,會調(diào)用對應(yīng)的異常方法。異常方法的默認(rèn)實(shí)現(xiàn)是拋出異常架忌,可以重寫下面兩個(gè)方法進(jìn)行處理避免程序崩潰吞彤。

//取值時(shí)
- (id)valueForUndefinedKey:(NSString *)key;
//賦值時(shí)
- (void)setValue:(id)value forUndefinedKey:(NSString *)key;
KVO<NSKeyValueObserving>

是一個(gè)非正式協(xié)議,定義了對象之間觀察和通知狀態(tài)改變的通用機(jī)制,即當(dāng)被觀察對象A的某個(gè)屬性發(fā)生改變時(shí)饰恕,觀察者B會收到通知挠羔,并作出響應(yīng)。

KVO使用的條件和步驟:

1.被觀察對象必須支持KVC(繼承自NSObject的類都支持KVC)埋嵌,變更屬性值時(shí)執(zhí)行了setter方法破加、或者使用了KVC賦值,才會觸發(fā)KVO機(jī)制
2.觀察者必須實(shí)現(xiàn)-(void)observeValueForKeyPath:ofObject:change:context: 方法
3.被觀察者對象要用- (void)addObserver:forKeyPath:options:context:方法注冊觀察者
4.用完要移除觀察者- (void)removeObserver:forKeyPath: 或者- (void)removeObserver:forKeyPath:context:

方法參數(shù)說明:

1雹嗦、- (void)addObserver: forKeyPath:options: context:

/*
  observer:觀察者對象
  keyPath:被觀察的屬性
  options:監(jiān)聽選項(xiàng)范舀,這個(gè)值可以是NSKeyValueObservingOptions選項(xiàng)的組合
  context:添加觀察者時(shí)的上下文信息,它可以被用作區(qū)分那些綁定同一個(gè)keypath的不同對象的觀察者了罪。比如說觀察一些繼承自同一個(gè)父類的子類锭环,而這些子類都有一個(gè)相同的keyPath
*/
 - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
NSKeyValueObservingOptions
 //設(shè)置后會在observeValueForKeyPath方法的change字典里存入更新后的值。
NSKeyValueObservingOptionNew
//設(shè)置后會在observeValueForKeyPath方法的change字典里存入更新前的值泊藕,也就是原有的值辅辩。
NSKeyValueObservingOptionOld
//設(shè)置后會在添加觀察者的時(shí)候立即發(fā)送一次通知給觀察者,并且在注冊觀察者方法之前返回吱七。
//也就是在addObserver方法執(zhí)行之后就立即發(fā)送了一次通知。
NSKeyValueObservingOptionInitial
//會在值被改變之前發(fā)送一次通知鹤竭,并且在change字典里多了一個(gè)叫notificationIsPrior的key踊餐,值是1。
//而且change字典不會包含new(NSKeyValueChangeNewKey)這個(gè)key臀稚。當(dāng)然值改變后的那次通知也會發(fā)吝岭,也就是說會發(fā)送兩次通知。
NSKeyValueObservingOptionPrior

2吧寺、-(void)observeValueForKeyPath:ofObject:change:context:

 /*
   keyPath:被觀察的屬性
   object:被觀察的對象
   change:這是一個(gè)字典窜管,它包含了屬性被修改的一些信息。這個(gè)字典中包含的值會根據(jù)我們在添加觀察者時(shí)(addObserver方法)設(shè)置的options參數(shù)有所變化稚机。
   context:同上面的方法
 */
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context

change字典里面的鍵值對幕帆,系統(tǒng)提供了這些預(yù)定義的key供我們使用

NSKeyValueChangeKindKey 可以用@"kind"替代,也就是change[NSKeyValueChangeKindKey]等價(jià)于change[@"kind"]
NSKeyValueChangeNewKey 可以用@"new"替代
NSKeyValueChangeOldKey 可以用@"old"替代
NSKeyValueChangeIndexesKey 可以用@"indexes"替代
NSKeyValueChangeNotificationIsPriorKey 可以用@"notificationIsPrior"替代

change字典里面會有哪些key出現(xiàn)取決于在addObserver方法中options參數(shù)的設(shè)置情況赖条。
NewKey和OldKey失乾,就是options設(shè)置NSKeyValueObservingOptionNew和NSKeyValueObservingOptionOld時(shí)會在change里加入的鍵值對。

NSKeyValueChangeNotificationIsPriorKey是在設(shè)置了NSKeyValueObservingOptionPrior選項(xiàng)后當(dāng)被觀察的值將要改變(但是還未改變)時(shí)發(fā)送的通知里會有的key纬乍,對應(yīng)的是一個(gè)布爾值碱茁。

NSKeyValueChangeKindKey對應(yīng)的value是一個(gè)枚舉值,當(dāng)被觀察的值被設(shè)置時(shí)(setter方法調(diào)用時(shí))KindKey對應(yīng)的值為1(NSKeyValueChangeSetting)仿贬。如果觀測的值是一個(gè)可變數(shù)組纽竣,那么當(dāng)數(shù)組執(zhí)行插入,刪除,替換時(shí)kindKey會對應(yīng)Insertion蜓氨,Removal和Replacement聋袋。

typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
    NSKeyValueChangeSetting = 1,
    NSKeyValueChangeInsertion = 2,
    NSKeyValueChangeRemoval = 3,
    NSKeyValueChangeReplacement = 4,
};
KVO的實(shí)現(xiàn)原理

當(dāng)對一個(gè)實(shí)例對象的屬性添加監(jiān)聽,在程序運(yùn)行時(shí)语盈,會動態(tài)的創(chuàng)建一個(gè)類NSKVONotifying_XXX繼承自該實(shí)例對象的類舱馅,并修改該實(shí)例對象的isa指針指向NSKVONotifying_XXX類。新創(chuàng)建的類對應(yīng)被觀察屬性的setter方法實(shí)現(xiàn)是_NSSetXXXValueAndNotify函數(shù)刀荒,_NSSetXXXValueAndNotify內(nèi)部實(shí)現(xiàn)是:

[self willChangeValueForKey:XXX];
// 調(diào)用原來的setter實(shí)現(xiàn)
[self didChangeValueForKey:XXX];

didChangeValueForKey:內(nèi)部會調(diào)用observerobserveValueForKeyPath:ofObject:change:context:方法代嗤;
NSKVONotifying_XXX類會包含以下方法:

- (void)setXXX:(int)age;//內(nèi)部調(diào)用_NSSetXXXValueAndNotify
- (Class)class;//會返回實(shí)例對象的類對象,為了屏蔽內(nèi)部實(shí)現(xiàn)缠借,隱藏NSKVONotifying_XXX類的存在干毅;
- (BOOL)_isKVOA; //表示是KVO對象
- (void)dealloc; // 說明添加KVO的對象,在銷毀時(shí)需要做一些處理工作泼返;
手動通知

默認(rèn)情況下通知會被自動發(fā)送硝逢,但我們也可以手動觸發(fā)。這時(shí)候需要在被觀察對象的類里面重寫+ (BOOL)automaticallyNotifiesObserversForKey:方法绅喉。例如被觀察對象有一個(gè)屬性叫"count"渠鸽,我們希望這個(gè)屬性被修改時(shí)的通知由我們手動控制,就需要在被觀察對象的類文件里面這樣寫:

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
    // 如果屬性為count則關(guān)閉自動發(fā)送通知
      BOOL automatic = YES;
      if ([key isEqualToString:@"count"]) {
       automatic = NO;
    } else {
    // 對于對象中其它沒有處理的屬性柴罐,我們需要調(diào)用[super automaticallyNotifiesObserversForKey:key]徽缚,以避免無意中修改了父類的屬性的處理方式
      automatic = [super automaticallyNotifiesObserversForKey:key];
    }
      return automatic;
 }

然后再對"count"屬性的setter方法做如下處理:

 - (void)setCount:(NSString *) count{
      //當(dāng)兩次賦予的值完全相等時(shí)凿试,沒有必要再發(fā)送通知似芝。這個(gè)if的條件語句可以根據(jù)實(shí)際需要自行修改党瓮,或者干脆不寫寞奸。
       if (_ count != count) {
         [self willChangeValueForKey:@"count"];
         _count = count;
         [self didChangeValueForKey:@"count"];
      }
  }

注意 willChangeValueForKey:和didChangeValueForKey:方法在默認(rèn)自動發(fā)送通知的情況下是由系統(tǒng)自動調(diào)用的,在手動通知時(shí)需要我們自己來調(diào)用呻率,并且不應(yīng)該重寫這兩個(gè)方法礼仗。

注冊依賴建

有時(shí)一個(gè)屬性的改變需要依賴其他的屬性元践,比如一個(gè)叫"fullName"的屬性,這個(gè)屬性依賴于"firstName"和"lastName"沪羔。

//fullName的getter方法
- (NSString *)fullName{
    return [NSString stringWithFormat:@"%@  %@",   _firstName, _lastName];
}

這種情況下如果firstName發(fā)生了變化蔫饰,fullName的值自然也會改變鹃唯,但是由于沒有直接使用setter方法設(shè)置fullName,所以如果不做特殊設(shè)置的話KVO是不會發(fā)送通知的蚪拦。

這種情況就需要使用注冊依賴建來解決。

+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key{
     NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
     if ([key isEqualToString:@"fullName"]) {
        keyPaths = [keyPaths setByAddingObjectsFromArray:@[@"firstName", @"lastName"]];
      }
     return keyPaths;
 }

這樣不論firstName盛嘿,lastName括袒,fullName中的哪個(gè)值發(fā)生了變化,監(jiān)聽fullName的KVO都會被觸發(fā)类垦。

還可以使用下面這個(gè)方法來達(dá)到同樣的目的。這個(gè)方法的使用規(guī)則是+ (NSSet *)keyPathsForValuesAffecting + 屬性名(注意屬性名首字母大寫)糕伐。

+ (NSSet *)keyPathsForValuesAffectingFullName {
    return [NSSet setWithObjects:@"firstName", @"lastName", nil];
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市褥蚯,隨后出現(xiàn)的幾起案子赞庶,更是在濱河造成了極大的恐慌,老刑警劉巖澜薄,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肤京,死亡現(xiàn)場離奇詭異茅特,居然都是意外死亡忘分,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進(jìn)店門饭庞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人舟山,你說我怎么就攤上這事÷笨遥” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵突琳,是天一觀的道長若债。 經(jīng)常有香客問我,道長拆融,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任镜豹,我火速辦了婚禮趟脂,結(jié)果婚禮上昔期,老公的妹妹穿的比我還像新娘硼一。我一直安慰自己累澡,他們只是感情好惑申,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著翅雏,像睡著了一般圈驼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上望几,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天绩脆,我揣著相機(jī)與錄音,去河邊找鬼橄抹。 笑死靴迫,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的楼誓。 我是一名探鬼主播玉锌,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼疟羹!你這毒婦竟也來了主守?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤榄融,失蹤者是張志新(化名)和其女友劉穎参淫,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體愧杯,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡涎才,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了力九。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片耍铜。...
    茶點(diǎn)故事閱讀 40,664評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖跌前,靈堂內(nèi)的尸體忽然破棺而出棕兼,到底是詐尸還是另有隱情,我是刑警寧澤舒萎,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布程储,位于F島的核電站蹭沛,受9級特大地震影響臂寝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜摊灭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一咆贬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧帚呼,春花似錦掏缎、人聲如沸皱蹦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沪哺。三九已至,卻和暖如春酌儒,著一層夾襖步出監(jiān)牢的瞬間辜妓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工忌怎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留籍滴,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓榴啸,卻偏偏與公主長得像孽惰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子鸥印,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 概念 先來看看概念勋功,Key-value coding (KVC) 和 key-value observing (K...
    wuwy閱讀 1,333評論 0 1
  • 引言 今天看了KVC和KVO的視頻,總結(jié)一下库说,便于以后回顧酝润。KVC地址KVO地址 KVC(key-value-co...
    黑化肥發(fā)灰閱讀 818評論 0 50
  • 1. KVO 一.KVO原理的使用與證明 我們在開發(fā)的過程中經(jīng)常使用KVO和KVC,但是我們并不了解其底層原理和功...
    周灬閱讀 846評論 0 9
  • 目錄:1.KVC用法;2.KVC和對象的setter璃弄、getter方法的區(qū)別要销;3.key和keyPath的區(qū)別;4...
    倫倫子_f7b3閱讀 577評論 0 1
  • 本文結(jié)構(gòu)如下: Why? (為什么要用KVO) What? (KVO是什么) How? ( KVO怎么用) Mo...
    等開會閱讀 1,653評論 1 21