本文為L(zhǎng)_Ares個(gè)人寫作,以任何形式轉(zhuǎn)載請(qǐng)表明原文出處宣赔。
資料準(zhǔn)備 : AppleDevelopment - KVC文檔
代碼準(zhǔn)備 : 創(chuàng)建一個(gè)
Project
--->App
媳溺。創(chuàng)建JDPerson
類。添加NSString
類型的成員變量
,_name
罪针,_isName
,name
黄伊,isName
泪酱。
上一節(jié)主要介紹了一些KVC
的基本信息,包括知道了KVC
本身是一種間接訪問(wèn)機(jī)制还最,提供的是直接訪問(wèn)實(shí)例變量的setter
和getter
墓阀,還有一些常見(jiàn)或者常用的API
。
本節(jié)依然從文檔入手拓轻,探索KVC
的訪問(wèn)模式是怎樣的斯撮。
首先從最最常見(jiàn)的setValueForKey
來(lái)看,在我們對(duì)一個(gè)類的某個(gè)屬性利用KVC
進(jìn)行賦值的時(shí)候扶叉,比如說(shuō)JDPerson
有個(gè)name
屬性勿锅,訪問(wèn)的一般是name
的setter
或者getter
,那么這就會(huì)影響對(duì)KVC
本身的探索辜梳,所以為了純凈環(huán)境下探索KVC
的訪問(wèn)模式粱甫,我就選擇相對(duì)更純凈一點(diǎn)的成員變量
方式,而不是通過(guò)屬性的方式作瞄。
一茶宵、KVC的訪問(wèn)模式——設(shè)值過(guò)程
先看一下官方文檔里面對(duì)于KVC訪問(wèn)模式
中的Setter
的一些知識(shí)。
根據(jù)自己的理解宗挥,加上翻譯乌庶,可以將官方文檔給出的三個(gè)知識(shí)點(diǎn)總結(jié) :
【前言】 : setValue:forKey:
這個(gè)方法的默認(rèn)實(shí)現(xiàn),給定key
和value
作為輸入?yún)?shù),嘗試將名為key
的變量的值設(shè)置為value
(對(duì)于非對(duì)象屬性
,也就是上一節(jié)中見(jiàn)到過(guò)的結(jié)構(gòu)體
挣轨,是需要進(jìn)行一步包裝的)韩肝,設(shè)置的流程如下所述 :
- 第一,先尋找
set
或_set
方法领斥,如果找到了就調(diào)用set
或者_set
方法進(jìn)行賦值筋帖。
- 第二趟薄,如果沒(méi)有找到
set
或者_set
方法酗电,并且調(diào)用KVC
的對(duì)象的類的accessInstanceVariablesDirectly
方法返回值是YES
魄藕,那么就按照順序
查找具有如下名稱的實(shí)例變量(拿name
舉例),_name
撵术、_isName
背率、name
、isName
嫩与。如果找到這四個(gè)之一寝姿,直接對(duì)其進(jìn)行賦值為value
,并且完成賦值划滋。
- 第三饵筑,如果第一,第二條全都不滿足古毛,那就調(diào)用
setValue:forUndefinedKey:
翻翩。本來(lái)默認(rèn)是會(huì)拋出異常的,但是NSObject
的一個(gè)子類提供了這個(gè)方法來(lái)處理異常的key
稻薇。
按照步驟嫂冻,舉個(gè)例子 :
(1). 驗(yàn)證上面的第1點(diǎn) :
JDPerson.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface JDPerosn : NSObject
{
@public
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
}
@end
NS_ASSUME_NONNULL_END
JDPerson.m
#import "JDPerosn.h"
@implementation JDPerosn
#pragma mark - 開(kāi)啟或關(guān)閉實(shí)例變量的賦值
//默認(rèn)就是YES,設(shè)置為NO的話塞椎,如果沒(méi)有set或_set的方法桨仿,那么就無(wú)法對(duì)實(shí)例變量進(jìn)行賦值
+(BOOL)accessInstanceVariablesDirectly
{
return YES;
}
#pragma mark - KVC - setKey流程
//這就是文檔中說(shuō)的set<key>
- (void)setName:(NSString *)name
{
NSLog(@"%s---%@",__func__,name);
}
//_set<key>
- (void)_setName:(NSString *)name
{
NSLog(@"%s---%@",__func__,name);
}
- (void)setIsName:(NSString *)name
{
NSLog(@"%s---%@",__func__,name);
}
@end
ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
JDPerosn *person = [[JDPerosn alloc] init];
//1. KVC - 設(shè)置值的過(guò)程
[person setValue:@"LJD" forKey:@"name"];
NSLog(@"%@-%@-%@-%@",person->_name,person->_isName,
person->name,person->isName);
NSLog(@"%@-%@-%@",person->_isName,person->name,person->isName);
NSLog(@"%@-%@",person->name,person->isName);
NSLog(@"%@",person->isName);
}
執(zhí)行結(jié)果 :
可以再把- (void)setName:(NSString *)name
方法注釋掉,會(huì)發(fā)現(xiàn)調(diào)用- (void)_setName:(NSString *)name
案狠,如果把它也注釋掉服傍,則會(huì)調(diào)用- (void)setIsName:(NSString *)name
。4個(gè)實(shí)例變量
的值依然都是null
骂铁。
這就驗(yàn)證了上面說(shuō)的第一點(diǎn)吹零,在利用KVC
設(shè)置值的時(shí)候,會(huì)先找到set
或者_set
方法對(duì)其進(jìn)行賦值拉庵。
(2). 驗(yàn)證上面的第二點(diǎn) :
把JDPerson.m
中的有關(guān)set
的3個(gè)方法全部注釋掉灿椅,然后讓accessInstanceVariablesDirectly
變成return NO
。其余代碼不變钞支,再執(zhí)行茫蛹。結(jié)果如下圖 :
所以驗(yàn)證了第二點(diǎn)中,沒(méi)有set
或_set
方法的時(shí)候烁挟,accessInstanceVariablesDirectly
要設(shè)置成YES
才可以婴洼。
然后將accessInstanceVariablesDirectly
重新設(shè)置成YES
。再執(zhí)行撼嗓。結(jié)果如下圖 :
驗(yàn)證了第二點(diǎn)中會(huì)先給_name
賦值柬采。
然后依次的注釋掉JDPerson.h
中的成員變量_name
欢唾、_isName
、name
警没。
就會(huì)驗(yàn)證第二點(diǎn)中匈辱,KVC
賦值同名變量的順序是_name
、_isName
杀迹、name
、isName
押搪。
第三點(diǎn)我就不驗(yàn)證了树酪,因?yàn)榻?jīng)常會(huì)用到,比如字典轉(zhuǎn)模型的時(shí)候大州,大家應(yīng)該常用续语。
另外,KVC
為什么有第二點(diǎn)這種設(shè)計(jì)呢厦画?因?yàn)樵诰幾g期的時(shí)候疮茄,底層也會(huì)生成一些這樣的變量,比如之前我們?cè)谇懊娴恼鹿?jié)進(jìn)行clang
將.m
文件編譯成.cpp
文件的時(shí)候根暑,有一些屬性就會(huì)變成了_xxx
的形式的成員變量力试,或者BOOL
類型的值會(huì)變成isXXX
的形式,這也是因?yàn)檫\(yùn)行時(shí)的特性造成的排嫌。
總結(jié) :
二畸裳、KVC的訪問(wèn)模式——取值過(guò)程
一樣先來(lái)查閱官方文檔,然后根據(jù)自己的理解加上翻譯淳地,再做總結(jié)怖糊。
明顯getter
的過(guò)程比較多。還是按照官方的分成六點(diǎn)颇象。
【前言】 : valueForKey:
的默認(rèn)實(shí)現(xiàn)伍伤,給定key
作為輸入?yún)?shù),執(zhí)行下列程序遣钳,從接收valueForKey:
調(diào)用的類實(shí)例的內(nèi)部操作扰魂。
- 第一,按照順序查找耍贾,是否有
get<Key>, <key>, is<Key>,_<key>
這些方法阅爽。如果找到了就調(diào)用它,并且在下面的第五點(diǎn)
中處理結(jié)果荐开。如果找不到就進(jìn)入第二點(diǎn)
付翁。
- 第二,如果
第一點(diǎn)
中的4個(gè)get
方法都沒(méi)實(shí)現(xiàn)晃听,在實(shí)例方法中查找countOf<Key>
百侧、objectIn<Key>AtIndex:
還有<key>AtIndexes:
方法砰识。
- 如果
countOf<Key>
存在,并且objectIn<Key>AtIndex:
和<key>AtIndexes:
中至少一個(gè)方法存在佣渴,那么創(chuàng)建一個(gè)響應(yīng)所有的NSArray
方法的集合代理對(duì)象
辫狼,并且返回這個(gè)集合代理對(duì)象
。- 否則進(jìn)入
第三點(diǎn)
辛润。集合代理對(duì)象
隨后會(huì)將所有接收到的NSArray
的消息都轉(zhuǎn)換為countOf
膨处、objectInAtIndex:
和AtIndexes:
的一些組合,這些組合將消息發(fā)送給創(chuàng)建它的符合KVC
機(jī)制的對(duì)象砂竖。- 如果原始對(duì)象還實(shí)現(xiàn)了一個(gè)名為
get:range:
的可選方法真椿,代理對(duì)象也會(huì)在適當(dāng)?shù)臅r(shí)候使用它。- 實(shí)際上乎澄,代理對(duì)象與和
KVC
兼容的對(duì)象一起工作突硝,允許底層屬性像NSArray
一樣工作,即使它不是NSArray
置济。
- 第三解恰,如果沒(méi)有找到
第二點(diǎn)
中的3個(gè)方法,則同時(shí)查找countOf <Key>浙于,enumeratorOf<Key>和memberOf<Key>
這三個(gè)方法护盈,
- 如果這三個(gè)方法都找到了,創(chuàng)建一個(gè)響應(yīng)所有
NSSet
方法的集合代理對(duì)象
路媚,并將集合代理對(duì)象
返還黄琼。- 如果這三個(gè)方法也沒(méi)找到,那么就直接進(jìn)入
第四點(diǎn)
整慎。集合代理對(duì)象
隨后將它接收到的任何NSSet消息轉(zhuǎn)換為countOf
脏款、enumeratorOf
和memberOf:
消息的組合,并發(fā)送給創(chuàng)建它的對(duì)象裤园。- 實(shí)際上撤师,代理對(duì)象與遵循
KVC
的對(duì)象一起工作,允許底層屬性像NSSet
一樣運(yùn)行拧揽,即使它不是NSSet
剃盾。
- 第四,如果
get
方法和集合方法
都沒(méi)找到淤袜,并且接收者的類方法accessinstancevariables
返回YES痒谴,則按照順序查找_<key>, _is<Key>, <key>, is<Key>
變量。如果找到铡羡,直接獲取實(shí)例變量的值并進(jìn)入第五點(diǎn)
积蔚。否則,進(jìn)入第六點(diǎn)
烦周。
- 第五尽爆,根據(jù)搜索到的屬性值的類型怎顾,返回不同的結(jié)果
- 如果是
對(duì)象指針
,則直接返回結(jié)果漱贱。- 如果值是
NSNumber
支持的標(biāo)量類型槐雾,將其存儲(chǔ)在NSNumber
實(shí)例中并返回。- 如果結(jié)果是
NSNumber
不支持的標(biāo)量類型幅狮,轉(zhuǎn)換為NSValue對(duì)象
并返回它募强。
- 第六,如果上面的所有方法都沒(méi)有找到彪笼,調(diào)用
setValue:forUndefinedKey:
钻注。默認(rèn)情況下會(huì)拋出異常,可以執(zhí)行這個(gè)方法來(lái)處理配猫。
因?yàn)檫@里明顯的能看出來(lái),簡(jiǎn)單類型取值和集合類型是不一樣的杏死,先看簡(jiǎn)單類型泵肄,按照步驟,舉個(gè)例子 :
(1). 驗(yàn)證上面的第1點(diǎn)和第4點(diǎn) :
JDPerson.h
不發(fā)生改變淑翼。
JDPerson.m
#pragma mark - 開(kāi)啟或關(guān)閉實(shí)例變量的賦值
//默認(rèn)就是YES腐巢,設(shè)置為NO的話,如果沒(méi)有set或_set的方法玄括,那么就無(wú)法對(duì)實(shí)例變量進(jìn)行賦值
+(BOOL)accessInstanceVariablesDirectly
{
return YES;
}
#pragma mark - KVC - getKey流程
//文檔中說(shuō)的get<key>
- (NSString *)getName
{
return NSStringFromSelector(_cmd);
}
//\<key>
- (NSString *)name
{
return NSStringFromSelector(_cmd);
}
//is<key>
- (NSString *)isName
{
return NSStringFromSelector(_cmd);
}
//_<key>
- (NSString *)_name
{
return NSStringFromSelector(_cmd);
}
ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self jd_kvc_getter];
}
- (void)jd_kvc_getter
{
JDPerosn *person = [[JDPerosn alloc] init];
person->_name = @"_name";
person->_isName = @"_isName";
person->name = @"name";
person->isName = @"isName";
NSLog(@"KVC取值---%@",[person valueForKey:@"name"]);
}
- (void)jd_kvc_setter
{
JDPerosn *person = [[JDPerosn alloc] init];
//1. KVC - 設(shè)置值的過(guò)程
[person setValue:@"LJD" forKey:@"name"];
NSLog(@"%@-%@-%@-%@",person->_name,person->_isName,
person->name,person->isName);
NSLog(@"%@-%@-%@",person->_isName,person->name,person->isName);
NSLog(@"%@-%@",person->name,person->isName);
NSLog(@"%@",person->isName);
}
執(zhí)行結(jié)果 :
然后依次注釋掉4個(gè)get
方法冯丙,就可以驗(yàn)證第一點(diǎn),并且全部注釋掉以后會(huì)走到第四點(diǎn)遭京,順便把第四點(diǎn)也驗(yàn)證了胃惜。
從驗(yàn)證可以看出,
valueForKey
先找的不是我們對(duì)變量的賦值哪雕,而是先找的getter
方法船殉,只有getter
方法沒(méi)有實(shí)現(xiàn)的情況下,才會(huì)找到變量直接拿值斯嚎。簡(jiǎn)單類型
也就是非集合類型的
只會(huì)找到第一點(diǎn)
和第四點(diǎn)
如果有錯(cuò)誤會(huì)直接到第六點(diǎn)
利虫。
(1). 驗(yàn)證上面的第2點(diǎn)和第3點(diǎn) :
因?yàn)榈诙谌c(diǎn)的原理是一樣的堡僻,這里就拿更常見(jiàn)的數(shù)組來(lái)說(shuō)明糠惫,NSSet
的例子會(huì)放出來(lái),但是就不說(shuō)明了钉疫。
JDPerson.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface JDPerosn : NSObject
{
@public
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
NSArray *arr;
NSSet *set;
}
@end
NS_ASSUME_NONNULL_END
JDPerson.m
#pragma mark - KVC - 正常的數(shù)組取值
- (NSUInteger)countOfArr
{
NSLog(@"%s",__func__);
return [arr count];
}
- (id)objectInArrAtIndex:(NSUInteger)index
{
NSLog(@"%s",__func__);
return [NSString stringWithFormat:@"objectInArrAtIndex : %lu",index];
}
#pragma mark - KVC - 正常的集合取值
- (NSUInteger)countOfSet
{
NSLog(@"%s",__func__);
return [set count];
}
- (id)memberOfSet:(id)object
{
NSLog(@"%s",__func__);
return [set containsObject:object] ? object : nil;
}
- (id)enumeratorOfSet
{
NSLog(@"%s",__func__);
return [set objectEnumerator];
}
#pragma mark - KVC - 沒(méi)有Pens硼讽,但是我有方法,一樣可以取值數(shù)組
- (NSUInteger)countOfPens
{
NSLog(@"%s",__func__);
return [arr count];
}
- (id)objectInPensAtIndex:(NSUInteger)index
{
NSLog(@"%s",__func__);
return [NSString stringWithFormat:@"objectInPensAtIndex %lu", index];
}
- (id)pensAtIndexes:(NSUInteger)index
{
NSLog(@"%s",__func__);
return [NSString stringWithFormat:@"pensAtIndexes %lu", index];
}
#pragma mark - KVC - 沒(méi)有books陌选,但是我有方法理郑,一樣可以取值集合
// 個(gè)數(shù)
- (NSUInteger)countOfBooks{
NSLog(@"%s",__func__);
return [arr count];
}
// 是否包含這個(gè)成員對(duì)象
- (id)memberOfBooks:(id)object {
NSLog(@"%s",__func__);
return [set containsObject:object] ? object : nil;
}
// 迭代器
- (id)enumeratorOfBooks {
// objectEnumerator
NSLog(@"來(lái)了 迭代編譯");
return [arr reverseObjectEnumerator];
}
ViewController.m
- (void)jd_kvc_array_and_set
{
JDPerosn *person = [[JDPerosn alloc] init];
person->arr = @[@"pen0", @"pen1", @"pen2", @"pen3"];
NSLog(@"正常的數(shù)組取值 : %@",[person valueForKey:@"arr"]);
NSArray *array = [person valueForKey:@"pens"];
NSLog(@"就算沒(méi)有pens蹄溉,只要在類中實(shí)現(xiàn)了方法也可以objectAtIndex : %@",[array objectAtIndex:1]);
NSLog(@"就算沒(méi)有pens,只要在類中實(shí)現(xiàn)了方法也可以containsObject : %d",[array containsObject:@"pen1"]);
person->set = [NSSet setWithArray:person->arr];
NSLog(@"正常的集合取值 : %@",[person valueForKey:@"set"]);
NSSet *set = [person valueForKey:@"books"];
[set enumerateObjectsUsingBlock:^(id _Nonnull obj, BOOL * _Nonnull stop) {
NSLog(@"set遍歷 %@",obj);
}];
}
執(zhí)行結(jié)果就不貼圖了您炉,太長(zhǎng)了柒爵,可以自己運(yùn)行一下,會(huì)發(fā)現(xiàn)count
的方法都會(huì)打印兩次赚爵,也證明了的確是存在著代理集合的棉胀。所以驗(yàn)證也是符合的。
總結(jié) :
KVC
的valueForKey
的流程主要還是分了簡(jiǎn)單對(duì)象和集合對(duì)象冀膝,簡(jiǎn)單對(duì)象的取值和其setValueForKey
是一樣的唁奢。而集合對(duì)象則多需要幾步驟,但是可以防止取到不認(rèn)識(shí)的key
窝剖,也可以修改取值的結(jié)果麻掸。
三、KVC的一些特殊功能
1. KVC有自動(dòng)轉(zhuǎn)換類型的功能赐纱。
JDPerson.h
:
typedef struct {
float x, y, z;
} ThreeFloats;
@interface JDPerosn : NSObject
{
@public
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
NSArray *arr;
NSSet *set;
int age;
ThreeFloats threeFloats;
}
@end
1.1 NSNumber
支持的標(biāo)量類型
//1. NSNumber支持的標(biāo)量類型
JDPerosn *person = [[JDPerosn alloc] init];
[person setValue:@18 forKey:@"age"];
NSLog(@"%@-%@",[person valueForKey:@"age"],[[person valueForKey:@"age"] class]);//__NSCFNumber
[person setValue:@"20" forKey:@"age"];
NSLog(@"%@-%@",[person valueForKey:@"age"],[[person valueForKey:@"age"] class]);//__NSCFNumber
證明了 :
KVC
在對(duì)已知類型的value
設(shè)置的時(shí)候脊奋,如果類型是NSNumber
支持的標(biāo)量類型,則會(huì)將value
存儲(chǔ)到NSNumber
的實(shí)例中疙描,在取值的時(shí)候返回NSNumber
實(shí)例诚隙。
1.2 NSNumber不支持的標(biāo)量類型
//2. NSNumber不支持的標(biāo)量類型
ThreeFloats floats = {1.f, 2.f, 3.f};
NSValue *value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[person setValue:value forKey:@"threeFloats"];
NSLog(@"%@-%@",[person valueForKey:@"threeFloats"],[[person valueForKey:@"threeFloats"] class]);//NSConcreteValue
證明了 :
KVC
存儲(chǔ)的類型如果是NSNumber
不支持的標(biāo)量類型,那么就要轉(zhuǎn)換為NSValue
存儲(chǔ)并且返回起胰。
2. 空值
在JDPerson.m
中添加KVC
監(jiān)控 :
- (void)setNilValueForKey:(NSString *)key{
NSLog(@"設(shè)置 %@ 是空值",key);
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key{
NSLog(@"沒(méi)有這個(gè)key : %@",key);
}
- (id)valueForUndefinedKey:(NSString *)key{
NSLog(@"沒(méi)有這個(gè)key : %@ - 給你一個(gè)其他的吧,別奔潰了!",key);
return @"LJD";
}
2.1 key
存在久又,value
設(shè)置為空
在-(void)viewDidLoad
中 :
JDPerosn *person = [[JDPerosn alloc] init];
[person setValue:nil forKey:@"age"]; // subject不會(huì)走 - 官方注釋里面說(shuō)只對(duì) NSNumber - NSValue
[person setValue:nil forKey:@"name"];
證明了 :
KVC
中的setNilValueForKey
只監(jiān)控NSNumber
和NSValue
的結(jié)構(gòu)體,不會(huì)監(jiān)控到其他類型效五。
2.2 key
不存在地消,value
設(shè)置為空
[person setValue:nil forKey:@"LJD"]; //LJD不是已知的key
可以實(shí)現(xiàn)這個(gè) :
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
進(jìn)行監(jiān)控。
2.3 key
為空
NSLog(@"%@",[person valueForKey:@"KC"]); //KC是不存在的key
這個(gè)可以使用 :
- (id)valueForUndefinedKey:(NSString *)key
進(jìn)行監(jiān)控火俄。
3. 鍵值驗(yàn)證
JDPerson.m
中添加 :
- (BOOL)validateValue:(inout id _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError *__autoreleasing _Nullable *)outError{
if([inKey isEqualToString:@"name"]){
[self setValue:[NSString stringWithFormat:@"可以修改一下: %@",*ioValue] forKey:inKey];
return YES;
}
*outError = [[NSError alloc]initWithDomain:[NSString stringWithFormat:@"%@ 不是 %@ 的屬性",inKey,self] code:16688 userInfo:nil];
return NO;
}
viewDidLoad
中 :
NSError *error;
NSString *name = @"LJD";
JDPerosn *person = [[JDPerosn alloc] init];
if (![person validateValue:&name forKey:@"names" error:&error]) {
NSLog(@"%@",error);
}else{
NSLog(@"%@",[person valueForKey:@"name"]);
}
結(jié)果 :