- 老司機們,快上車
- 回到標題的內(nèi)容: KVO淺析
- 先來看一下系統(tǒng)原生的效果:
#import "ViewController.h"
#import "LJModel.h"
@interface ViewController ()
@property (strong, nonatomic) LJModel *model;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
LJModel *model = [[LJModel alloc]init];
// 賦值
_model = model;
// 添加觀察者 ,監(jiān)聽model的 name 屬性的值的 改變
[model addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:@"ViewController"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
NSLog(@"%@",_model.name);
// 點擊三次的打印結(jié)果為:
/*
2016-07-12 12:56:31.453 answerDemo[2584:753037] 小強 1 號
2016-07-12 12:56:40.509 answerDemo[2584:753037] 小強 2 號
2016-07-12 12:56:42.195 answerDemo[2584:753037] 小強 3 號
*/
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 用于判斷每次點擊,都給name屬性賦值
static int i = 0;
i ++;
// 通過set方法 給model的name賦值
_model.name = [NSString stringWithFormat:@"小強 %d 號",i];
}
@end
我們新建一個繼承自
NSObject
的LJModel
類給這個類添加一個
name
屬性@property (copy, nonatomic) NSString *name;
在
viewDidload
的方法中,我們創(chuàng)建一個LJModel對象 model
,并給全局的model
屬性賦值
給
model
添加一個觀察者(當前的ViewController控制器),用來監(jiān)聽name
屬性的值的變化最后重寫
(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
這個方法,并在這個方法里面輸出一下model
的name
屬性的值點擊三次屏幕,可以看到,每次
name
屬性的賦值都可以成功的被監(jiān)聽
這個是如何實現(xiàn)的呢?
- 我們知道,要想監(jiān)聽某個值的改變,我們可以在
set
方法中去監(jiān)聽 - 比如網(wǎng)絡請求,我們就可以在set方法中拿到請求回來的數(shù)據(jù)來刷新界面
那我們現(xiàn)在來做這樣一件事情:
#import <Foundation/Foundation.h>
@interface LJModel : NSObject
{
@public
NSString *_name;
}
@end
我們不用
@property修飾name
,然后也不提供set
和get
方法然后在點擊屏幕的時候,直接訪問
model
的成員變量_name
進行賦值:
// 直接訪問成員變量進行賦值
_model -> _name = [NSString stringWithFormat:@"小強 %d 號",i];
- 我們再在
(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
打印_name
:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
NSLog(@"%@",_model -> _name);
}
此時再點擊屏幕,
debug
沒任何輸出,這個時候觀察者ViewController
就監(jiān)聽不到_name
的值得變化了然后我們把
model的_name
改成用@property
修飾的name
這時候系統(tǒng)就會自動生成
_name
屬性,并且生成get
和set
方法,我們就可以通過點語法
賦值和取值了-
現(xiàn)在的問題就是,我們沒有重寫
model的setName
方法,ViewController
是怎么通過set
方法監(jiān)聽的呢- 憋說話,看圖:
我們在給
model
添加觀察者
的地方打個斷點,上面兩張圖是單步調(diào)試之后的結(jié)果我們可以看到?jīng)]給
model
添加觀察者之前,isa
(暫時先簡單的理解成指針,這里不做探討)指針指向的是LJModel
這個類添加
觀察者
之后,isa
指針指向了NSKVONotifying_LJModel
,這TM是個什么鬼我們在創(chuàng)建類的時候,系統(tǒng)都會自動自動生成一個
isa
指針,指向這個類,當這個類或這個類的實例化對象
調(diào)用這個方法的時候,會先根據(jù)isa
找到對應的類,來判斷有沒有這個方法,有則調(diào)用,沒有就會報錯顯然在這里,系統(tǒng)修改了我們的
isa
指針,那我們在調(diào)用model
的setName
方法就會去NSKVONotifying_LJModel
里面找,然后這里又沒有報錯,這說明NSKVONotifying_LJModel
這個類里面有setName
方法,并且對model
的name
屬性成功的監(jiān)聽想要監(jiān)聽
model
的name
的值得改變,我們可以在model
里面重寫setName
方法,但是這里系統(tǒng)已經(jīng)將isa
指針指向了NSKVONotifying_LJModel
這個類,并不是LJModel
這個類這時候,我們可以大概猜測一下
NSKVONotifying_LJModel
這個類是LJModel
的分類或者子類,因為LJModel
的分類或者子類可以重寫setName
方法, 然后系統(tǒng)重寫了setName
方法,成功的對name
的值得改變進行監(jiān)聽但是又因為分類重寫方法,系統(tǒng)會優(yōu)先調(diào)用分類的方法,這樣就會覆蓋原有類的方法,如果原有類重寫了
setName
,那被分類
覆蓋掉之后,導致重寫失敗,KVO
只是監(jiān)聽值得改變,并不會覆蓋掉原有類的方法,所以,這個類應該是子類
,然后在子類
重寫父類
的方法,并且調(diào)用[super setName]
方法,從而在不影響父類的值得情況下,對父類進行監(jiān)聽
到這里之后,我們嘗試的寫了一下,簡單的仿照系統(tǒng),自己搞個可以監(jiān)聽name
的值得變化的方法:
- 開始我們先來看一下系統(tǒng)的.
com + 左鍵
到系統(tǒng)的方法里面去,進去之前,系統(tǒng)會提示有三個方法,先隨便選一個,然后找到NSObject(NSKeyValueObserverRegistration)
:
@interface NSObject(NSKeyValueObserverRegistration)
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context NS_AVAILABLE(10_7, 5_0);
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end
- 可以看到,這里面有好幾個分類,都有這個方法,這里因為
LJModel
是直接繼承NSObject
的,所以,model
在調(diào)用這個方法的時候,會選擇NSObject
的分類的方法
既然這樣,我們就
com + C
和com + V
然后我們也搞一個這樣的方法,為了和系統(tǒng)的方法區(qū)分開來,我們給方法改個名字,加上自己的前綴:
#import "NSObject+Extension.h"
#import <objc/runtime.h>
#import "LJSubModel.h"
@implementation NSObject (Extension)
- (void)lj_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
// 將當前對象的指針指向觀察者
object_setClass(self, [LJSubModel class]);
// 給當前對象添加一個觀察者(ViewController)的屬性
objc_setAssociatedObject(self, "observer", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
- 我們仿照系統(tǒng),創(chuàng)建
NSObject
的分類和一個LJModel
的子類 - 在分類的這個方法里面,將當前類的
isa
指向子類,這樣,model
調(diào)用setName
就會到subModel
里面掉setName
方法 - 然后將
觀察者
動態(tài)添加到subModel
的屬性
然后看一下subModel
的setName
方法的實現(xiàn):
#import "LJSubModel.h"
#import <objc/runtime.h>
@implementation LJSubModel
- (void)setName:(NSString *)name {
[super setName:name];
// 獲取觀察者
id observer = objc_getAssociatedObject(self, @"observer");
// 通知觀察者調(diào)用方法
[observer observeValueForKeyPath:@"name" ofObject:observer change:nil context:nil];
}
@end
最后調(diào)用自己的方法實現(xiàn)監(jiān)聽:
// 添加觀察者 ,監(jiān)聽model的 name 屬性的值的 改變
[model lj_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
首先我們調(diào)用
[super setName:name];
,這樣就可以給model.name
賦值了然后拿到觀察者,并手動調(diào)用
[observer observeValueForKeyPath:@"name" ofObject:self change:nil context:nil]; }
這里就相當于外面
viewController
調(diào)用這個方法,并將ViewController
作為作為觀察者這樣,我們在
ViewController
里面用model
調(diào)用我自己的方法, 然后實現(xiàn)obserVerForKeyPath
的時候,就可以監(jiān)聽到name
的值得改變了這時候,我們再來看一下:
這樣,用我們自己的方法,也可以實現(xiàn)簡單的KVO了
KVO很強大,有很多值的我們探究的地方,這里只是簡單的介紹了一下KVO的實現(xiàn)機制,希望能幫到大家
demo傳送門:https://github.com/lauding/answerDemo
另外附上一篇個人覺得比較好的介紹KVO的實現(xiàn)的博客:http://www.cocoachina.com/ios/20150313/11321.html
對我的Demo有什么疑問或者建議,歡迎留言