前言
iOS-KVO原理分析这篇文章我们分析了KVO的原理,接下来,我们自己动手简单实现一个KVO。
准备工作
首先建一个RoKVO的工程,再建一个RoViewController,在storyboard中关联起来,如图:
![](https://img.haomeiwen.com/i25536745/767e1428f36489e7.png)
KVO自定义思路
我们知道KVO是通过添加NSObject的分类的实现,我们也模仿一下,新建一个NSObject+RoKVO的分类文件,然后我们再模护系统的添加和移除观察的观察的方法。如下:
- (void)ro_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;
- (void)ro_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
思路如下:
- 判断setter方法不让实例进来。
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];
}
}
在ro_addObserver这个方法中进行判断。
- KVO 核心:isa_siwzilling
- 1 申请类
- 2 添加方法
- 3 注册类
- isa指向交换
- setter 业务逻辑
动态生成子类
ro_addObserver 调用*- (Class)createChildClassWithKeyPath:(NSString )keyPath创建子类,后面会详细讲这个函数。
接着进行isa交换,代码如下:
object_setClass(self, newClass);
我们再讲createChildClassWithKeyPath这个核心功能。
首先我们要先申请类,调用* *这个API。
代码如下:
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@"%@%@",roKVOPrefix,oldClassName];
Class newClass = NSClassFromString(newClassName);
// 判断是否存在,存在直接返回
if (newClass) return newClass;
// 创建类
objc_allocateClassPair([self class],newClassName.UTF8String, 0);
接着我们去注册类,如下
objc_registerClassPair(newClass);
接着我们再讲下这个方法的核心功能,添加方法,如下:
- (Class)createChildClassWithKeyPath:(NSString *)keyPath {
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@"%@%@",roKVOPrefix,oldClassName];
Class newClass = NSClassFromString(newClassName);
// 判断是否存在,存在直接返回
if (newClass) return newClass;
// 创建类
newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
// 添加方法 setter
SEL setterSel = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod([self class], setterSel);
const char *setterType = method_getTypeEncoding(setterMethod);
class_addMethod(newClass, setterSel, (IMP)ro_setter, setterType);
// 添加方法 class
SEL classSel = NSSelectorFromString(@"class");
Method classMethod = class_getClassMethod([self class], classSel);
const char *classType = method_getTypeEncoding(classMethod);
class_addMethod(newClass, setterSel, (IMP)ro_class, classType);
// 注册类
objc_registerClassPair(newClass);
return newClass;
}
自定义KVO的setter
子类动态创建完了,我们来看下setter方法,如何重写。
由于篇幅过长,我们这里直接发代码,代码会有相关的注释。
如下:
static void ro_setter(id self,SEL _cmd,id newValue) {
// 往父类发送消息
struct objc_super ro_objc_super;
ro_objc_super.receiver = self;
ro_objc_super.super_class = class_getSuperclass(object_getClass(self));
void (*ro_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
ro_msgSendSuper(&ro_objc_super,_cmd,newValue);
// 既然观察到了,下一步是回调,接着让我们的观察者调用
//拿到观察者
id observer = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(RoKVOAssiociateKey));
// 2: 消息发送给观察者
SEL observerSEL = @selector(observeValueForKeyPath:ofObject:change:context:);
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
objc_msgSend(observer,observerSEL,keyPath,self,@{keyPath:newValue},NULL);
}
自定义KVO的多元素观察和移除观察
当我们需要观察多个元素的时候,要怎么处理呢,接下来我们来看下。
我们添加了一个RoKVOInfo用来存储观察KVO的数据的存储(代码会在结尾发出来)。
如何观察多元素呢,如ro_addObserver代码:
- (void)ro_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context {
if (keyPath.length ==0 ) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"keyPath不能为空"] userInfo:nil];
}
// 判断setter方法是否存在
[self judgeSetterMethodFromKeyPath:keyPath];
// 动态生成子类
Class newClass = [self createChildClassWithKeyPath:keyPath];
// isa指针交换
object_setClass(self, newClass);
//保存KVO信息,收集信息
RoKVOInfo *info = [[RoKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath options:(RoKeyValueObservingOptions)options];
NSMutableArray *array = objc_getAssociatedObject(self,(__bridge const void *)(roKVOAssiociateKey));
if (!array) {
array = [NSMutableArray arrayWithCapacity:1];
}
[array addObject:info];
objc_setAssociatedObject(self, (__bridge const void *)(roKVOAssiociateKey), array, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
ro_setter的代码:
static void ro_setter(id self,SEL _cmd,id newValue) {
// 通过cmd(sel)获取keyPath
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
id oldValue = [self valueForKey:keyPath];
// 往父类发送消息
struct objc_super ro_objc_super;
ro_objc_super.receiver = self;
ro_objc_super.super_class = class_getSuperclass(object_getClass(self));
void (*ro_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
ro_msgSendSuper(&ro_objc_super,_cmd,newValue);
NSMutableArray *array = objc_getAssociatedObject(self,
(__bridge const void *)roKVOAssiociateKey);
// 遍历查找是setter方法在观察
for (RoKVOInfo *info in array) {
if ([info.keyPath isEqualToString:keyPath]) {
dispatch_async(dispatch_queue_create(0, 0), ^{
NSMutableDictionary<NSKeyValueChangeKey, id> *change = [NSMutableDictionary dictionaryWithCapacity:1];
// 对新旧值进处处理
if (info.options & RoKeyValueObservingOptionNew) {
[change setObject:newValue forKey:NSKeyValueChangeNewKey];
}
if (info.options & RoKeyValueObservingOptionOld) {
[change setObject:@"" forKey:NSKeyValueChangeOldKey];
if (oldValue) {
[change setObject:oldValue forKey:NSKeyValueChangeOldKey];
}
}
void (*ro_objc_msgSend)(NSObject *observer,SEL , NSString *keyPath, id, NSMutableDictionary *change, void *context) = (void *)objc_msgSend;
// 消息发送给观察者
SEL observerSel = @selector(ro_observeValueForKeyPath:ofObject:change:context:);
ro_objc_msgSend(info.observer,observerSel,keyPath,self,change,NULL);
});
}
}
}
那么我们该如何移除我们的观察呢?
我们知道,KVO在移除观察时,会交换isa,那么我们模访官方的做法,如下代码:
- (void)ro_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
NSMutableArray *array = objc_getAssociatedObject(self,(__bridge const void *)(roKVOAssiociateKey));
if (array.count <=0) {
return;
}
for (RoKVOInfo *info in array) {
if ([info.keyPath isEqualToString:keyPath]) {
[array removeObject:info];
objc_setAssociatedObject(self, (__bridge const void *)(roKVOAssiociateKey), array, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
break;
}
}
if (array.count <=0) {
Class superClass = [self class];
object_setClass(self, superClass);
}
}
自定义KVO的函数式
我们知道KVO观察写的代码要多,一个回调会有很多if else的这种结构,那么我们怎么优化呢,当然可以通过函数式编程要优化,我们可以通过block实现。
- 定义一个block *typedef void(^RoKVOBlock)(id observer,NSString *keyPath,id oldValue,id newValue);
- 这个block需要存储在RoKVOInfo中。
- 发送消息的时候,直接使用block传数据。
函数式是映射数学上的函数概念,是定义域映射到值域上的关系,函数可以做为参数传参,也可以作为返回值
自定义KVO自动销毁
我们知道KVO是不会自动销毁的,需要手动销毁,我们实现了KVO的函数式,那么我们可以实现自动销毁吗?答案 是肯定的。下面我们来实现下。
流程:
- 重写dealloc方法
2.在重写的dealloc方法中释放。
结语
以个就是个在实现的一个简单的KVO,还有很多不完善的地方,后续会发出来一个比较完整的自定义的KVO。
附上KVO的代码
RoKVOInfo.h文件代码
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef void(^RoKVOBlock)(id observer,NSString *keyPath,id oldValue,id newValue);
@interface RoKVOInfo : NSObject
@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, copy) RoKVOBlock block;
- (instancetype)initWitObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath
block:(RoKVOBlock)block;
@end
NS_ASSUME_NONNULL_END
RoKVOInfo.m文件代码
#import "RoKVOInfo.h"
@implementation RoKVOInfo
- (instancetype)initWitObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath
block:(RoKVOBlock)block {
self = [super init];
if (self) {
_observer = observer;
_keyPath = keyPath;
_block = block;
}
return self;
}
@end
NSObject+RoKVO.h文件代码
#import <Foundation/Foundation.h>
#import "RoKVOInfo.h"
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (RoKVO)
- (void)ro_addObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context
block:(RoKVOBlock)block;
- (void)ro_removeObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath;
@end
NS_ASSUME_NONNULL_END
NSObject+RoKVO.m文件代码
#import "NSObject+RoKVO.h"
#import <objc/message.h>
static NSString *const roKVOPrefix = @"ROKVONotifying_"; //动态子类的前缀
static NSString *const roKVOAssiociateKey = @"ROKVO_AssiociateKey";
@implementation NSObject (RoKVO)
- (void)ro_addObserver:(NSObject *)observer
forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context
block:(RoKVOBlock)block {
if (keyPath.length ==0 ) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"keyPath不能为空"] userInfo:nil];
}
[self judgeSetterMethodFromKeyPath:keyPath];
// 动态生成子类
Class newClass = [self createChildClassWithKeyPath:keyPath];
// isa的指向 : LGKVONotifying_LGPerson
object_setClass(self, newClass);
// 保存信息
RoKVOInfo *info = [[RoKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath block:block];
NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(roKVOAssiociateKey));
if (!mArray) {
mArray = [NSMutableArray arrayWithCapacity:1];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(roKVOAssiociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[mArray addObject:info];
}
- (void)ro_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(roKVOAssiociateKey));
if (observerArr.count<=0) {
return;
}
for (RoKVOInfo *info in observerArr) {
if ([info.keyPath isEqualToString:keyPath]) {
[observerArr removeObject:info];
objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(roKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
break;
}
}
if (observerArr.count<=0) {
// 指回给父类
Class superClass = [self class];
object_setClass(self, superClass);
}
}
#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 {
NSString *oldClassName = NSStringFromClass([self class]);
NSString *newClassName = [NSString stringWithFormat:@"%@%@",roKVOPrefix,oldClassName];
Class newClass = NSClassFromString(newClassName);
// 防止重复创建生成新类
if (newClass) return newClass;
// 申请类
newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
// 注册类
objc_registerClassPair(newClass);
// 添加class
SEL classSEL = NSSelectorFromString(@"class");
Method classMethod = class_getInstanceMethod([self class], classSEL);
const char *classTypes = method_getTypeEncoding(classMethod);
class_addMethod(newClass, classSEL, (IMP)ro_class, classTypes);
// 添加setter
SEL setterSEL = NSSelectorFromString(setterForGetter(keyPath));
Method setterMethod = class_getInstanceMethod([self class], setterSEL);
const char *setterTypes = method_getTypeEncoding(setterMethod);
class_addMethod(newClass, setterSEL, (IMP)ro_setter, setterTypes);
// 添加dealloc
SEL deallocSEL = NSSelectorFromString(@"dealloc");
Method deallocMethod = class_getInstanceMethod([self class], deallocSEL);
const char *deallocTypes = method_getTypeEncoding(deallocMethod);
class_addMethod(newClass, deallocSEL, (IMP)ro_dealloc, deallocTypes);
return newClass;
}
static void ro_dealloc(id self,SEL _cmd){
Class superClass = [self class];
object_setClass(self, superClass);
}
#pragma mark - setter
static void ro_setter(id self,SEL _cmd,id newValue){
NSString *keyPath = getterForSetter(NSStringFromSelector(_cmd));
id oldValue = [self valueForKey:keyPath];
// 消息转发 转发给父类
// 改变父类的值 --- 可以强制类型转换
void (*ro_msgSendSuper)(void *,SEL , id) = (void *)objc_msgSendSuper;
struct objc_super superStruct = {
.receiver = self,
.super_class = class_getSuperclass(object_getClass(self)),
};
//objc_msgSendSuper(&superStruct,_cmd,newValue)
ro_msgSendSuper(&superStruct,_cmd,newValue);
// 信息数据回调
NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(roKVOAssiociateKey));
for (RoKVOInfo *info in mArray) {
if ([info.keyPath isEqualToString:keyPath] && info.block) {
info.block(info.observer, keyPath, oldValue, newValue);
}
}
}
Class ro_class(id self,SEL _cmd){
return class_getSuperclass(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
网友评论