志愿原理
對(duì)于志愿的原理,很多人都比較清楚了大概是這樣子的:
假定我們自己的類是Object和它的對(duì)象obj,當(dāng)obj發(fā)送addObserverForKeypath:keypath消息后,系統(tǒng)會(huì)做3件事情:
創(chuàng)建33動(dòng)態(tài)一個(gè)Object的子類踊谋,名字可自定義假設(shè)叫做Object_KVONotify。
同時(shí),子類動(dòng)態(tài)增加方法setKeypath:初婆,動(dòng)態(tài)添加的方法會(huì)綁定到一個(gè)?語(yǔ)言的函數(shù)。
調(diào)用object_setClass函數(shù)猿棉,將OBJ的類設(shè)置為Object_KVONotify磅叛。
這樣做會(huì)相當(dāng)于建立如下結(jié)構(gòu):
//Object@interfaceObject:NSObject@property(nonatomic,copy)NSString*keypath;@end@implementationObject-(void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void*)context{NSLog(@" --- Object observeValueForKeyPath:%@ ofObject:%@ change:%@ context:%@", keyPath, object, change, context);}-(NSString*) description{return[NSStringstringWithFormat:@"This is %@ instance keypath = %@",self.class,self.keypath];}@end//Object_KVONotify@interfaceObject_KVONotify:Object@endstaticvoiddynamicSetKeyPath(idobj, SEL sel,idv){... ...}@implementationObject_KVONotify-(void) setKeypath:(NSString*)keypath{dynamicSetKeyPath(self,@selector(setKeyPath:), keypath);}@end//objObject *obj = [[Object alloc] init];object_setClass(obj, Object_KVONotify.class);//上面2句其實(shí)相當(dāng)于Object_KVONotify *obj = [[Object_KVONotify alloc] init]
這樣一來(lái),當(dāng)我們調(diào)用
obj.keypath ="hello world";
實(shí)際上調(diào)用的是
dynamicSetKeyPath(self,@selector(setKeypath:), keypath);
此時(shí)dynamicSetKeyPath要做2件事情萨赁。
父調(diào)用類的setKeyPath:方法弊琴。
調(diào)用observeValueForKeyPath方法,觸發(fā)回調(diào)杖爽。
所以dynamicSetKeyPath函數(shù)應(yīng)該是這樣的:
staticvoiddynamicSetKeyPath(idobj, SEL sel,idv){Method superMethod = class_getInstanceMethod(Object.class, sel);((void(*)(id, Method,id))method_invoke)(obj, superMethod, v);NSMutableDictionary* change = [[NSMutableDictionaryalloc] init];change[@"new"] = v;[obj observeValueForKeyPath:@"keypath"ofObject:obj change:change context:nil];}
或者這樣
staticvoiddynamicSetKeyPath(idobj, SEL sel,idv){object_setClass(obj, Object.class);[obj setValue: v forKey:@"keyPath"];object_setClass(obj, Object_Notify.class);[(Object *)obj observeValueForKeyPath:@"keypath"ofObject: objChange:@{@"new":v} context:nil];}
在對(duì)象類中添加測(cè)試代碼
+(void)test{Object*obj = [[Objectalloc] init];obj.keypath = @"inited";NSLog(@"%@", obj);object_setClass(obj, Object_KVONotify.class);obj.keypath = @"hello world";}
調(diào)用測(cè)試代碼敲董,產(chǎn)生輸入如下
This isObjectinstance keypath = initedObjectobserveValueForKeyPath:keypath ofObject:This is Object_KVONotify instance keypath = hello world change:{new="hello world";} context:(null)
上述過(guò)程就是志愿具體流程及測(cè)試代碼紫皇。具體的演示可以代碼在這里找到。
志愿痛點(diǎn)
大家都知道臣缀,系統(tǒng)KVO略有點(diǎn)難用坝橡,主要因?yàn)檫@幾點(diǎn):
addObserver后,不會(huì)在對(duì)象釋放時(shí)精置,自動(dòng)釋放计寇,只能我們?cè)赿ealloc中手動(dòng)removeObserver。在這樣的疏忽下情況忘記removeObserver可能會(huì)導(dǎo)致崩潰脂倦。另外番宁,這個(gè)限制讓我們無(wú)法在一個(gè)類中為其他類對(duì)象增加監(jiān)聽(tīng)。
如果沒(méi)有addObserver的英文不能removeObserver的赖阻,會(huì)崩潰蝶押。
不支持塊。
重新實(shí)現(xiàn)KVO
要重新實(shí)現(xiàn)志愿火欧,根據(jù)志愿原理棋电,我們需要?jiǎng)?chuàng)建一個(gè)增加監(jiān)聽(tīng)的函數(shù),并在函數(shù)內(nèi)做到:
動(dòng)態(tài)創(chuàng)建當(dāng)前類的的子類苇侵,名字帶固定后綴_NotifyKVO赶盔。
同時(shí),子類動(dòng)態(tài)增加方法setXXXX:榆浓,動(dòng)態(tài)添加的方法會(huì)綁定到一個(gè)?語(yǔ)言的函數(shù)于未。
調(diào)用object_setClass函數(shù),將OBJ的類設(shè)置為XXXX_NotifyKVO陡鹃。
首先我們創(chuàng)建一個(gè)NSObject的的分類烘浦,添加創(chuàng)建志愿方法。
@implementationNSObject(BlockKVO)-(void) addObserverForKeyPath:(NSString*)keyPath option:(NSKeyValueObservingOptions)option block:((^)(idobj,NSDictionary *change))block{//self.blockKVO是通過(guò)associate與NSObject對(duì)象綁定的//這樣我們就把所有邏輯轉(zhuǎn)移到了BlockKVO這個(gè)類中[self.blockKVO addObserver:selfforKeyPath:keyPath option:option block:block];}//這里覆蓋了系統(tǒng)的KVO監(jiān)聽(tīng)萍鲸,里面僅僅調(diào)用了添加監(jiān)聽(tīng)時(shí)的block//這樣做闷叉,可以讓系統(tǒng)的KVO監(jiān)聽(tīng)方法也能收到通過(guò)blockKVO添加的事件。-(void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void*)context{BlockKVOItem *item = [self.blockKVO itemWithKeyPath:keyPath];if(item.block) {item.block(self, keyPath, change);}}@end
由于我們有很多參數(shù)和狀態(tài)需要存儲(chǔ)猿推,而OC的類別中保存屬性是很麻煩的片习。
所以我們將創(chuàng)建一個(gè)新的類來(lái)處理所有的綁定邏輯,這就需要將所有參數(shù)及對(duì)象本身傳遞到這個(gè)類對(duì)象中蹬叭。
請(qǐng)仔細(xì)閱讀代碼中的注釋。
@implementationBlockKVO//這里的參數(shù)obj就是需要kvo的對(duì)象状知,這個(gè)函數(shù)很重要秽五,它做到了2件事//1 為obj的class 創(chuàng)建一個(gè)以`_NotifyKVO`為后綴的子類//2. 將obj的class指向XXX_NotifyKVO這個(gè)子類//搞這么多幺蛾子的好處是實(shí)現(xiàn)了AOP,原有的類沒(méi)有任何改變饥悴,obj仍然能訪問(wèn)原類的所有屬性方法坦喘,而且obj可以通過(guò)擴(kuò)展XXX_NotifyKVO方法盲再,增加功能,也能修改原來(lái)類的行為瓣铣,而不會(huì)影響原來(lái)類的結(jié)構(gòu)答朋。-(void) initKVOClassWithObj:(id) obj{if(self.srcClass ==nil){self.srcClass = [objclass];//添加子類NSString*dynamicClassName = [NSStringstringWithFormat:@"%@_NotifyKVO",NSStringFromClass(self.srcClass)];Class dynamicClass =NSClassFromString(dynamicClassName);if(!dynamicClass) {dynamicClass = objc_allocateClassPair(self.srcClass, dynamicClassName.UTF8String,0);objc_registerClassPair(dynamicClass);}self.dynamicClass = dynamicClass;//將obj的類換成新創(chuàng)建的子類,否則不會(huì)調(diào)到dynamicSetKeyPathobject_setClass(obj, dynamicClass);}}//這個(gè)方法是從原類中接收參數(shù)的棠笑,它只做2件事://1. 收到參數(shù)后梦碗,保存到observers字典中。//2. 根據(jù)keyPath蓖救,添加setter方法洪规。-(void) addObserver: (id) obj forKeyPath:(NSString*)keyPath option:(NSKeyValueObservingOptions)option block:(void(^)(idobj,NSString*keyPath,NSDictionary *change))block{[selfinitKVOClassWithObj:obj];if(self.observers ==nil){self.observers = [[NSMutableDictionaryalloc] init];}if(self.observers[keyPath] !=nil){return;}//添加方法SEL methodSel = getSetSelector(keyPath);class_addMethod(self.dynamicClass, methodSel, (IMP)dynamicSetKeyPath,"v@:@");//保存BlockKVOItem *item = [[BlockKVOItem alloc] init];item.obj = obj;item.keyPath = keyPath;item.options = option;item.block = block;self.observers[keyPath] = item;}@end
會(huì)我們注意到class_addMethod方法,最后一個(gè)參數(shù)是一個(gè)奇怪的字符串循捺。這個(gè)字符串是為了表示所添加方法的類型斩例,包括返回值類型和所有參數(shù)類型。
這東西又叫做TypeEncoding从橘,為啥有這個(gè)東西呢念赶?
我們知道,OC是動(dòng)態(tài)語(yǔ)言恰力,它發(fā)送消息是要通過(guò)SEL去查找函數(shù)的叉谜,一旦找到了函數(shù)我們?cè)偃フ{(diào)用它就不是動(dòng)態(tài)調(diào)用了,而是靜態(tài)調(diào)用牺勾。
靜態(tài)調(diào)用參數(shù)的數(shù)量和類型就很重要了正罢。參數(shù)數(shù)量和類型其中任意一個(gè)對(duì)不上都會(huì)導(dǎo)致程序出錯(cuò)。
對(duì)于class_addMethod函數(shù)來(lái)說(shuō)驻民,TypeEncoding可以為添加的方法標(biāo)記出它的返回值類型翻具,參數(shù)個(gè)數(shù)和每個(gè)參數(shù)的類型。
上面的“v @:@”表示的是回还,所添加的函數(shù)指針裆泳,返回值為虛,有3個(gè)參數(shù)柠硕,第一個(gè)參數(shù)是id工禾,第二個(gè)參數(shù)是SEL,第三個(gè)參數(shù)是id蝗柔。很簡(jiǎn)單闻葵。
OC的類property可以很多種類型,不僅僅是id癣丧。所以如果想為不同類型調(diào)用class_addMethod槽畔,就要編寫不同的TypeEncoding。
列一下常用的TypeEncoding:( 更多細(xì)節(jié)查閱點(diǎn)這里TypeEncoding)
“v @:q”=> setKeyPath :(很長(zhǎng))
“v @:c”=> setKeyPath:(char)
“v @:{CGSize = dd}”=> setKeypPath:(CGSize)
通過(guò)上述代碼胁编,我們當(dāng)對(duì)象的調(diào)用再setKeyPath:方法的時(shí)候厢钧,調(diào)用實(shí)際上的的英文dynamicSetKeyPath函數(shù)鳞尔,我們看一下它的實(shí)現(xiàn):
//這個(gè)函數(shù)的定義符合我們定義的typeencoding:"v@:@"staticvoiddynamicSetKeyPath(idobj, SEL sel,idvalue){BlockKVO *blockKVO = [obj blockKVO];//這里肯定不會(huì)為空,習(xí)慣性防御寫法if(blockKVO !=nil) {//根據(jù)SEL獲取keyPathNSString*keypath = getKeyPath(sel);//獲取到注冊(cè)KVO時(shí)傳入的參數(shù)早直,包括block啥的寥假。BlockKVOItem *item = [blockKVO itemWithKeyPath:keypath];//這里先將obj的class恢復(fù),否則會(huì)陷入循環(huán)object_setClass(obj, blockKVO.srcClass);//獲取舊值idoldValue = [obj valueForKey:keypath];//設(shè)置新值[obj setValue:value forKey: keypath];//設(shè)置成子類object_setClass(obj, blockKVO.dynamicClass);//將oldValue和newValue通過(guò)observerValueForKeyPath:ofObject:change:方法通知給調(diào)用方(調(diào)用了block)NSMutableDictionary* change = [[NSMutableDictionaryalloc] init];if(item.options &NSKeyValueObservingOptionNew){change[@"old"] = oldValue;}if(item.options &NSKeyValueObservingOptionOld) {change[@"new"] = value;}[obj observeValueForKeyPath:keypath ofObject:obj change:change context:nil];}}
這樣霞扬,每次我們調(diào)用setKeyPath:的時(shí)候糕韧,前面注冊(cè)的KVO監(jiān)聽(tīng)的塊都會(huì)被調(diào)用。
整個(gè)KVO流程就完成了祥得。
當(dāng)然兔沃,如果實(shí)現(xiàn)完整的志愿,上面的代碼是不夠的你還需要解決如下問(wèn)題:
不同類型的屬性支持
setValue:forKey:處理级及,弱變量可以通過(guò)這個(gè)函數(shù)處理乒疏。
線程安全(如果你只在主線程使用,則不必要)
動(dòng)態(tài)創(chuàng)建類的釋放
其他可能出現(xiàn)的問(wèn)題
作者:hard_man
鏈接:http://www.reibang.com/p/2a2a03681814
來(lái)源:簡(jiǎn)書
簡(jiǎn)書著作權(quán)歸作者所有饮焦,任何形式的轉(zhuǎn)載都請(qǐng)聯(lián)系作者獲得授權(quán)并注明出處怕吴。