KVC之-(id)valueForKey:(NSString *)key的实现原理与验证

KVC之-(id)valueForKey:(NSString *)key的实现原理与验证

2.-(id)valueForKey:(NSString *)key的实现原理与验证;

#######功能:使用一个字符串类型的属性标示符,获取一个属性的值,支持普通对象和NSSet,NSArray集合对象,如果是NSArray对象就则返回不可变数组,如果是NSet就返回不可变NSSet.

这个方法的默认实现如下:


1.查找读访问器(getter)方法中与key相匹配的方法,匹配模式为-get<Key>, -<key>-is<Key>,按照这样的顺序如果找到一个方法,就执行它.如果返回结果类型是一个对象的类型的指针就简单返回,如果返回结果类型是可以通过NSNumber转换支持的原始类型,先把转换为NSNumber然后在返回,否则就转换为一个NSValue对象返回.


2.否则(简单的getter方法没有找到),那么就查找接受者的类中与 -countOf<Key> and -indexIn<Key>OfObject: and -objectIn<Key>AtIndex: (NSSet类中定义的私有方法) and also -<key>AtIndexes: (对应与 -[NSOrderedSet objectsAtIndexes:]). 如果找到了-countOf<Key> , -indexIn<Key>OfObject: 以及这两个 -objectIn<Key>AtIndex:, -<key>AtIndexes: 方法中一个,那么将组合使用 -countOf<Key>, -indexIn<Key>OfObject:, -objectIn<Key>AtIndex:, and -<key>AtIndexes: 获取集合中的原始对象,然后给原始对象发送-valueForKey:消息,把结果存放入一个能够相应NSOrderedSet所有方法的集合代理对象中,返回这个代理对象.如果接收者类中实现了 -get<Key>:range: 当为获取最佳性能则使用该方法


3.否则(1和2都没有满足),那么就接收者类中寻找名字与-countOf<Key> and -objectIn<Key>AtIndex: (定义在 NSArray 类中的私有方法) and -<key>AtIndexes: (对应与 -[NSArray objectsAtIndexes:]). 如果找到了-countOf<Key>方法 和 -objectIn<Key>AtIndex: (定义在 NSArray 类中的私有方法) 与 -<key>AtIndexes: 中的其中一个,那么,通过 -countOf<Key>, -objectIn<Key>AtIndex:, and -<key>AtIndexes: 给原始对象发送 -valueForKey:消息,把返回结果放入一个能够响应NSArray所有方法的代理对象中,然后返回该代理对象.如果接收器的类对象实现,一个名字与-get<Key>:range:.匹配可选方法,那么为了最好性能讲使用该方法.


4.否则(1,2,3都没有满足[没有简单的getter方法,没有有序Set的访问方法也没有数组的访问方法]),查找接收者对象的类中查找下面三个方法的组合,他们名字匹配模式为-countOf<Key>,-enumeratorOf<Key>, and -memberOf<Key>: (对应定义在NSSet类中一个私有方法).如果上面三个方法都找到了,组合使用-countOf<Key>, -enumeratorOf<Key>, -memberOf<Key>:这三个方法,给原始对象发送-valueForKey:消息,把返回结果放入一个能够响应NSSet的所有方法的代理对象中,然后返回这个代理对象


5.否则(1,2,3,4都没有满足[没有简单的访问器方法也没有集合的访问器方法]),如果接受器类的 +accessInstanceVariablesDirectly 方法返回YES,依照匹配模式 _<key>, _is<Key>, <key>, or is<Key> 去匹配实例变量名称,如果有一个实例变量找到了,就返回这个实例变量的值,如果结果类型是一个对象的类型的指针就简单返回,如果返回结果类型是可以通过NSNumber转换支持的原始类型,先把转换为NSNumber然后在返回,否则就转换为一个NSValue对象返回.


6.否则(1,2,3,4,5都没有满足[没有简单的访问器方法,没有集合访问方法,没有实例变量]),那么将会执行-valueForUndefinedKey:方法,默认产生一个NSUndefinedKeyException的异常,但是你可以重写该方法.


验证

Person.h

typedef struct {
    int day;
    int month;
    int year;
} Date;

@interface Person : NSObject
{
    NSString *_name;
    int _age;
    Date *birthday;
//    NSString *address;
//    NSString *_address;
//    NSString *isAddress;
     NSString *_isAddress;
    
}

@property (nonatomic, copy) NSString *name;

@property (nonatomic,assign) int age;


@end

Person.m

@implementation Person

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


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

@end

验证

1.查找读访问器(getter)方法中与key相匹配的方法,匹配模式为-get<Key>, -<key>, or -is<Key>,按照这样的顺序如果找到一个方法,就执行它.
修改name的getter方法:

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

输出结果为:

2015-08-15 23:53:16.771 company[1253:77763] -[Person getName]----------小明

再次修改name的getter方法::

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

输出结果

2015-08-15 23:54:36.060 company[1275:78595] -[Person name]----------小明

再次修改name的getter方法:

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

输出结果,为空,也就是说根本没有执行isName方法
修改name属性的声明为

@property (nonatomic, copy,getter=isName) NSString *name;

输出结果为

2015-08-15 23:58:59.271 company[1387:81935] -[Person isName]----------小明

2.如果返回结果类型是一个对象的类型的指针就简单返回,如果返回结果类型是可以通过NSNumber转换支持的原始类型,先把转换为NSNumber然后在返回,否则就转换为一个NSValue对象返回.

测试代码

    p.name = @"小明";
    NSString *name = [p valueForKey:@"name"];
    NSLog(@"%@",[name class]);
    NSLog(@"%@",name);
    
    p.age = 10;
    NSNumber *age = [p valueForKey:@"age"];
    NSLog(@"%@",[age class]);
    NSLog(@"%@",age);
    
    p.birthday = (Date){1990,5,1};
    NSValue *birthday = [p valueForKey:@"birthday"];
    NSLog(@"%@",[birthday class]);
    NSLog(@"%@",birthday);

输出结果

2015-08-16 00:03:03.420 company[1440:84446] -[Person isName]----------小明
2015-08-16 00:03:03.421 company[1440:84446] __NSCFConstantString
2015-08-16 00:03:03.421 company[1440:84446] 小明
2015-08-16 00:03:03.421 company[1440:84446] -[Person age]------10
2015-08-16 00:03:03.421 company[1440:84446] __NSCFNumber
2015-08-16 00:03:03.421 company[1440:84446] 10
2015-08-16 00:03:03.421 company[1440:84446] NSConcreteValue
2015-08-16 00:03:03.421 company[1440:84446] <c6070000 05000000 01000000>


3.否则(简单的getter方法没有找到),那么就查找接受者的类中与 -countOf<Key> and -indexIn<Key>OfObject: and -objectIn<Key>AtIndex: (NSSet类中定义的私有方法) and also -<key>AtIndexes: (对应与 -[NSOrderedSet objectsAtIndexes:]). 如果找到了-countOf<Key> , -indexIn<Key>OfObject: 以及这两个 -objectIn<Key>AtIndex:, -<key>AtIndexes: 方法中一个,那么将组合使用 -countOf<Key>, -indexIn<Key>OfObject:, -objectIn<Key>AtIndex:, and -<key>AtIndexes: 获取集合中的原始对象,然后给原始对象发送-valueForKey:消息,把结果存放入一个能够相应NSOrderedSet所有方法的集合代理对象中,返回这个代理对象.如果接收者类中实现了 -get<Key>:range: 当为获取最佳性能则使用该方法

此处我使用系统自带的类进行验证

测试代码:
    Person *p = [[Person alloc] init];
    p.name = @"小明";
    Person *p1 = [[Person alloc] init];
    [p1 setValue:@"小明" forKey:@"name"];
    Person *p2 = [[Person alloc] init];
    [p2 setValue:@"小花" forKey:@"name"];
    Person *p3 = [[Person alloc] init];
    [p3 setValue:@"happy" forKey:@"name"];
   
     NSOrderedSet *set = [[NSOrderedSet alloc] initWithObjects:p1,p2,p3, nil];
    NSOrderedSet *names =  [set valueForKey:@"name"];
    NSLog(@"%@",[names class]);
    NSLog(@"%@",names);

输出结果:

2015-08-16 00:35:39.134 company[1687:109689] -[Person isName]----------小明
2015-08-16 00:35:39.134 company[1687:109689] -[Person isName]----------小花
2015-08-16 00:35:39.134 company[1687:109689] -[Person isName]----------happy
2015-08-16 00:35:39.134 company[1687:109689] __NSOrderedSetI
2015-08-16 00:35:39.134 company[1687:109689] {(
    "\U5c0f\U660e",
    "\U5c0f\U82b1",
    happy
)}

这里的names 类为一个能够响应NSOrderedSet所有方法的的代理对象,请注意这里所说的代理对象,与代理设计模式不是同一个概念,
这里代理就相当于中介,比如你想租某个房东租房子,但是你找不到这个房东,只能通过房产中介,传递信息给房东,这个中介
就可以理解为代理对象


4.否则(1,2,3都没有满足[没有简单的getter方法,没有有序Set的访问方法也没有数组的访问方法]),查找接收者对象的类中查找下面三个方法的组合,他们名字匹配模式为-countOf<Key>,-enumeratorOf<Key>, and -memberOf<Key>: (对应定义在NSSet类中一个私有方法).如果上面三个方法都找到了,组合使用-countOf<Key>, -enumeratorOf<Key>, -memberOf<Key>:这三个方法,给原始对象发送-valueForKey:消息,把返回结果放入一个能够响应NSSet的所有方法的代理对象中,然后返回这个代理对象

测试代码:
    Person *p = [[Person alloc] init];
    p.name = @"小明";
    Person *p1 = [[Person alloc] init];
    [p1 setValue:@"小明" forKey:@"name"];
    Person *p2 = [[Person alloc] init];
    [p2 setValue:@"小花" forKey:@"name"];
    Person *p3 = [[Person alloc] init];
    [p3 setValue:@"happy" forKey:@"name"];
   
    NSArray *set = [[NSArray alloc] initWithObjects:p1,p2,p3, nil];
    NSArray *names =  [set valueForKey:@"name"];
    NSLog(@"%@",[names class]);
    NSLog(@"%@",names);

输出结果:

2015-08-16 00:33:21.741 company[1670:108148] -[Person isName]----------小明
2015-08-16 00:33:21.741 company[1670:108148] -[Person isName]----------小花
2015-08-16 00:33:21.741 company[1670:108148] -[Person isName]----------happy
2015-08-16 00:33:21.741 company[1670:108148] __NSArrayI
2015-08-16 00:33:21.741 company[1670:108148] (
    "\U5c0f\U660e",
    "\U5c0f\U82b1",
    happy
)

这里的names 类为一个能够响应NSArray所有方法的的代理对象


4.否则(1,2,3都没有满足[没有简单的getter方法,没有有序Set的访问方法也没有数组的访问方法]),查找接收者对象的类中查找下面三个方法的组合,他们名字匹配模式为-countOf<Key>,-enumeratorOf<Key>, and -memberOf<Key>: (对应定义在NSSet类中一个私有方法).如果上面三个方法都找到了,组合使用-countOf<Key>, -enumeratorOf<Key>, -memberOf<Key>:这三个方法,给原始对象发送-valueForKey:消息,把返回结果放入一个能够响应NSSet的所有方法的代理对象中,然后返回这个代理对象

测试代码:

    Person *p1 = [[Person alloc] init];
    [p1 setValue:@"小明" forKey:@"name"];
    Person *p2 = [[Person alloc] init];
    [p2 setValue:@"小花" forKey:@"name"];
    Person *p3 = [[Person alloc] init];
    [p3 setValue:@"happy" forKey:@"name"];
    
    
    NSSet *set = [[NSSet alloc] initWithObjects:p1,p2,p3, nil];
    NSSet *names =  [set valueForKey:@"name"];
    NSLog(@"%@",[names class]);
    NSLog(@"%@",names);

输出结果:

2015-08-16 00:29:50.879 company[1646:106351] -[Person isName]----------happy
2015-08-16 00:29:50.879 company[1646:106351] -[Person isName]----------小花
2015-08-16 00:29:50.879 company[1646:106351] -[Person isName]----------小明
2015-08-16 00:29:50.879 company[1646:106351] __NSSetI
2015-08-16 00:29:50.879 company[1646:106351] {(
    happy,
    "\U5c0f\U660e",
    "\U5c0f\U82b1"
)}

这里的names 类为一个能够响应NSSet所有方法的的代理对象


5.否则(1,2,3,4都没有满足[没有简单的访问器方法也没有集合的访问器方法]),如果接受器类的 +accessInstanceVariablesDirectly 方法返回YES,依照匹配模式 _<key>, _is<Key>, <key>, or is<Key> 去匹配实例变量名称,如果有一个实例变量找到了,就返回这个实例变量的值,如果结果类型是一个对象的类型的指针就简单返回,如果返回结果类型是可以通过NSNumber转换支持的原始类型,先把转换为NSNumber然后在返回,否则就转换为一个NSValue对象返回.

Person类中,分别使用下面address进行测试

@interface Person : NSObject
{
//    NSString *address;
//    NSString *_address;
//    NSString *isAddress;
     NSString *_isAddress;

}

测试代码

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

输出结果均为

2015-08-16 00:41:16.387 company[1776:113187] 金燕龙大厦

6.否则(1,2,3,4,5都没有满足[没有简单的访问器方法,没有集合访问方法,没有实例变量]),那么将会执行-valueForUndefinedKey:方法,默认产生一个NSUndefinedKeyException的异常,但是你可以重写该方法.

     NSString *工具 = [p valueForKey:@"工具"];
     NSLog(@"工具=%@",工具);

运行结果: 产生一个NSUnknownKeyException的异常

Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Person 0x7fb2b2727b50> valueForUndefinedKey:]: this class is not key value coding-compliant for the key 工具.'

在Person.m文件中重写-valueForUndefinedKey: 方法

- (id)valueForUndefinedKey:(NSString *)key
{
    NSLog(@"%s",__func__);
    NSLog(@"key == %@",key);
    return @"黄瓜";
}

再次运行程序输出结果为:

2015-08-16 00:53:01.568 company[1841:119255] -[Person valueForUndefinedKey:]
2015-08-16 00:53:01.568 company[1841:119255] key == 工具
2015-08-16 00:53:01.568 company[1841:119255] 工具=黄瓜
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容