KVC概述
KVC全稱是Key-Value-Coding,NSObject類及其子類和內(nèi)建基本數(shù)據(jù)類型都可以通過KVC的方法賦值和取值氓鄙,不需要通過get和set璧坟。
KVC使用
以一個(gè)保存多個(gè)字典的數(shù)組的取值為例:
NSArray *jjArray = @[@{@"name": @"aa", @"age": @(15)},
@{@"name": @"bb", @"age": @(16)},
@{@"name": @"cc", @"age": @(17)}];
// 遍歷數(shù)組獲取key為"name"時(shí)對應(yīng)的value
NSMutableArray *nameArray1 = [NSMutableArray array];
for (NSDictionary *dict in jjArray) {
[nameArray1 addObject:dict[@"name"]];
}
NSLog(@"nameArray1: %@", nameArray1);
// KVC方法篩選key為"name"時(shí)對應(yīng)的value,返回一個(gè)數(shù)組
NSArray *nameArray2 = [jjArray valueForKey:@"name"];
NSLog(@"nameArray2: %@", nameArray2);
日志輸出:
nameArray1: (
aa,
bb,
cc
)
nameArray2: (
aa,
bb,
cc
)
遍歷數(shù)組和KVC的方式得到了相同的結(jié)果讶隐,但明顯KVC方式的代碼量要遠(yuǎn)小于遍歷的方式起胰。
對于復(fù)雜的數(shù)據(jù)結(jié)構(gòu),例如自定義類巫延,KVC也可以快速的取值效五。
// 定義一個(gè)Teacher類
@interface Teacher : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end
// 定義一個(gè)Student類
@interface Student : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end
// ViewController.m
NSMutableArray *aArray = [[NSMutableArray alloc] initWithCapacity:0];
Teacher *teacher = [[Teacher alloc] init];
// 屬性賦值
teacher.name = @"teacher";
teacher.age = 30;
// KVC賦值
// [teacher setValue:@"teacher" forKey:@"name"];
// [teacher setValue:@30 forKey:@"age"];
[aArray addObject:teacher];
for (int i = 0; i < 3; i++) {
Student *student = [[Student alloc] init];
student.name = [NSString stringWithFormat:@"student%d", i];
student.age = i;
[aArray addObject:student];
}
// KVC方法篩選每個(gè)對象中key值為"name"的value
NSArray *teacherArray = [aArray valueForKey:@"name"];
// aArray保存了1個(gè)Teacher對象和3個(gè)Student對象,每個(gè)對象中都有name屬性烈评,所以teacherArray數(shù)組會(huì)有4個(gè)值火俄。
NSLog(@"teacherArray: %@", teacherArray);
日志輸出:
nameArray: (
teacher,
student0,
student1,
student2
)
對于自定義類中含有自定義類的情況,valueForKey: 方法已經(jīng)無法獲取到正確的值了讲冠,需要調(diào)用valueForKeyPath: 方法傳入屬性的路徑來獲取(xxx.xxx.xxx)瓜客。
// Teacher.h
@interface Teacher : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
// 在Teacher類中增加一個(gè)Student類型的數(shù)據(jù)屬性students
@property (nonatomic, strong) NSMutableArray<Student *> *students;
@end
// ViewController.m
NSMutableArray *aArray = [[NSMutableArray alloc] initWithCapacity:0];
Teacher *teacher = [[Teacher alloc] init];
teacher.name = @"teacher";
teacher.age = 30;
teacher.students = [NSMutableArray array];
[aArray addObject:teacher];
for (int i = 0; i < 3; i++) {
Student *student = [[Student alloc] init];
student.name = [NSString stringWithFormat:@"student%d", i];
student.age = i;
[teacher.students addObject:student];
}
NSArray *nameArray = [aArray valueForKey:@"name"];
NSArray *studentNameArray = [aArray valueForKeyPath:@"students.name"];
NSLog(@"nameArray: %@, studentNameArray: %@", nameArray, studentNameArray);
日志輸出:
nameArray: (
teacher
),
studentNameArray: (
(
student0,
student1,
student2
)
)
在這段代碼中,[aArray valueForKey:@"name"]方法只獲取到了teacher對象name屬性的值竿开,而[aArray valueForKeyPath:@"students.name"]才能獲取到student對象name屬性的值谱仪。
KVC的實(shí)現(xiàn)
在網(wǎng)上查了一些資料,對KVC的鍵值查找方式否彩、KVC的實(shí)現(xiàn)原理和內(nèi)部機(jī)制做了詳細(xì)的闡述疯攒,本文直接引用過來。
KVC鍵值查找
setValue:forKey:搜索方式
1列荔、首先搜索setKey:方法敬尺。(key指成員變量名枚尼,首字母大寫)
2、上面的setter方法沒找到砂吞,如果類方法accessInstanceVariablesDirectly返回YES署恍。那么按 _key,_isKey蜻直,key盯质,iskey的順序搜索成員名。(NSKeyValueCodingCatogery中實(shí)現(xiàn)的類方法概而,默認(rèn)實(shí)現(xiàn)為返回YES)
3呼巷、如果沒有找到成員變量,調(diào)用setValue:forUnderfinedKey:
valueForKey:的搜索方式
1赎瑰、首先按getKey王悍,key,isKey的順序查找getter方法乡范,找到直接調(diào)用配名。如果是BOOL、int等內(nèi)建值類型晋辆,會(huì)做NSNumber的轉(zhuǎn)換渠脉。
2、上面的getter沒找到瓶佳,查找countOfKey芋膘、objectInKeyAtindex、KeyAtindexes格式的方法霸饲。如果countOfKey和另外兩個(gè)方法中的一個(gè)找到为朋,那么就會(huì)返回一個(gè)可以響應(yīng)NSArray所有方法的代理集合的NSArray消息方法。
3厚脉、還沒找到习寸,查找countOfKey、enumeratorOfKey傻工、memberOfKey格式的方法霞溪。如果這三個(gè)方法都找到,那么就返回一個(gè)可以響應(yīng)NSSet所有方法的代理集合中捆。
4鸯匹、還是沒找到,如果類方法accessInstanceVariablesDirectly返回YES泄伪。那么按 _key殴蓬,_isKey,key蟋滴,iskey的順序搜索成員名染厅。
5痘绎、再?zèng)]找到,調(diào)用valueForUndefinedKey肖粮。
KVC實(shí)現(xiàn)原理
KVC運(yùn)用了isa-swizzing技術(shù)简逮。isa-swizzing就是類型混合指針機(jī)制。KVC通過isa-swizzing實(shí)現(xiàn)其內(nèi)部查找定位尿赚。isa指針(is kind of 的意思)指向維護(hù)分發(fā)表的對象的類,該分發(fā)表實(shí)際上包含了指向?qū)崿F(xiàn)類中的方法的指針和其他數(shù)據(jù)蕉堰。
比如說如下的一行KVC代碼:
[site setValue:@"sitename" forKey:@"name"];
//會(huì)被編譯器處理成
SEL sel = sel_get_uid(setValue:forKey);
IMP method = objc_msg_loopup(site->isa,sel);
method(site,sel,@"sitename",@"name");
每個(gè)類都有一張方法表凌净,是一個(gè)hash表,值是還書指針I(yè)MP屋讶,SEL的名稱就是查表時(shí)所用的鍵冰寻。
SEL數(shù)據(jù)類型:查找方法表時(shí)所用的鍵。定義成char*皿渗,實(shí)質(zhì)上可以理解成int值斩芭。
IMP數(shù)據(jù)類型:他其實(shí)就是一個(gè)編譯器內(nèi)部實(shí)現(xiàn)時(shí)候的函數(shù)指針。當(dāng)Objective-C編譯器去處理實(shí)現(xiàn)一個(gè)方法的時(shí)候乐疆,就會(huì)指向一個(gè)IMP對象划乖,這個(gè)對象是C語言表述的類型。
KVC內(nèi)部機(jī)制
一個(gè)對象在調(diào)用setValue的時(shí)候進(jìn)行了如下操作:
- 根據(jù)方法名找到運(yùn)行方法的時(shí)候需要的環(huán)境參數(shù)
- 他會(huì)從自己的isa指針結(jié)合環(huán)境參數(shù)挤土,找到具體的方法實(shí)現(xiàn)接口
- 再直接查找得來的具體的實(shí)現(xiàn)方法
引用地址:http://www.cnblogs.com/zy1987/p/4616063.html
KVC在SDWebImage中的應(yīng)用
SDWebImage庫Downloader模塊中的SDWebImageDownloaderOperation類負(fù)責(zé)執(zhí)行下載任務(wù)琴庵,它定義了一個(gè)屬性callbackBlocks
typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
@property (strong, nonatomic, nonnull) NSMutableArray<SDCallbacksDictionary *> *callbackBlocks;
callbackBlocks是一個(gè)可變素組,其中每個(gè)元素是SDCallbacksDictionary類型的字典仰美,用鍵值對的方式保存每個(gè)下載任務(wù)的progressBlock和completedBlock迷殿。progressBlock和completedBlock由外部傳入,負(fù)責(zé)下載過程中和下載完成時(shí)或下載異常情況的處理咖杂。
假如想取到其中所有的progressBlock或completedBlock庆寺,一種方法是遍歷callbackBlocks數(shù)組,根據(jù)key來獲取value诉字,保存到一個(gè)新的數(shù)組中懦尝。另一種快速的方式就是KVC,源代碼如下:
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
LOCK(self.callbacksLock);
[self.callbackBlocks addObject:callbacks];
UNLOCK(self.callbacksLock);
return callbacks;
}
- (nullable NSArray<id> *)callbacksForKey:(NSString *)key {
LOCK(self.callbacksLock);
NSMutableArray<id> *callbacks = [[self.callbackBlocks valueForKey:key] mutableCopy];
UNLOCK(self.callbacksLock);
// We need to remove [NSNull null] because there might not always be a progress block for each callback
[callbacks removeObjectIdenticalTo:[NSNull null]];
return [callbacks copy]; // strip mutability here
}
callbacksLock屬性是一個(gè)信號量鎖奏窑,初始值為1导披,表示同時(shí)期只能有一個(gè)線程來訪問callbacks,保證callbacks數(shù)組在賦值和取值過程中的線程安全埃唯。
@property (strong, nonatomic, nonnull) dispatch_semaphore_t callbacksLock;
// 設(shè)置信號量為1
_callbacksLock = dispatch_semaphore_create(1);
總結(jié)
雖然KVC在效率上要優(yōu)于遍歷的方式撩匕,但是濫用KVC會(huì)導(dǎo)致異常問題的出現(xiàn),假如valueForKey: 或valueForKeyPath:傳入了不存在的key值墨叛,那么就會(huì)導(dǎo)致程序崩潰止毕。所以在沒有把握的情況下模蜡,慎用KVC!