19-KVC分析

前言

KVC(Key-Value Coding)键值编码:是一种由NSKeyValueCoding非正式协议启用的机制,对象采用该机制来提供对其属性的间接访问。
KVC是一个基本概念,它是许多其他 Cocoa 技术的基础,例如键值观察、Cocoa 绑定、核心数据和 AppleScript 能力。在某些情况下,键值编码还有助于简化您的代码。

官方文档:Key-Value Coding Programming Guide
通过官方文档你可以完全了解到什么是KVC并且如何去使用。

KVC基础

获取属性值

获取值的通用方法(valueForKey:) 还有两个相关方法 valueForKeyPath:dictionaryWithValuesForKeys:;分别返回对应的数据结构。

按照流程:比如这里需要搜索一个[person valueForKey:@"name"];

1: 先按照下面的格式 getName, name,isName, _name去搜索结果。如果找到
2: 如果没找到,那么执行 NSArray 的相关方法 countOfNameobjectInNameAtIndex:(对应于NSArray类定义的原始方法)和 nameAtIndexes:(对应于NSArray 方法 objectsAtIndexes:)。
3: 如果还是没有找到,寻找NSSet类的方法,方法名为 countOfNameenumeratorOfNamememberOfName:(对应于由所定义的原始的方法NSSet类)。
4: 如果发现收集的访问方法没有简单的存取方法或者组,如果接收器的类方法accessInstanceVariablesDirectly 返回 YES,搜索名为实例变量__is,或者is,按照这个顺序。如果找到,直接获取实例变量的值并进行步骤5,否则进行步骤6。
5: 如果检索到的属性值是一个对象指针,只需返回结果即可;如果该值是 支持的变量类型NSNumber,则将其存储在一个NSNumber实例中并返回该实例;如果结果是 NSNumber 不支持的变量类型,则转换为NSValue对象并返回
6: 如果所有其他方法都失败,请调用 valueForUndefinedKey:,默认情况下,这会引发异常,但 NSObject 的子类可能会提供特定于键的行为。

1、直接搜索格式如下代码所示

- (NSString *)getName{
    return NSStringFromSelector(_cmd);
}
- (NSString *)name{
    return NSStringFromSelector(_cmd);
}
- (NSString *)isName{
    return NSStringFromSelector(_cmd);
}
- (NSString *)_name{
    return NSStringFromSelector(_cmd);
}
 
    LGPerson *person = [[LGPerson alloc] init];
    //1、获取值
    id v = [person valueForKey:@"name"];
    NSLog(@"name = '%@'", v);

2、NSArray 集合类的方法

// 个数
- (NSUInteger)countOfPens{
    NSLog(@"%s",__func__);
    return [self.arr count];
}
// 获取值
- (id)objectInPensAtIndex:(NSUInteger)index {
    NSLog(@"%s",__func__);
    return [NSString stringWithFormat:@"pens %lu", index];
} 
// 或者这个
- (id)pensAtIndexes:(NSIndexSet *)indexs {
    NSLog(@"%s",__func__);
    return [self.arr objectsAtIndexes:indexs];
}

LGPerson *person = [[LGPerson alloc] init];
    // 3: KVC - 集合类型
    person.arr = @[@"pen0", @"pen1", @"pen2", @"pen3"];
    NSArray *array = [person valueForKey:@"pens"];
    NSLog(@"%@",[array objectAtIndex:1]);
    NSLog(@"%@",[array objectsAtIndexes:[NSIndexSet indexSetWithIndex:2]]);
    NSLog(@"%d",[array containsObject:@"pen1"]);

3、NSSet 集合类型的方法

// 个数
- (NSUInteger)countOfBooks{
    NSLog(@"%s",__func__);
    return [self.set count];
}
// 是否包含这个成员对象
- (id)memberOfBooks:(id)object {
    NSLog(@"%s",__func__);
    return [self.set containsObject:object] ? object : nil;
}
// 迭代器
- (id)enumeratorOfBooks {
    // objectEnumerator
    NSLog(@"来了 迭代编译");
    return [self.arr reverseObjectEnumerator];
}

person.set = [NSSet setWithArray:person.arr];
    NSSet *set = [person valueForKey:@"books"];
    [set enumerateObjectsUsingBlock:^(id  _Nonnull obj, BOOL * _Nonnull stop) {
        NSLog(@"set遍历 %@",obj);
    }];

4、+ accessInstanceVariablesDirectly 方法返回YES。

@interface LGPerson : NSObject{
    @public
    NSString *_name;        //1
    NSString *_isName;      //2
    NSString *name;         //3
    NSString *isName;       //4 
}

LGPerson *person = [[LGPerson alloc] init];
    person->_isName = @"CD_isName";
    person->name = @"CDname";
    person->isName = @"CDisName";
    person->_name = @"CD_name";
    
    //1、获取值
    id v = [person valueForKey:@"name"]; 
    NSLog(@"name = '%@'", v);

5、如果是基本数据类型必须包装成number,如果结构体就转换成 NSValue对象。
这也符合面向对象的概念,需要转换为对象。

///基本数据类型
@property (nonatomic, assign) NSInteger mun;
@property (nonatomic, assign) BOOL      sex;
@property (nonatomic, copy) NSNumber   *hei;

///结构体
@property (nonatomic, assign) CGPoint   point;

person.mun = 1234;
    person.sex = true;
    person.hei = @(188.8);
    person.point = CGPointMake(10, 20);

//    id num = [person valueForKey:@"num"];
    id sex = [person valueForKey:@"sex"];
    id hei = [person valueForKey:@"hei"];
    id point = [person valueForKey:@"point"];
    
//    NSLog(@"num = %@", num);
    NSLog(@"sex = %@", sex);
    NSLog(@"hei = %@", hei);
    NSLog(@"point = %@", point);

6、如果所有其他方法都失败,请调用 valueForUndefinedKey: 默认情况下,这会引发异常。当然可以重写 valueForUndefinedKey: 实现。

- (id)valueForUndefinedKey:(NSString *)key {
    //重写了方法,就可以返回默认值。如果这里设置了,那么上面的 num 就可以获到值了。
    return @(-1);
}
getter流程图

设置属性值

同样 setter 也有一些规则。设置值的默认实现方法为 setValue: forKey: 和扩展 setValue: forKeyPath: 这两个方法。

还是以name 为例说明,其中 == Name, = name

1 .按顺序 查找名为set:,_set:,setIs的方法。
2 .如果查找失败,且accessInstanceVariablesDirectly 返回 true。那么去找实例变量如下: __isis
3 .如果依然找不到,那么执行 setValue:forUndefinedKey:。默认实现爆出异常。

1、按照上诉方法去寻找对应的setter。在测试两个方法 setIs:_setIs: 这两个方法。

//MARK: - setKey. 的流程分析
- (void)setName:(NSString *)name{
    NSLog(@"%s - %@",__func__,name);
}
- (void)_setName:(NSString *)name{
    NSLog(@"%s - %@",__func__,name);
}
- (void)setIsName:(NSString *)name{
    NSLog(@"%s - %@",__func__,name);
}
- (void)_setIsName:(NSString *)name{
    NSLog(@"%s - %@",__func__,name);
}

[person setValue:@"6666" forKey:@"name"];

通过上面的测试。记过发现 setIsName: 执行了,但是 _setIsName: 没有执行。所以实现了三个方法 set: ,_set:setIs:

2 如果 accessInstanceVariablesDirectly 返回 true 。那么执行成员变量查找。如下代码调试。

@interface LGPerson : NSObject{
    @public
    NSString *_name;        //1
    NSString *_isName;      //2
    NSString *name;         //3
    NSString *isName;       //4
 }
//按照如下代码分别调试
     NSLog(@"%@-%@-%@-%@",person->_name,person->_isName,person->name,person->isName);
//     NSLog(@"%@-%@-%@",person->_isName,person->name,person->isName);
//     NSLog(@"%@-%@",person->name,person->isName);
//     NSLog(@"%@",person->isName);

通过上面的代码调试发现,会依次对成员变量 _name; _isName; name; isName; 赋值。

3 如果上面的步骤都失败,那么会查看是否实现了 -setValue: forUndefinedKey: 否则报异常。

- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    NSLog(@"%s", __func__);
}
setter 流程

使用和自定义KVC

上面既然有了 setValue: forKey:valueForKey: 的流程,那么我们来尝试一下自定义KVC。

头文件, 因为 NSKeyValueCoding 也是分类,所以呀参考分类来写。

@interface NSObject (CDKVC)

///这是两个默认kvc 方法
- (void)cd_setValue:(id)value forKey:(NSString *)key;
- (id)cd_valueForKey:(NSString *)key;

/// 这个方法默认返回true
+ (BOOL)cd_accessInstanceVariablesDirectly;

/// 这两个方法默认实现抛出异常
- (void)cd_setValue:(id)value forUndefinedKey:(NSString *)key;
- (id)cd_valueForUndefinedKey:(NSString *)key;
    
@end

实现文件


- (void)cd_setValue:(id)value forKey:(NSString *)key {
    
    // KVC 自定义
    // 1: 判断什么 key,必须是string,且长度大于0
    if (![key isKindOfClass:[NSString class]]) {
        return;
    }
    if (key == nil || key.length == 0) {
        return;
    }
    
    
    // 2: 查找 set: ,_set: 和 setIs:
    // 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 cd_performSelectorWithMethodName:setKey value:value]) {
        NSLog(@"*********%@**********",setKey);
        return;
    }else if ([self cd_performSelectorWithMethodName:_setKey value:value]) {
        NSLog(@"*********%@**********",_setKey);
        return;
    }else if ([self cd_performSelectorWithMethodName:setIsKey value:value]) {
        NSLog(@"*********%@**********",setIsKey);
        return;
    }
    
    // 3: 判断是否响应 accessInstanceVariablesDirectly 返回YES NO 奔溃
    // 3:判断是否能够直接赋值实例变量
    if (![self.class cd_accessInstanceVariablesDirectly] ) {
        
        //3.2 调用默认的 setValue: forUndefinedKey: 这个方法
        [self cd_setValue:value forUndefinedKey:key];
    }
    
    // 4: 间接变量
    // 获取 ivar -> 遍历 containsObjct -
    // 4.1 定义一个收集实例变量的可变数组
    NSMutableArray *mArray = [self getIvarListName];
    // _ _is  is
    NSString *_key = [NSString stringWithFormat:@"_%@",key];
    NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
    NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
    if ([mArray containsObject:_key]) {
        // 4.2 获取相应的 ivar
       Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
        // 4.3 对相应的 ivar 设置值
       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:如果找不到相关实例
    [self cd_setValue:value forUndefinedKey:key];
    
}

- (id)cd_valueForKey:(NSString *)key {
    
    // 1:刷选key 判断非空
    if (![key isKindOfClass:[NSString class]]) {
        return nil;
    }
    if (key == nil  || key.length == 0) {
        return nil;
    }

    // 2:找到相关方法 get  countOf  objectInAtIndex
    // 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 _is  is
    // _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);;
    }
    
    // 5 最后也没有查找到。
    return [self cd_valueForUndefinedKey:key];

}

+ (BOOL)cd_accessInstanceVariablesDirectly {
    return true;
}

- (void)cd_setValue:(id)value forUndefinedKey:(NSString *)key {
#if DEBUG
    NSLog(@"CDKVC - %s", __func__);
#endif
    @throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key name.****",self,NSStringFromSelector(_cmd)] userInfo:nil];
}

- (id)cd_valueForUndefinedKey:(NSString *)key {
#if DEBUG
    NSLog(@"CDKVC - %s", __func__);
#endif
    @throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
}


#pragma mark - 相关方法
- (BOOL)cd_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

使用

    CDPerson *person = [[CDPerson alloc] init]; 
    [person cd_setValue:@"6666" forKey:@"name"];

应用场景

KVC 的使用场景还是与很多地方的,下面是笔者用到的一些地方。当然还有其他的地方。

1 通过KVC 可以修改一些系统类的内部函数。
2 通过KVC 可以实现字典模型转换的自动实现。
3 一些简单数据结构,没必要采用模型化,直接KVC赋、取值。
4 系统的一些功能,比如相机拍照的参数,音视频的编解码参数等。

你可能感兴趣的:(19-KVC分析)