小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。
自定义KVO的思路
在自定义KVO
之前,我们首先需要分析,系统的KVO
是如何实现的,它的思路是什么?
我们自定义一个Person
类,然后查看一下KVO
方法的来源:
发现addObserver
方法来自于NSObject
的分类@interfaceNSObject(NSKeyValueObserverRegistration)
,是针对NSObject
的拓展;
其回调方法来自于NSObject
的分类@interfaceNSObject(NSKeyValueObserving)
;
那么我们就可以仿照系统的做法,我们给NSObject
添加一个自定义的KVO
分类,同样声明添加
,监听
,删除
三个方法:
为了与系统方法进行区分,我们添加了自定义KVO
的前缀ck
;那么声明过后,我们如何实现这三个方法呢?这个代码我们应该如何去写? 根据我们之前的文章对系统KVO
的分析,我们可以大致得出自定义KVO
的步骤如下:
- 因为
KVO
是监听属性的变化,那么我们可以在setter
方法中进行判断键值等问题;成员变量没有setter
方法 ; - 动态生成一个子类
- 修改
isa
的指向,指向子类 - 保存观察者
验证是否存在setter
方法代码如下:
#pragma mark - 验证是否存在setter方法
- (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath{
Class superClass = object_getClass(self);
SEL setterSeletor = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod(superClass, setterSeletor);
if (!setterMethod) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"没有当前%@的setter",keyPath] userInfo:nil];
}
}
#pragma mark - 从get方法中获取set方法的名称 key ===>>> setKey:
static NSString *setterForGetter(NSString *getter){
if (getter.length <= 0) { return nil;}
NSString *firstString = [[getter substringToIndex:1] uppercaseString];
NSString *leaveString = [getter substringFromIndex:1];
return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
}
复制代码
自定义KVO动态生成子类
动态生成一个子类的流程分为三步:
- 申请类
- 添加方法
- 注册类
申请类
申请类之前,我们首先要把类创建出来,根据旧类的类名,传建一个新类:
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@"CKKVONotifying_%@",oldClassName];
Class newClass = NSClassFromString(newClassName);
// 防止重复创建生成新类
if (newClass) return newClass;
复制代码
接下来,我们就能去申请类了:
newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
复制代码
添加方法
添加方法时,我们主要添加class
和setter
方法;
- 添加
class
方法
SEL classSEL = NSSelectorFromString(@"class");
Method classMethod = class_getInstanceMethod([self class], classSEL);
const char *classTypes = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSEL, (IMP)ck_class, classTypes);
复制代码
- 添加
setter
方法
SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod([self class], setterSEL);
const char *setterTypes = method_getTypeEncoding(setterMethod);
class_addMethod(newClass, setterSEL, (IMP)ck_setter, setterTypes);
复制代码
/// 子类重写imp
static void ck_setter(id self,SEL _cmd,id newValue){
NSLog(@"setter 方法");
}
// 直接获取父类的实现
Class ck_class(id self,SEL _cmd){
return class_getSuperclass(object_getClass(self));
}
复制代码
注册类
objc_registerClassPair(newClass);
复制代码
自定义KVO的setter方法
目前我们自定义的KVO
已经能够触发setter
方法,也就是ck_setter
能够被调用了,那么我们如何给ck_setter
方法添加实现呢?
说到底我们还是要改变原始类
也就是Person
类的属性值,那么就需要给Person
也就是父类
的setter
方法发送消息:
/// 子类重写imp
static void ck_setter(id self,SEL _cmd,id newValue){
// 给父类发送消息
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
void (*ck_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
struct objc_super superStruct = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self)),
};
ck_msgSendSuper(&superStruct,_cmd,newValue);
// 通知观察者 (在最开始的时候要使用关联对象保存观察者)
id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kCKKVOAssiociateKey));
[observer ck_observeValueForKeyPath:keyPath ofObject:self change:@{keyPath:newValue} context:NULL];
}
#pragma mark - 从set方法中获取getter方法的名称 set<Key>:===> key
static NSString *getterForSetter(NSString *setter){
if (setter.length <= 0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) { return nil;}
NSRange range = NSMakeRange(3, setter.length-4);
NSString *getter = [setter substringWithRange:range];
NSString *firstString = [[getter substringToIndex:1] lowercaseString];
return [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
}
复制代码
目前我们已经简单实现了监听,我们来验证一下:
ck_addObserver
方法实现如下
接下来验证一下监听逻辑:
我们已经初步实现了属性变化监听逻辑;
但是,这还不完美,我们如果要观察多个属性呢?如何移除观察者呢?我们下回继续分析......