美文网首页
Objective-C 之 KVC 原理

Objective-C 之 KVC 原理

作者: 正_文 | 来源:发表于2020-04-10 14:52 被阅读0次

    苹果官网地址
    Key-value coding is a mechanism enabled by the NSKeyValueCoding informal protocol that objects adopt to provide indirect access to their properties. When an object is key-value coding compliant, its properties are addressable via string parameters through a concise, uniform messaging interface. This indirect access mechanism supplements the direct access afforded by instance variables and their associated accessor methods.

    一、KVC简介

    KVC的全称是Key-Value Coding,俗称“键值编码”,可以通过一个key来访问某个属性。常见的API有:

    - (void)setValue:(id)value forKeyPath:(NSString *)keyPath;//通过keyPath可以设置属性的属性
    - (void)setValue:(id)value forKey:(NSString *)key;//通过key设置自己的属性
    - (id)valueForKeyPath:(NSString *)keyPath;//通过keyPath访问属性的属性
    - (id)valueForKey:(NSString *)key; //通过key访问自己的属性
    

    二、赋值:-setValue:forKey:

    2.1规则:

    1. 先查找setter方法,set<Key>_set<Key>
    2. 未找到,且+(BOOL)accessInstanceVariablesDirectly返回YES(默认为YES),则查找实例变量;
    3. 查找 _<key>, _is<Key>, <key>, or is<Key>,查到即执行
    4. 抛出异常,也可以执行-setValue:forUndefinedKey:处理异常

    2.2不同数据类型的使用

    新建一个工程,添加一个类Animal.h

    typedef struct {
        float a;
        float b;
        float c;
    }ThreeFloats;
    
    
    @interface Animal : NSObject
    
    
    @property (nonatomic,copy) NSString *nickname;
    
    @property (nonatomic,assign) int age;
    
    @property (nonatomic,strong) NSArray *friends;
    
    @property (nonatomic,assign) ThreeFloats floats;
    
    
    @end
    

    在viewcontroller里面

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        
        Animal *animal = [Animal alloc];
        
        //1、基本对象类型
        [animal setValue:@"dog" forKey:@"nickname"];
        NSLog(@"nickname:%@", animal.nickname);
    
        //2、集合类型
        [animal setValue:@[@"Jack", @"Rose"] forKey:@"friends"];
        NSLog(@"friends:%@", animal.friends);
        NSMutableArray *tmpFriends = [animal mutableArrayValueForKey:@"friends"];
        tmpFriends[1] = @"Lili";
        NSLog(@"friends:%@", animal.friends);
        
        //3、 NSNumber支持的基础类型
        [animal setValue:@3 forKey:@"age"];
        NSLog(@"age:%d", animal.age);
        
        //4、 其他类型,除上述类型外,其他类型要包装为NSValue进行赋值。
        ThreeFloats threeFloats = {1.1, 2.2, 3.3};
        NSValue *value = [NSValue valueWithBytes:&threeFloats
                                        objCType:@encode(ThreeFloats)];
        [animal setValue:value forKey:@"floats"];
        
        NSValue *floatsValue = [animal valueForKey:@"floats"];
        ThreeFloats floats;
        [floatsValue getValue:&floats];
        NSLog(@"\na = %f, b = %f, c = %f", floats.a, floats.b, floats.c);
        
        
    }
    

    三、取值:valueForKey:

    2.1规则:

    看下图,也可以到苹果官网查看,文章开头有链接


    1111.png
    1. 先查找该类中是否有直接的getter,如果有则调用getter,执行第5步;
    2. 没有直接的getter,查找类中是否有上述NSArray块中的方法,如果实现第一个后面两个中的一个或两个方法,则返回一个集合对象,这个集合对象可以响应所有NSArray的方法
    3. 查找实例是否实现了上述NSSet块中的3个方法,如果有,则返回一个集合,这个集合可以响应NSSet的所有方法。
    4. 检查类方法accessInstanceVariablesDirectly如果返回YES则查找对象的实例变量是否匹配下列各式:_<key>->_is<Key>-><key>->is<Key>,如果匹配,执行第5步,否则执行第6步
    5. 如果属性类型是对象则直接返回;如果属性类型是被NSNumber支持的类型,则返回一个NSNumber对象;否则返回一个NSValue对象。
    6. 抛出异常,也可以执行- (nullable id)valueForUndefinedKey:(NSString *)key处理异常

    2.2不同数据类型的使用

    在上面代码的基础上修改,在Animal.h

    - (NSInteger)countOfNames {
        return _friends.count;
    }
    
    - (id)objectInNamesAtIndex:(NSUInteger)index {
        return _friends[index];
    }
    
    
    -(NSArray *)namesAtIndexes:(NSIndexSet *)indexes{
        return [_friends objectsAtIndexes:indexes];
    }
    
    - (nullable id)valueForUndefinedKey:(NSString *)key{
        
        return @"UndefinedKey";
    }
    

    在viewcontroller里面

    Animal *animal = [[Animal alloc] init];
        
        //1、基本对象
        animal.nickname = @"Jack";
        NSString *nickname = [animal valueForKey:@"nickname"];
        NSLog(@"nickname: %@", nickname);
        
        
        //2、数组类型的使用,通过-valueForKey:取值时
        //   需要实现-countOf<Key>和-objectIn<Key>AtIndex:两个方法,我们以names为key,来获取friends属性:
        animal.friends = @[@"Jack", @"Rose"];
        NSArray *names = [animal valueForKey:@"names"];
        NSLog(@"names: %@", names);
        
        
        //3、NSNumber支持的基本类型
        animal.age = 3;
        NSNumber *number = [animal valueForKey:@"age"];
        NSLog(@"age: %d", [number intValue]);
        
        
        //4、NSValue类型
        ThreeFloats threeFloats = {1.1, 2.2, 3.3};
        animal.floats = threeFloats;
        NSValue *floatsValue = [animal valueForKey:@"floats"];
        ThreeFloats floats;
        [floatsValue getValue:&floats];
        NSLog(@"\na = %f, b = %f, c = %f", floats.a, floats.b, floats.c);
    

    四、KVC模式匹配的顺序及验证

    4.1 setter和getter方法

    添加代码

    @interface Animal () {
        NSString *_nickname;
    }
    
    @end
    
    @implementation Animal
    
    #pragma mark - setter
    // set<Key>
    //- (void)setNickname:(NSString *)nickname {
    //    printf("%s\n", __func__);
    //    _nickname = nickname;
    //}
    
    // _set<Key>
    - (void)_setNickname:(NSString *)nickname {
        printf("%s\n", __func__);
        _nickname = nickname;
    }
    
    // setIs<Key>
    - (void)setIsNickname:(NSString *)nickname {
        printf("%s\n", __func__);
        _nickname = nickname;
    }
    
    #pragma mark - getter
    //// get<Key>
    //- (NSString *)getNickname {
    //    printf("%s\n", __func__);
    //    return _nickname;
    //}
    
    // <key>
    - (NSString *)nickname {
        printf("%s\n", __func__);
        return _nickname;
    }
    
    // _<key>
    - (NSString *)_nickname {
        printf("%s\n", __func__);
        return _nickname;
    }
    
    // is<Key>
    - (NSString *)isNickname {
        printf("%s\n", __func__);
        return _nickname;
    }
    @end
    

    viewController相关代码

        Animal *animal = [[Animal alloc] init];
        [animal setValue:@"Jack" forKey:@"nickname"];
        NSLog(@"\nnickname: %@", [animal valueForKey:@"nickname"]);
    

    这里不在一一测试,可自行注释方法来验证

    4.2 实例变量

    @interface Animal : NSObject{
        @public
        //NSString *_nickname;
        NSString *nickname;
        NSString *_isNickname;
        NSString *isNickname;
    }
    
    @end
    
    @implementation Animal
    
    @end
    
    
        Animal *animal = [[Animal alloc] init];
        [animal setValue:@"Jack" forKey:@"nickname"];
        //NSLog(@"_nickname: %@", animal->_nickname);
        NSLog(@"nickname: %@", animal->nickname);
        NSLog(@"_isNickname: %@", animal->_isNickname);
        NSLog(@"isNickname: %@", animal->isNickname);
    

    打印结果:

    2020-04-10 11:23:20.867793+0800 kvc[31577:17258752] nickname: (null)
    2020-04-10 11:23:20.868476+0800 kvc[31577:17258752] _isNickname: Jack
    2020-04-10 11:23:20.868991+0800 kvc[31577:17258752] isNickname: (null)
    

    4.3 + (BOOL)accessInstanceVariablesDirectly方法

    添加代码

    @implementation Animal
    
    + (BOOL)accessInstanceVariablesDirectly {
        return NO;
    }
    
    @end
    

    再次运行,抛出异常:'NSUnknownKeyException', reason: '[<Animal 0x6000015fb5c0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key nickname.'

    五、kvc特殊异常用法

    5.1 自动类型转换

    当赋值对象是intbool等基本类型时,赋值NSString,取值会自动转换为对象类型。如下,当age赋值为NSString,取值时对应类型时__NSCFNumber

    //@property (nonatomic, assign) int  age;
    [person setValue:@"20" forKey:@"age"]; // int - string
     NSLog(@"%@-%@",[person valueForKey:@"age"],[[person valueForKey:@"age"] class]);
    

    5.2 设置空值

    赋值nil,当方法参数类型为NSNumber或者NSValue时,可以重写setNilValueForKey方法重定向。

    5.3

    设值或者取值找不到key,也可以重写对应的方法setValue: forUndefinedKey:valueForUndefinedKey重定向,这个上面有说过。

    5.4 键值验证

    - (BOOL)validateValue:(inout id  _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError *__autoreleasing  _Nullable *)outError{
        if([inKey isEqualToString:@"name"]){
            [self setValue:[NSString stringWithFormat:@"里面修改一下: %@",*ioValue] forKey:inKey];
            return YES;
        }
        *outError = [[NSError alloc]initWithDomain:[NSString stringWithFormat:@"%@ 不是 %@ 的属性",inKey,self] code:10088 userInfo:nil];
        return NO;
    }
    

    我们可以用这个方法,进行容错派发消息转发等操作。

    相关文章

      网友评论

          本文标题:Objective-C 之 KVC 原理

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