前言
前两篇KVO的文章介绍了一些KVO的使用方式和KVO的实现原理,这篇文章我们来自定义KVO;
自定义KVO
自定义KVO首先要有个大概思路,根据KVO原理来进行操作:
1.添加通知即:
- (void)xz_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
1.1.验证是否有setter方法:没有不然进来
因为KVO是基于setter方法监听的,所以需要判断,类中是否有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];
}
}
1.2.动态生成子类
这里需要注意几个点:
1.类为动态生成的,这里需要同时添加几个类方法,class,setter方法等,
1.1:申请类
1.2:注册类
1.3:添加class方法
1.4 添加set方法
2.消息转发
2.1首先消息转发给父类,在外部使用self.person.nickName = @”Alan“这里实际类调用的是XZKVONotifying_类,所以需要让XZPerson对象中的值变化,所以需要转发给父类;
2.2:获取观察者观察者,并使用观察者进行消息转发
static NSString *const kXZKVOPrefix = @"XZKVONotifying_";
static NSString *const kXZKVOAssiociateKey = @"kXZKVO_AssiociateKey";
//调用生成新类
Class newClass = [self createChildClassWithKeyPath:keyPath];
#pragma mark -
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
// 2.1 判断是否有了
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@"%@%@",kXZKVOPrefix,oldClassName];// XZKVONotifying_XZPerson
Class newClass = NSClassFromString(newClassName);
if (newClass) {
return newClass;
}
/**
* 如果内存不存在,创建生成
* 参数一: 父类
* 参数二: 新类的名字
* 参数三: 新类的开辟的额外空间
*/
// 2.1 申请类
newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
// 2.2 注册类
objc_registerClassPair(newClass);
// 2.3.1 添加class方法
SEL classSEL = NSSelectorFromString(@"class");
Method classMethod = class_getClassMethod([self class], @selector(class));
const char *classType = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSEL, (IMP)xz_class, classType);
// 2.3.2 添加setter方法 setNickname
SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getClassMethod([self class], setterSEL);
const char *setterType = method_getTypeEncoding(setterMethod);
class_addMethod(newClass, setterSEL, (IMP)xz_setter, setterType);
return newClass;
}
static void xz_setter(id self,SEL _cmd,id newValue){
NSLog(@"来了:%@",newValue);
//4:消息转发: 转发给父类
//改变父类的值---可以强制类型转换
void (*xz_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
/**
newvalue 这里修改了子类的的值,父类值是没有改变的
使用 objc_msgSendSuper给父类的setter方法发送消息修改值
*/
// 回调给外界
struct objc_super superStruct = {
.receiver = self,
.super_class = [self class]
};
//4.1转发给父类
xz_msgSendSuper(&superStruct,_cmd,newValue);
//4.2 获取观察者观察者
id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kXZKVOAssiociateKey));
//4.3消息发送观察者
SEL observerSel = @selector(observeValueForKeyPath:ofObject:change:context:);
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
objc_msgSend(observer,observerSel,keyPath,self,@{keyPath:newValue},NULL);
}
//因为系统KVO中调用class时返回的是XZPerson类所以这里也返回父类指针节点
Class xz_class(id self,SEL _cmd){
//这里返回父类的isa
return class_getSuperclass(object_getClass(self));
}
#pragma mark - 从set方法获取getter方法的名称 set<Key>:===> key
static NSString *getterForSetter(NSString *setter){
if (setter.length <= 0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) { return nil;
}
//setter 值为setNickName
NSRange range = NSMakeRange(3, setter.length-4);
//去掉set和属性首字母
NSString *getter = [setter substringWithRange:range];
//首字母需要小写
NSString *firstString = [[getter substringToIndex:1] lowercaseString];
//转换为nickname
return [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
}
1.3.isa指向:XZKVONotifying_XZPerson
// 3: isa 指向 isa_swizzling
object_setClass(self, newClass);
1.4 保存观察者
//4.保存观察者
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kXZKVOAssiociateKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
源码为:
- (void)xz_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
// 1: 验证setter
[self judgeSetterMethodFromKeyPath:keyPath];
// 2: 动态生成子类
Class newClass = [self createChildClassWithKeyPath:keyPath];
// 3: isa 指向 isa_swizzling
object_setClass(self, newClass);
//4.保存观察者
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kXZKVOAssiociateKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
2.消息转发:响应回调使用原有系统回调即可(这里直接使用系统的,直接转发消息了)
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context;
3.移除通知
移除通知,指回父
- (void)xz_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
{
// 指回给父类
Class superClass = [self class];
object_setClass(self, superClass);
}
外部调用
- (void)viewDidLoad {
[super viewDidLoad];
self.person = [[XZPerson alloc] init];
[self.person xz_addObserver:self forKeyPath:@"nickName" options:(NSKeyValueObservingOptionNew) context:NULL];
// Do any additional setup after loading the view.
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"实际情况:%@",self.person.nickName);
self.person.nickName = @"alan";
}
#pragma mark - KVO回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@",change);
}
- (void)dealloc{
[self.person xz_removeObserver:self forKeyPath:@"nickName"];
}
输出结果:
总结
这篇文章实现了KVO的简单的自定义,只是通过监听单独的属性.但是还是有缺陷的:如监听多个属性呢,没有实现监听的自动销毁等;下篇文章进行自定义KVO进阶。
希望对大家有用处,欢迎大家点赞+评论,关注我的CSDN,我会定期做一些技术分享!未完待续。。。