一.原理
KVO是系統(tǒng)用運(yùn)行時(shí)機(jī)制給需要觀察的對(duì)象增加一個(gè)子類的方法實(shí)現(xiàn)的锋华,如果一個(gè)對(duì)象被觀察氛驮,系統(tǒng)會(huì)給他增加以各子類葛作,重寫它的set方法寿羞,從而實(shí)現(xiàn)觀察對(duì)象屬性變化的功能,為了使外部看不出來赂蠢,系統(tǒng)還重寫了被觀察對(duì)象的-class绪穆,-isEqual:等方法。
二.自定義KVO(僅實(shí)現(xiàn)key,未實(shí)現(xiàn)keyPath)
1.這個(gè)方式是給要觀察的類添加一個(gè)子類
- (Class)achiveNewSubClass:(NSString *)className
withCurrentClass:(Class)currentClass
{
Class newClass = objc_allocateClassPair([self class],
className.UTF8String,
0);
// 可以在這里給類添加成員變量等
objc_registerClassPair(newClass);
return newClass;
}
2.這個(gè)是重寫被觀察對(duì)象的set方法
void kvo_setter(id self, SEL selecter, id newValue) {
KHHandle handle = objc_getAssociatedObject(self,
@"123".UTF8String);
NSString * key = achiveKeyPath(selecter);
struct objc_super superclazz = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self))
};
id oldValue = [self valueForKey:key];
void (* objc_msgSendToSuper)(struct objc_super * ,SEL , id) = (void *)objc_msgSendSuper;
// 在這里調(diào)取運(yùn)行時(shí)創(chuàng)建的子類的父類的set方法賦值
objc_msgSendToSuper(&superclazz, selecter, newValue);
NSDictionary * dict = @{KHKeyValueNew : newValue ?: @"",
KHKeyValueOld : oldValue ?: @"",
};
handle(dict);
}
3.這個(gè)是提供給外部調(diào)用的方法
- (void)KH_addObserverWithKeyPath:(NSString *)keyPath
withBlock:(KHHandle)handle
{
Class currentClass = [self class];
NSString * className = NSStringFromClass(currentClass);
if ([className hasPrefix:CLASS_PREFIX]) return;
className = [CLASS_PREFIX stringByAppendingString:className];
Class subClass =NSClassFromString(className);
if (subClass) return;
objc_setAssociatedObject(self,
@"123".UTF8String,
handle,
OBJC_ASSOCIATION_COPY_NONATOMIC);
subClass = [self achiveNewSubClass:className
withCurrentClass:currentClass];
object_setClass(self, subClass);
SEL selecter = NSSelectorFromString(achiveMethodName(keyPath));
Method me = class_getInstanceMethod(currentClass, selecter);
const char * types = method_getTypeEncoding(me);
BOOL isSuccess = class_addMethod(subClass, selecter, (IMP)kvo_setter, types);
NSLog(@"%d",isSuccess);
}