16、iOS底层分析 - 自定义KVO

自定义KVO

分析了KVO的实现原理,为了加深印象模仿系统的方式自定义一个KVO

一、接口设计

1.1 基本功能接口

模仿系统的KVO接口,提供以下基本接口。

  1. 注册观察者
  2. 移除观察者
  3. 回调
//  NSObject+LJLKVO.h
#import <Foundation/Foundation.h>
#import "LJLKVOInfo.h"

@interface NSObject (LJLKVO)
//注册观察者
- (void)ljl_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LJLKeyValueObservingOptions)options context:(nullable void *)context;
//回调
- (void)ljl_observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
//移除观察者
- (void)ljl_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;
@end

LJLKVOInfo 抽象对象

//  LJLKVOInfo.h
#import <Foundation/Foundation.h>

typedef NS_OPTIONS(NSUInteger, LJLKeyValueObservingOptions) {
    LJLKeyValueObservingOptionNew = 0x01,
    LJLKeyValueObservingOptionOld = 0x02,
};

@interface LJLKVOInfo : NSObject
//这个地方 NSObject 要用 weak 进行修饰。
//因这个 对象在外层是(person)self -> mArray -> info - > observer(外层的VC) -> person
//如果使用stong 会造成循环引用
@property(nonatomic, weak) NSObject * observer;
@property(nonatomic, copy) NSString * keyPath;
@property(nonatomic, assign) LJLKeyValueObservingOptions options;

- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LJLKeyValueObservingOptions)options;
@end
//  LJLKVOInfo.m
#import "LJLKVOInfo.h"

@implementation LJLKVOInfo
- (instancetype)initWitObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LJLKeyValueObservingOptions)options
{
    self = [super init];
    if (self) {
        self.observer = observer;
        self.keyPath = keyPath;
        self.options = options;
    }
    return self;
}
@end

注册观察者时需要完成以下功能:

  1. 入参检查(观察的是setter,过滤掉实例变量)
  2. 检查是否有属性的setter
  3. 动态创建对象子类BLKVOClass_xxx
  4. isa-swizzling
  5. 重写-class-dealloc方法
  6. 重写setter
  7. 保存观察者信息,在属性发生变化时回调

2.2.1 基本思路

  1. 根据keyPath 获取 setter 以及根据 setter 获取 keyPath,参照KVC的查找优先级,并注意要转换 keyPath 的首字母大小写
  2. 动态创建的 LJLKVONotifying__xxx 子类,只创建一次后不再销毁,因此每次要检查是否已经创建过
  3. 重写的方法也要检查是否已存在
  4. 重写的 class 方法返回父类,即原先的类,这样外界调用[obj class]可以得到正确的类型
  5. dealloc 方法有些特殊,在ARC下不能直接重写,也不能显式的调用[super dealloc],因此在实现中采用method-swizzling的方式。
  6. 添加观察者时也要查重,如果observerkeyPathcontext都相同,则不再重复添加
  7. 移除观察者的设计是,observerkeyPathcontext三个参数都可以是空,当observernil,则移除所有观察者,否则按照其他两个参数进行精确匹配来移除。
  8. 由于KVO是通过分类实现,因此保存观察者信息通过关联对象的方式实现,为当前被观察者添加一个可变字典的关联对象,以keyPathkey,可变数组为value,可变数组元素是model
//  NSObject+LJLKVO.m

#import "NSObject+LJLKVO.h"
#import <objc/message.h>

static NSString * const kLJLKVOPrefix = @"LJLKVONotifying_";
static NSString *const kLJLKVOAssiociateKey = @"kLJLKVO_AssiociateKey";

@implementation NSObject (LJLKVO)

-(void)ljl_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(LJLKeyValueObservingOptions)options context:(void *)context
{
//    1、验证是否存在setter 方法,过滤实例变量
    [self judgeSetterMethodFromKeyPath:keyPath];
//    2、动态生成子类
    Class newClass = [self createChildClassWithKeyPath:keyPath];
    // 3: isa的指向 : LGKVONotifying_LGPerson
    object_setClass(self, newClass);
    // 4: 保存观察者信息
//    面向对象的封装LJLKVOInfo
    LJLKVOInfo *info = [[LJLKVOInfo alloc] initWitObserver:observer forKeyPath:keyPath options:options];
//    5、处理多属性关联对象的问题
//    获取存放关联对象的数组。
    NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLJLKVOAssiociateKey));
    
//    如果不存在关联对象的数组就进行创建
    if (!observerArr) {
        observerArr = [NSMutableArray arrayWithCapacity:1];
        [observerArr addObject:info];
//        创建关联对象集合
        objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLJLKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
}

- (void)ljl_observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context{
    
}

- (void)ljl_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath{
    
//    获取关联对象
    NSMutableArray *observerArr = objc_getAssociatedObject(self, (__bridge const void * _Nonnull)(kLJLKVOAssiociateKey));
    if (observerArr.count<=0) {
        return;
    }
    
    for (LJLKVOInfo *info in observerArr) {
        if ([info.keyPath isEqualToString:keyPath]) {
            [observerArr removeObject:info];
            objc_setAssociatedObject(self, (__bridge const void * _Nonnull)(kLJLKVOAssiociateKey), observerArr, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            break;
        }
    }
    
    if (observerArr.count<=0) {
        // 将对象的 isa 指回给父类
        Class superClass = [self class];
        object_setClass(self, superClass);
    }
}

#pragma mark - 验证是否存在setter方法
- (void)judgeSetterMethodFromKeyPath:(NSString *)keyPath{
//   1、获取当前类的父类
    Class superClass = object_getClass(self);
//    2、方法编号
    SEL setterSeletor = NSSelectorFromString(setterForGetter(keyPath));
//    3、方法实现
    Method setterMethod = class_getInstanceMethod(superClass, setterSeletor);
    if (!setterMethod) {
//        如果没有好到setter方法就抛出异常
        @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"没有找到当前 %@ 的setter",keyPath] userInfo:nil];
    }
}

#pragma mark -
- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
//   1、 获取旧的类名
    NSString *oldClassName = NSStringFromClass([self class]);
//  2、生成新的类名
    NSString *newClassName = [NSString stringWithFormat:@"%@%@",kLJLKVOPrefix,oldClassName];
//    3、用生成的新的类名去获取类
    Class newClass = NSClassFromString(newClassName);
    // 4、如果获取到了就直接返回。防止重复创建生成新类
    if (newClass) return newClass;
    /**
     * 如果内存不存在,创建生成
     * 参数一: 父类
     * 参数二: 新类的名字
     * 参数三: 新类的开辟的额外空间
     */
    // 2.1 : 申请类
    newClass = objc_allocateClassPair([self class], newClassName.UTF8String, 0);
    // 2.2 : 注册类
    objc_registerClassPair(newClass);
    // 2.3.1 : 添加class : class的指向是LJLKVOPerson
    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);
//    class 方法 IMP 是lg_clss  所以后面再调[self class] 是调用的lg_class
    
    // 2.3.2 : 添加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);
    return newClass;
}

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)(kLJLKVOAssiociateKey));
    
    for (LJLKVOInfo *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 & LJLKeyValueObservingOptionNew) {
                    [change setObject:newValue forKey:NSKeyValueChangeNewKey];
                }
                if (info.options & LJLKeyValueObservingOptionOld) {
                    [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);
//              把self 换成superClass更好
//                objc_msgSend(info.observer,observerSEL,keyPath,class_getSuperclass(self),change,NULL);
            });
        }
    }
}

Class lg_class(id self,SEL _cmd){
//    这个地方过来的self 是对象,需要获取他的类 object_getClass(self)
    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];
}

以上就是一个完整的KVO是实现。下面调用一下试试

    self.person = [[LJLKVOPerson alloc] init];
    [self.person ljl_addObserver:self
                      forKeyPath:@"name"
                         options:(LJLKeyValueObservingOptionOld | LJLKeyValueObservingOptionNew)
                         context:NULL];
    
    [self printClassAllMethod:[LJLKVOPerson class]];
    [self printClasses:[LJLKVOPerson  class]];
    self.person.name = @"LJL";
    self.person->_nikcName    = @"1234";

经常测试打印正常,具体的打印内容就不再沾出来了。可以参考上一篇文章  iOS底层分析 - KVO
 

总结一下

自定义KVO
1、校验
      验证是否存在setter方法:不让实例变量进来
2、动态生成子类

  1.  动态开辟一个新类:LJLKVONotifying_xxx
  2.  注册类
  3.  添加class : class 的指向是 LGPerson
  4. setter方法:通过setter 赋值,实现父类的方法 self.name = @"ljl";
  5.  objc_getAssociatedObject 关联住我们的观察者

3、isa 的指向 LJLKVONotifying_LGPerson
4、消息转发:相应回调

 上篇文章分析到KVO 的生成的动态子类重写了 setter  、class 、dealloc 、_isKVOA 方法,这个dealloc 方法为什么要重写呢?又能做些什么呢?

dealloc 是进行析构,释放。KVO需要在不用的时候需要手动移除观察者,这样比较麻烦而且如果万一忘记移除还会出现一些问题,那么既然要自定义KVO 就做个自动释放吧。

//NSObject+LJLKVO.m
//这里不能直接写dealloc 因为这是NSObject 的分类会有很多系统的类调用到(在还没启动的时候都有可能会被调用到)
//- (void)dealloc{
//    Class superClass = [self class];
//    object_setClass(self, superClass);
//}
//进行方法交换
+ (BOOL)kc_hookOrigInstanceMenthod:(SEL)oriSEL newInstanceMenthod:(SEL)swizzledSEL {
    Class cls = self;
    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
    
    if (!swiMethod) {
        return NO;
    }
    if (!oriMethod) {
        class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
        method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ }));
    }
    
    BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
    if (didAddMethod) {
        class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
    }else{
        method_exchangeImplementations(oriMethod, swiMethod);
    }
    return YES;
}


+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        [self kc_hookOrigInstanceMenthod:NSSelectorFromString(@"dealloc") newInstanceMenthod:@selector(myDealloc)];
    });
}

- (void)myDealloc{
    //将isa 指针指回去
    Class superClass = [self class];
    object_setClass(self, superClass);
    [self myDealloc];
}

- (Class)createChildClassWithKeyPath:(NSString *)keyPath{
    
    ......
    // 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);
  
//进行方法交换,然后myDealloc 的时候释放 但是这样写的话如果
//    Method m1 = class_getInstanceMethod([self class], NSSelectorFromString(@"dealloc"));
//    Method m2 = class_getInstanceMethod([self class], @selector(myDealloc));
//    method_exchangeImplementations(m1, m2);
//    method_exchangeImplementations(m1, m2);
    
    return newClass;
}

除了模仿系统给的KVO之外,还实现了函数式KVO,使用Block 回调代替了回调方法 - observeValueForKeyPath:ofObject:change:context:; ,跟容易知道监听对象对应的内容,同时还有自动释放,不用在手动进行移除,以免出现忘记造成其他问题。完整代码:LGKVO

扩展:

最后说一下调用objc_msgSend 报错的问题。需要传参使用的时候需要修改一下配置

发布了83 篇原创文章 · 获赞 12 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/shengdaVolleyball/article/details/104742218