KVC实现过程简析

KVC可以访问私有属性,有两个方法- (void)setValue:(nullable id)value forKey:(NSString *)key- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath,这肯定大家都知道。接下来我们自定义一个KVC,从而了解一下它的实现过程。本文旨在探索他的实现过程,所以只针对对象类型,未对非对象类型进行检验。

查找规则

首先会根据key查找相关方法,如果没找到会再找相关成员变量。

赋值:

相关方法:set:, _set:, setIs:

相关成员变量:_, _is, , is

取值:

相关方法::, _:

相关成员变量:_ _is is

了解上边的规则后,我们来创建一个NSObject的分类,因为要用到runtime,所以得打开runtime开关,并导入投文件#import ,仿照系统KVC创建自己的方法,如下:

赋值

- (void)sj_setValue:(nullable id)value forKey:(NSString *)key {
   
    // 判断是否合法,key不能为空,否则会报UndefineKey的错误
    if (key == nil && key.length == 0) {
        return;
    }
    
    // Key
    NSString* Key = key.capitalizedString;
    
    /// 先找相关方法
    //set:, _set:, setIs:
    NSString* setKey = [NSString stringWithFormat:@"set%@:", Key];
    if ([self respondsToSelector:NSSelectorFromString(setKey)]) {
        [self performSelector:NSSelectorFromString(setKey) withObject:value];
        return;
    }
    
    NSString* _setKey = [NSString stringWithFormat:@"_set%@:", Key];
    if ([self respondsToSelector:NSSelectorFromString(_setKey)]) {
        [self performSelector:NSSelectorFromString(_setKey) withObject:value];
        return;
    }
    
    NSString* setIsKey = [NSString stringWithFormat:@"setIs%@:", Key];
    if ([self respondsToSelector:NSSelectorFromString(setIsKey)]) {
        [self performSelector:NSSelectorFromString(setIsKey) withObject:value];
        return;
    }

    // 如果accessInstanceVariablesDirectly返回NO的话,表示不允许查找成员变量,代码就不会往下走了,默认为YES
    if (![self.class accessInstanceVariablesDirectly]) {
        NSException* exception = [NSException exceptionWithName:@"NSUnkonwnKeyException" reason:@"setValue:forUndefineKey" userInfo:nil];
        @throw exception;
    }
    
    /// 再找相关变量
    /// 获取所以成员变量- 运用到runtime
    unsigned int count = 0;
    Ivar* ivars = class_copyIvarList([self class], &count);// 获取成员变量的列表和数量
    
    NSMutableArray* arr = [[NSMutableArray alloc] init];
    
    for (int i = 0; i < count; i++) {
        Ivar var = ivars[i];
        const char* varName = ivar_getName(var);
        NSString* name = [NSString stringWithUTF8String:varName];
        [arr addObject:name];
    }
    
    // _ _is  is
    for (int i = 0; i < count; i++) {
        NSString* keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"_%@", key]]) {
            object_setIvar(self, ivars[i], value);// 给成员变量赋值
            free(ivars);
            return;
        }
    }
    
    for (int i = 0; i < count; i++) {
        NSString* keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"_is%@", Key]]) {
            object_setIvar(self, ivars[i], value);
            free(ivars);
            return;
        }
    }
    
    for (int i = 0; i < count; i++) {
        NSString* keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"%@", key]]) {
            object_setIvar(self, ivars[i], value);
            free(ivars);
            return;
        }
    }
    
    for (int i = 0; i < count; i++) {
        NSString* keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"is%@", Key]]) {
            object_setIvar(self, ivars[i], value);
            free(ivars);
            return;
        }
    }

    // 如果都没找到,手动出发报错
    [self setValue:value forUndefinedKey:key];
    free(ivars);//这里要手动释放
    
}

可以在操作的类中实现下面这个方法,来控制是否可以查找成员变量

// 是否允许查找成员变量,默认为YES
+(BOOL)accessInstanceVariablesDirectly {
    return NO;
}

取值

跟赋值一模一样,前提是按照前边的规则来取:

- (id)sj_valueForKey:(NSString *)key  {
    
    // 判断是否合法
    if (key == nil && key.length == 0) {
        return nil;
    }
    
    /// 先找相关方法
    // :, _:
    NSString* Key = [NSString stringWithFormat:@"get%@", key];
    if ([self respondsToSelector:NSSelectorFromString(Key)]) {
        id value = [self performSelector:NSSelectorFromString(Key)];
        return value;
    }
    
    NSString* _Key = [NSString stringWithFormat:@"_%@", key];
    if ([self respondsToSelector:NSSelectorFromString(_Key)]) {
        id value = [self performSelector:NSSelectorFromString(_Key)];
        return value;
    }
    
    if (![self.class accessInstanceVariablesDirectly]) {
        NSException* exception = [NSException exceptionWithName:@"NSUnkonwnKeyException" reason:@"setValue:forUndefineKey" userInfo:nil];
        @throw exception;
    }
    
    /// 再找相关变量
    /// 获取所以成员变量
    unsigned int count = 0;
    Ivar* ivars = class_copyIvarList([self class], &count);
    
    NSMutableArray* arr = [[NSMutableArray alloc] init];
    
    for (int i = 0; i < count; i++) {
        Ivar var = ivars[i];
        const char* varName = ivar_getName(var);
        NSString* name = [NSString stringWithUTF8String:varName];
        [arr addObject:name];
    }
    
    // _ _is  is
    for (int i = 0; i < count; i++) {
        NSString* keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"_%@", key]]) {
//            object_setIvar(self, ivars[i], value);
            id value = object_getIvar(self, ivars[i]);
            free(ivars);
            return value;
        }
    }
    
    for (int i = 0; i < count; i++) {
        NSString* keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"_is%@", Key]]) {
            id value = object_getIvar(self, ivars[i]);
            free(ivars);
            return value;
        }
    }
    
    for (int i = 0; i < count; i++) {
        NSString* keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"%@", key]]) {
            id value = object_getIvar(self, ivars[i]);
            free(ivars);
            return value;
        }
    }
    
    for (int i = 0; i < count; i++) {
        NSString* keyName = arr[i];
        if ([keyName isEqualToString:[NSString stringWithFormat:@"is%@", Key]]) {
            id value = object_getIvar(self, ivars[i]);
            free(ivars);
            return value;
        }
    }
    
    [self valueForUndefinedKey:key];
    free(ivars);
    
    return nil;
}

容错

1.key不能为空,不管是设值还是取值。
2.对非对象类型,值不能为空。

所以我们在类中实现以下几个方法来防止崩溃:

// 对非对象类型,值不能为空
- (void) setNilValueForKey:(NSString *)key {
    NSLog(@"%@ 的值不能为空", key);
}

// 赋值key值不存在
- (void) setValue:(id)value forUndefinedKey:(NSString *)key {
    NSLog(@"key = %@不存在 ", key);
}

// 取值key值不存在
- (id) valueForUndefinedKey:(NSString *)key {
    NSLog(@"key = %@ 的值不存在", key);
    return nil;
}

校验

比如定义一个Persion类,加一个age的属性,可以在.m中验证age的值,这个在实际开发中应用会比较广,用于限制属性的合法性:

- (BOOL) validateAge:(inout id  _Nullable __autoreleasing *)ioValue  error:(out NSError * _Nullable __autoreleasing *)outError {
    NSNumber* value = (NSNumber*)*ioValue;
    NSLog(@"%@", value);
    if (value <= 0 || value >= 200) {
        return NO;
    }
    return YES;
}

方法名要对应属性名,比如属性为age,那么方法名就是validateAge:....

调用

- (void)viewDidLoad {
    [super viewDidLoad];

    Person* p = [Person new];
    
    NSNumber* value = @200;
    if ([p validateValue:&value forKey:@"age" error:NULL]) {
        [p setValue:value forKey:@"age"];
    }

}

你可能感兴趣的:(KVC实现过程简析)