聽起來(lái)KVO很強(qiáng)大院水,其實(shí)自己實(shí)現(xiàn)也是可以的泵琳。但是想做到像系統(tǒng)實(shí)現(xiàn)的那樣還是比較費(fèi)事的佃扼,這次我們就搞一個(gè)簡(jiǎn)單一點(diǎn)的正卧,但是功能和系統(tǒng)提供的幾乎一致迅皇。
首先說(shuō)一下原理榴徐,其實(shí)就是在調(diào)用方法
addObserver:(NSObject*)observer forKeyPath:(NSString*)keyPath options:(NSKeyValueObservingOptions)options context:(nullablevoid*)context
的時(shí)候技掏,系統(tǒng)在這個(gè)方法內(nèi)部通過(guò)runtime創(chuàng)建了需要監(jiān)聽對(duì)象的類的子類署恍,然后將isa指針指向了這個(gè)子類隔显,同時(shí)又添加了需要監(jiān)聽屬性的set方法却妨、dealloc、_isKOVA括眠、重寫了class方法彪标。
因?yàn)楸槐O(jiān)聽對(duì)象的isa指針指向了動(dòng)態(tài)創(chuàng)建的子類,所以當(dāng)修改屬性的時(shí)候就調(diào)用了子類的set方法掷豺,然后在set方法內(nèi)部做一些處理捞烟,比如獲取舊值,設(shè)置新值当船,調(diào)用?willChangeValueForKey题画、didChangeValueForKey,向observeValueForKeyPath:ofObject:change:context:發(fā)送消息等等一些操作德频。
重寫class方法主要是為了讓開發(fā)者調(diào)用[xx class]的時(shí)候返回的還是原來(lái)的類苍息,但是如果使用object_getClass(xx)這個(gè)時(shí)候獲取到的就是真正的類了。
當(dāng)然想看一下動(dòng)態(tài)創(chuàng)建子類的所有方法可以使用runtime的class_copyMethodList方法獲取壹置。
下面開始自己實(shí)現(xiàn)KVO竞思。
先來(lái)觀察兩張圖片
通過(guò)這兩張圖就可以看出來(lái)蘋果也是在添加觀察者的時(shí)候自動(dòng)創(chuàng)建了一個(gè)被觀察對(duì)象的子類,知道了原理就好辦了
其實(shí)核心就是使用runtime,好了廢話不多說(shuō)直接開擼.
1,首先創(chuàng)建一個(gè)NSObject分類, ?在.h里邊搞一個(gè)供外部添加觀察者的方法
@interfaceNSObject (YHKVO)
- (void)YH_addObserver:(NSObject*)observer forKeyPath:(NSString*)keyPath options:(NSKeyValueObservingOptions)options context:(nullablevoid*)context;
@end
2, ? ?在.m實(shí)現(xiàn)該方法
首先通過(guò)原類創(chuàng)建一個(gè)原類的子類,創(chuàng)建好了以后不要忘記注冊(cè),系統(tǒng)創(chuàng)建的子類前綴是NSKVONotifying_,我們也搞一個(gè)和系統(tǒng)差不多的YHKVONotifying_
????NSString *oldName = NSStringFromClass(self.class);
? ? NSString *newName = [@"YHKVONotifying_" stringByAppendingString:oldName];
? ? ClassMyClass =objc_getClass(newName.UTF8String);
? ? if(!MyClass) {
? ? ? ? MyClass =objc_allocateClassPair(self.class, newName.UTF8String, 0);
? ? ? ? //注冊(cè)類
? ? ? ? objc_registerClassPair(MyClass);
? ? }
3,需要在子類中添加一個(gè)方法,獲取添加方法需要的所有參數(shù)
????//set方法首字母大寫
? ? NSString *keyPathChange = [[[keyPath substringToIndex:1] uppercaseString] stringByAppendingString:[keyPath substringFromIndex:1]];
? ? NSString*setNameStr = [NSStringstringWithFormat:@"set%@", keyPathChange];
? ? SEL setSEL = NSSelectorFromString([setNameStr stringByAppendingString:@":"]);
? ? //添加子類的set方法
? ? Method getMethod = class_getInstanceMethod([self class], @selector(keyPath));
? ? constchar*types =method_getTypeEncoding(getMethod);
? ? /**
?? ? *class 給哪個(gè)類添加方法
?? ? *sel 方法編號(hào)
?? ? *imp 方法實(shí)現(xiàn)(函數(shù)指針)
?? ? *type返回值類型以及參數(shù)
?? ? */
? ? class_addMethod(MyClass, setSEL, (IMP)setMethod, types);
4,修改當(dāng)前指針指向子類,修改了以后在添加觀察者的時(shí)候打斷點(diǎn)就可以看到指針的指向改變了
????//修改isa指針????
? ? object_setClass(self, MyClass);
5,保存接下來(lái)需要使用的方法
????//保存方法
? ? objc_setAssociatedObject(self, YH_observer, observer, OBJC_ASSOCIATION_ASSIGN);
? ? objc_setAssociatedObject(self, YH_settter, setNameStr, OBJC_ASSOCIATION_COPY_NONATOMIC);
? ? objc_setAssociatedObject(self, YH_getter, keyPath, OBJC_ASSOCIATION_COPY_NONATOMIC);
6,實(shí)現(xiàn)void setMethod方法
所有的objc_msgSend()最少需要兩個(gè)參數(shù),第一個(gè)參數(shù)self, 第二個(gè)_cmd
????void setMethod(id self, SEL _cmd, id newVlaue) {
? ? NSString *setName = objc_getAssociatedObject(self, YH_settter);
? ? NSString *getName = objc_getAssociatedObject(self, YH_getter);
? ? Classclass = [selfclass];//獲取當(dāng)前類型
? ? object_setClass(self, class_getSuperclass(class));//修改isa指針到父類
? ? //調(diào)用原類get, 獲取舊值
? ? id oldValue = objc_msgSend(self, NSSelectorFromString(getName));
? ? //調(diào)用原類,設(shè)置新值
? ? objc_msgSend(self, NSSelectorFromString([setName stringByAppendingString:@":"]), newVlaue);
? ? id observer = objc_getAssociatedObject(self, YH_observer);
? ? NSMutableDictionary * change = [NSMutableDictionary dictionary];
? ? if(newVlaue) {
? ? ? ? change[NSKeyValueChangeNewKey] = newVlaue;
? ? }
? ? if(oldValue) {
? ? ? ? change[NSKeyValueChangeOldKey] = oldValue;
? ? }
? ? if(observer) {
? ? ? ? ????????objc_msgSend(observer,@selector(observeValueForKeyPath:ofObject:change:context:), getName,self, change,nil);
? ? }
? ? //修改回子類,供下次使用
? ? object_setClass(self, class);
}
測(cè)試:不出意外一切正常
- (void)viewDidLoad {
? ? [super viewDidLoad];
? ? _p= [[Persionalloc]init];
? ? [_p YH_addObserver:self forKeyPath:NSStringFromSelector(@selector(age)) options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil];
}
- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void*)context {
? ? NSLog(@"%@",_p.age);
? ? NSLog(@"%@", change);
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event {
? ? staticinta =0;
? ? a++;
? ? _p.age = [NSString stringWithFormat:@"%d", a];
}
如有不足請(qǐng)指正