看看了cocoachina的文章突然想自己來(lái)研究一下,研究后發(fā)現(xiàn)cocoachina中說(shuō)也不完全準(zhǔn)確。http://www.cocoachina.com/ios/20151215/14695.html
修正后如下: 在IOS中許多機(jī)制來(lái)供我們來(lái)進(jìn)行回調(diào),說(shuō)起回調(diào)那么久多說(shuō)兩句: 包括:協(xié)議(delegate),KVO,block,addtager等 單說(shuō)KVO這個(gè)有趣的機(jī)制能在很多情況下,來(lái)解決我們的實(shí)際問(wèn)題,特別是隨機(jī)觸發(fā)事件。
使用對(duì)于我們來(lái)說(shuō)已經(jīng)不存在問(wèn)題,我們來(lái)關(guān)心更深入的底層?xùn)|西:
編譯器如何完成監(jiān)聽(tīng)這個(gè)任務(wù)尼?
蘋(píng)果文檔給出了描述:Automatic key-value observing is implemented using a technique called isa-swizzling... When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class .. 簡(jiǎn)單理解是這樣的:我們對(duì)某個(gè)對(duì)象完成監(jiān)聽(tīng)的注冊(cè)后,isa這個(gè)指針指向一個(gè)新生成的中間類(lèi),編譯器會(huì)監(jiān)聽(tīng)這個(gè)中間類(lèi),從某個(gè)意義上來(lái)說(shuō),這是一個(gè)騙局。 類(lèi)的結(jié)構(gòu):
typedef struct objc_class *Class;
typedef struct objc_object
{
Class isa;
} *id;
這里說(shuō)明一下isa這個(gè)指針, isa是一個(gè)指向Class類(lèi)指針(專(zhuān)業(yè)術(shù)語(yǔ)是指向元類(lèi),pointer to the metaclass),用來(lái)指向類(lèi)的類(lèi)型,我們可以通過(guò)object_getClass方法來(lái)獲取這個(gè)值; 正常來(lái)說(shuō),class方法內(nèi)部的實(shí)現(xiàn)就是獲取這個(gè)isa指針代表的元類(lèi)(metaclass),但在kvo機(jī)制中蘋(píng)果注冊(cè)監(jiān)聽(tīng)對(duì)象后 通過(guò)objc_allocateClassPair動(dòng)態(tài)重新創(chuàng)建了一個(gè)新類(lèi)和元類(lèi),此時(shí)object_getClass()獲取的事就是不適原來(lái)isa而是是新建的元類(lèi) 參見(jiàn)蘋(píng)果文檔:Creates a new class and metaclass.You can get a pointer to the new metaclass by calling object_getClass(newClass))。
另外備注下[self class]和object_getClass(self)可是不一樣的,具體什么不一樣參考:http://stackoverflow.com/questions/15906130/object-getclassobj-and-obj-class-give-different-results(一個(gè)返回的是類(lèi),一個(gè)是實(shí)例,能一樣嗎?)
在oc中,規(guī)定只要擁有isa指針的變量,通通都屬于對(duì)象。 最上邊的代碼 objc_object就是我們常用的基類(lèi)NSObject的結(jié)構(gòu)體。因此oc不允許非NSObject子類(lèi)的對(duì)象(block例外),原因 你當(dāng)然懂,如果出現(xiàn)了非這樣類(lèi),蘋(píng)果的好多機(jī)制就成擺設(shè)了,不是變成C++了嗎?
蘋(píng)果沒(méi)有說(shuō)具體的細(xì)節(jié),那么我們通過(guò)實(shí)驗(yàn)來(lái)一點(diǎn)點(diǎn)扣出其中的KVO運(yùn)行奧秘。 具體做法原理如下: 既然說(shuō)監(jiān)聽(tīng)后改變isa的指針,那么我們就來(lái)輸出監(jiān)聽(tīng)前后isa的對(duì)象對(duì)比,來(lái)進(jìn)一步探秘kvo。
例如: 監(jiān)聽(tīng)前:
NSLog(@"address: %p", self);
NSLog(@"class method: %@", self.class);
NSLog(@"description method: %@", self.description);
NSLog(@"use runtime to get class: %@", object_getClass(self));
[self addObserver: self forKeyPath: @"view" options: NSKeyValueObservingOptionNew context: nil];
監(jiān)聽(tīng)后:
NSLog(@"address: %p", self);
NSLog(@"class method: %@", self.class);
NSLog(@"description method: %@", self.description);
NSLog(@"use runtime to get class %@", object_getClass(self));
address: 0x7f927a81d200
class method: UITableView
description method:
use runtime to get class: UIViewController
===================================================
address: 0x7f927a81d200
class method: UITableView
description method:
use runtime to get class NSKVONotifying_UIViewController
除了通過(guò)object_getClass獲取的類(lèi)型之外,其他的輸出沒(méi)有任何變化;另外,我們還看到了新類(lèi)相對(duì)于self類(lèi)添加了一個(gè)NSKVONotifying_前綴,添加這個(gè)前綴是為了避免多次創(chuàng)建監(jiān)聽(tīng)子類(lèi),節(jié)省資源。
那么怎么實(shí)現(xiàn)類(lèi)似的效果尼? 既然知道了蘋(píng)果的實(shí)現(xiàn)過(guò)程,那么我們可以自己動(dòng)手通過(guò)運(yùn)行時(shí)機(jī)制來(lái)實(shí)現(xiàn)KVO。runtime允許我們?cè)诔绦蜻\(yùn)行時(shí)動(dòng)態(tài)的創(chuàng)建新類(lèi)、拓展方法、method-swizzling、綁定屬性等等這些有趣的事情。
在創(chuàng)建新類(lèi)之前,我們應(yīng)該學(xué)習(xí)蘋(píng)果的做法,判斷當(dāng)前是否存在這個(gè)類(lèi),如果不存在我們?cè)龠M(jìn)行創(chuàng)建,并且重新實(shí)現(xiàn)這個(gè)新類(lèi)的class方法來(lái)掩蓋具體實(shí)現(xiàn)。基于這些原則,我們用下面的方法來(lái)獲取新類(lèi)
(Class)createKVOClassWithOriginalClassName: (NSString *)className { NSString * kvoClassName = [kLXDkvoClassPrefix stringByAppendingString: className]; Class observedClass = NSClassFromString(kvoClassName); if (observedClass) { return observedClass; } //創(chuàng)建新類(lèi),并且添加LXDObserver_為類(lèi)名新前綴 Class originalClass = object_getClass(self); Class kvoClass = objc_allocateClassPair(originalClass, kvoClassName.UTF8String, 0); //獲取監(jiān)聽(tīng)對(duì)象的class方法實(shí)現(xiàn)代碼,然后替換新建類(lèi)的class實(shí)現(xiàn) Method classMethod = class_getInstanceMethod(originalClass, @selector(class)); const char * types = method_getTypeEncoding(classMethod); class_addMethod(kvoClass, @selector(class), (IMP)kvo_Class, types); objc_registerClassPair(kvoClass); return kvoClass; } 另外,在判斷是否需要中間類(lèi)來(lái)完成監(jiān)聽(tīng)的注冊(cè)前,我們還要判斷監(jiān)聽(tīng)的屬性的有效性。通過(guò)獲取變量的setter方法名(將首字母大寫(xiě)并加上前綴set),以此來(lái)獲取setter實(shí)現(xiàn),如果不存在實(shí)現(xiàn)代碼,則拋出異常使程序崩潰。
SEL setterSelector = NSSelectorFromString(setterForGetter(key)); Method setterMethod = class_getInstanceMethod([self class], setterSelector); if (!setterMethod) { @throw [NSException exceptionWithName: NSInvalidArgumentException reason: [NSString stringWithFormat: @"unrecognized selector sent to instance %p", self] userInfo: nil]; return; } Class observedClass = object_getClass(self); NSString * className = NSStringFromClass(observedClass); //如果被監(jiān)聽(tīng)者沒(méi)有LXDObserver_,那么判斷是否需要?jiǎng)?chuàng)建新類(lèi) if (![className hasPrefix: kLXDkvoClassPrefix]) { observedClass = [self createKVOClassWithOriginalClassName: className]; object_setClass(self, observedClass); } //重新實(shí)現(xiàn)setter方法,使其完成 const char * types = method_getTypeEncoding(setterMethod); class_addMethod(observedClass, setterSelector, (IMP)KVO_setter, types); 在重新實(shí)現(xiàn)setter方法的時(shí)候,有兩個(gè)重要的方法:willChangeValueForKey和didChangeValueForKey,分別在賦值前后進(jìn)行調(diào)用。此外,還要遍歷所有的回調(diào)監(jiān)聽(tīng)者,然后通知這些監(jiān)聽(tīng)者:
static void KVO_setter(id self, SEL _cmd, id newValue) { NSString * setterName = NSStringFromSelector(_cmd); NSString * getterName = getterForSetter(setterName); if (!getterName) { @throw [NSException exceptionWithName: NSInvalidArgumentException reason: [NSString stringWithFormat: @"unrecognized selector sent to instance %p", self] userInfo: nil]; return; } id oldValue = [self valueForKey: getterName]; struct objc_super superClass = { .receiver = self, .super_class = class_getSuperclass(object_getClass(self)) }; [self willChangeValueForKey: getterName]; void (*objc_msgSendSuperKVO)(void *, SEL, id) = (void *)objc_msgSendSuper; objc_msgSendSuperKVO(&superClass, _cmd, newValue); [self didChangeValueForKey: getterName]; //獲取所有監(jiān)聽(tīng)回調(diào)對(duì)象進(jìn)行回調(diào) NSMutableArray * observers = objc_getAssociatedObject(self, (__bridge const void *)kLXDkvoAssiociateObserver); for (LXD_ObserverInfo * info in observers) { if ([info.key isEqualToString: getterName]) {
dispatch_async(dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ info.handler(self, getterName, oldValue, newValue); }); } } } 所有的監(jiān)聽(tīng)者通過(guò)動(dòng)態(tài)綁定的方式將其存儲(chǔ)起來(lái),但這樣也會(huì)產(chǎn)生強(qiáng)引用,所以我們還需要提供釋放監(jiān)聽(tīng)的方法:
(void)LXD_removeObserver:(NSObject *)object forKey:(NSString *)key { NSMutableArray * observers = objc_getAssociatedObject(self, (__bridge void *)kLXDkvoAssiociateObserver); LXD_ObserverInfo * observerRemoved = nil; for (LXD_ObserverInfo * observerInfo in observers) { if (observerInfo.observer == object && [observerInfo.key isEqualToString: key]) { observerRemoved = observerInfo; break; } } [observers removeObject: observerRemoved]; } 雖然上面已經(jīng)粗略的實(shí)現(xiàn)了kvo,并且我們還能自定義回調(diào)方式。使用target-action或者block的方式進(jìn)行回調(diào)會(huì)比單一的系統(tǒng)回調(diào)要全面的多。但kvo真正的實(shí)現(xiàn)并沒(méi)有這么簡(jiǎn)單,上述代碼目前只能實(shí)現(xiàn)對(duì)象類(lèi)型的監(jiān)聽(tīng),基本類(lèi)型無(wú)法監(jiān)聽(tīng),況且還有keyPath可以監(jiān)聽(tīng)對(duì)象的成員對(duì)象的屬性這種更強(qiáng)大的功能。
尾言
對(duì)于基本類(lèi)型的監(jiān)聽(tīng),蘋(píng)果可能是通過(guò)void *類(lèi)型對(duì)對(duì)象進(jìn)行橋接轉(zhuǎn)換,然后直接獲取內(nèi)存,通過(guò)type encoding我們可以獲取所有setter對(duì)象的具體類(lèi)型,雖然實(shí)現(xiàn)比較麻煩,但是確實(shí)能夠達(dá)成類(lèi)似的效果。
鉆研kvo的實(shí)現(xiàn)可以讓我們對(duì)蘋(píng)果的代碼實(shí)現(xiàn)有更深層次的了解,這些知識(shí)涉及到了更深層次的技術(shù),探究它們對(duì)我們的開(kāi)發(fā)視野有著很重要的作用。同時(shí),對(duì)比其他的回調(diào)方式,KVO的實(shí)現(xiàn)在創(chuàng)建子類(lèi)、重寫(xiě)方法等等方面的內(nèi)存消耗是很巨大的,因此博主更加推薦使用delegate、block等回調(diào)方式,甚至直接使用method-swizzling來(lái)替換這種重寫(xiě)setter方式也是可行的。