1. 不调用实例变量的方法
2. 动态生成子类 (利用runtime生成:申请类,添加一些方法-set-class等方法,注册类 )
****常量类型不能添加观察者
#import
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (FXKVO)
- (void)fx_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)fx_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context;
- (void)fx_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end
NS_ASSUME_NONNULL_END
添加一个NSObject的分类,重写观察者方法
#pragma mark - 验证是否存在setter方法,如果不存在抛出异常(例如成员变量添加观察者会崩溃)
- (void)fx_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
//1. 验证set方法
[self judgeSetterMethodFromKeyPath:keyPath];
//2. 动态生成子类
Class newClass = [self creatChildClass];
//3. 当前对象的类,isa指向newClass
object_setClass(self, newClass);
//4. 观察者(为当前类添加属性,来记录观察者,用来做回调的时候使用)
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(FXKVOAssiociateKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#pragma mark - 动态生成子类
- (Class)creatChildClass{
// LGKVO_LGPerson
// 2. 动态生成子类
NSString *oldName = NSStringFromClass([self class]);
NSString *newName = [NSString stringWithFormat:@"%@%@", FXKVOPrefix, oldName];
Class newClass = objc_allocateClassPair([self class], newName.UTF8String, 0);
//申请注册到内存中
objc_registerClassPair(newClass);
// 2.1 子类添加一些方法 class setter
// class
SEL classSEL = NSSelectorFromString(@"class");
Method classM = class_getInstanceMethod([self class], classSEL);
const char *type = method_getTypeEncoding(classM);
class_addMethod(newClass, classSEL, (IMP)fx_class, type);
// setter
SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
Method setterM = class_getInstanceMethod([self class], setterSEL);
const char *settertype = method_getTypeEncoding(setterM);
class_addMethod(newClass, setterSEL, (IMP)fx_setter, settertype);
return newClass;
}
#pragma mark - 函数部分
static void fx_setter(id self, SEL _cmd, id newValue) {
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
// 消息发送 setName:
[self willChangeValueForKey:keyPath];
// newValue给谁, newClass KVOPerson
// 给父类发送消息
void (*fx_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
struct objc_super fx_objc_super = {
.receiver = self,
.super_class = [self class],
};
fx_msgSendSuper(&fx_objc_super, _cmd,newValue);
[self didChangeValueForKey:keyPath];
//响应回调 -- KVOVC(观察者) --调用某个方法 -- obbserve
//把观察者存起来,先用属性存起来(关联存储)
id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(FXKVOAssiociateKey));
//响应
//sel
SEL obserSEL = @selector(observeValueForKeyPath:ofObject:change:context:);
((id (*)(id, SEL, id, id, id, id))objc_msgSend)(observer, obserSEL, keyPath,self,@{keyPath:newValue},NULL);
}
//注意这里不能写成class_getSuperclass([self class]); //会递归崩溃 [self class]的底层imp是 <=> class_getSuperclass([self class]) 所以会无限递归
Class fx_class(id self,SEL _cmd){
return class_getSuperclass(object_getClass(self));
}
#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];
}
#pragma mark - 验证是否存在setter方法
- (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath{
Class superclass = object_getClass(self);
SEL setterSelector = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod(superclass, setterSelector);
if(!setterMethod) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"老铁没有当前%@的setter", keyPath] userInfo:nil];
}
}
#pragma mark - 从set方法获取getter方法的名称 set
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];
}
运行结果:(与系统KVO打印信息相同)
目前还存在的问题:
1. 只能添加一个观察者对象
2. 目前考虑是否可以使用block回调(代码逻辑和功能逻辑放到一起)
3. 每次添加网观察者还需要手动移除,是否可以实现自动移除
后面还需要优化(在下一篇博客中优化)
4. 为什么要生成动态子类,并且重写方法(谈下自己的理解,互不影响,是一中设计,这个动态子类是随时都会消失,是一个临时的子类,减少对原类的入侵,不影响本类,就像我们会新建子类处理一些子类的逻辑,而不影响父类一样)
比较成功的KVO框架(FBKVO)