KVO原理:當(dāng)一個(gè)對(duì)象被觀察時(shí), 系統(tǒng)會(huì)新建一個(gè)子類NSNotifying_A ,在子類中重寫了對(duì)象被觀察屬性的 set方法, 并且改變了該對(duì)象的 isa 指針的指向(指向了新建的子類) , 當(dāng)屬性的值發(fā)生改變了, 會(huì)調(diào)用子類的set方法, 然后發(fā)出通知
一. 創(chuàng)建NSObject分類, 創(chuàng)建類方法和回調(diào)用的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
{
//創(chuàng)建子類 默認(rèn)子類格式:'IBKVO'+'Notifying_'+ClassName
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@"IBKVONotifying_%@",oldClassName];
//判斷子類是否存在 不存在就創(chuàng)建一個(gè)并注冊(cè)
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方法 監(jiān)聽set方法的調(diào)用 從而block回調(diào)
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));
//調(diào)用setter 賦給新的值
objc_msgSend(self, NSSelectorFromString([setterName stringByAppendingString:@":"]),newValue);
//記錄前后改變的值
NSMutableDictionary *change = [NSMutableDictionary new];
if (newValue) {
change[NSKeyValueChangeNewKey] = newValue;
}
if (oldValue) {
change[NSKeyValueChangeOldKey] = oldValue;
}
//返回給調(diào)用者 類似于delegate
IBKVOBlock block = objc_getAssociatedObject(self, IBKVO_block);
if (block) {
block(change);
}
// isa 指向子類
object_setClass(self, subClass);
}
@end