一、個人理解
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消息.