上篇文章KVO我们探究了KVO
的一些使用方法和其原理,今天我们来自定义一个KVO
。
首先我们创建一个NSObject
的分类NSObject (LGKVO)
,我们知道KVO
的addObserver
和removeObserver
需要是成对的,我们重写一下这两个方法:
@interface NSObject (LGKVO)
- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;
- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end
首先我们要知道自定义lg_addObserver
中要做什么操作:
- 通过
Method
判断是否有这个key
对应的selector
,如果没有则Crash
,也就是检查方法的合法性。
- 通过
- 判断当前类是否是
KVO
子类,如果不是则创建,并设置其isa
指针。
- 判断当前类是否是
- 如果没有实现,则添加
Key
对应的setter
方法。
- 如果没有实现,则添加
- 将调用对象添加到数组中,保存
observer
信息。
- 将调用对象添加到数组中,保存
第一步:进行方法的合法性检查
这里我们自定义judgeSetterMethodFromKeyPath
进行检查:
#pragma mark - 验证是否存在setter方法
- (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath{
Class superClass = object_getClass(self);
SEL setterSeletor = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod(superClass, setterSeletor);
if (!setterMethod) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"老铁没有当前 %@ 的setter",keyPath] userInfo:nil];
}
}
这里第二步和第三步放一起了:
// 2: 动态生成子类
Class newClass = [self createChildClassWithKeyPath:keyPath];
// 3: isa的指向 : LGKVONotifying_LGPerson
object_setClass(self, newClass);
#pragma mark -
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLGKVOPrefix,oldClassName];
Class newClass = NSClassFromString(newClassName);
// 防止重复创建生成新类
if (newClass) return newClass;
/**
* 如果内存不存在,创建生成
* 参数一: 父类
* 参数二: 新类的名字
* 参数三: 新类的开辟的额外空间
*/
// 2.1 : 申请类
newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
// 2.2 : 注册类
objc_registerClassPair(newClass);
// 2.3.1 : 添加class : class的指向是LGPerson
SEL classSEL = NSSelectorFromString(@"class");
Method classMethod = class_getInstanceMethod([self class], classSEL);
const char *classTypes = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSEL, (IMP)lg_class, classTypes);
// 2.3.2 : 添加setter ,如果没有实现,则添加Key对应的setter方法。
SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod([self class], setterSEL);
const char *setterTypes = method_getTypeEncoding(setterMethod);
class_addMethod(newClass, setterSEL, (IMP)lg_setter, setterTypes);
// 2.3.3 : 添加dealloc
SEL deallocSEL = NSSelectorFromString(@"dealloc");
Method deallocMethod = class_getInstanceMethod([self class], deallocSEL);
const char *deallocTypes = method_getTypeEncoding(deallocMethod);
class_addMethod(newClass, deallocSEL, (IMP)lg_dealloc, deallocTypes);
return newClass;
}
我们使用KVO
时候,是对操作的类(例如LGPerson
)的属性赋值,这里因为将isa
指向了新创建的中间子类(NSKVONotifying_LGPerson
),所以这里需要调用父类(LGPerson)
的setter
方法进行属性赋值。我们这里通过objc_msgSendSuper
发送消息来实现。
属性改变后我们还需要通过observeValueForKeyPath
方法通知观察者。我们仍然是使用消息发送来实现通知观察者。
static void lg_setter(id self,SEL _cmd,id newValue){
NSLog(@"来了:%@",newValue);
// 4: 消息转发 : 转发给父类
// 改变父类的值 --- 可以强制类型转换
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
id oldValue = [self valueForKey:keyPath];
void (*lg_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
// void /* struct objc_super *super, SEL op, ... */
struct objc_super superStruct = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self)),
};
//objc_msgSendSuper(&superStruct,_cmd,newValue)
lg_msgSendSuper(&superStruct,_cmd,newValue);
// 1: 拿到观察者
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
for (LGKVOInfo *info in observerArr) {
if ([info.keyPath isEqualToString:keyPath]) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSMutableDictionary<NSKeyValueChangeKey,id> *change = [NSMutableDictionary dictionaryWithCapacity:1];
// 对新旧值进行处理
if (info.options & LGKeyValueObservingOptionNew) {
[change setObject:newValue forKey:NSKeyValueChangeNewKey];
}
if (info.options & LGKeyValueObservingOptionOld) {
[change setObject:@"" forKey:NSKeyValueChangeOldKey];
if (oldValue) {
[change setObject:oldValue forKey:NSKeyValueChangeOldKey];
}
}
// 2: 消息发送给观察者
SEL observerSEL = @selector(lg_observeValueForKeyPath:ofObject:change:context:);
objc_msgSend(info.observer,observerSEL,keyPath,self,change,NULL);
});
}
}
}
当对象销毁的时候会调用dealloc,在该方法中将isa支持重新指向原来的类。
static void lg_dealloc(id self,SEL _cmd){
Class superClass = [self class];
object_setClass(self, superClass);
}
第四步:
将调用对象添加到数组中,保存observer
信息:
LGKVOInfo *info = [[LGKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath options:options];
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
if (!observerArr) {
observerArr = [NSMutableArray arrayWithCapacity:1];
[observerArr addObject:info];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
这一步中要先创建一个继承NSObject
的类LGKVOInfo
:
// ✅ .h中
typedef NS_OPTIONS(NSUInteger, LGKeyValueObservingOptions) {
LGKeyValueObservingOptionNew = 0x01,
LGKeyValueObservingOptionOld = 0x02,
};
@interface LGKVOInfo : NSObject
@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, assign) LGKeyValueObservingOptions options;
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGKeyValueObservingOptions)options;
@end
.m中:
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGKeyValueObservingOptions)options{
self = [super init];
if (self) {
self.observer = observer;
self.keyPath = keyPath;
self.options = options;
}
return self;
}
到这里是不是觉得我们自定义KVO
结束了,如果感觉结束了,那就文章最开始的时候没有认真看哦,addObserver
和removeObserver
需要是成对的哦,所以,我们还需要重写removeObserver
方法。
自定义removeObserver
传入观察者和keyPath
,当观察者所有keyPath
都移除后则从KVO
中移除观察者对象。
- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
if (observerArr.count<=0) {
return;
}
for (LGInfo *info in observerArr) {
if ([info.keyPath isEqualToString:keyPath]) {
[observerArr removeObject:info];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
break;
}
}
if (observerArr.count<=0) {
// 指回给父类
Class superClass = [self class];
object_setClass(self, superClass);
}
}
网友评论