iOS中KVC总结

KVC: Key-value Coding 一个非正式的Protocol,主要实现在NSObject以及容器的NSKeyValueCoding分类中

一、基本使用

设置keyPath和key的区别:keyPath可以嵌套多层取值,而key只能直接取值

JRTestKvcObj *t1 = [JRTestKvcObj new];
 t1.child = [JRTestKvcObj new];
    
[t1 setValue:@"stone" forKey:@"name"];
NSLog(@"%@",[t1 valueForKey:@"name"]);
    
[t1 setValue:@"jirun" forKeyPath:@"name"];
NSLog(@"%@",[t1 valueForKeyPath:@"name"]);
    
[t1 setValue:@"tom" forKeyPath:@"child.name"];
NSLog(@"%@",[t1 valueForKeyPath:@"child.name"]);
    
@try {
    [t1 setValue:@"jimmy" forKey:@"child.name"];
    NSLog(@"%@",[t1 valueForKey:@"child.name"]);
} @catch (NSException * e) {
    NSLog(@"%@",e);
}

二、简单的字典模型互转

NSDictionary *dict = @{@"name":@"jirun",@"age":@(1)};
    
JRTestKvcObj *t2 = [JRTestKvcObj new];
[t2 setValuesForKeysWithDictionary:dict];
    
NSLog(@"%@",[t2 dictionaryWithValuesForKeys:@[@"name",@"age"]]);

三、容器的KVC

字典取值三种方式的区别:

  1. setValue:forKeyPath:和valueForKeyPath:可以嵌套层级
  2. setObject:forKey:和ObjectForKey:中的key可以不为NSString
  3. setObject:forKey:中的object不能为空,但是另外两者可以
NSMutableDictionary *mDict1 = [NSMutableDictionary dictionary];
    
[mDict1 setValue:@"value1" forKey:@"key1"];
[mDict1 setObject:@"value2" forKey:@"key2"];
[mDict1 setValue:@"value3" forKeyPath:@"key3"];
NSLog(@"%@",[mDict1 valueForKey:@"key1"]);
NSLog(@"%@",[mDict1 objectForKey:@"key2"]);
NSLog(@"%@",[mDict1 valueForKeyPath:@"key3"]);
    
[mDict1 setValue:[NSMutableDictionary dictionary] forKey:@"key1"];
[mDict1 setValue:@"a" forKeyPath:@"key1.key2"];
NSLog(@"%@", [mDict1 valueForKeyPath:@"key1.key2"]);
     
[mDict1 setObject:@"value3" forKey:@(1)];
NSLog(@"%@",[mDict1 objectForKey:@(1)]);
    
@try {
    id object;
    [mDict1 setObject:object forKey:@"key"];
} @catch (NSException *e) {
    NSLog(@"%@",e);
}

四、KVC查询步骤

setValue:forKey:查找步骤:
  1. 查找-set<Key>:方法
  2. 检查+(BOOL)accessInstanceVariablesDirectly是否返回YES,返回YES则继续寻找,否则调用setValue:forUndefinedKey:,默认返回YES
  3. 查找变量_<key> _is<Key>
  4. 查找变量<key> is<Key>
  5. 如果还是没找到,则调用setValue:forUndefinedKey:(默认抛出异常)
valueForKey:查找步骤:
  1. 查找-get<Key>,<key>,is<Key>方法
  2. 查找countOf<Key>,objectIn<Key>AtIndex或<key>AtIndexes,如果同时存在第一个方法以及二三方法中的一个,则返回一个NSKeyValueArray对象
  3. 如果同时实现countOf<Key>,enumeratorOf<Key>,memberOf<Key>三个方法,则返回一个NSSet对象
  4. 检查+(BOOL)accessInstanceVariablesDirectly是否返回YES,返回YES则继续寻找,否则调用valueForUndefinedKey:
  5. 查找变量_<key>,_is<Key>,<key>,is<Key>
    6.如果还没找到调用valueForUndefineKey:(默认抛出异常)
mutableArrayValueForKey:查找步骤:
  1. 查找insertObject:in<Key>AtIndex: , removeObjectFrom<Key>AtIndex: 或者 insert<Key>AtIndexes , remove<Key>AtIndexes,只要存在一组插入和获取的方法则直接返回代理集合对象,调用代理集合的插入和删除方法内部会调用相对应的对象方法
  2. 搜索set<Key>:,如果存在则返回代理集合,修改代理集合都会自动重新对原属性赋值(触发KVO)
  3. + (BOOL)accessInstanceVariablesDirectly是否返回YES,返回YES则继续寻找,否则调用valueForUndefinedKey:(默认抛出异常)
  4. 搜索变量_<key>,<key>,发送给代理集合的修改方法都会直接发送给该成员对象
  5. 还没有找到则直接返回一个代理集合(此时没有调用valueForUndefinedKey:),但如果调用代理集合的方法都会触发valueForUndefinedKey:(默认抛出异常)
JRTestKvcObj * testObj = [JRTestKvcObj new];
testObj.arr = [NSArray array];

NSArray *originArr = testObj.arr;

NSArray *arr = [testObj valueForKey:@"arr"];
NSLog(@"%d",arr == testObj.arr);//1

NSMutableArray *delegateArr = [testObj mutableArrayValueForKey:@"arr"];
[delegateArr addObject:@"1"];
NSLog(@"%ld - %ld",testObj.arr.count, delegateArr.count); // 1 - 1
NSLog(@"%d",testObj.arr == delegateArr); //0
NSLog(@"%d",testObj.arr == originArr); //0
五、KVC的异常防护
个人推荐使用第一种和第三种
  1. 通过重写setValue:forUndefinedKey:、setNilValueForKey:、valueForUndefineKey:,这种方法只能运用在自己类的对象中
  2. swizzling NSObject类或者非自定义类的上述三个方法,可以运用在所有的对象中,但是影响未知
  3. 通过trycatch捕获异常(最好类扩展方法,内部异常转error)
  4. 方法查询,通过查询对象是否存在访问器和变量,然后再调用KVC
JRTestKvcObj *obj = [JRTestKvcObj new];

// 触发NSUndefinedKeyException异常,通过重写setValue:forUndefinedKey:避免异常
[obj setValue:@"value" forKey:@"unDefineKey"];

// 触发NSUndefinedKeyException异常,通过重写valueForUndefineKey:避免异常
id value = [obj valueForKey:@"unDefineKey"];

// 如可以封装成NSNumber或者NSValue的基本数据类型,如果设置的时候value为空会触发异常
// 可以通过重写setNilValueForKey:避免异常
[obj setValue:nil forKey:@"num"];

/*
 validateValue:forKey:error:可以验证value的合法性,需要手动调用
 默认该方法会调用-validate<Key>:error方法,如果该方法没有实现则直接返回YES。
 也就是说默认的validateValue:forKey:error:返回了YES
 */
NSError *e;
id age = @"15";
if ([obj validateValue:&age forKey:@"age" error:&e]) {
    [obj setValue:age forKey:@"age"];
}
六、KVC使用扩展

@min,@max,@avg,@count,@sum
@unionOfObjects,@"distinctUnionOfObjects",@unionOfArrays,distinctUnionOfArrays等

  • unionOfObjects和unionOfArrays获取并集
  • distinctUnionOfObjects和distinctUnionOfArrays会对并集做去重操作
  • unionOfArrays和distinctUnionOfArrays用于处理数组的元素仍是数组的情况
  • 去重后的数组顺序不定
  • 如果元素为空则结果集中会存在NSNull
JRTestKvcObj *obj1 = [JRTestKvcObj new];
obj1.name = @"name1";
obj1.length = @"1";
JRTestKvcObj *obj2 = [JRTestKvcObj new];
obj2.name = @"name2";
obj2.length = @"2";
JRTestKvcObj *obj3 = [JRTestKvcObj new];
obj3.name = @"name2";
obj3.length = @"3";

NSArray *arr1 = @[obj1,obj2,obj3];

NSArray *lengthArr = [arr1 valueForKeyPath:@"length"];
NSArray *nameArr = [arr1 valueForKeyPath:@"name.uppercaseString"];
NSArray *distictNameArr = [arr1 valueForKeyPath:@"@distinctUnionOfObjects.name"];

NSNumber *min = [arr1 valueForKeyPath:@"length.@min.doubleValue"];
NSNumber *max = [arr1 valueForKeyPath:@"length.@max.doubleValue"];
NSNumber *average = [arr1 valueForKeyPath:@"length.@avg.doubleValue"];
NSNumber *sum = [arr1 valueForKeyPath:@"length.@sum.doubleValue"];
NSNumber *count = [arr1 valueForKey:@"@count"];

NSArray *arr2 = @[@[obj1,obj2],@[obj1,obj2,obj3]];
NSArray *nameArr2 = [arr2 valueForKeyPath:@"name"]; // [[nam1,name2],[name1,name2,name2]]
NSArray *unionArr2 = [arr2 valueForKeyPath:@"@unionOfArrays.name"]; //[name1,name2,name1,name2,name2]
NSArray *distictArr2 = [arr2 valueForKeyPath:@"@distinctUnionOfArrays.name"]; //[name2,name1]
七、KVC的优缺点(个人观点)

优点:

  1. 功能强大,可以访问私有域
  2. 解耦的一种途径
  3. 减少代码量

缺点:

  1. 没有条件防护或不恰当的使用容易引发崩溃
  2. 缺少编译期检查,把异常推迟到运行期间

建议:

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