淺談iOS KVO鍵值觀察者模式

一、個人理解

KVO : (Key - Value - Observer) 鍵值觀察者,是觀察者設計模式的一種具體實現(xiàn)(C層和M層的通信)

KVO觸發(fā)機制:一個對象(觀察者),檢測另一個對象(被觀察者)的某屬性是否發(fā)生變化,若被監(jiān)測的屬性發(fā)生了更改,會觸發(fā)觀察者的一個方法(方法名固定,類似代理方法)
我腦海里復現(xiàn)出一個場景有助于記住這個觸發(fā)機制,大家可以想象諜戰(zhàn)片中有兩個人,路人甲手里拿著槍指著路人乙,路人甲就是觀察者,路人乙就是被觀察者,路人乙腰中暗藏著的槍就是將要發(fā)生變化的屬性,只要乙要拔出腰中的槍(屬性發(fā)生改變),甲就開槍(觸發(fā)了觀察者的固定方法)

二、使用步驟

1、注冊觀察者(為被觀察這指定觀察者以及被觀察者屬性)
2、實現(xiàn)回調方法
3、觸發(fā)回調方法
4、移除觀察者

三、使用時需要注意

KVO奔潰的原因:
1、被觀察的對象銷毀掉了(被觀察的對象是一個局部變量)
2、觀察者被釋放掉了,但是沒有移除監(jiān)聽(如模態(tài)推出,push,pop等)
3、注冊的監(jiān)聽沒有移除掉,又重新注冊了一遍監(jiān)聽

四、具體實現(xiàn)過程

(這兒是對Person類的name屬性的一個觀察)
Person.h文件

@interface Person : NSObject 
@property(nonatomic,strong)NSString *name;//姓名 
//第一種就是直接賦值 
- (void)changeName:(NSString*)name; 
//第二種點語法賦值 
- (void)changeNameFromSetter:(NSString*)name; 
@end

Person.m文件

#import "Person.h" 
@implementation Person 
//第一種就是直接賦值 
- (void)changeName:(NSString*)name{ 
    _name = name; 
} 
//第二種點語法賦值 
- (void)changeNameFromSetter:(NSString*)name { 
    self.name = name; 
} 
@end

rootViewController.m文件

#pragma mark - 初始化person 
- (void)initPerson{ 
    self.person = [[Person alloc] init]; 
    self.person.name = @"最初的名字"; 
} 
 
 
#pragma mark - 按鈕方法 
- (void)changeColor { 
//    self.view.backgroundColor = [UIColor colorWithRed:arc4random()%256/255.0 green:arc4random()%256/255.0 blue:arc4random()%256/255.0 alpha:1]; 
    self.view.backgroundColor = [UIColor redColor]; 
}

1.注冊觀察者

//observer觀察者 (觀察self.view對象的屬性的變化) 
    //KeyPath: 被觀察屬性的名稱 
    //options: 觀察屬性的新值,舊值等的一些配置(枚舉值) 
    //context:上下文 可以為kvo的回調方法傳值 
    //這兒的self.view是被觀察者 
    //注冊觀察者(可以是多個) 
    /* 
     options: 有4個值,分別是: 
 
       NSKeyValueObservingOptionOld 把更改之前的值提供給處理方法 
 
       NSKeyValueObservingOptionNew 把更改之后的值提供給處理方法 
 
       NSKeyValueObservingOptionInitial 把初始化的值提供給處理方法,一旦注冊,立馬就會調用一次。通常它會帶有新值,而不會帶有舊值。 
 
       NSKeyValueObservingOptionPrior 分2次調用。在值改變之前和值改變之后。 
     */ 
    [self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld context:nil];

2.實現(xiàn)回調方法

#pragma mark - kvo的回調方法(系統(tǒng)提供的回調方法) 
//keyPath:屬性名稱 
//object:被觀察的對象 
//change:變化前后的值都存儲在change字典中 
//context:注冊觀察者的時候,context傳遞過來的值 
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context { 
    id oldName = [change objectForKey:NSKeyValueChangeOldKey]; 
    NSLog(@"oldName----------%@",oldName); 
    id newName = [change objectForKey:NSKeyValueChangeNewKey]; 
    NSLog(@"newName-----------%@",newName); 
    //當界面要消失的時候,移除kvo 
//    [object removeObserver:self forKeyPath:@"name"]; 
}

*3.觸發(fā)回調方法
(這兒需要注意一點,在Person.m文件中如果賦值沒有通過setter方法或者是kvc,例如(_name = name)這個時候不會觸發(fā)kvc的回調方法,也就是說賦值必須得通過setter方法或者KVC賦值,才會觸發(fā)回調方法)

//導航欄左按鈕 
    self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"下劃線" style:UIBarButtonItemStylePlain target:self action:@selector(oldAction)]; 
    //導航欄右按鈕 
    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"點語法" style:UIBarButtonItemStylePlain target:self action:@selector(newAction)];
#pragma mark - 導航欄按鈕方法(如果賦值沒有通過setter方法或者是kvc,例如(_name = @"新值"),這個時候不會觸發(fā)kvc的回調方法) 
//通過下劃線賦值(不會觸發(fā)回調方法) 
- (void)oldAction { 
    [self.person changeName:@"張三"]; 
} 
//通過點語法賦值 
- (void)newAction { 
    [self.person changeNameFromSetter:@"李四"]; 
}

4.移除觀察者

有兩種方法:
第一種:當界面要消失的時候,移除KVO

    //當界面要消失的時候,移除kvo 
    [object removeObserver:self forKeyPath:@"name"];
- (void)dealloc { 
    [self.person removeObserver:self forKeyPath:@"name"]; 
    self.person = nil; 
}

五、KVO 底層實現(xiàn)

KVO是iOS中常用的傳遞消息信息中的一種
相關的API:

addObserver:forKeyPath:options:context: 給對象的某個屬性添加observer
observeValueForKeyPath:ofObject:change:context:屬性發(fā)生變化時候調用的通知方法
removeObserver:forKeyPath:移除監(jiān)聽
automaticallyNotifiesObserversForKey:是否自動觸發(fā)KVO

KVO 底層實現(xiàn)

Aapple官方文檔中解釋道, KVO底層使用了 isa-swizling的技術.如果你了解runtime, 那么你應該知道OC中每個對象/類都有isa指針, 一個對象的isa指針指向object's class, 這個object's class對象中有SEL - IMP的dispatch-table.簡而言之, isa 表示這個對象是哪個類的對象.

當給對象的某個屬性注冊了一個 observer, 那么這個對象的isa指針指向的class會被改變, 此時系統(tǒng)會創(chuàng)建一個新的中間類(intermediate class)繼承原來的class, 然后通過runtime 將原來的isa指針指向這個新的中間類.然后中間類會重寫setter方法, 重寫的 setter 方法會負責在調用原 setter 方法之前和之后添加willChangeValueForKey:, didChangeValueForKey:兩個方法,通知所有觀察對象值的更改, 從而觸發(fā)KVO消息.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,656評論 1 32
  • 本文參考鏈接: iOS KVO詳解 Foundation: NSKeyValueObserving(KVO) KV...
    擰發(fā)條鳥xds閱讀 3,139評論 0 6
  • 上半年有段時間做了一個項目,項目中聊天界面用到了音頻播放,涉及到進度條,當時做android時候處理的不太好,由于...
    DaZenD閱讀 3,100評論 0 26
  • 面向對象的三大特性:封裝、繼承、多態(tài) OC內存管理 _strong 引用計數(shù)器來控制對象的生命周期。 _weak...
    運氣不夠技術湊閱讀 1,222評論 0 10
  • 她 是一個平凡的女孩 平凡的成績 平凡的性格 平凡的人緣 不惹人喜歡 也不惹人討厭 他 是一位性格高冷的男孩 成績...
    落欣兒閱讀 563評論 0 2

友情鏈接更多精彩內容