- KVO相信大家已经很熟悉了,但是就开发中使用情况来看,KVO有以下的不方便之处:
- 所有的observe处理都放在一个方法里处理,如果需要监听多个属性就需要判断
- 添加observe和处理observe的代码过于分散,可读性不好
- 调用多次相同的removeObserve会导致crash
- 这里可以利用Runtime来实现一个带block的KVO,这样就把添加observe和处理observe的代码集中起来了,下面看代码
- 给NSObject写一个KVO的分类,实现自己的监听方法
- (void)xhr_addObserverForKey:(NSString *)key block:(void (^) (NSDictionary *valueInfo))valueChangedBlock;
- (void)xhr_removeObserverForKey:(NSString *)key;
- (void)xhr_removeAllObserver;
- 在addObserverForKey方法中实现自己的KVO
- 根据传进来的key获取setter方法名
NSString *selectorName = [NSString stringWithFormat:@"set%@:",key.capitalizedString];
SEL setterSEL = NSSelectorFromString(selectorName);- 记录被监听者setter方法IMP指针
- 根据传进来的key获取setter方法名
//记录被监听者的setter方法的IMP指针
Method superSetter = class_getInstanceMethod([self class], setterSEL);
_VIMP superSetterIMP = (_VIMP)method_getImplementation(superSetter);
- 获取动态创建的子类的类名
//获取子类的类名
NSString *childClassName = NSStringFromClass([self class]);
if (![childClassName hasPrefix:childClassPrefix])
{
childClassName = [NSString stringWithFormat:@"%@%@",childClassPrefix,NSStringFromClass([self class])];
}
else
{
superSetter = class_getInstanceMethod(class_getSuperclass([self class]),setterSEL);
}
- 获取被监听属性的数据类型,根据RunTime的映射来判断
//获取被监听属性的数据类型
NSString *methodType = [NSString stringWithUTF8String:method_getTypeEncoding(superSetter)];
//NSLog(@"%@",methodType);//v20@0:8i16 //
methodType = [methodType componentsSeparatedByString:@"@0:8"].lastObject;
NSString *valueType = [methodType substringToIndex:methodType.length - 2];
-
动态地创建类继承自被监听者
//判断需要创建的类是否存在 objc_getClass Class ChildClass = objc_getClass(childClassName.UTF8String); if (!ChildClass) { //创建类 ChildClass = objc_allocateClassPair([self class], childClassName.UTF8String, 0); //注册类 objc_registerClassPair(ChildClass); }
- 改变self的isa指针
//使当前类的isa指针指向新创建的类
object_setClass(self, ChildClass);
- 根据key的不同类型来重写子类的setter方法
IMP childSetValue = nil;
if ([valueType isEqualToString:[NSString stringWithUTF8String:@encode(int)]]) {
childSetValue = imp_implementationWithBlock(^(id _self, int newValue) {
//调用被监听对象(父类)的setter方法给被监听者属性赋值//@"こうさか ほのか"
(*superSetterIMP)(_self,setterSEL,newValue);
id oldValue = objc_getAssociatedObject(weakself, key.UTF8String);
objc_setAssociatedObject(weakself, key.UTF8String, @(newValue), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (!oldValue) {
oldValue = [NSNull null];
}
NSDictionary *valueInfoDictionary = @{@"oldValue":oldValue,@"newValue":@(newValue)};
!valueChangedBlock?:valueChangedBlock(valueInfoDictionary);
});
}
- 添加setter方法
//给子类添加setter方法
class_replaceMethod(ChildClass, setterSEL, childSetValue, [NSString stringWithFormat:@"v@:%@",valueType].UTF8String);
- 移除监听
- (void)xhr_removeAllObserver,这个简单,就是移除所有子类的关联,改变isa指针指向父类
```
if (![NSStringFromClass([self class]) hasPrefix:childClassPrefix])return;
objc_removeAssociatedObjects(self);
object_setClass(self, class_getSuperclass([self class]));
- (void)xhr_removeObserverForKey:(NSString *)key,这个就得判断调用的类是不是子类,如果不是就直接返回,防止重复移除导致的crash,如果监听者数组中没有需要监听的key,就直接移除所有,不然self的isa指针还是指向子类
if (![NSStringFromClass([self class]) hasPrefix:childClassPrefix]) return;
XHRKVOAssistClass *KVOAssistInstance = objc_getAssociatedObject(self, XHRKVOAssistInstance);
/**
* 防止重复移除监听
*/
if (!KVOAssistInstance.observerKeyArray.count) {
[self xhr_removeAllObserver];
return;
}
//获取被监听者的类
Class superClass = class_getSuperclass([self class]);
//获取setter方法名
NSString *selectorName = [NSString stringWithFormat:@"set%@:",key.capitalizedString];
IMP superSetterIMP = class_getMethodImplementation(superClass, NSSelectorFromString(selectorName));
//获取被监听key属性的数据类型
NSString *methodType = [NSString stringWithUTF8String:method_getTypeEncoding(class_getInstanceMethod(superClass, NSSelectorFromString(selectorName)))];
methodType = [methodType componentsSeparatedByString:@"@0:8"].lastObject;
NSString *valueType = [methodType substringToIndex:methodType.length - 2];
class_replaceMethod([self class], NSSelectorFromString(selectorName), superSetterIMP, [NSString stringWithFormat:@"v@:%@",valueType].UTF8String);
[KVOAssistInstance.observerKeyArray removeObject:@{valueType:key}];
//判断移除后数组是否为空,为空即移除所监听
if (!KVOAssistInstance.observerKeyArray.count) {
[self xhr_removeAllObserver];
return;
}
注意事项:
1.addObserver的blcok里面如果需要用到self必须使用weakself
2.不能使用在同一个类的不同对象的同一个属性的监听
作者:胥鸿儒
demo地址:https://github.com/xuhongru/XHRBlockKVO/tree/master