前言
之前只是了解KVO的原理,但是从未自己手动实现过KVO,主要是因为之前对runtime的操作函数没有那么熟练,所以一直耽搁了。我希望在不参考网上代码的之提下自己实现KVO, 有问题自己解决,除非碰到一些坎。
自己手动实现KVO
过程是这样的:
- 创建目标对象的子类,前面加上"MyObserver_"的前缀作标识。
- 保存原来setter的对应的IMP,将其存到"orgi_"+setter的方法下
- 用我们的IMP替换原来setter对应的IMP
- 注册子类
- 替换isa
- 保存监听的属性和block值到一个静态字典(这个最后废弃而用了关联引用)
@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);//注册类
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}];
}
用一个静态全局的字典
myobserveDic
存储数据,key是object, value是sel、block等信息。但是这样的话,运行会报错。
- 因为它的Key要支持
<NSCopy>
协议- object的强引用会出现问题
解决方法:
想到了用NSValue在object外包一层,这样就解可以解决这些问题了。
全局字典和关联引用哪个更好?
如果用全局字典
的话会有一个问题,就是在dealloc里从全局字典
移除相关信息,相比关联引用
多了一个步骤。所以我后来选择了用关联引用实现。
改进后的代码如下:
@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);//注册类
object_setClass(object, MyObserveClass);//替换isa
//监听属性的数组
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))]) {
//调用原始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方法调用时,会用performSelector先调用原始的setter方法,然后再调用block。
但是会有leak的黄色警告,用-Warc-performSelector-leaks
就可以去除掉。
不过我看网上的一些实现方法不是用的performSelector,而是用的objc_msgSendSuper方法,如下:
// 调用原类的setter方法
struct objc_super superClazz = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};
// 这里需要做个类型强转, 否则会报too many argument的错误
((void (*)(void *, SEL, id))objc_msgSendSuper)(&superClazz, _cmd, newValue);
刚开始看了有点不解,为什么要调supper的方法,后来一想是因为我添加了一个叫orgin方法来存储原始的IMP, 所以它是现成的,可以直接用performSelector调用。
替换class方法
忘了添加class的方法替换了, 这里补上
//覆盖class方法
Method classMethod = class_getInstanceMethod(MyObserveClass, sel_registerName("class"));
class_addMethod(MyObserveClass, sel_registerName("class"), (IMP)(myClass), method_getTypeEncoding(classMethod));
myClass实现如下:
Class myClass(id self, SEL _cmd) {
Class cls = object_getClass(self);
Class supCls = class_getSuperclass(cls);
return supCls;
}
这里我踩了个坑,千万不要直接调用superClass方法:
Class myClass(id self, SEL _cmd) {
return [self superClass];
}
这样会导致无限递归,我看了一下runtime源码才知道superClass
里面又调用了[self class]
方法,所以。。。
- (Class)superclass {
return [self class]->superclass;
}