美文网首页
KVC实现原理

KVC实现原理

作者: 85ca4232089b | 来源:发表于2020-03-13 18:28 被阅读0次

首先在开始之前介绍一个常见的术语:

一. 成员变量、实例变量、属性

  1. 成员变量:@interface() 生命的全部都是成员变量
  2. 实例变量: 实例变量是一种特殊的成员变量,由实例创建的对象 class类实例化出来的对象就是实例对象、id是一种特殊的class
  3. 属性 :@property声明的都是属性


    propertyy.png

    苹果最早的编译器是GCC 目前升级为LLVM,它的优点是:
    没有匹配到实例变量的属性时,会自动创建一个带下划线的属性,_name

@property

根据了解,@property标签主要就是用来帮大家生成get\set方法还能管理内存,但是在xcode4.4之前是需要配合一个叫@synthsize的标签才能合成set\get方法,这么说,也就是说看上去我们只写了一个@property其实系统还是默认加上了@synthsize。

@synthsize

是在编译期间系统会检查,用户是否实现了set\get方法如果实现了,就使用用户自己实现的,如果没有系统会默认加上.
@sythesie namep = _namep;指定属性对应的实例变量.

@dynamic

经查阅这个标签是告诉编译器,大爷我自己实现set/get方法,不管我到底实现没别来烦我(提示警告),如果依然没实现get\set而你又调用了该属性,那么结果就是程序崩溃.

二. KVC原理探索(分析源码)

推荐一个好的分析工具 苹果的官方文件

  1. KVC 是什么
    KVC是一种机制,通过NSKeyValueCoding 这种协议间接来访问他们的成员变量 ,通过键值编码的方式 (字典key-value).
  2. KVC有什么作用
    键值编码,属性的间接访问,赋值..
    [p setValue:@"hello" forKey:@"namep"];//key
    [self.textFiled setValue:[UIColor orangeColor] forKeyPath:@"_placeholderLabel.textColor"];//路由的方式

需要注意的是:如果是通过路由的方式访问一定要使用KeyPath的方式否则会奔溃.

  1. KVC的原理是什么
  2. 赋值&取值的过程
    赋值过程:
    1• 先找相关方法:通过set<Key>,_set <key>,setis<key> 的顺序来取值 ,对于非对象属性,将value的未包装版本设置为value。
    2• 若没有相关方法 +(BOOL)accessInstanceVariablesDriectly,判断是否可以间接访问成员变量
    • 如果是NO,直接执行KVC的setValue:forUndefineKey:(系统会跑出一个异常,未定义key)
    • 如果是YES,继续查找相关变量 _<key>,_is<Key>,<Key>,is<Key>
    3• 如果成员变量都不存在,setValue:forUndefineKey 抛出异常.
    setter.png
    Person *p = [[Person alloc] init];
    [p setValue:@"hello" forKey:@"name"];    
    NSLog(@"%@",p->isName);//(null)
    NSLog(@"%@-%@",p->name,p->isName);//(null)-(null)
    NSLog(@"%@-%@-%@",p->name,p->isName,p->_isName);//(null)-(null)-(null)
    NSLog(@"%@-%@-%@-%@",p->name,p->isName,p->_name,p->_isName);//(null)-(null)-hello-(null)

取值过程:
• 先找相关方法 :(分为两种类型: 简单的类型&集合类型)
1• 简单类型Basic Setter 在实例中搜索第一个访问器方法,该方法的名称是 get<Key>, <key>, is<Key>, or _<key>方法有没有实现,如果找到:
如果检索到的属性值是基础数据类型,则只需返回结果。
如果该值是由NSNumber支持的标量类型,则将其存储在NSNumber实例中并返回该值。
如果结果是不被NSNumber支持的标量类型,转换成NSValue对象并返回它。 否则下一步

2• 集合类型:通过countOf<Key> & objectIn<Key>AtIndex: (来源于NSArray和NSSet)
如果没有找到简单的访问方法,则在实例中搜索名称countOf<Key> & objectIn<Key>AtIndex:(对应于NSArray类定义的基本方法)和 AtIndexes:(对应于NSArray方法objectsAtIndexes:)匹配的方法。
如果找到其中一个方法那么创建一个集合代理对象来响应所有NSArray方法并返回它,否则下一步.
3• 如果没有找到简单的访问方法或数组访问方法组,则查找名为countOf、enumeratorOf和memberOf:(对应于NSSet类定义的基本方法)的三种方法。

4• 若没有找到相关方法 +(BOOL)accessInstanceVariablesDriectly,判断是否可以间接访问成员变量,默认返回YES。YES就会访问下层一系列的 _<key>, _is<Key>, <key>, or is<Key>
• 如果是NO,直接执行KVC的setValue:forUndefineKey:(系统会抛出一个异常,未定义key)
• 如果是YES,继续查找相关变量 _<key>,_is<Key>,<Key>,is<Key>
5• 如果成员变量都不存在,setValue:forUndefineKey 抛出异常.


path.png
    Person *p = [[Person alloc] init];
    [p setValue:@"name" forKey:@"name"];
    p->_name   = @"_name";
    p->_isName = @"_isName"; 
    p->name    = @"name";
    p->isName  = @"isName";
    NSLog(@"%@",[p valueForKey:@"name"]); // 方法 getName 输出: _name  第三步

三. 自定义KVC

仿照objc的源码的思路

- (void)customer_setValue:(nullable id)value forKey:(NSString *)key{
    
    // 1:刷选key 判断非空
    if (key == nil  || key.length == 0) {
        return;
    }
    
    // 2:找到相关方法 set<Key> _set<Key> setIs<Key>
    // key 要大写
    NSString *Key = key.capitalizedString;
    // 拼接方法
    NSString *setKey = [NSString stringWithFormat:@"set%@:",Key];
    NSString *_setKey = [NSString stringWithFormat:@"_set%@:",Key];
    NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:",Key];

    if ([self performSelectorWithMethodName:setKey value:value]) {
        NSLog(@"*********%@**********",setKey);
        return;
    }else if ([self performSelectorWithMethodName:_setKey value:value]) {
        NSLog(@"*********%@**********",_setKey);
        return;
    }else if ([self performSelectorWithMethodName:setIsKey value:value]) {
        NSLog(@"*********%@**********",setIsKey);
        return;
    }
    
    // 3:判断是否能够直接赋值实例变量
    if (![self.class accessInstanceVariablesDirectly] ) {
        @throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
    }

    // 4.找相关实例变量进行赋值
    // 4.1 定义一个收集实例变量的可变数组
    NSMutableArray *mArray = [self getIvarListName];
    // _<key> _is<Key> <key> is<Key>
    NSString *_key = [NSString stringWithFormat:@"_%@",key];
    NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
    NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
    if ([mArray containsObject:_key]) {
        Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
        object_setIvar(self , ivar, value);
        return;
    }else if ([mArray containsObject:_isKey]) {
        Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
        object_setIvar(self , ivar, value);
        return;
    }else if ([mArray containsObject:key]) {
        Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
        object_setIvar(self , ivar, value);
        return;
    }else if ([mArray containsObject:isKey]) {
        Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
        object_setIvar(self , ivar, value);
        return;
    }
    
    // 5:如果找不到相关实例
    @throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key name.****",self,NSStringFromSelector(_cmd)] userInfo:nil];
    
}
- (nullable id)customer_valueForKey:(NSString *)key{
    
    // 1:刷选key 判断非空
    if (key == nil  || key.length == 0) {
        return nil;
    }

    // 2:找到相关方法 get<Key> <key> countOf<Key>  objectIn<Key>AtIndex
    // key 要大写
    NSString *Key = key.capitalizedString;
    // 拼接方法
    NSString *getKey = [NSString stringWithFormat:@"get%@",Key];
    NSString *countOfKey = [NSString stringWithFormat:@"countOf%@",Key];
    NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",Key];
        
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    if ([self respondsToSelector:NSSelectorFromString(getKey)]) {
        return [self performSelector:NSSelectorFromString(getKey)];
    }else if ([self respondsToSelector:NSSelectorFromString(key)]){
        return [self performSelector:NSSelectorFromString(key)];
    }else if ([self respondsToSelector:NSSelectorFromString(countOfKey)]){
        if ([self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)]) {
            int num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
            NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
            for (int i = 0; i<num-1; i++) {
                num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
            }
            for (int j = 0; j<num; j++) {
                id objc = [self performSelector:NSSelectorFromString(objectInKeyAtIndex) withObject:@(num)];
                [mArray addObject:objc];
            }
            return mArray;
        }
    }
#pragma clang diagnostic pop
    
    // 3:判断是否能够直接赋值实例变量
    if (![self.class accessInstanceVariablesDirectly] ) {
        @throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
    }
    
    // 4.找相关实例变量进行赋值
    // 4.1 定义一个收集实例变量的可变数组
    NSMutableArray *mArray = [self getIvarListName];
    // _<key> _is<Key> <key> is<Key>
    // _name -> _isName -> name -> isName
    NSString *_key = [NSString stringWithFormat:@"_%@",key];
    NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
    NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
    if ([mArray containsObject:_key]) {
        Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
        return object_getIvar(self, ivar);;
    }else if ([mArray containsObject:_isKey]) {
        Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
        return object_getIvar(self, ivar);;
    }else if ([mArray containsObject:key]) {
        Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
        return object_getIvar(self, ivar);;
    }else if ([mArray containsObject:isKey]) {
        Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
        return object_getIvar(self, ivar);;
    }

    return @"";
}

#pragma mark - 相关方法
- (BOOL)performSelectorWithMethodName:(NSString *)methodName value:(id)value{
 
    if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
        
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [self performSelector:NSSelectorFromString(methodName) withObject:value];
#pragma clang diagnostic pop
        return YES;
    }
    return NO;
}

- (id)performSelectorWithMethodName:(NSString *)methodName{
    if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        return [self performSelector:NSSelectorFromString(methodName) ];
#pragma clang diagnostic pop
    }
    return nil;
}

- (NSMutableArray *)getIvarListName{
    
    NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([self class], &count);
    for (int i = 0; i<count; i++) {
        Ivar ivar = ivars[i];
        const char *ivarNameChar = ivar_getName(ivar);
        NSString *ivarName = [NSString stringWithUTF8String:ivarNameChar];
        NSLog(@"ivarName == %@",ivarName);
        [mArray addObject:ivarName];
    }
    free(ivars);
    return mArray;
}
- (BOOL)performSelectorWithMethodName:(NSString *)methodName value:(id)value{
 
    if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
        
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [self performSelector:NSSelectorFromString(methodName) withObject:value];
#pragma clang diagnostic pop
        return YES;
    }
    return NO;
}

demo下载地址

相关文章

网友评论

      本文标题:KVC实现原理

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