KVO原理:当一个对象被观察时, 系统会新建一个子类NSNotifying_A ,在子类中重写了对象被观察属性的 set方法, 并且改变了该对象的 isa 指针的指向(指向了新建的子类) , 当属性的值发生改变了, 会调用子类的set方法, 然后发出通知
一. 创建NSObject分类, 创建类方法和回调用的block
typedef void(^IBKVOBlock)(NSDictionary *_Nonnull dictionary);
@interface NSObject (IBKVO)
+ (void)IB_AddObserverWithKeyPath:(NSString *)keypath option:(NSKeyValueObservingOptions)option block:(IBKVOBlock)block;
@end
二. 具体代码以及注解
#import "NSObject+IBKVO.h"
#import <objc/runtime.h>
#import <objc/message.h>
static const char *IBKVO_getter = "IBKVO_getter";
static const char *IBKVO_setter = "IBKVO_setter";
static const char *IBKVO_block = "IBKVO_block";
@implementation NSObject (IBKVO)
+ (void)IB_AddObserverWithKeyPath:(NSString *)keypath option:(NSKeyValueObservingOptions)option block:(IBKVOBlock)block
{
//创建子类 默认子类格式:'IBKVO'+'Notifying_'+ClassName
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@"IBKVONotifying_%@",oldClassName];
//判断子类是否存在 不存在就创建一个并注册
Class subClass = objc_getClass(newClassName.UTF8String);
if (!subClass) {
subClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
objc_registerClassPair(subClass);
}
//将keypath 首字母大写 拼成setValue方法名
NSString *KeyPath = [[keypath substringToIndex:1] uppercaseString];
NSString *setKeyPath = [@"set" stringByAppendingString:KeyPath];
//判断属性存不存在
Method setM = class_getInstanceMethod(subClass, NSSelectorFromString(setKeyPath));
if (!setM) {
@throw [NSExpression expressionWithFormat:@"属性不存在"];
}
SEL setSel = NSSelectorFromString([setKeyPath stringByAppendingString:@":"]);
//添加set方法------
//先找到本来的set方法的type
Method setMethod = class_getInstanceMethod([self class], NSSelectorFromString(setKeyPath));
const char* setTypes = method_getTypeEncoding(setMethod);
//添加自定义的set方法 监听set方法的调用 从而block回调
class_addMethod(subClass, setSel, (IMP)setMethod, setTypes);
//将self指向子类
object_setClass(self, subClass);
//保存set get方法名
objc_setAssociatedObject(self, IBKVO_setter, setKeyPath, OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_setAssociatedObject(self, IBKVO_getter, keypath, OBJC_ASSOCIATION_COPY_NONATOMIC);
//保存block
objc_setAssociatedObject(self, IBKVO_block, block, OBJC_ASSOCIATION_COPY);
}
void setMethod(id self, SEL _cmd,id newValue)
{
//获取getter setter 名
NSString *setterName = objc_getAssociatedObject(self, IBKVO_setter);
NSString *getterName = objc_getAssociatedObject(self, IBKVO_getter);
//保存subclass
Class subClass = [self class];
//self(isa) 指向super
object_setClass(self, class_getSuperclass(subClass));
//获取更改前的值 必须让self指向super才能获取
id oldValue = objc_msgSend(self, NSSelectorFromString(getterName));
//调用setter 赋给新的值
objc_msgSend(self, NSSelectorFromString([setterName stringByAppendingString:@":"]),newValue);
//记录前后改变的值
NSMutableDictionary *change = [NSMutableDictionary new];
if (newValue) {
change[NSKeyValueChangeNewKey] = newValue;
}
if (oldValue) {
change[NSKeyValueChangeOldKey] = oldValue;
}
//返回给调用者 类似于delegate
IBKVOBlock block = objc_getAssociatedObject(self, IBKVO_block);
if (block) {
block(change);
}
// isa 指向子类
object_setClass(self, subClass);
}
@end