美文网首页
iOS底层原理:KVC简析&自定义

iOS底层原理:KVC简析&自定义

作者: 木槿WEIXIAO | 来源:发表于2021-07-05 23:04 被阅读0次

简介

  • KVC全程Key-Value Coding
    键值编码是NSKeyValueCoding非正式协议支持的一种机制,对象采用这种非正式协议来提供对其属性的间接访问。当一个对象的键值编码是兼容的,它的属性可以通过一个简洁、统一的消息传递接口通过字符串参数寻址。这种间接访问机制补充了实例变量及其关联访问器方法提供的直接访问。

常用API

- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
- (nullable id)valueForKey:(NSString *)key;
- (nullable id)valueForKeyPath:(NSString *)keyPath;

原理

  • 通过代码跟踪发现KVC相关API在Foundation框架中,而Foundation又不开源,所以我们只能通过反汇编获取官方文档的方式进行探索,下面我们通过官网文档探索KVC文档地址

取值原理

  • 1.首先查找get方法,查找顺序get<Key>, <key>, is<Key>, or _<key>,找到的话直接返回结果
  • 2.如果get方法没找到,会查找countOf <Key>和objectIn <Key> AtIndex :和<key> AtIndexes :
    • 如果找到countOf <Key>和其他两个中的一个,则会创建一个响应所有NSArray方法的集合代理对象,并返回该对象,即NSKeyValueArray,是NSArray的子类。代理对象随后将接收到的所有NSArray消息转换为countOf<Key>,objectIn<Key> AtIndex:和<key>AtIndexes:消息的某种组合,用来创建键值编码对象。如果原始对象还实现了一个名为get<Key>:range:之类的可选方法,则代理对象也将在适当时使用该方法(注意:方法名的命名规则要符合KVC的标准命名方法,包括方法签名。)
  • 3.如果没有找到上面的方法,则会同时查找countOf <Key> enumeratorOf<Key>和memberOf<Key>这三个方法,如果这三个方法都找到,则会创建一个响应所有NSSet方法的集合代理对象,并返回该对象,此代理对象随后将其收到的所有NSSet消息转换为countOf<Key>,enumeratorOf<Key>和memberOf<Key>:消息的某种组合,用于创建它的对象
  • 4.这一步会先判断 accessInstanceVariablesDirectly是否返回YES,如果返回YES,继续往下查找,如果为NO,直接报错信息
  • 5.依次查找成员变量_<key>, _is<Key>, <key>, or is<Key>,找到的话返回结果,没找到报错信息

设值原理

  • 1.首先依次查找set方法 set<Key>:-> _set<Key> -> setIs<Key>
  • 2.判断accessInstanceVariablesDirectly是否返回YES,如果返回YES,继续依次查找成员变量_<key> -> _is<Key> -> <key> -> is<Key>,如果为NO直接报错信息

自定义KVC

#import "NSObject+YX_KVC.h"
#import <objc/runtime.h>
@implementation NSObject (YX_KVC)

- (id)yx_valueForKey:(NSString *)key
{
    if (key == nil || key.length == 0) {
        return nil;
    }
    
    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
    
    if (![[self class] accessInstanceVariablesDirectly]) {
        @throw [NSException exceptionWithName:@"YXUnknowKey" reason:[NSString stringWithFormat:@"找不到%@的相关get方法",key] userInfo:nil];
    }
    
    NSMutableArray *ivarNameArray = [self getIvarsNameArray];
    NSString *_key = [NSString stringWithFormat:@"_%@",key];
    NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
    NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
    if ([ivarNameArray containsObject:_key]) {
        Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
        return object_getIvar(self, ivar);
    }
    if ([ivarNameArray containsObject:_isKey]) {
        Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
        return object_getIvar(self, ivar);
    }
    if ([ivarNameArray containsObject:key]) {
        Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
        return object_getIvar(self, ivar);
    }
    if ([ivarNameArray containsObject:isKey]) {
        Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
        return object_getIvar(self, ivar);
    }
    
    return @"";
}

- (void)yx_setValue:(id)value forKey:(NSString *)key
{
    if (key == nil || key.length == 0) {
        return;
    }
    
    NSString *Key = key.capitalizedString;
    NSString *setKey = [NSString stringWithFormat:@"set%@",Key];
    NSString *_setKey = [NSString stringWithFormat:@"_set%@",Key];
    NSString *setIsKey = [NSString stringWithFormat:@"setIs%@",Key];
    
    if ([self yx_performSelectorWithSelName:setKey value:value]) {
        return;
    }
    if ([self yx_performSelectorWithSelName:_setKey value:value]) {
        return;
    }
    if ([self yx_performSelectorWithSelName:setIsKey value:value]) {
        return;
    }
    
    if (![[self class] accessInstanceVariablesDirectly]) {
        @throw [NSException exceptionWithName:@"YXUnknowKey" reason:[NSString stringWithFormat:@"找不到%@的相关set方法",key] userInfo:nil];
    }
    
    NSMutableArray *ivarNameArray = [self getIvarsNameArray];
    NSString *_key = [NSString stringWithFormat:@"_%@",key];
    NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
    NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
    
    if ([ivarNameArray containsObject:_key]) {
        Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
        object_setIvar(self, ivar, value);
        return;
    }
    if ([ivarNameArray containsObject:_isKey]) {
        Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
        object_setIvar(self, ivar, value);
        return;
    }
    if ([ivarNameArray containsObject:isKey]) {
        Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
        object_setIvar(self, ivar, value);
        return;
    }
    
    if (![[self class] accessInstanceVariablesDirectly]) {
        @throw [NSException exceptionWithName:@"YXUnknowKey" reason:[NSString stringWithFormat:@"找不到%@的相关set方法",key] userInfo:nil];
    }
}
- (BOOL)yx_performSelectorWithSelName:(NSString *)name value:(id)value
{
    if ([self respondsToSelector:NSSelectorFromString(name)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [self performSelector:NSSelectorFromString(name) withObject:value];
#pragma clang diagnostic pop
        return YES;
    }
    return NO;
}

- (NSMutableArray *)getIvarsNameArray
{
    NSMutableArray *nameArray = [[NSMutableArray alloc] init];
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([self class], &count);
    for (int i = 0; i < count; i ++) {
        Ivar ivar = ivars[i];
        const char * ivarName = ivar_getName(ivar);
        NSString *ivarStr = [NSString stringWithUTF8String:ivarName];
        [nameArray addObject:ivarStr];
    }
    return nameArray;
}
@end

  • 上述只是简单的KVC实现,如果想了解更多,可以看看这个反汇编实现

相关文章

  • iOS底层原理:KVC简析&自定义

    简介 KVC全程Key-Value Coding键值编码是NSKeyValueCoding非正式协议支持的一种机制...

  • KVC

    KVC原理剖析 - CocoaChina_让移动开发更简单 iOS开发底层细究:KVC和KVO底层原理 | iOS...

  • KVC底层实现步骤

    参考 iOS底层-KVC使用实践以及实现原理 [a setValue:value forKeyPath:@"ico...

  • iOS-底层原理21:KVO底层原理

    上一篇文章iOS-底层原理20:KVC底层原理[https://www.jianshu.com/p/71940e1...

  • iOS - KVO

    [toc] 参考 KVO KVC 【 iOS--KVO的实现原理与具体应用 】 【 IOS-详解KVO底层实现 】...

  • iOS底层原理:KVO简析&自定义

    1.KVO的简单使用 三部曲 自动开关 KVO观察数组 数组的观察比较特殊,需要用到KVC的方式进行改变 原理 1...

  • iOS 底层-KVC底层原理

    KVC是什么? KVC的全称是Key-Value Coding,翻译成中文是 键值编码,键值编码是由NSKeyVa...

  • iOS 底层原理 - KVC

    概述 KVC (Key-Value Coding), 也就是键值编码,是利用 NSKeyValueCoding 非...

  • iOS KVC底层原理

    什么是KVC? KVC的全称叫Key-Value Coding,也叫做键值编码,在apple官方文档中是这么解释的...

  • iOS KVC 底层原理

    KVC的全称是Key-Value Coding,翻译成中文是 键值编码,键值编码是由NSKeyValueCodin...

网友评论

      本文标题:iOS底层原理:KVC简析&自定义

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