KVC之-setValue:forKey:方法实现原理与验证

作者: 满山李子 | 来源:发表于2015-08-15 22:18 被阅读10768次

- (void)setValue:(id)value forKey:(NSString *)key方法,实现原理与验证

功能:使用一个字符串标示符给一个对象的属性赋值.它支持普通对象和集合对象
这个方法的默认实现如下:

(1).首先去接收者(调用方法的那个对象)的类中查找与key相匹配的访问器方法(-set<Key>),如果找到了一个方法,就检查它参数的类型,如果它的参数类型不是一个对象指针类型,但是只为nil,就会执行setNilValueForKey:方法,setNilValueForKey:方法的默认实现,是产生一个NSInvalidArgumentException的异常,但是你可以重写这个方法.如果方法参数的类是一个对象指针类型,就会简单的执行这个方法,传入对应的参数.如果方法的参数类型是NSNumberNSValue的对应的基本类型,先把它转换为基本数据类,再执行方法,传入转换后的数据.****


(2).如果没有对应的访问器方法(setter方法),如果接受者的类的+accessInstanceVariablesDirectly方法返回YES,那么就查找这个接受者的与key相匹配的实例变量(匹配模式为_<key>,_is<Key>,<key>,is<Key>):比如:keyage,只要属性存在_age,_isAge,age,isAge中的其中一个就认为匹配上了,如果找到这样的一个实例变量,并且的类型是一个对象指针类型,首先released对象上的旧值,然后把传入的新值retain后的传入的值赋值该成员变量,如果方法的参数类型是NSNumberNSValue的对应的基本类型,先把它转换为基本数据类,再执行方法,传入转换后的数据.



(3).如果访问器方法和实例变量都没有找到,执行setValue:forUndefinedKey:方法,该方法的默认实现是产生一个 NSUndefinedKeyException 类型的异常,但是我们可以重写setValue:forUndefinedKey:方法


验证:
定义一个Person类:如下


@interface Person : NSObject
{
    NSString *_name;
    int _age;
    NSString *_address;
}

@property (nonatomic, copy) NSString *name;

@property (nonatomic,assign) int age;

@end


@implementation Person


-(void)setName:(NSString *)name
{
    NSLog(@"%s----------%@",__func__,name);
   _name = name;
}

- (void) setAge:(int)age
{
    _age = age;

    NSLog(@"%s------%d",__func__,age);
}


- (int) age
{
    NSLog(@"%s------%d",__func__,_age);
    return _age;
}


- (NSString *) name
{
    NSLog(@"%s----------%@",__func__,_name);
    return _name;
}

@end

测试代码
1)验证: setValue:forKey:确实会调用-set<Key>方法

    Person *p = [[Person alloc] init];
    [p setValue:@"小明" forKey:@"name"];

输出结果

2015-08-15 20:56:56.975 company[1254:98490] -[Person setName:]----------小明
2015-08-15 20:56:56.975 company[1254:98490] -[Person setAge:]------10

2)验证:如果它的参数类型不是一个对象指针类型,但是只为nil,就会执行setNilValueForKey:方法,setNilValueForKey:方法的默认实现,是产生一个NSInvalidArgumentException的异常
测试代码
[p setValue:nil forKey:@"age"];
运行结果:

2015-08-15 20:59:36.111 company[1300:100841]
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason:

3)可以重写这个方法setNilValueForKey:
在Person类的实现文件中,重写setNilValueForKey:

- (void) setNilValueForKey:(NSString *)key
{
    NSLog(@"%s",__func__);
}

再次运行,结果:

2015-08-15 21:29:21.167 company[528:6226] -[Person setNilValueForKey:]

4)验证如果方法的参数类型是NSNumberNSValue的对应的基本类型,先把它转换为基本数据类,再执行方法,传入转换后的数据,测试代码
Person.m 文件中:

- (void) setAge:(int)age
{
    _age = age;

    NSLog(@"%s------%d",__func__,age);
}

测试方法中

     [p setValue:@(10) forKey:@"age"];

执行结果

2015-08-15 21:54:23.477 company[607:15602] -[Person setAge:]------10

5)验证如果如果没有对应的访问器方法(setter方法),如果接受者的类的+accessInstanceVariablesDirectly方法返回YES,那么就查找这个接受者的与key相匹配的实例变量(匹配模式为_<key>,_is<Key>,<key>,is<Key>):比如:keyage,只要属性存在_age,_isAge,age,isAge中的其中一个就认为匹配上了,如果找到这样的一个实例变量,并且的类型是一个对象指针类型,首先released对象上的旧值,然后把传入的新值retain后的传入的值赋值该成员变量,如果方法的参数类型是NSNumberNSValue的对应的基本类型,先把它转换为基本数据类,再执行方法,传入转换后的数据.
验证:+accessInstanceVariablesDirectly默认返回YES
测试代码

    NSLog(@"%d",[Person accessInstanceVariablesDirectly]);

输出结果:

    2015-08-15 22:05:22.646 company[782:21098] 1

Person类中分别使用

@interface Person : NSObject
{
//    NSString *address;
//    NSString *_address;
//   注意is后面第一个字母必须大写否则会产生NSUnknownKeyException异常
//    NSString *isAddress;
     NSString *_isAddress;

}

测试代码

    NSLog(@"%d",[Person accessInstanceVariablesDirectly]);
    [p setValue:@"金燕龙大厦" forKey:@"address"];
    NSString *address = [p valueForKey:@"address"];

输出结果:

    2015-08-15 22:05:22.646 company[782:21098] 金燕龙大厦

6)验证:如果访问器方法和实例变量都没有找到,执行setValue:forUndefinedKey:方法,该方法的默认实现是产生一个 NSUndefinedKeyException 类型的异常,但是我们可以重写setValue:forUndefinedKey:方法
测试代码:

      [p setValue:@"美女" forKey:@"老婆"];

结果产生一个NSUnknownKeyException:

Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Person 0x7fd0394a4c10> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key 老婆.'

Person.m文件中重写 - (void)setValue:(id)value forUndefinedKey:(NSString *)key

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

再次运行程序输出结果:

2015-08-15 22:14:19.866 company[885:25268] -[Person setValue:forUndefinedKey:]
2015-08-15 22:14:19.866 company[885:25268] 老婆=美女

相关文章

  • KVC/KVO原理

    KVC/KVO原理 KVC setValue:forKey原理 调用setValue:forKey:方法,会直接寻...

  • KVC

    KVC(NSKeyValueCoding) 常用的KVC方法:setValue: forKey:与setValue...

  • 【iOS】KVC的实现

    KVC的基本使用 KVC的基本原理 setValue:forKey 验证在MJPerson中重写这几个方法,首先打...

  • KVC之-setValue:forKey:方法实现原理与验证

    - (void)setValue:(id)value forKey:(NSString *)key方法,实现原理与...

  • 面试总结

    1,KVC的实现原理 当调用[persion setValue:@”lv” forKey:@”name”]的代码时...

  • iOS KVC

    setValue:forKey实现原理 Value:forKey实现原理

  • KVC

    一、KVC的原理(赋值取值过程) KVC相关常用的API KVC设置值的原理(setValue: forKey:的...

  • setvalue:forkeypath

    kvc:setValue:forkey:的原理按照setKey._setKey:顺序找 取值的过程

  • 【iOS 1 行代码系列】之 一行代码解耦控制器UIViewCo

    实现原理: NSClassFromString() KVC 新建分类,重写方法:- (void)setValue:...

  • IOS KVC,KVO

    KVC KVC是key Value Coding的缩写,主要方法有setValue:forKey和valueFor...

网友评论

  • 开发者老岳:_isAge并不会执行,楼主可以试试。
  • 瓦力wali:看晕了
  • 不明Xia落:看到后面我晕了
  • C_HPY:学习了。
  • AidenRao:原理呢?
    LarkinQin:@满山李子 runtime
    满山李子:@饶志臻 这个不是原理吗,你理解的原理是什么呢?
  • 0ae8e443fa6e:有个问题请问下李老师,KVC调用属性的setter方法会触发KVO,但是对成员变量调用setValueForKey是什么原因会调用KVO呢
    MountainHill:@满山李子 你好,我试了下,直接使用setValue:forKey是不会调用KVO的。
    MountainHill:你好,我试了下,直接使用setValue:forKey是不会调用KVO的。
    满山李子:@格式化油条 调用setter 方法或直接设置成员变量的值都会调用KVO。 KVC的本质也是调用setter或直接给成员变量赋值的方式进行设置的。所以会调用KVC。
  • ForestSen:太好了收藏了
  • 2e7a1963d292:支持山鸡哥
  • 0ae8e443fa6e:支持小飞燕老师~

本文标题:KVC之-setValue:forKey:方法实现原理与验证

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