iOS 鍵值編碼 KVC

KVC

KVC -- Key Value Coding 鍵值編碼

  • 鍵值編碼的基本概念
    • 鍵值編碼是一個用于簡介訪問對象屬性的機制敏晤,使用該機制不需要調(diào)用存取方法和變量實例就可以訪問對象屬性
    • 鍵值編碼的方法在object-c非正式協(xié)議(類目)NSKeyValueCoding中被聲明嘴脾,默認的實現(xiàn)方法有NSObject提供。
    • 鍵值編碼支持帶有對象值得屬性彩倚,同時也支持純數(shù)值類型和結(jié)果扶平。非對象參數(shù)和返回值類型會被識別并自動封裝/解封结澄。
  • 設(shè)置和訪問
      鍵值編碼中的基本調(diào)用包括-valueForKey: 和 -setValue:forKey:這兩個方法,它們以字符串的形式向?qū)ο蟀l(fā)送消息们妥,字符串是我們關(guān)注屬性的關(guān)鍵监婶。
  Person *person = [[Person alloc] init];
  [person setValue:@"小明" forKey:@"name"];
  NSString *personName = [person valueForKey:@"name"];
  /*
   [person setValue:@"小明" forKey:@"_name"];
   NSString *personName = [person valueForKey:@"_name"];
   */
  /*
   KVC 首先會去調(diào)用對象的setter齿桃、getter方法短纵,如果setter、getter方法不存在則會查找實例變量名為name的屬性
       如果實例變量名為name的屬性不存在會查找實例變量名為_name的屬性鱼冀。如果都不存在會報錯。
   */

打印結(jié)果:

personName = 小明

是否存在setter充易、getter方法蔽氨,如果不存在帆疟,它將在內(nèi)部查找名為_key 或 key的實例變量宇立。通過KVC妈嘹,可以獲取不存在getter方法的對象值,無需通過對象指針直接訪問柬脸。這里我們需要注意倒堕,當(dāng)我們通過-setValue:forKey:設(shè)置對象的值爆价,或通過-valueForKey: 來獲取對象的值時,如若對象的實例變量為基本數(shù)據(jù)類型時(char骤宣、int憔披、float爸吮、BOOL)拗胜,我們需要對數(shù)據(jù)進行封裝。

  • 路徑
      除了通過鍵值設(shè)置外锈遥,鍵值編碼還支持指定路徑,像文件系統(tǒng)一樣丽惶。
    [person setValue:@"小明" forKeyPath:@"name"];
    
    NSString *personName = [person valueForKeyPath:@"name"];
    
    
先創(chuàng)建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進行字典轉(zhuǎn)模型
*
*  @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"];
  */

打印結(jié)果:

姓名: 小明
年齡: 22
金錢: 1000.00(傳入的是一個字符串類型@"1000",說明KVC可以自動進行類型轉(zhuǎn)換)
狗名: 旺財
  • forKey 和 forKeyPath 的一些差異
  • forKeyPath 包含了所有 forKey 的功能
  • forKeyPath 可以進行內(nèi)部的點語法,層層訪問內(nèi)部的屬性
```
[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];

打印結(jié)果:

  //初始化默認號碼
 電話為: 18200002222
  //修改過之后的號碼
 電話為: 15066669999

使用KVC實現(xiàn)字典轉(zhuǎn)模型
Person.h 中聲明部分
/**
*  通過KVC進行字典轉(zhuǎn)模型
*
*  @param dic 需要解析的字典
*/
- (void)dictionaryToModel:(NSDictionary *)dic;

Person.m 中實現(xiàn)部分
- (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);

打印結(jié)果:
姓名: 小王
年齡: 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對象狗屬性賦值的時候相當(dāng)于
   [person setValue:@{@"name":@"wang cai",
   @"price":@"500"} forKey:@"dog"];
   直接把@{@"name":@"wang cai",@"price":@"500"}賦值給dog屬性
   所以dog得class打印出來是__NSDictionaryI
   
   開發(fā)中不介意使用setValuesForKeysWithDictionary:
   1.字典中的key值必須在模型的屬性中找到
   2.如果模型中的屬性帶有模型,setValuesForKeysWithDictionary:不能正確轉(zhuǎn)換
   應(yīng)用場景:簡單的字典轉(zhuǎn)模型 ----> 框架(MJExtention)
   
   */
KVC setValuesForKeysWithDictionary:底層實現(xiàn),解決模型中的屬性帶有模型不能正確轉(zhuǎn)換問題
  • KVC setValuesForKeysWithDictionary:底層實現(xiàn)

Person.h 中聲明部分
/**
*  通過KVC進行字典轉(zhuǎn)模型
*
*  @param dic 需要解析的字典
*/
- (void)dictionaryToModel:(NSDictionary *)dic;

Person.m 中實現(xiàn)部分

- (void)dictionaryToModel:(NSDictionary *)dic{
  
//    [self setValuesForKeysWithDictionary:dic];
  
  // KVC
  // setValuesForKeysWithDictionary:底層實現(xiàn)
  // 遍歷字典中的所有key,去模型中查找有沒有對應(yīng)的屬性名着撩,如果就給這個屬性賦值
  
  [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方法匾委,如果有就直接調(diào)用
       [self setName:@"小王"];
       2.沒有setName方法赂乐,繼續(xù)去模型中查找有沒有name屬性,如果有辐啄,就直接訪問成員屬性
       name = @"小王";
       3.如果沒有name屬性运嗜,繼續(xù)去模型中查找有沒有_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);

打印結(jié)果:
姓名: 小王
年齡: 33
金錢: 1000.00
人所擁有的狗: {
  name = "wang cai";
  price = 500;
}
狗的類型: __NSDictionaryI

  • 解決模型中的屬性帶有模型不能正確轉(zhuǎn)換問題
修改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屬性執(zhí)行屬性賦值操作
 *  如果傳入的是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);

打印結(jié)果:
 姓名: 小王
 年齡: 33
 金錢: 1000.00
 人所擁有的狗: <Dog: 0x7fddbad16700>
 狗的類型: Dog
 狗的名字: wang cai
 狗的價格: 500


使用KVC實現(xiàn)模型轉(zhuǎn)字典
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);

打印結(jié)果:
dic = {
  age = 22;
  dog = "<Dog: 0x7feaa9703360>";
  money = 1000;
  name = "xiao ming";
}


使用KVC取出數(shù)組中所有模型的某個屬性(數(shù)組中所有模型類型相同)
  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);
打印結(jié)果:
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面前所以屬性都是透明可操作的寄锐。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末橄仆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子援雇,更是在濱河造成了極大的恐慌椎扬,老刑警劉巖具温,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铣猩,死亡現(xiàn)場離奇詭異达皿,居然都是意外死亡,警方通過查閱死者的電腦和手機龄寞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門汤功,熙熙樓的掌柜王于貴愁眉苦臉地迎上來滔金,“玉大人,你說我怎么就攤上這事科阎》拮澹” “怎么了蝌矛?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵朴读,是天一觀的道長衅金。 經(jīng)常有香客問我簿煌,道長,這世上最難降的妖魔是什么惩琉? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任瞒渠,我火速辦了婚禮技扼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘窍箍。我一直安慰自己丽旅,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著茅撞,像睡著了一般乡翅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上尚洽,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天靶累,我揣著相機與錄音,去河邊找鬼潮酒。 笑死急黎,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的淤击。 我是一名探鬼主播故源,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼绳军,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了耳贬?” 一聲冷哼從身側(cè)響起猎唁,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤诫隅,失蹤者是張志新(化名)和其女友劉穎帐偎,沒想到半個月后削樊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡甸箱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年芍殖,在試婚紗的時候發(fā)現(xiàn)自己被綠了谴蔑。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡计贰,死狀恐怖蒂窒,靈堂內(nèi)的尸體忽然破棺而出刘绣,到底是詐尸還是另有隱情,我是刑警寧澤纬凤,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布停士,位于F島的核電站恋技,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蜻底。R本人自食惡果不足惜薄辅,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一站楚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧窿春,春花似錦旧乞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽左权。三九已至痴颊,卻和暖如春蠢棱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背糕再。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工玉转, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留究抓,地道東北人。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓绑嘹,卻偏偏與公主長得像橘茉,于是被迫代替她去往敵國和親畅卓。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容