[关闭]
@chenbinghua 2015-09-12T10:34:12.000000Z 字数 3615 阅读 1631

iOS开发之KVC

iOS笔记


参考
容芳志专栏 Objective-C语法之KVC的使用

什么是KVC (Key Value Coding)

作用:存取对象的属性
除了一般的赋值和取值的方法,我们还可以用Key-Value-Coding(KVC)键值编码来访问你要存取的成员属性,甚至是受保护的,没有getter和setter方法访问的成员属性。
在核心动画中,KVC大量使用,通常是通过KVC设置动画的KeyPath

使用

新建一个Student类
Student.h

  1. #import <Foundation/Foundation.h>
  2. @interface Student : NSObject
  3. {
  4. // 默认是@protected,不能被外界访问
  5. NSString *name;
  6. }
  7. @end

Student.m

  1. #import "Student.h"
  2. @implementation Student
  3. @end

NSString *name成员变量默认是@protected修饰,也没有getter和setter方法访问,所以不能用结构体的->或点语法访问,但是可以用KVC访问。

  1. #import <Foundation/Foundation.h>
  2. #import "Student.h"
  3. int main(int argc, const char * argv[]) {
  4. Student *student = [[Student alloc]init ];
  5. [student setValue:@"张三" forKey:@"name"];
  6. // student->name; 报错
  7. // 没有getter和setter方法,也就没有点语法访问
  8. NSString *name = [student valueForKey:@"name"];
  9. NSLog(@"学生姓名:%@",name);
  10. return 0;
  11. }

如果存的时候key和类属性的名称不一致会怎么样呢?
代码改成

  1. [student setValue:@"张三" forKey:@"name1"];

运行,程序崩溃 ,打印:

  1. 2015-09-04 18:52:26.356 KVC[5203:259489] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<Student 0x100206980> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key name1.'

这个原因this class is not key value coding-compliant for the key name1是不是很熟悉

// IBOutlet底层实现

键路径访问属性

如果访问这个类里中的属性中的属性呢?那就用到了键路径
关键字:键路径取值valueForKeyPath 键路径存值:forKeyPath

  1. // Student有属性Course *course,Course有
  2. [student setValue:@"数学课" forKeyPath:@"course.CourseName"];

自动封装基本数据类型

  1. // Student有属性NSInteger point;
  2. [student setValue:@"88" forKeyPath:@"point"]; NSString *point = [student valueForKey:@"point"];

操作集合

  1. #import "Student.h"
  2. @implementation Student
  3. {
  4. NSString *name;
  5. NSInteger point;
  6. NSArray *otherStudent;
  7. }
  8. @end
  1. Student *student = [[Student alloc]init];
  2. Student *student1 = [[Student alloc]init];
  3. Student *student2 = [[Student alloc]init];
  4. Student *student3 = [[Student alloc]init];
  5. [student1 setValue:@"65" forKey:@"point"];
  6. [student2 setValue:@"77" forKey:@"point"];
  7. [student3 setValue:@"99" forKey:@"point"];
  8. NSArray *array = @[student1,student2,student3];
  9. [student setValue:array forKey:@"otherStudent"];
  10. NSLog(@"其他学生的成绩%@", [student valueForKeyPath:@"otherStudent.point"]);// (65、77、99)
  11. NSLog(@"共%@个学生", [student valueForKeyPath:@"otherStudent.@count"]);// 共3个学生
  12. NSLog(@"最高成绩:%@", [student valueForKeyPath:@"otherStudent.@max.point"]);// 99
  13. NSLog(@"最低成绩:%@", [student valueForKeyPath:@"otherStudent.@min.point"]);// 64
  14. NSLog(@"平均成绩:%@", [student valueForKeyPath:@"otherStudent.@avg.point"]);// 80.333333333333333333333333333333333333
  15. NSLog(@"平均成绩:%@", [student valueForKeyPath:@"otherStudent.@sum.point"]);// 241

setValue:forKey:方法

key:value
1.首先在模型中查找是否有setValue:方法,有就调用setValue:方法赋值
1.如果没有setValue:方法,就查找是否有_value属性
3.如果没有_value属性,就查找value属性
4.如果还没有就报错

  1. - (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;

setValuesForKeysWithDictionary:方法就是遍历字典里面的所有key,遍历操作就是调用setValue:forKey:方法,如果没找到属性赋值就会报错。

当字典或JSON数据中有关键字,如

  1. {
  2. "id" : "tripleCC",
  3. "age" : "30",
  4. "address" : "杭州",
  5. "schooll" : "HDU"
  6. ...
  7. }

方法一:可以重写setValue:forKey:方法

  1. - (void)setValue:(id)value forKey:(NSString *)key
  2. {
  3. if ([key isEqualToString:@"id"]) {
  4. [self setValue:value forKeyPath:@"ID"];
  5. }else{
  6. [super setValue:value forKey:key];
  7. }
  8. }

方法二:使用runtime
MJExtention实现原理
遍历模型中属性名,遍历操作是根据属性名去字典中找,找到就赋值,可以传进来的字典改变属性名对应字典中的值
抽成NSObject的分类

  1. #import "NSObject+Model.h"
  2. #import <objc/runtime.h>
  3. @implementation NSObject (Model)
  4. + (instancetype)objcWithDict:(NSDictionary *)dict mapDict:(NSDictionary *)mapDict
  5. {
  6. id objc = [[self alloc] init];
  7. // 遍历模型中属性
  8. unsigned int count = 0;
  9. Ivar *ivars = class_copyIvarList(self, &count);
  10. for (int i = 0 ; i < count; i++) {
  11. Ivar ivar = ivars[i];
  12. // 属性名称
  13. NSString *ivarName = @(ivar_getName(ivar));
  14. // 获取出来的是`_`开头的成员变量名,需要截取`_`之后的字符串
  15. ivarName = [ivarName substringFromIndex:1];
  16. id value = dict[ivarName];
  17. // 需要由外界通知内部,模型中属性名对应字典里面的哪个key
  18. // ID -> id
  19. if (value == nil) {
  20. if (mapDict) {
  21. NSString *keyName = mapDict[ivarName];
  22. value = dict[keyName];
  23. }
  24. }
  25. [objc setValue:value forKeyPath:ivarName];
  26. }
  27. return objc;
  28. }
  29. @end

底层原理

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注