KVO

KVO 簡介

KVO 鍵值觀察機(jī)制盆犁,就是觀察指定對象的指定屬性變化情況渐裸。

KVO 鍵值觀察 依賴于 KVC 健值編碼

Key-value observing 通常用于 MVC 中,model 與 controller直接的通訊掘譬。

繼承于 NSObject 才可以擁有 KVO 機(jī)制

兩種 KVO 應(yīng)用方式

  • 自動 KVO
  • 手動 KVO

舉個例子,一個 Person 類, 一個 Account

  • [圖1]

當(dāng) Account 的屬性 余額 balance 改變時通知 Person
Account 添加觀察者 Person
[account addObserver:person forKeyPath:@"balance" options:options context:context];

  • [圖2]

Person 觀察 Account 的屬性變化
Person 中實現(xiàn)觀察者方法 observeValueForKeyPath:ofObject:change:context:

  • [圖3]

Account 移除觀察者 Person
[account removeObserver:person forKeyPath:@"balance" context:context]

  • [圖4]

這篇文章的重要內(nèi)容

  1. Registering for Key-Value Observing 注冊鍵值觀察的過程

  2. Registering Dependent Keys 對于存在 key 依賴的鍵值觀察

  3. Key-Value Observing Implementation Details 鍵值觀察的實現(xiàn)細(xì)節(jié)


1. Registering for Key-Value Observing 注冊鍵值觀察的過程

KVO 生命周期的過程欣舵,必須完成下面這三個方法

  1. 給被觀察者注冊觀察者 addObserver:forKeyPath:options:context:.
  2. 在觀察者里實現(xiàn)方法 接受通知 observeValueForKeyPath:ofObject:change:context:
  3. 移除觀察者 removeObserver:forKeyPath: 要在觀察者的內(nèi)存銷毀之前 移除觀察者機(jī)制

- 注冊觀察者 addObserver:forKeyPath:options:context:.

  • Options
    Options 影響方法 observeValueForKeyPath:ofObject:change:context: 中的 change 字典

    • NSKeyValueObservingOptionOld: change 包含 old value
    • NSKeyValueObservingOptionNew: 'change' 包含 new value
    • NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew: 即包含 old 也包含 new
    • NSKeyValueObservingOptionInitial : change 中不包含 key 的值,會在 kvo 注冊的時候立即發(fā)送通知缀磕。
    • NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew : 注冊kvo時立即發(fā)送通知 change 中有 new 值缘圈,這里的 new 值是注冊之前 key 的值。
    • NSKeyValueObservingOptionPrior : 會在值發(fā)生改變前發(fā)出一次通知袜蚕,當(dāng)然改變后的通知依舊還會發(fā)出糟把,也就是每次change都會有兩個通知。值變化之前發(fā)送通知的 change 中包含一個鍵值對 NSKeyValueChangeNotificationIsPriorKey:@(1), 值發(fā)生變化之后的的通知 change 不包含上面提到的 鍵值對, 可以跟 willChange 手動通知搭配使用
  • Context
    是一個 void * 指針牲剃,可以傳任意數(shù)據(jù)進(jìn)去遣疯,可以防止父類跟子類同時對同一個 key 注冊觀察者造成的異常。

    可以采用下面的方法創(chuàng)建一個 Context

            static void *PersonAccountBalanceContext = &PersonAccountBalanceContext;
            static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext;
    

    命名規(guī)范:static void * 類名+屬性名+Context

- Receiving Notification of a Change 接收通知響應(yīng)

        所有的觀察者必須實現(xiàn)方法 `observeValueForKeyPath:ofObject:change:context: message.`
  • keyPath : 注冊 KVO 時的 keyPath
  • object : 被觀察者對象
  • change : 根據(jù)注冊 KVO 時 option 不同而展示不同的內(nèi)容凿傅,change中可能會包含 keyPath 變化前后的值缠犀,對應(yīng)的鍵有
    • NSKeyValueChangeOldKey
    • NSKeyValueChangeNewKey

keyPath是個標(biāo)量或者 c 語言結(jié)構(gòu)體,會把這些包裝成 NSNumberNSValue,

當(dāng)keyPath 是個容器狭归, change中可能會包含這個容器 inserted,removed,replaced 的情況 對應(yīng)的鍵有

- `NSKeyValueChangeInsertion`
- `NSKeyValueChangeRemoval` 
- `NSKeyValueChangeReplacement`

keypath 是個 NSIndexSet : change 中可能會包含數(shù)組

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
 
     if (context == PersonAccountBalanceContext) {
     // Do something with the balance…
      } else if (context == PersonAccountInterestRateContext) {
    // Do something with the interest rate…
      } else {
                    // Any unrecognized context must belong to super
      [super observeValueForKeyPath:keyPath
                                         ofObject:object
                                           change:change
                                           context:context];
       }
}

- Removing an Object as an Observer 移除觀察者

觀察者被移除之后就不會再接受到通知夭坪。

        - (void)unregisterAsObserverForAccount:(Account*)account {
            [account removeObserver:self
                         forKeyPath:@"balance"
                            context:PersonAccountBalanceContext];
         
            [account removeObserver:self
                         forKeyPath:@"interestRate"
                            context:PersonAccountInterestRateContext];
        }

需要注意的地方

  • 不要重復(fù)移除觀察者,會造成異常过椎,如果不知道這個觀察者是否已經(jīng)被移除室梅,可以在 try/catch 安全移除觀察者
  • 在觀察者內(nèi)存銷毀之前從觀察者中釋放出來,不然會造成內(nèi)存異常
  • 無法檢測一個對象是否處于觀察者模式中疚宇,
    • init 或者 viewDidLoad 中添加觀察者
    • dealloc 中移除觀察者

Automatic Change Notification 自動 KVO 通知

NSObject 提供了自動健值更改通知的實現(xiàn)亡鼠,自動 KVO 通知依賴于 KVC 編碼機(jī)制獲取, KVC method敷待,和 集合代理(collection proxy)mutableArrayValueForKey:

手動 KVO

當(dāng)你想要控制整個 KVO 的進(jìn)程可以采用手動 KVO, 比如減少不必要的通知间涵,比如將大量的通知控制到一個通知中。
手動 KVO 跟 自動 KVO 可以共存榜揖,比如同一個對象的同一個屬性勾哩,可以在父類里自動 KVO, 在子類里手動 KVO, 重寫方法 automaticallyNotifiesObserversForKey即可

  • 打開手動 KVO 的開關(guān) automaticallyNotifiesObserversForKey

    + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
    
       BOOL automatic = NO;
       if ([theKey isEqualToString:@"balance"]) {
           automatic = NO;
       }
       else {
           automatic = [super automaticallyNotifiesObserversForKey:theKey];
       }
       return automatic;
    }
    
    • 手動觸發(fā) KVO(一般是在 set 方法里)
      • willChangeValueForKey
      • didChangeValueForKey
      - (void)setBalance:(double)theBalance {
          if (theBalance != _balance) {
              [self willChangeValueForKey:@"balance"];
              _balance = theBalance;
              [self didChangeValueForKey:@"balance"];
          }
      }
      
    • 也可以把多個觸發(fā) KVO 的 key 放到一起
      - (void)setBalance:(double)theBalance {
          [self willChangeValueForKey:@"balance"];
          [self willChangeValueForKey:@"itemChanged"];
          _balance = theBalance;
          _itemChanged = _itemChanged+1;
          [self didChangeValueForKey:@"itemChanged"];
          [self didChangeValueForKey:@"balance"];
      }
      
    • 容器內(nèi)部更改時抗蠢,采用采用手動 KVO(當(dāng)容器內(nèi)容修改時,會觸發(fā) KVO)
      注意:根據(jù)容器變化的類型去設(shè)置對應(yīng)的 NSKeyValueChange
      • NSKeyValueChangeInsertion
      • NSKeyValueChangeRemoval
      • NSKeyValueChangeReplacement

      針對容器對象的 KVO 思劳,需要借用 KVC 機(jī)制創(chuàng)建的 容器代理迅矛。

    - (void)removeTransactionsAtIndexes:(NSIndexSet *)indexes {
        [self willChange:NSKeyValueChangeRemoval
            valuesAtIndexes:indexes forKey:@"transactions"];
     
        // Remove the transaction objects at the specified indexes.
     
        [self didChange:NSKeyValueChangeRemoval
        valuesAtIndexes:indexes forKey:@"transactions"];
    }
    

2.KVO - 注冊依賴 key

單個關(guān)系

重寫下面兩個方法中的一個即可

  • keyPathsForValuesAffectingValueForKey

  • keyPathsForValuesAffecting<Key>
    用例如下

    + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
    
        NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
     
        if ([key isEqualToString:@"fullName"]) {
            NSArray *affectingKeys = @[@"lastName", @"firstName"];
            keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
        }
        return keyPaths;
    }
    
    + (NSSet *)keyPathsForValuesAffectingFullName {
        return [NSSet setWithObjects:@"lastName", @"firstName", nil];
    }
    

多個關(guān)系

比如,有個部門類 Department潜叛,有個員工類 Employees, 每個員工的薪水都會影響這個部門的總薪水秽褒。
可以在 Department 添加、刪除 Employees 的時候威兜,給 Employees 注冊销斟、移除觀察者 Employees

用例如下

```
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

    if (context == totalSalaryContext) {
        [self updateTotalSalary];
    }
    else
    // deal with other observations and/or invoke super...
}
 
- (void)updateTotalSalary {
    [self setTotalSalary:[self valueForKeyPath:@"employees.@sum.salary"]];
}
 
- (void)setTotalSalary:(NSNumber *)newTotalSalary {
 
    if (totalSalary != newTotalSalary) {
        [self willChangeValueForKey:@"totalSalary"];
        _totalSalary = newTotalSalary;
        [self didChangeValueForKey:@"totalSalary"];
    }
}
 
- (NSNumber *)totalSalary {
    return _totalSalary;
}
```

3. KVO 內(nèi)部實現(xiàn)細(xì)節(jié)

使用 isa-swizzling 原理
當(dāng) 對象的屬性注冊到觀察者中時,會創(chuàng)建一個中間類椒舵,重寫了被觀察屬性的 setter 方法蚂踊。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市逮栅,隨后出現(xiàn)的幾起案子悴势,更是在濱河造成了極大的恐慌,老刑警劉巖措伐,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件特纤,死亡現(xiàn)場離奇詭異,居然都是意外死亡侥加,警方通過查閱死者的電腦和手機(jī)捧存,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來担败,“玉大人毫目,你說我怎么就攤上這事膛壹。” “怎么了?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵辈毯,是天一觀的道長格粪。 經(jīng)常有香客問我宅广,道長饶碘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任拓哺,我火速辦了婚禮勇垛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘士鸥。我一直安慰自己闲孤,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布烤礁。 她就那樣靜靜地躺著讼积,像睡著了一般肥照。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上勤众,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天建峭,我揣著相機(jī)與錄音,去河邊找鬼决摧。 笑死,一個胖子當(dāng)著我的面吹牛凑兰,可吹牛的內(nèi)容都是我干的掌桩。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼姑食,長吁一口氣:“原來是場噩夢啊……” “哼波岛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起音半,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤则拷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后曹鸠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體煌茬,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年彻桃,在試婚紗的時候發(fā)現(xiàn)自己被綠了坛善。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡邻眷,死狀恐怖眠屎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情肆饶,我是刑警寧澤改衩,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站驯镊,受9級特大地震影響葫督,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜阿宅,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一候衍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧洒放,春花似錦蛉鹿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽惋戏。三九已至,卻和暖如春他膳,著一層夾襖步出監(jiān)牢的瞬間响逢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工棕孙, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留舔亭,地道東北人。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓蟀俊,卻偏偏與公主長得像钦铺,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子肢预,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,086評論 2 355

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

  • KVO編程指南 Key-Value Observing Programming Guide 1 Introduct...
    codeTao閱讀 605評論 0 0
  • 引言 鍵值觀察(KVO)提供了一種機(jī)制以允許對象被告知其他對象的特定屬性的更改矛洞,它對應(yīng)用程序中的模型和控制器層之間...
    漸z閱讀 576評論 0 0
  • 本文結(jié)構(gòu)如下: Why? (為什么要用KVO) What? (KVO是什么) How? ( KVO怎么用) Mo...
    等開會閱讀 1,652評論 1 21
  • 本文由我們團(tuán)隊的 糾結(jié)倫 童鞋撰寫。 文章結(jié)構(gòu)如下: Why? (為什么要用KVO) What? (KVO是什么...
    知識小集閱讀 7,412評論 7 105
  • 三十二 宸妃 自趙嬪晉位后烫映,皇帝的心漸漸回到了蘇妃身上沼本,對蘇妃多加寵愛和憐惜。 而蘇妃仍舊淡淡的锭沟,不驕不躁抽兆。 君后...
    君清兮閱讀 181評論 0 0