iOS 键值编码 KVC

KVC

KVC -- Key Value Coding 键值编码

  • 键值编码的基本概念
    • 键值编码是一个用于简介访问对象属性的机制,使用该机制不需要调用存取方法和变量实例就可以访问对象属性
    • 键值编码的方法在object-c非正式协议(类目)NSKeyValueCoding中被声明,默认的实现方法有NSObject提供。
    • 键值编码支持带有对象值得属性,同时也支持纯数值类型和结果。非对象参数和返回值类型会被识别并自动封装/解封。
  • 设置和访问
      键值编码中的基本调用包括-valueForKey: 和 -setValue:forKey:这两个方法,它们以字符串的形式向对象发送消息,字符串是我们关注属性的关键。
  Person *person = [[Person alloc] init];
  [person setValue:@"小明" forKey:@"name"];
  NSString *personName = [person valueForKey:@"name"];
  /*
   [person setValue:@"小明" forKey:@"_name"];
   NSString *personName = [person valueForKey:@"_name"];
   */
  /*
   KVC 首先会去调用对象的setter、getter方法,如果setter、getter方法不存在则会查找实例变量名为name的属性
       如果实例变量名为name的属性不存在会查找实例变量名为_name的属性。如果都不存在会报错。
   */

打印结果:

personName = 小明

是否存在setter、getter方法,如果不存在,它将在内部查找名为_key 或 key的实例变量。通过KVC,可以获取不存在getter方法的对象值,无需通过对象指针直接访问。这里我们需要注意,当我们通过-setValue:forKey:设置对象的值,或通过-valueForKey: 来获取对象的值时,如若对象的实例变量为基本数据类型时(char、int、float、BOOL),我们需要对数据进行封装。

  • 路径
      除了通过键值设置外,键值编码还支持指定路径,像文件系统一样。
    [person setValue:@"小明" forKeyPath:@"name"];
    
    NSString *personName = [person valueForKeyPath:@"name"];
    
    
先创建Person 和 Dog 这两个类

Person类

#import <Foundation/Foundation.h>

@class Dog;

@interface Person : NSObject

@property(nonatomic,copy) NSString *name;

@property(nonatomic,assign) int age;

@property(nonatomic,assign) float money;

@property (nonatomic, strong) Dog *dog;

- (void)printTelephone;

/**
*  通过KVC进行字典转模型
*
*  @param dic 需要解析的字典
*/
- (void)dictionaryToModel:(NSDictionary *)dic;

@end

#import "Person.h"
#import "Dog.h"

@interface Person ()

// Person 私有属性
@property (nonatomic, strong) NSString *telephone;

@end

@implementation Person

/**
*  初始化方法
*
*  @return <#return value description#>
*/
- (instancetype)init{
  
  self = [super init];
  
  if (self != nil) {
      
      _telephone = @"18200002222";
      
  };
  
  return self;
}

- (void)printTelephone{
  
  NSLog(@"电话为: %@",_telephone);
}

- (void)dictionaryToModel:(NSDictionary *)dic{
  
  [self setValuesForKeysWithDictionary:dic];
  
}
@end

Dog类

#import <Foundation/Foundation.h>

@interface Dog : NSObject

@property (nonatomic, strong) NSString *name;

@property (nonatomic, assign) int price;

@end

#import "Dog.h"

@implementation Dog

@end

使用KVC给Person 和 Dog 的属性赋值
  Person *person = [[Person alloc] init];
  
  person.dog = [[Dog alloc] init];
  
  [person setValue:@"小明" forKey:@"name"];
  
  [person setValue:@"22" forKey:@"age"];
  
  [person setValue:@"1000" forKeyPath:@"money"];
  
  [person setValue:@"旺财" forKeyPath:@"dog.name"];
  /*
   这种方式也可以给dog的name赋值
  [person.dog setValue:@"旺财" forKeyPath:@"name"];
  */

打印结果:

姓名: 小明
年龄: 22
金钱: 1000.00(传入的是一个字符串类型@"1000",说明KVC可以自动进行类型转换)
狗名: 旺财
  • forKey 和 forKeyPath 的一些差异
  • forKeyPath 包含了所有 forKey 的功能
  • forKeyPath 可以进行内部的点语法,层层访问内部的属性
```
[person setValue:@"旺财" forKeyPath:@"dog.name"];
```
- key值一定要在属性中找到,否则会有crash
```
[person setValue:@"小明" forKey:@"name11"];

  ```

  ```

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

可以通过KVC修改类的私有成员变量
    Person *person = [[Person alloc] init];
    
    [person printTelephone];
    
    [person setValue:@"15066669999" forKey:@"telephone"];
    
    [person printTelephone];

打印结果:

  //初始化默认号码
 电话为: 18200002222
  //修改过之后的号码
 电话为: 15066669999

使用KVC实现字典转模型
Person.h 中声明部分
/**
*  通过KVC进行字典转模型
*
*  @param dic 需要解析的字典
*/
- (void)dictionaryToModel:(NSDictionary *)dic;

Person.m 中实现部分
- (void)dictionaryToModel:(NSDictionary *)dic{
  
  [self setValuesForKeysWithDictionary:dic];
  
}

使用过程:
  NSDictionary *dic = @{@"name":@"小王",
                        @"age":@"33",
                        @"money":@"1000",
                        @"dog":@{@"name":@"wang cai",
                                 @"price":@"500"},
                        };
  
  
  Person *person = [[Person alloc] init];
  person.dog = [[Dog alloc] init];
  [person dictionaryToModel:dic];
  NSLog(@"姓名: %@",person.name);
  NSLog(@"年龄: %d",person.age);
  NSLog(@"金钱: %.2f",person.money);
  NSLog(@"人所拥有的狗: %@",person.dog);
  NSLog(@"狗的类型: %@",person.dog.class);

打印结果:
姓名: 小王
年龄: 33
金钱: 1000.00
人所拥有的狗: {
  name = "wang cai";
  price = 500;
}
狗的类型: __NSDictionaryI

如果此时要打印狗的信息:name 和 price 会报错
  NSDictionary *dic = @{@"name":@"小王",
                        @"age":@"33",
                        @"money":@"1000",
                        @"dog":@{@"name":@"wang cai",
                                 @"price":@"500"},
                        };
  
  
  Person *person = [[Person alloc] init];
  
  person.dog = [[Dog alloc] init];
  
  [person dictionaryToModel:dic];
  
  NSLog(@"姓名: %@",person.name);
  NSLog(@"年龄: %d",person.age);
  NSLog(@"金钱: %.2f",person.money);
  NSLog(@"人所拥有的狗: %@",person.dog);
  NSLog(@"狗的类型: %@",person.dog.class);
  NSLog(@"狗的名字: %@",person.dog.name);
  NSLog(@"狗的价格: %d",person.dog.price);

姓名: 小王
年龄: 33
金钱: 1000.00
人所拥有的狗: {
  name = "wang cai";
  price = 500;
}
狗的类型: __NSDictionaryI
-[__NSDictionaryI name]: unrecognized selector sent to instance 0x7fa461e0df20
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSDictionaryI name]: unrecognized selector sent to instance 0x7fa461e0df20'
*** First throw call stack:
(
  0   CoreFoundation                      0x000000010796ce65 __exceptionPreprocess + 165
  1   libobjc.A.dylib                     0x00000001073e5deb objc_exception_throw + 48
  2   CoreFoundation                      0x000000010797548d -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
  3   CoreFoundation                      0x00000001078c290a ___forwarding___ + 970
  4   CoreFoundation                      0x00000001078c24b8 _CF_forwarding_prep_0 + 120
  5   图片处理                        0x0000000106ee4df1 -[ViewController test1] + 865
  6   图片处理                        0x0000000106ee4469 -[ViewController viewDidLoad] + 73
  7   UIKit                               0x0000000107eaff98 -[UIViewController loadViewIfRequired] + 1198
  8   UIKit                               0x0000000107eb02e7 -[UIViewController view] + 27
  9   UIKit                               0x0000000107d86ab0 -[UIWindow addRootViewControllerViewIfPossible] + 61
  10  UIKit                               0x0000000107d87199 -[UIWindow _setHidden:forced:] + 282
  11  UIKit                               0x0000000107d98c2e -[UIWindow makeKeyAndVisible] + 42
  12  UIKit                               0x0000000107d11663 -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 4131
  13  UIKit                               0x0000000107d17cc6 -[UIApplication _runWithMainScene:transitionContext:completion:] + 1760
  14  UIKit                               0x0000000107d14e7b -[UIApplication workspaceDidEndTransaction:] + 188
  15  FrontBoardServices                  0x000000010a6e5754 -[FBSSerialQueue _performNext] + 192
  16  FrontBoardServices                  0x000000010a6e5ac2 -[FBSSerialQueue _performNextFromRunLoopSource] + 45
  17  CoreFoundation                      0x0000000107898a31 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
  18  CoreFoundation                      0x000000010788e95c __CFRunLoopDoSources0 + 556
  19  CoreFoundation                      0x000000010788de13 __CFRunLoopRun + 867
  20  CoreFoundation                      0x000000010788d828 CFRunLoopRunSpecific + 488
  21  UIKit                               0x0000000107d147cd -[UIApplication _run] + 402
  22  UIKit                               0x0000000107d19610 UIApplicationMain + 171
  23  图片处理                        0x0000000106ee570f main + 111
  24  libdyld.dylib                       0x000000010a0a892d start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException


 /*
   KVC 赋值是非常暴力的
   给person对象狗属性赋值的时候相当于
   [person setValue:@{@"name":@"wang cai",
   @"price":@"500"} forKey:@"dog"];
   直接把@{@"name":@"wang cai",@"price":@"500"}赋值给dog属性
   所以dog得class打印出来是__NSDictionaryI
   
   开发中不介意使用setValuesForKeysWithDictionary:
   1.字典中的key值必须在模型的属性中找到
   2.如果模型中的属性带有模型,setValuesForKeysWithDictionary:不能正确转换
   应用场景:简单的字典转模型 ----> 框架(MJExtention)
   
   */
KVC setValuesForKeysWithDictionary:底层实现,解决模型中的属性带有模型不能正确转换问题
  • KVC setValuesForKeysWithDictionary:底层实现

Person.h 中声明部分
/**
*  通过KVC进行字典转模型
*
*  @param dic 需要解析的字典
*/
- (void)dictionaryToModel:(NSDictionary *)dic;

Person.m 中实现部分

- (void)dictionaryToModel:(NSDictionary *)dic{
  
//    [self setValuesForKeysWithDictionary:dic];
  
  // KVC
  // setValuesForKeysWithDictionary:底层实现
  // 遍历字典中的所有key,去模型中查找有没有对应的属性名,如果就给这个属性赋值
  
  [dic enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
     
      [self setValue:obj forKey:key];
      
      NSLog(@"%@  %@",key,obj);
      /*
       age  33
       money  1000
       dog  {
       name = "wang cai";
       price = 500;
       }
       name  小王
       */
      /*
       以name属性为例:
       [self setValue:@"小王" forKey:@"name"];
       1.首先去模型中查找有么有setName方法,如果有就直接调用
       [self setName:@"小王"];
       2.没有setName方法,继续去模型中查找有没有name属性,如果有,就直接访问成员属性
       name = @"小王";
       3.如果没有name属性,继续去模型中查找有没有_name属性,如果有,就直接访问成员属性
       _name = @"小王";
       4.如果都找不到,就直接报错。
       
       */

  }];
}
使用过程:
  NSDictionary *dic = @{@"name":@"小王",
                        @"age":@"33",
                        @"money":@"1000",
                        @"dog":@{@"name":@"wang cai",
                                 @"price":@"500"},
                        };
  
  
  Person *person = [[Person alloc] init];
  person.dog = [[Dog alloc] init];
  [person dictionaryToModel:dic];
  NSLog(@"姓名: %@",person.name);
  NSLog(@"年龄: %d",person.age);
  NSLog(@"金钱: %.2f",person.money);
  NSLog(@"人所拥有的狗: %@",person.dog);
  NSLog(@"狗的类型: %@",person.dog.class);

打印结果:
姓名: 小王
年龄: 33
金钱: 1000.00
人所拥有的狗: {
  name = "wang cai";
  price = 500;
}
狗的类型: __NSDictionaryI

  • 解决模型中的属性带有模型不能正确转换问题
修改Person.m,添加dog的setter方法

#import "Person.h"
#import "Dog.h"

@interface Person ()

// Person 私有属性
@property (nonatomic, strong) NSString *telephone;

@end

@implementation Person

/**
 *  初始化方法
 *
 *  @return <#return value description#>
 */
- (instancetype)init{
    
    self = [super init];
    
    if (self != nil) {
        
        _telephone = @"18200002222";
        
    };
    
    return self;
}

- (void)printTelephone{
    
    NSLog(@"电话为: %@",_telephone);
}

- (void)dictionaryToModel:(NSDictionary *)dic{
    
    [self setValuesForKeysWithDictionary:dic];
    
}

/**
 *  修改dog的set方法,如果传入的是Dog类型就给dog属性执行属性赋值操作
 *  如果传入的是dit字典类型就通setValuesForKeysWithDictionary:给Dog模型赋值
 *
 *  @param dog <#dog description#>
 */
- (void)setDog:(id)dog{
    
    if ([dog isKindOfClass:[Dog class]]) {
        
        _dog = dog;
        
    } else {
        
        if (_dog != nil && [dog isKindOfClass:[NSDictionary class]]) {
            
            [_dog setValuesForKeysWithDictionary:dog];
        }
    }
}

@end

使用过程:
    NSDictionary *dic = @{@"name":@"小王",
                          @"age":@"33",
                          @"money":@"1000",
                          @"dog":@{@"name":@"wang cai",
                                   @"price":@"500"},
                          };
    
    
    Person *person = [[Person alloc] init];
    
    person.dog = [[Dog alloc] init];
    
    [person dictionaryToModel:dic];
    
    NSLog(@"姓名: %@",person.name);
    NSLog(@"年龄: %d",person.age);
    NSLog(@"金钱: %.2f",person.money);
    NSLog(@"人所拥有的狗: %@",person.dog);
    NSLog(@"狗的类型: %@",person.dog.class);
    NSLog(@"狗的名字: %@",person.dog.name);
    NSLog(@"狗的价格: %d",person.dog.price);

打印结果:
 姓名: 小王
 年龄: 33
 金钱: 1000.00
 人所拥有的狗: <Dog: 0x7fddbad16700>
 狗的类型: Dog
 狗的名字: wang cai
 狗的价格: 500


使用KVC实现模型转字典
Person *person = [[Person alloc] init];
  
  person.dog = [[Dog alloc] init];
  
  [person setValue:@"xiao ming" forKeyPath:@"name"];
  
  [person setValue:@"22" forKey:@"age"];
  
  [person setValue:@"1000" forKeyPath:@"money"];
  
  [person setValue:@"旺财" forKeyPath:@"dog.name"];

  NSDictionary *dic = [person dictionaryWithValuesForKeys:@[@"name",@"age",@"money",@"dog"]];

  NSLog(@"dic = %@",dic);

打印结果:
dic = {
  age = 22;
  dog = "<Dog: 0x7feaa9703360>";
  money = 1000;
  name = "xiao ming";
}


使用KVC取出数组中所有模型的某个属性(数组中所有模型类型相同)
  NSMutableArray *personArray = [[NSMutableArray alloc] initWithCapacity:10];
  
  for (int i = 0; i < 10; i++) {
      
      Person *person = [[Person alloc] init];
      
      person.dog = [[Dog alloc] init];
      
      NSString *name = [NSString stringWithFormat:@"xiao ming %d",i];
      
      [person setValue:name forKeyPath:@"name"];
      
      NSString *age = [NSString stringWithFormat:@"2%d",i];
      
      [person setValue:age forKey:@"age"];
      
      NSString *money = [NSString stringWithFormat:@"1%d00",i];
      
      [person setValue:money forKeyPath:@"money"];
      
      [personArray addObject:person];
  }
  
  NSArray *nameArray = [personArray valueForKeyPath:@"name"];
  NSArray *ageArray = [personArray valueForKeyPath:@"age"];
  NSArray *moneyArray = [personArray valueForKeyPath:@"money"];
  
  NSLog(@"nameArray = %@",nameArray);
  NSLog(@"ageArray = %@",ageArray);
  NSLog(@"moneyArray = %@",moneyArray);
打印结果:
nameArray = (
  "xiao ming 0",
  "xiao ming 1",
  "xiao ming 2",
  "xiao ming 3",
  "xiao ming 4",
  "xiao ming 5",
  "xiao ming 6",
  "xiao ming 7",
  "xiao ming 8",
  "xiao ming 9"
)

ageArray = (
  20,
  21,
  22,
  23,
  24,
  25,
  26,
  27,
  28,
  29
)

moneyArray = (
  1000,
  1100,
  1200,
  1300,
  1400,
  1500,
  1600,
  1700,
  1800,
  1900
)

在KVC面前所以属性都是透明可操作的。

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

推荐阅读更多精彩内容