KVO相信大家已經(jīng)很熟悉了,但是就開發(fā)中使用情況來看,KVO有以下的不方便之處:
所有的observe處理都放在一個方法里處理,如果需要監(jiān)聽多個屬性就需要判斷
添加observe和處理observe的代碼過于分散,可讀性不好
調(diào)用多次相同的removeObserve會導(dǎo)致crash
這里可以利用Runtime來實現(xiàn)一個帶block的KVO,這樣就把添加observe和處理observe的代碼集中起來了,下面看代碼
給NSObject寫一個KVO的分類,實現(xiàn)自己的監(jiān)聽方法
- (void)xhr_addObserverForKey:(NSString*)key block:(void(^) (NSDictionary*valueInfo))valueChangedBlock;- (void)xhr_removeObserverForKey:(NSString*)key;- (void)xhr_removeAllObserver;
在addObserverForKey方法中實現(xiàn)自己的KVO
根據(jù)傳進(jìn)來的key獲取setter方法名
//獲取setter方法名NSString*selectorName = [NSStringstringWithFormat:@"set%@:",key.capitalizedString];SEL setterSEL =NSSelectorFromString(selectorName);
記錄被監(jiān)聽者setter方法IMP指針
//記錄被監(jiān)聽者的setter方法的IMP指針MethodsuperSetter = class_getInstanceMethod([selfclass],setterSEL);_VIMP superSetterIMP = (_VIMP)method_getImplementation(superSetter);
獲取動態(tài)創(chuàng)建的子類的類名
//獲取子類的類名NSString*childClassName =NSStringFromClass([selfclass]);if(![childClassName hasPrefix:childClassPrefix]){childClassName = [NSStringstringWithFormat:@"%@%@",childClassPrefix,NSStringFromClass([selfclass])];}else{superSetter = class_getInstanceMethod(class_getSuperclass([selfclass]),setterSEL);}
獲取被監(jiān)聽屬性的數(shù)據(jù)類型,根據(jù)RunTime的映射來判斷
//獲取被監(jiān)聽屬性的數(shù)據(jù)類型NSString*methodType = [NSStringstringWithUTF8String:method_getTypeEncoding(superSetter)];//NSLog(@"%@",methodType);//v20@0:8i16 //methodType = [methodType componentsSeparatedByString:@"@0:8"].lastObject;NSString*valueType = [methodType substringToIndex:methodType.length-2];
動態(tài)地創(chuàng)建類繼承自被監(jiān)聽者
//判斷需要創(chuàng)建的類是否存在 objc_getClassClassChildClass=? objc_getClass(childClassName.UTF8String);if(!ChildClass) {//創(chuàng)建類ChildClass= objc_allocateClassPair([selfclass],childClassName.UTF8String, 0);//注冊類objc_registerClassPair(ChildClass);? }
改變self的isa指針
//使當(dāng)前類的isa指針指向新創(chuàng)建的類object_setClass(self, ChildClass);
根據(jù)key的不同類型來重寫子類的setter方法
IMP childSetValue =nil;if([valueType isEqualToString:[NSStringstringWithUTF8String:@encode(int)]]) {? ? ? childSetValue = imp_implementationWithBlock(^(id_self,intnewValue) {//調(diào)用被監(jiān)聽對象(父類)的setter方法給被監(jiān)聽者屬性賦值//@"こうさか ほのか"(*superSetterIMP)(_self,setterSEL,newValue);idoldValue = objc_getAssociatedObject(weakself, key.UTF8String);? ? ? ? ? objc_setAssociatedObject(weakself, key.UTF8String, @(newValue), OBJC_ASSOCIATION_RETAIN_NONATOMIC);if(!oldValue) {? ? ? ? ? ? ? oldValue = [NSNullnull];? ? ? ? ? }NSDictionary*valueInfoDictionary = @{@"oldValue":oldValue,@"newValue":@(newValue)};? ? ? ? ? !valueChangedBlock?:valueChangedBlock(valueInfoDictionary);? ? ? });? }
添加setter方法
//給子類添加setter方法class_replaceMethod(ChildClass, setterSEL, childSetValue, [NSStringstringWithFormat:@"v@:%@",valueType].UTF8String);
移除監(jiān)聽
(void)xhr_removeAllObserver,這個簡單,就是移除所有子類的關(guān)聯(lián),改變isa指針指向父類
if(![NSStringFromClass([selfclass])hasPrefix:childClassPrefix])return;objc_removeAssociatedObjects(self);object_setClass(self, class_getSuperclass([selfclass]));
(void)xhr_removeObserverForKey:(NSString *)key,這個就得判斷調(diào)用的類是不是子類,如果不是就直接返回,防止重復(fù)移除導(dǎo)致的crash,如果監(jiān)聽者數(shù)組中沒有需要監(jiān)聽的key,就直接移除所有,不然self的isa指針還是指向子類
if(![NSStringFromClass([selfclass]) hasPrefix:childClassPrefix])return;? ? XHRKVOAssistClass *KVOAssistInstance = objc_getAssociatedObject(self, XHRKVOAssistInstance);/**
*? 防止重復(fù)移除監(jiān)聽
*/if(!KVOAssistInstance.observerKeyArray.count) {? ? ? ? [selfxhr_removeAllObserver];return;? ? }//獲取被監(jiān)聽者的類Class superClass = class_getSuperclass([selfclass]);//獲取setter方法名NSString*selectorName = [NSStringstringWithFormat:@"set%@:",key.capitalizedString];? ? IMP superSetterIMP = class_getMethodImplementation(superClass,NSSelectorFromString(selectorName));//獲取被監(jiān)聽key屬性的數(shù)據(jù)類型NSString*methodType = [NSStringstringWithUTF8String:method_getTypeEncoding(class_getInstanceMethod(superClass,NSSelectorFromString(selectorName)))];? ? methodType = [methodType componentsSeparatedByString:@"@0:8"].lastObject;NSString*valueType = [methodType substringToIndex:methodType.length-2];? ? class_replaceMethod([selfclass],NSSelectorFromString(selectorName), superSetterIMP, [NSStringstringWithFormat:@"v@:%@",valueType].UTF8String);? ? [KVOAssistInstance.observerKeyArrayremoveObject:@{valueType:key}];//判斷移除后數(shù)組是否為空,為空即移除所監(jiān)聽if(!KVOAssistInstance.observerKeyArray.count) {? ? ? ? [selfxhr_removeAllObserver];return;? ? }
注意事項:
1.addObserver的blcok里面如果需要用到self必須使用weakself
2.不能使用在同一個類的不同對象的同一個屬性的監(jiān)聽