浅学-自定义KVO
2019-05-28 本文已影响0人
厦门_小灰灰
(1)KVO是基于runtime机制实现的,
(2)类A监听类B的一个属性的变化,当B被观察时,系统会在运行期动态创建一个B的子类NSKVONotifying_B,
(3)每个类都有一个isa指针指向当前类,B的isa指针会指向派生类NSKVONotifying_B,然后重写被观察的属性的setter方法,
(4)键值观察依赖于NSObject的两个方法willChangeValueForKey和didChangeValueForKey,在属性被改变之前调用willChangeValueForKey记录旧值,发生改变之后调用didChangeValueForKey用来储存新值,然后调用observeValueForKey:ofObject:change:context
仿照系统的方式进行实现,代码如下
- 动态创建子类
- 重写被监听属性的set方法
- 改变对象的isa指针,使其指向子类
创建一个NSObject的类别
.h文件
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (Extension)
- (void)lh_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
@end
NS_ASSUME_NONNULL_END
.m文件
#import "NSObject+Extension.h"
#import <objc/runtime.h>
#import <objc/message.h>
static const NSString *lh_getterName = @"lh_getterName";
static const NSString *lh_setterName = @"lh_setterName";
static const NSString *lh_observer = @"lh_observer";
@implementation NSObject (Extension)
- (void)lh_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
//获取当前的类名
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@"LHKVONotifying_%@", oldClassName];
//创建新的类
Class cls = objc_getClass(newClassName.UTF8String);
if ( !cls ) {
cls = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
objc_registerClassPair(cls);
}
//set方法
NSString *setFuncName = [NSString stringWithFormat:@"set%@:", [keyPath capitalizedString]];
SEL setSel = NSSelectorFromString(setFuncName);
class_addMethod(cls, setSel, (IMP)setter, "v@:@");
//将isa指向改变
object_setClass(self, cls);
//关联对象
objc_setAssociatedObject(self, &lh_observer, observer, OBJC_ASSOCIATION_ASSIGN);
objc_setAssociatedObject(self, &lh_setterName, setFuncName, OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_setAssociatedObject(self, &lh_getterName, keyPath, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
void setter(id self, SEL _cmd, id newValue) {
NSString *setterName = objc_getAssociatedObject(self, &lh_setterName);
NSString *getterName = objc_getAssociatedObject(self, &lh_getterName);
//获取子类的类
Class cls = [self class];
//获取子类的父类, 并改变 isa 的指向
object_setClass(self, class_getSuperclass(cls));
//获取getter 的旧值
id oldValue = ((id(*)(id, SEL))objc_msgSend)(self, NSSelectorFromString(getterName));
//调用原类的set方法
((id(*)(id, SEL, id))objc_msgSend)(self, NSSelectorFromString(setterName), newValue);
id observer = objc_getAssociatedObject(self, &lh_observer);
NSMutableDictionary *change = @{}.mutableCopy;
if ( oldValue ) {
change[NSKeyValueChangeOldKey] = oldValue;
}
if ( newValue ) {
change[NSKeyValueChangeNewKey] = newValue;
}
((id(*)(id, SEL, id, id, id, id))objc_msgSend)(observer, @selector(observeValueForKeyPath:ofObject:change:context:), setterName, self, change, nil);
//重新将isa指向子类
object_setClass(self, cls);
}
@end