原理
注冊一個中間類KVO_xxx繼承自要觀察的類胆筒,通過isa-swizzling將xxx類的isa指像新的的類KVO_XXX. 并且動態(tài)給新類重寫setter方法眶根,以達到屬性攔截目的,從而觸發(fā)回調(diào)通知觀察者屬性是否改變道批。
isa指針,顧名思義, 指向的對象的類維護調(diào)度表欣硼。這個調(diào)度表基本上包含指向類實現(xiàn)?方法,以 及其他數(shù)據(jù)。 將這個isa指向子類KVO_XXX從而在用戶無感知的狀態(tài)下實現(xiàn)setter方法重寫拙泽。
實現(xiàn)步驟
--先看步驟,最后有demo裸燎。
1.自定義一個category 當調(diào)用xlAddObserver的時候開始生成中間類顾瞻,當有監(jiān)聽的屬性改變的時候觸發(fā)回調(diào)
@interface NSObject (XLKVO)
//監(jiān)聽
-(void)xlAddObserver:(id)obj forKeypath:(NSString *)keyPath;
//回調(diào)
-(void)xlObserveValueForKeyPath:(NSString *)keyPath ofObject:(id)object;
@end
2.根據(jù)要監(jiān)聽的類生成一個KVO_XXX的中間類
-(void)xlAddObserver:(id)obj forKeypath:(NSString *)keyPath {
//1.注冊一個中間類XLKVO_開頭的子類 繼承自當前類
Class superClass = object_getClass(self);
NSString * newClassName = [NSString stringWithFormat:@"XLKVO_%@",NSStringFromClass(superClass)];
//判斷下是否注冊過
Class newClass;
if (!NSClassFromString(newClassName)) {
//創(chuàng)建新類
newClass = [self registerNewClass:superClass newClassName:newClassName];
//改變 當前類的isa只向newClass 此時所有的函數(shù)執(zhí)行都要經(jīng)過子類newClass了
object_setClass(self, newClass);
}
}
//注冊一個新的類
-(Class)registerNewClass:(Class)superClass newClassName:(NSString *)newClassName{
//生成一個類繼承自superClass
Class newClass = objc_allocateClassPair(superClass, newClassName.UTF8String, 0);
//注冊
objc_registerClassPair(newClass);
return newClass;
}
2.關(guān)聯(lián)對象,關(guān)聯(lián)observer和keypath以便重寫setter的時候調(diào)用觸發(fā)
//關(guān)聯(lián)對象 obj顺少,以便在后面通知執(zhí)行回調(diào)方法
objc_setAssociatedObject(self, @"xlobserver", obj, OBJC_ASSOCIATION_RETAIN);
objc_setAssociatedObject(self, @"keyPath", keyPath, OBJC_ASSOCIATION_RETAIN);
3. 給KVO_XXX類動態(tài)追加上寫要監(jiān)聽的屬性的setter方法 。 這里的self已經(jīng)經(jīng)過了object_setClass(self, newClass);函數(shù)指向了新的KVO類了
//動態(tài)給newClass添加一個setter方法 實現(xiàn)方法重寫完成監(jiān)聽回調(diào)
//注:這里的所有的self都是指的新的類
NSString * setterName = [self setterNameFromKeyPath:keyPath];
Method setterMethod = class_getInstanceMethod(superClass, NSSelectorFromString(setterName));
const char * types = method_getTypeEncoding(setterMethod);
class_addMethod(newClass, NSSelectorFromString(setterName), (IMP)xlKVOStter, types);
根據(jù)屬性名字拼接setter方法
//拼接setter方法名
-(NSString *)setterNameFromKeyPath:(NSString *)name {
NSString * firstName = [[name substringToIndex:1]uppercaseString];
NSString * otherStr = [name substringFromIndex:1];
return [NSString stringWithFormat:@"set%@%@:",firstName,otherStr];
}
指定setter函數(shù)的imp實現(xiàn),在調(diào)用類的setter函數(shù)的時候脆炎,其實是在完成一個消息發(fā)送梅猿,objc_msgSend,最終指向行真正的函數(shù)實現(xiàn),在這個地方我們就完成了屬性的攔截秒裕,從而達到監(jiān)聽的目的袱蚓。
//setter方法的IMP實現(xiàn) 前兩個參數(shù)固定的寫法 _cmd在Objective-C的方法中表示當前方法的selector
static void xlKVOStter(id self,SEL _cmd,id value) {
//1.執(zhí)行父類的方法
//消息轉(zhuǎn)發(fā) 直接去執(zhí)行父類的
//結(jié)構(gòu)體 點的作用是結(jié)構(gòu)體成員指定初始化
struct objc_super superStruct = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};
//取結(jié)構(gòu)體地址 執(zhí)行消息發(fā)送
objc_msgSendSuper(&superStruct,_cmd,value);
//2.調(diào)用observer通知屬性即將改變
id obj = objc_getAssociatedObject(self, @"xlobserver");
NSString * keyPath = objc_getAssociatedObject(self, @"keyPath");
[obj xlObserveValueForKeyPath:keyPath ofObject:self];
}
細節(jié)優(yōu)化
在此我們就已經(jīng)完成了KVO的基本功能了,不過還有一些小細節(jié)需要優(yōu)化一下几蜻。
1.在給KVO類添加setter的時候先判斷一下喇潘,以免重復(fù)添加.
我們可以通過獲取該類的method列表來判斷是否已經(jīng)追加了
//判斷是否存在該方法
-(BOOL)hasSelector:(SEL)selector {
Class myclass = object_getClass(self);
unsigned int methodCount = 0;
//獲取該類的方法列表,列表是一個結(jié)構(gòu)體指針的數(shù)組
Method * methodList = class_copyMethodList(myclass, &methodCount);
for (int i = 0; i < methodCount; i++) {
SEL sel = method_getName(methodList[i]);
if (sel == selector) {
free(methodList);
return YES;
}
}
free(methodList);
return NO;
}
2.給KVO添加一個class方法,返回父類的class梭稚,以達到無感知的目的
//重寫對象的class方法,將KVO_XXX的class指向監(jiān)聽類的class颖低,達到用戶無感知的目的
Method classMethod = class_getInstanceMethod(superClass, NSSelectorFromString(@"class"));
const char * classTypes = method_getTypeEncoding(classMethod);
class_addMethod(newClass, NSSelectorFromString(@"class"), (IMP)KVOClass, classTypes);
static Class KVOClass (id self,SEL _cmd) {
return class_getSuperclass(object_getClass(self));
}
到此整個功能已經(jīng)完成了。
需要demo可以加QQ群:839813029