iOS開發(fā)之——KVO

引子

KVO:即Key-Value-Observer,鍵值觀測模式肋乍,它是一種允許當(dāng)某些對象的特定屬性值改變時鹅颊,及時通知給對象的觀察者(其他對象)的機制。

觀察者的注冊和移除

KVO的大致流程包括:給要監(jiān)聽的屬性所屬的類添加觀察者墓造;接收到屬性改變的通知后進行處理堪伍;處理完之后接觸觀察者三大步驟。流程很簡單觅闽,就像要把大象裝進冰箱總共需幾步類似杠娱。其中,一對一代表對非集合類的屬性監(jiān)聽谱煤,一對多代表對集合類的屬性監(jiān)聽摊求。

注冊

注冊方法:

[person addObserver:observer
             forKeyPath:@"age"
                options:NSKeyValueObservingOptionNew
                context:NULL];

各個參數(shù)的意義很明了。分別依次是:被觀察類的實例對象刘离,觀察者類的實例對象室叉,被觀察的屬性名稱,觀察選項硫惕,額外參數(shù)茧痕。

注冊選項

注冊選項包括四個,它們的名字和效果依次是:

  • NSKeyValueObservingOptionNew:通知觀察者屬性發(fā)生變化時的新值恼除;
  • NSKeyValueObservingOptionOld:通知觀察者屬性發(fā)生變化時之前的舊值踪旷;
  • NSKeyValueObservingOptionInitial:在注冊觀察者方法(addObserver:observer)未返回時就會開始發(fā)送通知,因為被觀察者的初始化(initial value)對于觀察者來說也是新變化的值豁辉;
  • NSKeyValueObservingOptionPrior:發(fā)送兩條通知令野,也就是當(dāng)屬性值即將要發(fā)生變化時,即下邊要說的willChangeValueForKey觸發(fā)時間相對應(yīng)徽级,預(yù)先發(fā)送給觀察者一條通知气破,待屬性值改變之后跟上述三個選項一樣還會發(fā)出通知。

通知的處理

觀察者收到通知后餐抢,需要通過特定的方法進行處理现使,樣例代碼:

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary<NSString *,id> *)change
                       context:(void *)context
{
    NSLog(@"%@", change);
    if ([keyPath isEqualToString:@"name"])
    {
        //通知事件的處理
        NSLog(@"%@的名字發(fā)生了變化低匙!%@", object, change);
    }
}

這個方法一定要在觀察者的類中進行重寫。

通知信息

通知處理方法中的change:(NSDictionary<NSString *,id> *)change是一個字典類型的對象碳锈,包含了此次變化的信息顽冶,例如NSKeyValueObservingOptionNew選項下,一對一的屬性發(fā)生變化時接收到的變化信息如下:

{
    kind = 1;
    new = hexintao;
}

第一條的kindNSKeyValueChange類型的枚舉值售碳,它有如下四個定義:

  • NSKeyValueChangeSetting:設(shè)置新值渗稍,被監(jiān)聽的是一對一的屬性或者一對多的屬性;
  • NSKeyValueChangeInsertion:一對多的屬性新插入了一個對象团滥;
  • NSKeyValueChangeRemoval:一對多的屬性移除了一個對象竿屹;
  • NSKeyValueChangeReplacement:一對多的屬性替換了其中的某個對象。

第二條則會根據(jù)NSKeyValueObservingOptions觀察選項灸姊、一對一或者一對多的屬性不同而不同拱燃。大致也就是顯示設(shè)置的新值、變化之前的舊值或者是一對多屬性的添加力惯、移除碗誉、替換的對象和序號(index)等。具體的信息可以command+observeValueForKeyPath父晶,查看通知處理方法的官方注釋哮缺,寫的非常詳細(xì)。

移除

待屬性值發(fā)生變化的通知處理完畢之后甲喝,我們需要對注冊的觀察者進行手動解除尝苇,解除的方法是:

- (void)removeObserver:(NSObject *)anObserver
            forKeyPath:(NSString *)keyPath

- (void)removeObserver:(NSObject *)observer
            forKeyPath:(NSString *)keyPath
               context:(void *)context

對沒有沒有進行監(jiān)聽的屬性(keyPath)執(zhí)行解除操作,會拋出異常埠胖。同樣糠溜,如果對已經(jīng)注冊的監(jiān)聽屬性沒有執(zhí)行解除操作,也會拋出異常直撤。

自動通知和手動通知

如果按照以上操作步驟執(zhí)行非竿,則默認(rèn)使用的是自動通知,即只要對屬性值進行重新賦值(不管新值和舊值是否相同)谋竖,觀察者都會收到通知红柱。而在實際應(yīng)用中,有可能我們想根據(jù)自己的需要蓖乘,待屬性值滿足我們的條件之后才給觀察者發(fā)送通知锤悄,這時候我們就需要通過手動模式修改發(fā)送通知的條件和時間來達到目的了。

自動通知1

如上述操作驱敲,發(fā)送通知的時機和條件無法進行修改铁蹈。

//第一次修改可以正常接收到通知
[person setValue:@"hexintao" forKey:@"name"];
//自動通知模式下宽闲,接下來這兩次依然會接收到通知
[person setValue:@"hexintao" forKey:@"name"];
[person setValue:@"hexintao" forKey:@"name"];

當(dāng)然众眨,實際應(yīng)用中握牧,連續(xù)賦同樣的值的情況不多見也不推薦,我們只是為了以此說明自動通知模式下的情況娩梨。

手動通知

首先需要關(guān)閉自動通知沿腰,在被觀察者的類中重寫類方法:
(2017.01.04修改:如果不重寫這個類方法,則系統(tǒng)會在屬性 setter 方法的之前之后自動調(diào)用 willChangeValueForKeydidChangeValueForKey狈定,造成同一次屬性的修改調(diào)用兩次 KVO 監(jiān)聽方法颂龙。)

+ (BOOL)automaticallyNotifiesObserversOf<Key>
{
    return NO;
}

或者是:

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
    BOOL automatic = YES;
    if ([key isEqualToString:@"name"])
    {
        automatic = NO;
    }
    else
    {
        automatic = [super automaticallyNotifiesObserversForKey:key];
    }
}

后一個方法較復(fù)雜,而且有把屬性名稱(key)拼錯的風(fēng)險纽什,所以還是推薦使用第一種方法措嵌。
接下來,要在需要發(fā)出通知的地方手動調(diào)用兩個方法芦缰,這個例子中我們就取屬性值的setter方法:

- (void)setName:(NSString *)name
{
    if (name != _name)
    {
        //子類不能重寫這兩個方法企巢,否則無法完成手動觸發(fā)KVO
        //通過改變這兩個方法的位置,可以自定義KVO觸發(fā)的條件
        [self willChangeValueForKey:@"name"];
        _name = name;
        [self didChangeValueForKey:@"name"];
    }
}

上述兩個方法一定要成對調(diào)用才會成功發(fā)出通知让蕾。
剛才那個例子:

//第一次修改可以正常接收到通知
[person setValue:@"hexintao" forKey:@"name"];
//手動通知模式下浪规,接下來這兩次不會接收到通知
[person setValue:@"hexintao" forKey:@"name"];
[person setValue:@"hexintao" forKey:@"name"];

依賴鍵的注冊

有時候,我們監(jiān)聽的某個屬性值可能會依賴于其他多個屬性探孝,只要其他屬性發(fā)生了改變都會導(dǎo)致我們監(jiān)聽的屬性發(fā)生變化笋婿,這種就叫做依賴鍵。例如顿颅,在Person類中有一個personInfo的屬性缸濒,它返回的是對象的nameage的組合:

- (NSString *)personInfo
{
    return [NSString stringWithFormat:@"person's name:%@ age:%d", self.name, self.age];
}

如果我們對personInfo進行監(jiān)聽,則nameage的變化也會導(dǎo)致personInfo發(fā)生變化粱腻,這時候我們就需要設(shè)置依賴鍵绍填。

+ (NSSet *)keyPathsForValuesAffectingPersonInfo
{
    return [NSSet setWithObjects:@"name", @"age", nil];
}

然后再對personInfo進行注冊監(jiān)聽,之后如果我們對name或者age的值進行修改的時候栖疑,觀察者就會收到這樣的通知:

CollectionViewTest[742:17933] {
    kind = 1;
    new = "person's name:hexintao age:0";
}

也就是當(dāng)關(guān)聯(lián)屬性中的任何一個發(fā)生了變化讨永,我們監(jiān)聽的這個屬性就會收到通知,說明其值發(fā)生了變化遇革。

集合屬性的監(jiān)聽

集合屬性整體還是部分卿闹?

一對一屬性的監(jiān)聽相對來說比較簡單,只要值發(fā)生了變化我們收到通知進行處理即可萝快。對于一對多集合類的屬性來說锻霎,牽扯到是監(jiān)聽整個集合發(fā)生的變化還是其中元素的變化?這兩種行為都可以通過KVO監(jiān)聽到揪漩,不過日常使用來說旋恼,我們更傾向于監(jiān)聽后者。
對于集合屬性奄容,正常的添加或刪除對象的操作并不能觸發(fā)KVO冰更,例如[person.personFriends addObject:@"2in"]产徊,觀察者并不會收到變化通知,不過對于集合屬性整體的改變蜀细,例如person.personFriends = [[NSMutableArray alloc]init]舟铜,觀察者可以正常收到通知。不過我們重點討論通過特定的方法監(jiān)聽集合屬性中對象的變化奠衔。大致分為兩種方法:

手動監(jiān)聽

集合屬性的手動監(jiān)聽即:在被監(jiān)聽的類中重寫一些修改集合元素的方法谆刨,之后調(diào)用這些方法對屬性進行修改就可以觸發(fā)KVO監(jiān)聽。
我們可以根據(jù)需要實現(xiàn):

//插入對象
- (void)insertObject:(id)object in<Key>AtIndex:(NSUInteger)index
//移除對象
-(void)removeObjectFrom<Key>AtIndex:(NSUInteger)index

具體的操作元素的方法可見官方手冊:KVC官方指導(dǎo)
之后我們通過調(diào)用重寫后的方法修改集合屬性時即可觸發(fā)KVO归斤。

自動監(jiān)聽

自動監(jiān)聽大致是:通過mutableArrayValueForKey方法獲得一個可變對象的代理痊夭,對其進行修改即可自動觸發(fā)KVO。而valueForKey返回的則是不可變對象脏里。
使用樣例:

[[person mutableArrayValueForKey:@"personFriends"] addObject:@"3in"];

// 但是如果是將上述操作賦值給一個可變數(shù)組生兆,再調(diào)用正常的類似于addObject方法將不會觸發(fā)KVO監(jiān)聽。
NSMutableArray *friends = [person mutableArrayValueForKey:@"personFriends"];
//不能觸發(fā)KVO模式
[person.personFriends addObject:@"4in"];  

//這兩個方法能觸發(fā)KVO膝宁,但是friends和person.personFriends指向的并不是同一個對象鸦难,不過其內(nèi)容卻完全一樣,
//對friends操作會影響到person.personFriends的值员淫,反過來也是如此合蔽!
[friends insertObject:@"5in" atIndex:0];  //可以觸發(fā)KVO
[friends removeObjectAtIndex:0];  //可以觸發(fā)KVO

參考資料

Apple KVO官方手冊
NSHipster KVO
可變對象在KVO中的監(jiān)聽
KVO詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市介返,隨后出現(xiàn)的幾起案子拴事,更是在濱河造成了極大的恐慌,老刑警劉巖圣蝎,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刃宵,死亡現(xiàn)場離奇詭異,居然都是意外死亡徘公,警方通過查閱死者的電腦和手機牲证,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來关面,“玉大人坦袍,你說我怎么就攤上這事〉忍” “怎么了捂齐?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長缩抡。 經(jīng)常有香客問我奠宜,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任压真,我火速辦了婚禮娩嚼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘榴都。我一直安慰自己待锈,他們只是感情好漠其,可當(dāng)我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布嘴高。 她就那樣靜靜地躺著,像睡著了一般和屎。 火紅的嫁衣襯著肌膚如雪拴驮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天柴信,我揣著相機與錄音套啤,去河邊找鬼。 笑死随常,一個胖子當(dāng)著我的面吹牛潜沦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播绪氛,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼唆鸡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了枣察?” 一聲冷哼從身側(cè)響起争占,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎序目,沒想到半個月后臂痕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡猿涨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年握童,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叛赚。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡舆瘪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出红伦,到底是詐尸還是另有隱情英古,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布昙读,位于F島的核電站召调,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜唠叛,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一只嚣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧艺沼,春花似錦册舞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至挽荡,卻和暖如春藐石,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背定拟。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工于微, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人青自。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓株依,卻偏偏與公主長得像,于是被迫代替她去往敵國和親延窜。 傳聞我的和親對象是個殘疾皇子恋腕,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,647評論 2 354

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

  • KVO:(Key-Value-Observer)鍵值觀察者,是觀察者設(shè)計模式的一種具體實現(xiàn)需曾。 在我們編程中吗坚,很多時...
    邦奇諾閱讀 263評論 0 2
  • 本質(zhì)(更新于 2019年02月26日23:17:19): 首先,KVO是觀察者模式的一種實現(xiàn)其次呆万,Apple使用了...
    helloDolin閱讀 1,186評論 5 4
  • 上半年有段時間做了一個項目商源,項目中聊天界面用到了音頻播放,涉及到進度條谋减,當(dāng)時做android時候處理的不太好牡彻,由于...
    DaZenD閱讀 3,017評論 0 26
  • 本文結(jié)構(gòu)如下: Why? (為什么要用KVO) What? (KVO是什么) How? ( KVO怎么用) Mo...
    等開會閱讀 1,649評論 1 21
  • 么么噠
    老毛zoo閱讀 116評論 4 0