一、KVO 自定义 setter 函数
上篇中自定义 KVO 时,有个方法是在生成自定义子类时,对生成的子类中 setter 进行了实现,只做了一半,就是当外面对父类对象的属性值赋值时,调用setter 方法时其实就会来到动态子类的setter,在上篇自定义 KVO 的时候,因为lg_setter 方法里面什么也没做,通过打印可知并没有调用对象中的 setter 方法,但是我们平时肉眼看到的是,确实父类对象中的属性的值确实也发生了变化,所以底层在实现这个方法的时候肯定有做些什么了,于是就一起来看看动态子类中的 setter 到底做了什么了
static void lg_setter(id self,SEL _cmd,id newValue){
NSLog(@"来了:%@",newValue);
// 应该些什么? 内部 -> 相应点 newValue oldValue option context -> change
// 回调给外界
}
还记得前一篇有提到 LGPerson 是 NSKVONotifying_LGPerson 的父类,那在这里是不是就是要把子类的消息发送给父类,要是消息的发送,这里便要用到 objc_msgSendSuper, 因为是子类与父类的关系,接下来我们来验证一下。
结合上一篇的自定义KVO,这里代码就不贴重复贴出了,只写做改动的部分
static void lg_setter(id self,SEL _cmd,id newValue){
NSLog(@"来了:%@",newValue);
//1:消息发送给父类方法
struct objc_super superStruct = {
.receiver = self,
.super_class = class_getSuperclass([self class])
};
void (* lg_objc_msgSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper;
lg_objc_msgSendSuper(&superStruct, _cmd, newValue);
}
上篇有一点是,将 class 的实现重定义给了 lg_class
Class lg_class(id self,SEL _cmd){
return object_getClass(self);
}
所以在 lg_setter 中的[self class] 实际上走的就是 lg_class 方法,lg_class 的self , 所以object_getClass 得到的就是动态子类 ,然后在通过动态子类得到它的父类,LGPerson,好将方法发送给父类
打印结构:
接下来就探索一下,当检测到属性的值改变后,或其中调用了 lg_setter 又是怎么回调的呢?于是我们又模拟一下观察的回调的方法的实现
代码实现:
NSObject+LGKVO.h 类中添加一个方法,并实现
- (void)lg_observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object oldValue:(id)oldValue newValue:(id)newValue;
NSObject+LGKVO.m
#import "NSObject+LGKVO.h"
#import <objc/message.h>
static NSString *const kLGKVOPrefix = @"LGKVONotifying_";
static NSString *const kLGKVOAssiociateKey = @"kLGKVO_AssiociateKey";
@implementation NSObject (LGKVO)
- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context{
//新添加步骤
//4:保存观察者
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// 1: 验证setter
[self judgeSetterMethodFromKeyPath:keyPath];
// 2: 动态生成子类
Class newClass = [self createChildClassWithKeyPath:keyPath];
// 3: isa 指向 isa_swizzling
object_setClass(self, newClass);
}
#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];
}
}
#pragma mark -
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
// 2.1 判断是否有了
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLGKVOPrefix,oldClassName];// LGKVONotifying_LGPerson
Class newClass = NSClassFromString(newClassName);
if (!newClass) {
// 2.2 申请类
newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
// 2.3 注册类
objc_registerClassPair(newClass);
// 2.4.1 添加class方法
SEL classSEL = NSSelectorFromString(@"class");
Method classMethod = class_getClassMethod([self class], @selector(class));
const char *classType = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSEL, (IMP)lg_class, classType);
}
// 2.4.2 添加setter方法 setNickname
// 判断一下
SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getClassMethod([self class], setterSEL);
const char *setterType = method_getTypeEncoding(setterMethod);
class_addMethod(newClass, setterSEL, (IMP)lg_setter, setterType);
return newClass;
}
static void lg_setter(id self,SEL _cmd,id newValue){
NSLog(@"来了:%@",newValue);
//0:取出旧值
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
id oldValue = [self valueForKey:keyPath];
//1:消息发送给父类方法
struct objc_super superStruct = {
.receiver = self,
.super_class = class_getSuperclass([self class])
};
void (* lg_objc_msgSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper;
lg_objc_msgSendSuper(&superStruct, _cmd, newValue);
//2:KVO 观察到你有变化了, -> 回调信息 新添加步骤
//取出观察者
id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
SEL observerSEL = @selector(lg_observeValueForKeyPath:ofObject:oldValue:newValue:);
objc_msgSend(observer, observerSEL, self, keyPath, oldValue, newValue);
}
Class lg_class(id self,SEL _cmd){
return object_getClass(self);
}
#pragma mark - 从get方法获取set方法的名称 key ===>>> setKey:
static NSString *setterForGetter(NSString *getter){
if (getter.length <= 0) { return nil;}
NSString *firstString = [[getter substringToIndex:1] uppercaseString];
NSString *leaveString = [getter substringFromIndex:1];
return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
}
#pragma mark - 从set方法获取getter方法的名称 set<Key>:===> key
static NSString *getterForSetter(NSString *setter){
if (setter.length <= 0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) { return nil;}
NSRange range = NSMakeRange(3, setter.length-4);
NSString *getter = [setter substringWithRange:range];
NSString *firstString = [[getter substringToIndex:1] lowercaseString];
return [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
}
@end
验证:
LGViewController.m
- (void)lg_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object oldValue:(id)oldValue newValue:(id)newValue {
NSLog(@"oldValue = %@ --- newValue = %@", oldValue,newValue);
}
打印结果:
注意:其中关联对象 objc_setAssociatedObject 的使用
//设置关联对象
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy)
//获取关联对象
OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
//移除
OBJC_EXPORT void
objc_removeAssociatedObjects(id _Nonnull object)
自定义 KVO 的数据传输
上面说到的关联对象可以用来保存观察者,是可以的,在前面的运用中存一个,是没毛病,那同时观察多个的时候再用上面的方法,就有些狭隘了,一般我们在存多个相同类型的数据或对象时,我们会想到用数组,如果一个存储目标有多个属性时,就可以用模型或数组来赋值后,在用数组存储,接下来就实现一下
在 NSObject+LGKVO 里新添一个类,并实现初始化方法。
NSObject+LGKVO.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_OPTIONS(NSUInteger, LGKeyValueObservingOptions) {
LGKeyValueObservingOptionsNew = 0x01,
LGKeyValueObservingOptionsOld = 0x02,
};
@interface LGKVOInfo : NSObject
@property (nonatomic, strong) NSObject *observer;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, assign) LGKeyValueObservingOptions options;
- (instancetype)initWithObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGKeyValueObservingOptions)options;
@end
@interface NSObject (LGKVO)
- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGKeyValueObservingOptions)options context:(nullable void *)context;
- (void)lg_observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object oldValue:(id)oldValue newValue:(id)newValue;;
- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end
NSObject+LGKVO.m
#import "NSObject+LGKVO.h"
#import <objc/message.h>
static NSString *const kLGKVOPrefix = @"LGKVONotifying_";
static NSString *const kLGKVOAssiociateKey = @"kLGKVO_AssiociateKey";
@implementation LGKVOInfo : NSObject
- (instancetype)initWithObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGKeyValueObservingOptions)options {
if (self = [super init]) {
self.observer = observer;
self.keyPath = keyPath;
self.options = options;
}
return self;
}
@end
@implementation NSObject (LGKVO)
- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGKeyValueObservingOptions)options context:(nullable void *)context{
// 1: 验证setter
[self judgeSetterMethodFromKeyPath:keyPath];
// 2: 动态生成子类
Class newClass = [self createChildClassWithKeyPath:keyPath];
// 3: isa 指向 isa_swizzling
object_setClass(self, newClass);
//4:保存观察者
LGKVOInfo *info = [[LGKVOInfo alloc] initWithObserver:self forKeyPath:keyPath options:options];
//用一个集合来收集
NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
if (!mArray) {
mArray = [NSMutableArray arrayWithCapacity:1];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[mArray addObject:info];
}
#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];
}
}
#pragma mark -
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
// 2.1 判断是否有了
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLGKVOPrefix,oldClassName];// LGKVONotifying_LGPerson
Class newClass = NSClassFromString(newClassName);
if (!newClass) {
// 2.2 申请类
newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
// 2.3 注册类
objc_registerClassPair(newClass);
// 2.4.1 添加class方法
SEL classSEL = NSSelectorFromString(@"class");
Method classMethod = class_getClassMethod([self class], @selector(class));
const char *classType = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSEL, (IMP)lg_class, classType);
}
// 2.4.2 添加setter方法 setNickname
// 判断一下
SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getClassMethod([self class], setterSEL);
const char *setterType = method_getTypeEncoding(setterMethod);
class_addMethod(newClass, setterSEL, (IMP)lg_setter, setterType);
return newClass;
}
static void lg_setter(id self,SEL _cmd,id newValue){
NSLog(@"来了:%@",newValue);
//0:取出旧值
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
id oldValue = [self valueForKey:keyPath];
//1:消息发送给父类方法
struct objc_super superStruct = {
.receiver = self,
.super_class = class_getSuperclass([self class])
};
void (* lg_objc_msgSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper;
lg_objc_msgSendSuper(&superStruct, _cmd, newValue);
//2:KVO 观察到你有变化了, -> 回调信息
//取出观察者
NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
for (LGKVOInfo *info in mArray) {
if ([info.keyPath isEqualToString:keyPath]) {
if (info.options & LGKeyValueObservingOptionsNew) {
SEL observerSEL = @selector(lg_observeValueForKeyPath:ofObject:oldValue:newValue:);
objc_msgSend(info.observer, observerSEL, self, keyPath, oldValue, newValue);
}
}
}
}
Class lg_class(id self,SEL _cmd){
return object_getClass(self);
}
#pragma mark - 从get方法获取set方法的名称 key ===>>> setKey:
static NSString *setterForGetter(NSString *getter){
if (getter.length <= 0) { return nil;}
NSString *firstString = [[getter substringToIndex:1] uppercaseString];
NSString *leaveString = [getter substringFromIndex:1];
return [NSString stringWithFormat:@"set%@%@:",firstString,leaveString];
}
#pragma mark - 从set方法获取getter方法的名称 set<Key>:===> key
static NSString *getterForSetter(NSString *setter){
if (setter.length <= 0 || ![setter hasPrefix:@"set"] || ![setter hasSuffix:@":"]) { return nil;}
NSRange range = NSMakeRange(3, setter.length-4);
NSString *getter = [setter substringWithRange:range];
NSString *firstString = [[getter substringToIndex:1] lowercaseString];
return [getter stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:firstString];
}
@end
到这里同时监听多个keyPath 进行缓存相关参数的问题,就解决了
LGViewController.m 部分代码
#pragma mark - KVO回调
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@",change);
}
- (void)lg_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object oldValue:(id)oldValue newValue:(id)newValue {
NSLog(@"oldValue = %@ --- newValue = %@", oldValue,newValue);
}
然后我们看到外面使用的地方LGViewController 里还要单独调个回调来得到参数,要是同时监听了几个方法,那在回调里还要做个判断,到底是那个keyPath的对调,然后针对这个问题,我们又进一步优化,可以利用Block来完成优化,见下一部分
自定义 KVO 的函数式编程思想
代码:
NSObject+LGKVO.h 添加一个回调,然后添加监听的方法也可以做一下修改
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_OPTIONS(NSUInteger, LGKeyValueObservingOptions) {
LGKeyValueObservingOptionsNew = 0x01,
LGKeyValueObservingOptionsOld = 0x02,
};
typedef void(^LGKVOBlock)(id observe, NSString *keyPath, id oldValue, id newValue);
@interface LGKVOInfo : NSObject
@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, assign) LGKeyValueObservingOptions options;
@property (nonatomic, copy) LGKVOBlock kvoBlock;
- (instancetype)initWithObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LGKeyValueObservingOptions)options handle:(LGKVOBlock)handle;
@end
@interface NSObject (LGKVO)
- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handle:(LGKVOBlock)handle;
- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end
NS_ASSUME_NONNULL_END
NSObject+LGKVO.m 部分代码
#import "NSObject+LGKVO.h"
#import <objc/message.h>
static NSString *const kLGKVOPrefix = @"LGKVONotifying_";
static NSString *const kLGKVOAssiociateKey = @"kLGKVO_AssiociateKey";
@implementation LGKVOInfo : NSObject
- (instancetype)initWithObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handle:(nonnull LGKVOBlock)handle {
if (self = [super init]) {
_observer = observer;
_keyPath = keyPath;
_kvoBlock = handle;
}
return self;
}
@end
@implementation NSObject (LGKVO)
- (void)lg_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath handle:(nonnull LGKVOBlock)handle {
// 1: 验证setter
[self judgeSetterMethodFromKeyPath:keyPath];
// 2: 动态生成子类
Class newClass = [self createChildClassWithKeyPath:keyPath];
// 3: isa 指向 isa_swizzling
object_setClass(self, newClass);
//4:保存观察者
LGKVOInfo *info = [[LGKVOInfo alloc] initWithObserver:self forKeyPath:keyPath handle:handle];
//用一个集合来收集
NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
if (!mArray) {
mArray = [NSMutableArray arrayWithCapacity:1];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[mArray addObject:info];
}
static void lg_setter(id self,SEL _cmd,id newValue){
NSLog(@"来了:%@",newValue);
//0:取出旧值
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
id oldValue = [self valueForKey:keyPath];
//1:消息发送给父类方法
struct objc_super superStruct = {
.receiver = self,
.super_class = class_getSuperclass([self class])
};
void (* lg_objc_msgSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper;
lg_objc_msgSendSuper(&superStruct, _cmd, newValue);
//2:KVO 观察到你有变化了, -> 回调信息
//取出观察者
NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
for (LGKVOInfo *info in mArray) {
if ([info.keyPath isEqualToString:keyPath]) {
info.kvoBlock(self, keyPath, oldValue, newValue);
}
}
}
- (void)lg_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLGKVOAssiociateKey));
if (observerArr.count<=0) {
return;
}
for (LGKVOInfo *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);
}
}
NSObject+LGKVO.m 中需做以上修改
再贴出调用的地方
LGViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.person = [[LGPerson alloc] init];
[self.person lg_addObserver:self forKeyPath:@"nickName" handle:^(id _Nonnull observe, NSString * _Nonnull keyPath, id _Nonnull oldValue, id _Nonnull newValue) {
NSLog(@"oldValue = %@ --- newValue = %@", oldValue,newValue);
}];
}
这样当观察的属性值改变时就可以直接在 回调里,直接捕捉到变化了,并做相应的逻辑处理
自定义 KVO 的自动销毁
上一章有说KVO 的最后一步是将对象的isa 只会父类就是销毁,这一步是否放在 dealloc,要是NSObject 里面想要是想 dealloc 又要开始消息的转发来实现,流程就像上面setter的的转发一样,添加 lg_dealloc
NSObject+LGKVO.m
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
// 2.1 判断是否有了
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLGKVOPrefix,oldClassName];// LGKVONotifying_LGPerson
Class newClass = NSClassFromString(newClassName);
if (newClass) return newClass;
// 2.2 申请类
newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
// 2.3 注册类
objc_registerClassPair(newClass);
// 2.3.1 添加class方法
SEL classSEL = NSSelectorFromString(@"class");
Method classMethod = class_getClassMethod([self class], @selector(class));
const char *classType = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSEL, (IMP)lg_class, classType);
// 2.3.2 添加setter方法 setNickname
// 判断一下
SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getClassMethod([self class], setterSEL);
const char *setterType = method_getTypeEncoding(setterMethod);
class_addMethod(newClass, setterSEL, (IMP)lg_setter, setterType);
// 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;
}
static void lg_dealloc(id self,SEL _cmd){
Class superClass = [self class];//这里的 superClass原来的LGPerson类
object_setClass(self, superClass);//将当前对象指回 LGPerson
}
在 lg_dealloc 中将 isa的指回去,就大概完成了KVO整个流程