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   ???áaá?§?áêü                        0x0000000106ee4df1 -[ViewController test1] + 865
  6   ???áaá?§?áêü                        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  ???áaá?§?áêü                        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面前所以屬性都是透明可操作的。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容