前言
之前只是了解KVO的原理,但是從未自己手動(dòng)實(shí)現(xiàn)過KVO,主要是因?yàn)橹皩?duì)runtime的操作函數(shù)沒有那么熟練膳凝,所以一直耽擱了碑隆。我希望在不參考網(wǎng)上代碼的之提下自己實(shí)現(xiàn)KVO, 有問題自己解決,除非碰到一些坎蹬音。
自己手動(dòng)實(shí)現(xiàn)KVO
過程是這樣的:
- 創(chuàng)建目標(biāo)對(duì)象的子類上煤,前面加上"MyObserver_"的前綴作標(biāo)識(shí)。
- 保存原來setter的對(duì)應(yīng)的IMP著淆,將其存到"orgi_"+setter的方法下
- 用我們的IMP替換原來setter對(duì)應(yīng)的IMP
- 注冊(cè)子類
- 替換isa
- 保存監(jiān)聽的屬性和block值到一個(gè)靜態(tài)字典(這個(gè)最后廢棄而用了關(guān)聯(lián)引用)
@implementation NSObject (Observe)
static NSMutableDictionary *myobserveDic;
- (void)addMyObserver:(id)object propertyName:(NSString *)propertyName block:(void(^)(void))block{
propertyName = @"name";
Class cls = [object class];
NSString *observeClassName = [NSString stringWithFormat:@"MyObserver_%@", NSStringFromClass(cls)];
Class MyObserveClass = objc_allocateClassPair(cls, [observeClassName UTF8String], 0);
NSString *setter = [NSString stringWithFormat:@"set%@:", [propertyName capitalizedString]];
NSString *orginSetter = [NSString stringWithFormat:@"orgi_%@", setter];
SEL setterSEL = sel_registerName([setter UTF8String]);
SEL orginSetterSEL = sel_registerName([orginSetter UTF8String]);
Method method = class_getInstanceMethod(MyObserveClass, setterSEL);
//保存舊的IMP 名字前加orgi_
class_addMethod(MyObserveClass, orginSetterSEL, method_getImplementation(method), method_getTypeEncoding(method));
//替換IMP
class_replaceMethod(MyObserveClass, setterSEL, (IMP)setterXXX, method_getTypeEncoding(method));
objc_registerClassPair(MyObserveClass);//注冊(cè)類
object_setClass(object, MyObserveClass);//替換isa
myobserveDic = myobserveDic ?: [NSMutableDictionary dictionary];
NSValue *objectValue = [NSValue valueWithNonretainedObject:object];
NSMutableArray *names = [myobserveDic objectForKey:objectValue];
if (!names) {
names = [NSMutableArray array];
[myobserveDic setObject:names forKey:objectValue];
}
[names addObject:setter];
[names addObject:@{@"setter":setter, @"block": block}];
}
用一個(gè)靜態(tài)全局的字典
myobserveDic
存儲(chǔ)數(shù)據(jù)劫狠,key是object, value是sel、block等信息永部。但是這樣的話独泞,運(yùn)行會(huì)報(bào)錯(cuò)。
- 因?yàn)樗腒ey要支持
<NSCopy>
協(xié)議- object的強(qiáng)引用會(huì)出現(xiàn)問題
解決方法:
想到了用NSValue在object外包一層苔埋,這樣就解可以解決這些問題了懦砂。
全局字典和關(guān)聯(lián)引用哪個(gè)更好?
如果用全局字典
的話會(huì)有一個(gè)問題讲坎,就是在dealloc里從全局字典
移除相關(guān)信息孕惜,相比關(guān)聯(lián)引用
多了一個(gè)步驟愧薛。所以我后來選擇了用關(guān)聯(lián)引用實(shí)現(xiàn)晨炕。
改進(jìn)后的代碼如下:
@implementation NSObject (Observe)
- (void)addMyObserver:(id)object propertyName:(NSString *)propertyName block:(void(^)(void))block{
propertyName = @"name";
Class cls = [object class];
NSString *observeClassName = [NSString stringWithFormat:@"MyObserver_%@", NSStringFromClass(cls)];
Class MyObserveClass = objc_allocateClassPair(cls, [observeClassName UTF8String], 0);
NSString *setter = [NSString stringWithFormat:@"set%@:", [propertyName capitalizedString]];
NSString *orginSetter = [NSString stringWithFormat:@"orgi_%@", setter];
SEL setterSEL = sel_registerName([setter UTF8String]);
SEL orginSetterSEL = sel_registerName([orginSetter UTF8String]);
Method method = class_getInstanceMethod(MyObserveClass, setterSEL);
//保存舊的IMP 名字前加orgi_
class_addMethod(MyObserveClass, orginSetterSEL, method_getImplementation(method), method_getTypeEncoding(method));
//替換IMP
class_replaceMethod(MyObserveClass, setterSEL, (IMP)setterXXX, method_getTypeEncoding(method));
objc_registerClassPair(MyObserveClass);//注冊(cè)類
object_setClass(object, MyObserveClass);//替換isa
//監(jiān)聽屬性的數(shù)組
NSMutableArray *infoArray = objc_getAssociatedObject(self, &observerInfoArrayKey);
if (!infoArray) {
infoArray = [NSMutableArray array];
objc_setAssociatedObject(self, &observerInfoArrayKey, infoArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
MyObserveInfo *info = [[MyObserveInfo alloc] init];
info.setter = setter;
info.block = block;
[infoArray addObject:info];
}
static void setterXXX(id self, SEL _cmd, id value) {
NSMutableArray *observeInfoArray = objc_getAssociatedObject(self, &observerInfoArrayKey);
for (MyObserveInfo *info in observeInfoArray) {
if ([info.setter isEqualToString:@(sel_getName(_cmd))]) {
//調(diào)用原始setter方法
NSString *orgiSELString = [NSString stringWithFormat:@"orgi_%@", @(sel_getName(_cmd))];
SEL orginSetterSEL = sel_registerName([orgiSELString UTF8String]);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector:orginSetterSEL withObject:value];
#pragma clang diagnostic pop
if (info.block) {
info.block();
}
}
}
}
@end
MyObserveInfo類的定義:
@interface MyObserveInfo : NSObject
@property(nonatomic, strong) NSString *setter;
@property(nonatomic, copy) void(^block)(void);
@end
在setterXXX方法調(diào)用時(shí),會(huì)用performSelector先調(diào)用原始的setter方法毫炉,然后再調(diào)用block瓮栗。
但是會(huì)有l(wèi)eak的黃色警告,用-Warc-performSelector-leaks
就可以去除掉瞄勾。
不過我看網(wǎng)上的一些實(shí)現(xiàn)方法不是用的performSelector费奸,而是用的objc_msgSendSuper方法,如下:
// 調(diào)用原類的setter方法
struct objc_super superClazz = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};
// 這里需要做個(gè)類型強(qiáng)轉(zhuǎn), 否則會(huì)報(bào)too many argument的錯(cuò)誤
((void (*)(void *, SEL, id))objc_msgSendSuper)(&superClazz, _cmd, newValue);
剛開始看了有點(diǎn)不解进陡,為什么要調(diào)supper的方法愿阐,后來一想是因?yàn)槲姨砑恿艘粋€(gè)叫orgin方法來存儲(chǔ)原始的IMP, 所以它是現(xiàn)成的,可以直接用performSelector調(diào)用趾疚。
替換class方法
忘了添加class的方法替換了, 這里補(bǔ)上
//覆蓋class方法
Method classMethod = class_getInstanceMethod(MyObserveClass, sel_registerName("class"));
class_addMethod(MyObserveClass, sel_registerName("class"), (IMP)(myClass), method_getTypeEncoding(classMethod));
myClass實(shí)現(xiàn)如下:
Class myClass(id self, SEL _cmd) {
Class cls = object_getClass(self);
Class supCls = class_getSuperclass(cls);
return supCls;
}
這里我踩了個(gè)坑缨历,千萬不要直接調(diào)用superClass方法:
Class myClass(id self, SEL _cmd) {
return [self superClass];
}
這樣會(huì)導(dǎo)致無限遞歸,我看了一下runtime源碼才知道superClass
里面又調(diào)用了[self class]
方法糙麦,所以辛孵。。赡磅。
- (Class)superclass {
return [self class]->superclass;
}