美文网首页iOS
iOS-底层探索20:自定义KVO

iOS-底层探索20:自定义KVO

作者: differ_iOSER | 来源:发表于2021-01-09 17:05 被阅读0次

iOS 底层探索 文章汇总

目录

一、自定义KVO流程

下面通过简单自定义KVO实现系统类似的功能,加深对KVO底层实现逻辑的理解。

目标如下:
  1. 模拟系统的KVO功能
  2. 实现自动移除观察者
  3. 响应式+函数式方式整合KVO使用代码
实现步骤:
  1. 验证是否存在setter方法:观察属性,不观察实例变量、成员变量
  2. 动态生成子类
    2.1 申请类(申请类之后、注册类之前可以添加ivar
    2.2 注册类
  3. 修改 isa 指向
  4. 添加父类setter同名方法
    • 可以添加方法:注册类后新增加的方法存在rwe中(dirty memory)
    • 不能添加属性:ivar只存在ro中(clean memory),因为类加载完毕后其内存大小已经确定
  5. 调用父类 setter
  6. 观察者响应KVO通知

二、自定义KVO实现

NAPerson:用于观察该对象中的属性
@interface NAPerson : NSObject {

@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSMutableArray *dataArray;

@end
NAKVOInfo:保存信息
typedef void (^LCJKVOBlock)(NSObject *observer, NSString *keyPath, id oldValue, id newValue);

@interface NAKVOInfo : NSObject

@property (nonatomic, copy) NSString *keyPath;
@property (nonatomic, weak) NSObject *observer;
@property (nonatomic, assign)  void *context;
@property (nonatomic, copy) LCJKVOBlock handleBlock;

- (instancetype)initWithObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context handleBlock:(LCJKVOBlock)block;

@end

//NAKVOInfo.m
- (instancetype)initWithObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context handleBlock:(LCJKVOBlock)block {
    self = [super init];
    if (self) {
        self.observer = observer;
        self.keyPath  = keyPath;
        self.context  = context;
        self.handleBlock = block;
    }
    return self;
}
NSObject+LCJKVO:通过分类提供添加观察者的方法
@interface NSObject (LCJKVO)

- (void)lcj_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context handleBlock:(LCJKVOBlock)block;

@end

//NSObject+LCJKVO.m
static NSString *const kLCJKVOPrefix        = @"LCJKVONotifying_";
static NSString *const kLCJKVOAssiociateKey = @"LCJKVO_AssiociateKey";

@implementation NSObject (LCJKVO)

- (void)lcj_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(nullable void *)context handleBlock:(LCJKVOBlock)block {
    //1. 验证是否存在setter方法
    [self judgeSetterMethodFromKeyPath:keyPath];
    
    NAKVOInfo *info = [[NAKVOInfo alloc] initWithObserver:observer forKeyPath:keyPath context:context handleBlock:block];
    
    NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLCJKVOAssiociateKey));
    if (!mArray) {
        mArray = [NSMutableArray arrayWithCapacity:1];
        // 保存观察者(分类中主要使用关联对象保存变量信息)(TODO:如果有这个Key就不添加了)
        // 这里不会存在引用关系,更不会出现循环引用
        objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLCJKVOAssiociateKey), mArray, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    [mArray addObject:info];
    
    //2. 动态生成子类
    Class newClass = [self createChildClassWithKeyPath:keyPath];
    
    //3. 修改 isa 指向
    object_setClass(self, newClass);
    
    //4. 添加父类setter同名方法 - 不能添加属性:ivar存在ro中(clean memory),因为类加载完毕后其内存大小已经确定
    //                         可以添加方法:方法存在dirty memory
    SEL setterSel = NSSelectorFromString(setterFromGetter(keyPath));
    Method method = class_getInstanceMethod([self class], setterSel);
    const char *type = method_getTypeEncoding(method);
    class_addMethod(newClass, setterSel, (IMP)lcj_setter, type);
}

#pragma mark - 验证是否存在setter方法
- (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath {
    Class superClass    = object_getClass(self);
    SEL setterSeletor   = NSSelectorFromString(setterFromGetter(keyPath));
    Method setterMethod = class_getInstanceMethod(superClass, setterSeletor);
    if (!setterMethod) {
        @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"没有%@的setter方法",keyPath] userInfo:nil];
    }
}

#pragma mark - 创建KVO中间子类
- (Class)createChildClassWithKeyPath:(NSString *)keyPath {
    NSString *oldClassName = NSStringFromClass([self class]);
    NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLCJKVOPrefix,oldClassName];
    Class newClass = NSClassFromString(newClassName);
    if (newClass) return newClass;
    //  2.1 申请类
    newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
    //  2.2 注册类
    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)lcj_class, classTypes);
    
    // 添加父类dealloc同名方法
    SEL deallocSEL = NSSelectorFromString(@"dealloc");
    Method deallocMethod = class_getInstanceMethod([self class], deallocSEL);
    const char *deallocTypes = method_getTypeEncoding(deallocMethod);
    class_addMethod(newClass, deallocSEL, (IMP)lcj_dealloc, deallocTypes);
    
    return newClass;
}

// 子类重写的setter imp
static void lcj_setter(id self,SEL _cmd,id newValue) {
    CJLog(@"进入了子类重写的setter方法:%@",newValue);
    NSString *keyPath = getteFromSetter(NSStringFromSelector(_cmd));
    id oldValue = [self valueForKey:keyPath];
    //5. 调用父类 setter
    // 封装objc_msgSendSuper方法
    void (*lcj_msgSendSuper)(void *, SEL, id) = (void *)objc_msgSendSuper;
    struct objc_super superStruct = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self)),//= [self class]
    };
    lcj_msgSendSuper(&superStruct, _cmd, newValue);
    
    // TODO:实现KVO自动开关
    
    //6. 观察者响应KVO通知
    NSMutableArray *mArray = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLCJKVOAssiociateKey));
    for (NAKVOInfo *info in mArray) {
        if ([info.keyPath isEqualToString:keyPath] && info.handleBlock) {//TODO 暂未考虑context
            info.handleBlock(info.observer, keyPath, oldValue, newValue);
        }
    }
}

#pragma mark - 从get方法获取set方法的名称 key ===>>> setKey:
static NSString *setterFromGetter(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];
}

// 重写的class方法
Class lcj_class(id self, SEL _cmd) {
    return class_getSuperclass(object_getClass(self));
}

// 重写的dealloc方法
static void lcj_dealloc(id self,SEL _cmd) {
    // isa 指回父类
    Class superClass = [self class];//已重写了这个方法
    object_setClass(self, superClass);
}

#pragma mark - 从set方法获取getter方法的名称 set<Key>:===> key
static NSString *getteFromSetter(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
ViewController:使用自定义KVO
- (void)viewDidLoad {
    [super viewDidLoad];

    self.person = [NAPerson new];
    [self.person lcj_addObserver:self forKeyPath:@"name" context:NULL handleBlock:^(NSObject * _Nonnull observer, NSString * _Nonnull keyPath, id  _Nonnull oldValue, id  _Nonnull newValue) {
        NSLog(@"%@-%@",oldValue,newValue);
    }];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.person.name = @"differ";
}

三、扩展一:KVOController使用

#import "TestViewController.h"
#import "NAStudent.h"
#import <FBKVOController.h>

@interface TestViewController ()
@property (nonatomic, strong) NAPerson *person;
@property (nonatomic, strong) FBKVOController *kvoCtrl;
@end

@implementation TestViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.person = [NAPerson new];
    self.person.dataArray = [NSMutableArray arrayWithObject:@"1"];

    self.kvoCtrl = [FBKVOController controllerWithObserver:self];
    [self.kvoCtrl observe:self.person keyPath:@"name" options:(NSKeyValueObservingOptionNew) block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
        NSLog(@"****%@****",change);
    }];

    //集合类型的观察
    [self.kvoCtrl observe:self.person keyPath:@"dataArray" options:(NSKeyValueObservingOptionNew) block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
        NSLog(@"****%@****",change);
    }];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    self.person.name = @"differ";
    [[self.person mutableArrayValueForKey:@"dataArray"] addObject:self.person.name];
}

@end

使用KVOController也不需要手动移除观察者,进入其底层代码可看到对kvoCtrl做了弱引用:__weak FBKVOController *_controller;

KVOController流程图如下:

四、扩展一:GNU

由于Apple Foundation层源码不开源导致不清楚底层的实现逻辑,而GNU被认为是Foundation层最接近的源码实现。GNU下载地址 下载GNUstep Base

进入KVO原生添加观察者方法:- (void)addObserver:forKeyPath:options:context:
可以看到是在NSObject(NSKeyValueObserverRegistration)分类中。
GNU项目中搜索NSKeyValueObserverRegistration即可看到KVOGNU源码。

参考

Facebook开源KVO三方框架:KVOController

Advancements in the Objective-C runtime

ro、rw、rwe介绍:iOS-底层探索13:分类的加载

相关文章

网友评论

    本文标题:iOS-底层探索20:自定义KVO

    本文链接:https://www.haomeiwen.com/subject/ogebvktx.html