簡介
KVO相信iOS開發(fā)者們都聽說過,在面試中也會(huì)被常常問到,但是呢對(duì)于KVO來說更多的事情是由系統(tǒng)來做的,依賴于運(yùn)行時(shí),相對(duì)于Notification,delegate來說是比較簡單的,提供觀察屬性舊值與新值,以下單純的說下自己對(duì)KVO的實(shí)現(xiàn)原理粗略理解,用RunTime重寫一下KVO,有理解不恰當(dāng)?shù)牡胤?請(qǐng)?zhí)岢?謝謝大家
KVO基本使用和使用場景<觀察者模式較完美地將目標(biāo)對(duì)象與觀察者對(duì)象解耦>
a.首先說一下使用場景:
?一個(gè)目標(biāo)對(duì)象管理所有依賴于目標(biāo)對(duì)象的觀察者,在自身狀態(tài)改變的時(shí)候主動(dòng)通知觀察者->能夠監(jiān)聽某個(gè)對(duì)象屬性值的改變<1對(duì)多>
b.基本使用步驟:
1.給目標(biāo)對(duì)象添加觀察者
代碼示例:
[dog addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
2.處理變更通知
當(dāng)KVO 監(jiān)聽到目標(biāo)對(duì)象屬性值改變后,就會(huì)調(diào)用這個(gè)方法,change這個(gè)字典保存了變更信息至扰,具體是哪些信息取決于注冊時(shí)的NSKeyValueObservingOptions
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object chan
ge:(NSDictionary *)change context:(void *)context;
3.移除
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
探討:??
當(dāng)目標(biāo)對(duì)象調(diào)用了addObserver:self forKeyPath:<#(nonnull NSString *)#> options:<#(NSKeyValueObservingOptions)#> context:<#(nullable void *)#> 方法發(fā)生了什么事情,那么我們改變?yōu)槌蓡T變量的時(shí)候,還會(huì)有作用嗎?
解答:runtime到底做了什么,這個(gè)才是重點(diǎn),runtime動(dòng)態(tài)的給目標(biāo)對(duì)象類添加了一個(gè)子類,重寫set方法,改變了isa指針
代碼示例:
KVO原理
KVO--鍵(key)-值(value)觀察(observing)---->設(shè)計(jì)模式中的觀察者模式;
某個(gè)類被第一次觀察的時(shí),系統(tǒng)會(huì)在運(yùn)行期動(dòng)態(tài)的創(chuàng)建一個(gè)該類的子類,然后在子類中重寫被觀察屬性的setter方法,在子類中的重寫的setter方法中實(shí)現(xiàn)真正的通知機(jī)制,子類除了重寫setter方法外還重寫了class方法其欺騙調(diào)用者子類就是原本的父類(內(nèi)部其實(shí)是將父類的isa指針指向子類),父類就成為了派生類的對(duì)象,所以該對(duì)象對(duì)setter方法的調(diào)用,就會(huì)調(diào)用被重寫子類的setter方法,激活通知機(jī)制,除此之外子類還重寫了dealloc方法來釋放資源
怎樣用RunTime來重寫KVO
1.在我們自己定義的仿KVO方法中來寫
示例代碼:
- (void)MBXB_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context{?
? ? ? ? //搞事
}
2.實(shí)現(xiàn)步驟
a.動(dòng)態(tài)的生成一個(gè)類
動(dòng)態(tài)類名
NSString * oldClassName = NSStringFromClass([self class]);
NSString * newName = [@"NSMBXB_" stringByAppendingString:oldClassName];
const char * newClassName = [newName UTF8String];
b.定義一個(gè)類
Class MyClass = objc_allocateClassPair([self class], newClassName, 0);
c.添加setName方法---->也就是重寫方法
class_addMethod(MyClass, @selector(setName:), (IMP)setName, "v@:@");
d.注冊該類
objc_registerClassPair(MyClass);
e.修改被觀察者的isa指針,指向自定義的類
object_setClass(self, MyClass);
f.動(dòng)態(tài)綁定屬性
objc_setAssociatedObject(self, (__bridge const void *)@"123", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
3.實(shí)現(xiàn)setName重寫方法
void setName(id self,SEL _cmd,NSString * newName){
? ? ? ?//搞事情
}
a.保存當(dāng)前類型
id class = [self class];
b.指向父類
object_setClass(self, class_getSuperclass([self class]));
c.調(diào)用父類的setName方法
objc_msgSend(self, @selector(setName:),newName);
d.拿出觀察者
id observer = objc_getAssociatedObject(self, (__bridge const void *)@"123");
e.通知
objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:),self,@"name",@{@"new":newName},nil);
f.改回子類
object_setClass(self, class);
小結(jié):
此時(shí)我們用runtime重寫KVO基本簡單實(shí)現(xiàn)了,那么我們接下來實(shí)驗(yàn)一下
示例代碼:
//添加
Dog * dog = [[Dog alloc]init];
[dog MBXB_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
_dog = dog;
//實(shí)現(xiàn)
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void *)context{
? ? ? NSLog(@"%@===>YES成功了%@",change,_dog.name);
}
//點(diǎn)擊屏幕來測試:
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event {
? ? ?static int i = 0;
? ? ? i++;
? ? ? _dog.name = [NSString stringWithFormat:@"%d",i];
}
點(diǎn)擊屏幕測試結(jié)果,是不是成功了呢??
最后為大家奉獻(xiàn)上本文的代碼連接,大家多多點(diǎn)贊哦demo,希望大家下載star