面试基础iOS面试题+基础知识

自己实现KVO(block)

2021-07-13  本文已影响0人  晨曦中的花豹

先向先驱者致敬[https://tech.glowing.com/cn/implement-kvo/]

看了我之前的仿系统实现KVO自己实现KVO(代理)的同学,看这个应该不会太费劲,但是其中有几个优化点和注意的问题

下面是我在前者之上做了具体的分析
包含两部分kvo_block分类LXCObservationInfo block保存对象
kvo_block分类

//
//  NSObject+kvo_block.m
//  leetCode
//
//  Created by 刘晓晨 on 2021/7/13.
//

#import "NSObject+kvo_block.h"

static NSString *kvo_class_prefix = @"KVOClass_";
const void *kvo_observers = &kvo_observers;

@implementation NSObject (kvo_block)

- (void)lxc_addObserver:(NSObject *)observer forKey:(NSString *)key block:(AddObserverBlock)block {
    
    // 1.判断属性是否存在
    SEL setterSelector = NSSelectorFromString(setterFromGetter(key));
    Method setterMethod = class_getInstanceMethod([self class], setterSelector);
    if (!setterMethod) {
        // 报异常,方便查找问题
        NSString *reason = [NSString stringWithFormat:@"%@没有%@对应的setter", self, key];
        @throw [NSException exceptionWithName:NSInvalidArgumentException
                                       reason:reason
                                     userInfo:nil];
    }
    
    //原始类
    Class oldClass = object_getClass(self);
    // 拿到原始类名
    NSString *oldClassName = NSStringFromClass(oldClass);
    
    //这里的判断主要是为了判断当前被监听对象是否已经被监听过,如果有被监听,则class名称带kvo前缀,不用重新创建子类,使用当前类即可
    if (![oldClassName hasPrefix:kvo_class_prefix]) {
        Class kvoClass =  [self makeKvoClassWithOriginalClassName:oldClassName];
        // 3.修改isa指针,使得self->isa指向子类
        object_setClass(self, kvoClass);
    }
    
    //判断是否属性关联到kvo_setter上,比如name,第一次监听name需要将sel与kvo_setter关联上,而第二次监听则不需要再次关联,但是实际发现重复添加并无问题
    IMP imp = class_getMethodImplementation(object_getClass(self), setterSelector);
    if (imp != (IMP)kvo_setter) {
        const char *types = method_getTypeEncoding(setterMethod);
        class_addMethod(object_getClass(self), setterSelector, (IMP)kvo_setter, types);
    }
    
    // 5.保存block及用于筛选的参数key和observer(这两个参数主要是在remove时候筛选使用)
    LXCObservationInfo *info = [[LXCObservationInfo alloc] init];
    info.block = block;
    info.key = key;
    info.observer = observer;
    
    
    //这里创建数组是因为同一个对象可能会被多个地方监听,这个时候需要去执行多个block
    NSMutableArray *observers = objc_getAssociatedObject(self, kvo_observers);
    if (!observers) {
        //创建保存监听对象的数组
        observers = [NSMutableArray array];
        objc_setAssociatedObject(self, kvo_observers, observers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    [observers addObject:info];
}

-(void)lxc_removeObserver:(NSObject *)observer forKey:(NSString *)key {
    NSMutableArray *observers = objc_getAssociatedObject(self, kvo_observers);
    for (LXCObservationInfo *observerInfo in observers) {
        /*
         这里判断如果监听对象一致,监听属性一致,及删除
         此处遇到了一个颠覆我认知的一个问题如果在info中我用weak来修饰observer,这个时候这里observerInfo.observer是获取不到的,因为调用这个方法一般都是在dealloc中,所以我认为所有指向自己的弱引用指针被置为nil是发生在dealloc之前,是不是很不可思议,但是实践证明确实如此,所以在info中我改为用assign修饰,因为此时外部传入的observer是有值的,所以assign修饰不会出问题
         */
        if (observerInfo.observer == observer && [observerInfo.key isEqualToString:key]) {
            [observers removeObject:observerInfo];
            break;
        }
    }
}

//创建子类
- (Class)makeKvoClassWithOriginalClassName:(NSString *)originalClazzName {
    NSString *kvoClazzName = [kvo_class_prefix stringByAppendingString:originalClazzName];
    Class clazz = NSClassFromString(kvoClazzName);
    
    if (clazz) {
        return clazz;
    }
    
    Class originalClazz = object_getClass(self);
    
    //让新的Class继承自原始类
    Class kvoClazz = objc_allocateClassPair(originalClazz, kvoClazzName.UTF8String, 0);
    
    //仿苹果隐藏子类(及重写本类的class对象方法)
    Method clazzMethod = class_getInstanceMethod(originalClazz, @selector(class));
    const char *types = method_getTypeEncoding(clazzMethod);
    class_addMethod(kvoClazz, @selector(class), (IMP)kvo_class, types);
    
    //注册子类
    objc_registerClassPair(kvoClazz);
    
    return kvoClazz;
}

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

//统一管理setter方法
void kvo_setter(id self, SEL _cmd, id newName)
{
    
    NSString *setterName = NSStringFromSelector(_cmd);
    NSString *getterName = getterFromSetter(setterName);
    
    id  oldValue;
    if (getterName) {
        oldValue = [self valueForKey:getterName];
    }
    // 获取kvo类型
    id class = object_getClass(self);
    
    // 调用父类的方法(此处还有一种方式是修改self isa 指向原始类,修改后在修改为 子类,这里使用的是系统实现super的方式,顺便可以了解下super和self的区别)
    Class super_class = class_getSuperclass(class);
    struct objc_super * _Nonnull super_struct = malloc(sizeof(struct objc_super));
    super_struct->receiver = self;
    super_struct->super_class = super_class;
    objc_msgSendSuper(super_struct, _cmd,newName);
    
    id  newValue = [self valueForKey:getterName];
    
    NSMutableArray *observers = objc_getAssociatedObject(self, kvo_observers);
    for (LXCObservationInfo *observer in observers) {
        if ([observer.key isEqualToString:getterName]) {
            observer.block(oldValue, newValue);
        }
    }
}

//通过属性获取setter字符串
NSString* setterFromGetter(NSString *key) {
    if (key.length > 0) {
        NSString *resultString = [key stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:[[key substringToIndex:1] capitalizedString]];
        return [NSString stringWithFormat:@"set%@:",resultString];
    }
    return nil;
}

//通过setter 获取getter
NSString* getterFromSetter(NSString *key) {
    if (key.length > 0) {
        NSString *resultString = [key substringFromIndex:3];
        resultString = [resultString substringToIndex:resultString.length - 1];
        return [resultString lowercaseString];
    }
    return nil;
}

@end

LXCObservationInfo block保存对象

//
//  LXCObservationInfo.h
//  leetCode
//
//  Created by 刘晓晨 on 2021/7/9.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

typedef void(^AddObserverBlock)(id oldValue, id newValue);

@interface LXCObservationInfo : NSObject

@property (nonatomic,assign)id observer;
@property (nonatomic,copy)NSString *key;
@property (nonatomic,copy)AddObserverBlock block;

@end

NS_ASSUME_NONNULL_END

最后是小插曲的验证
A弱引用B,B实现block(其中打印A弱引用的B),在B的dealloc中调用block,发现block执行,A弱引用的B竟然是nil

A中

@property (nonatomic,weak)TwoViewController *two;
self.two = vc;
__weak typeof(self) weakSelf = self;
vc.block = ^{
        NSLog(@"twoblock%@",weakSelf.two);
};

B中

-(void)dealloc {
    self.block();
}

最终打印

2021-07-13 18:52:28.715064+0800 leetCode[74996:13935331] twoblock(null)

ok关于KVO的内容到此结束,因为weak引用的问题,之后我会更新一版weak的知识点,希望大家多多支持

上一篇下一篇

猜你喜欢

热点阅读