KVC
KVC(key-Value coding) 鍵值編碼拯啦,指iOS開(kāi)發(fā)中,可以允許開(kāi)發(fā)者通過(guò)Key名直接訪(fǎng)問(wèn)對(duì)象的屬性萍悴,或者給對(duì)象的屬性賦值夹厌。不需要調(diào)用明確的存取方法,這樣就可以在運(yùn)行時(shí)動(dòng)態(tài)訪(fǎng)問(wèn)和修改對(duì)象的屬性,而不是在編譯時(shí)確定。
KVC的定義都是對(duì)
NSObject
的擴(kuò)展來(lái)實(shí)現(xiàn)的(Objective-C
中有個(gè)顯示的NSKeyValueCoding
類(lèi)別名,而Swift沒(méi)有次屠,也不需要)。所以對(duì)于所有繼承NSObject
的類(lèi)型雳刺,也就是基本上所有的Objective-C
對(duì)象都能使用KVC
(一些純Swift類(lèi)和結(jié)構(gòu)體是不支持KVC的)
KVC最重要的四個(gè)方法
- (nullable id)valueForKey:(NSString *)key; //直接通過(guò)Key來(lái)取值
- (void)setValue:(nullable id)value forKey:(NSString *)key; //通過(guò)Key來(lái)設(shè)值
- (nullable id)valueForKeyPath:(NSString *)keyPath; //通過(guò)KeyPath來(lái)取值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; //通過(guò)KeyPath來(lái)設(shè)值
NSKeyValueCoding類(lèi)別中的其他方法
+ (BOOL)accessInstanceVariablesDirectly;
//默認(rèn)返回YES劫灶,表示如果沒(méi)有找到Set<Key>方法的話(huà),會(huì)按照_key掖桦,_iskey本昏,key,iskey的順序搜索成員枪汪,設(shè)置成NO就不這樣搜索
- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
//KVC提供屬性值正確性驗(yàn)證的API涌穆,它可以用來(lái)檢查set的值是否正確、為不正確的值做一個(gè)替換值或者拒絕設(shè)置新值并返回錯(cuò)誤原因雀久。
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
//這是集合操作的API宿稀,里面還有一系列這樣的API,如果屬性是一個(gè)NSMutableArray赖捌,那么可以用這個(gè)方法來(lái)返回祝沸。
- (nullable id)valueForUndefinedKey:(NSString *)key;
//如果Key不存在,且KVC無(wú)法搜索到任何和Key有關(guān)的字段或者屬性越庇,則會(huì)調(diào)用這個(gè)方法罩锐,默認(rèn)是拋出異常。
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
//和上一個(gè)方法一樣卤唉,但這個(gè)方法是設(shè)值涩惑。
- (void)setNilValueForKey:(NSString *)key;
//如果你在SetValue方法時(shí)面給Value傳nil,則會(huì)調(diào)用這個(gè)方法
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
//輸入一組key,返回該組key對(duì)應(yīng)的Value桑驱,再轉(zhuǎn)成字典返回竭恬,用于將Model轉(zhuǎn)到字典跛蛋。
設(shè)置
當(dāng)調(diào)用setValue:屬性值 forKey:@"name"
代碼時(shí),底層的執(zhí)行機(jī)制:
- 程序優(yōu)先調(diào)用
setKey:屬性值
方法痊硕,代碼通過(guò)setter
方法完成設(shè)置问芬。注意,這里的key是指成員變量名寿桨,首字母大小寫(xiě)要符合KVC的命名規(guī)范,下同 - 如果沒(méi)有找到
setName:
方法强戴,KVC機(jī)制會(huì)檢查+(BOOL)accessInstanceVariablesDirectly
方法有沒(méi)有返回YES亭螟,默認(rèn)返回的是YES,如果你重寫(xiě)了該方法讓其返回NO骑歹,那么在這一步KVC會(huì)執(zhí)行setValue: forUndefineKey:
方法预烙,不過(guò)一般不會(huì)這么做。所以KVC機(jī)制會(huì)搜索該類(lèi)里面有沒(méi)有名為_key
的成員變量道媚,無(wú)論該變量是在.h扁掸,還是在.m文件里定義,也不論用什么樣的訪(fǎng)問(wèn)修飾符最域,只要存在_key
命名的變量谴分,KVC都可以對(duì)該成員變量賦值。 - 如果該類(lèi)既沒(méi)有
setKey:
方法镀脂,也沒(méi)有_key
成員變量牺蹄,KVC機(jī)制會(huì)搜索_isKey
的成員變量。 - 同樣道理薄翅,如果該類(lèi)沒(méi)有
setKey:
方法沙兰,也沒(méi)有_key
和_isKey
成員變量,KVC還會(huì)繼續(xù)搜索key
和isKey
的成員變量翘魄,再給他們賦值鼎天。 - 如果上面列出的方法或者成員變量都不存在,系統(tǒng)將會(huì)執(zhí)行該對(duì)象的
setValue:forUndefinedKey:
方法暑竟,默認(rèn)是拋出異常斋射。
如果想讓這個(gè)類(lèi)禁用KVC,重寫(xiě)+(BOOL)accessInstanceVariablesDirectly
方法但荤,讓其返回NO即可绩鸣,這樣的話(huà),如果KVC沒(méi)有找到setKey:
時(shí)纱兑,會(huì)直接調(diào)用setValue:forUndefinedKey:
方法呀闻。
取值
當(dāng)調(diào)用valueForKey:@"name"
時(shí),KVC對(duì)key
的搜索方式不同于setValue:屬性值 forKey:@"name"
潜慎,方式如下
- 首先按
getKey
捡多,key
蓖康,isKey
的順序方法查找getter
方法。找到的話(huà)就會(huì)直接調(diào)用垒手。
如果是BOOL
或者Int
等值類(lèi)型蒜焊,會(huì)將其包裝成一個(gè)NSNumber
對(duì)象。 - 如果上面的
getter
沒(méi)有找到科贬,KVC則會(huì)查找countOfKey
泳梆,objectInKeyAtIndex
或keyAtIndexes
格式的方法。如果找到countOfKey
和另外兩個(gè)方法中的一個(gè)榜掌,那么就會(huì)返回一個(gè)可以響應(yīng)NSArray
所有方法的代理集合(他是NSKeyValueArray
优妙,是NSArray
的子類(lèi)),調(diào)用這個(gè)代理集合的方法或者說(shuō)給這個(gè)代理集合發(fā)送屬于NSArray
的方法憎账,就會(huì)以countOfKey
套硼,objectInKeyAtIndex
或keyAtIndexes
這幾個(gè)方法組合的形式調(diào)用,還有一個(gè)可選的getKey:range:
方法胞皱。所以如果你想重新定義KVC的一些功能邪意,你可以添加這些方法。注意的是方法名的命名規(guī)則要符合KVC的標(biāo)準(zhǔn)命名方法反砌,包括方法簽名雾鬼。 - 如果上面的方法沒(méi)有找到,那么會(huì)同時(shí)查找
countOfKey
宴树,enumeratorOfKey
呆贿,memberOfKey
格式的方法。如果這三個(gè)方法都找到森渐,那么就返回一個(gè)可以響應(yīng)NSSet
所有方法的代理集合做入,和上面一樣,給這個(gè)代理集合發(fā)NSSet
的消息同衣,就會(huì)以countOfKey
竟块,enumeratorOfKey
,memberOfKey
組合的形式調(diào)用耐齐。
簡(jiǎn)單的說(shuō)就是如果你在自己的類(lèi)定義了KVC的實(shí)現(xiàn)浪秘,并且實(shí)現(xiàn)了上面的方法,那么你可以將返回的對(duì)象當(dāng)做數(shù)組(NSArray)/集合(NSSet)用了埠况。
- 如果還沒(méi)有找到耸携,在檢查類(lèi)方法
+(BOOL)accessInstanceVariablesDirectly
,如果返回YES辕翰,那么和之前的設(shè)值一樣夺衍,會(huì)按_key,_isKey,key,isKey
的順序搜索成員變量名,這里不推薦這樣做喜命,因?yàn)檫@樣直接訪(fǎng)問(wèn)實(shí)例變量破壞了封裝性沟沙,使代碼更脆弱河劝。如果重寫(xiě)了+(BOOL)accessInstanceVariablesDirectly
返回NO的話(huà),那么直接調(diào)用valueForUndefinedKey:
- 如果還沒(méi)有找到的話(huà)矛紫,調(diào)用
valueForUndefinedKey:
使用keyPath
在開(kāi)發(fā)過(guò)程中赎瞎,一個(gè)類(lèi)的成員變量有可能是自定義類(lèi)或者其它的復(fù)雜數(shù)據(jù)類(lèi)型,可以先用KVC獲取該屬性颊咬,然后再用KVC來(lái)獲取這個(gè)自定義類(lèi)的屬性务甥。但是比較麻煩,可以使用KVC的鍵路徑keyPath
- (nullable id)valueForKeyPath:(NSString *)keyPath; //通過(guò)KeyPath來(lái)取值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; //通過(guò)KeyPath來(lái)設(shè)值
參考下面的例子:
// Address類(lèi)
@interface Address()
@property (nonatomic, copy) NSString *country;
@end
// People類(lèi)
@interface People()
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) Address *address;
@property (nonatomic, assign) NSInteger age;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
People *people1 = [People new];
Address *add = [Address new];
add.country = @"China";
people1.address = add;
NSString *country1 = people1.address.country;
NSString *country2 = [people1 valueForKeyPath:@"address.country"];
NSLog(@"country1:%@ country2:%@",country1,country2);
[people1 setValue:@"USA" forKeyPath:@"address.country"];
country1 = people1.address.country;
country2 = [people1 valueForKeyPath:@"address.country"];
NSLog(@"country1:%@ country2:%@",country1,country2);
}
return 0;
}
//打印結(jié)果
2016-04-17 15:55:22.487 KVCDemo[1190:82636] country1:China country2:China
2016-04-17 15:55:22.489 KVCDemo[1190:82636] country1:USA country2:USA
KVC處理集合
簡(jiǎn)單集合運(yùn)算符
@avg喳篇、@count敞临、@max、@min杭隙、@sum5種,直接從代碼種理解其中含義
#import <Foundation/Foundation.h>
@interface Book : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger price;
@end
@implementation Book
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Book *book1 = [Book new];
book1.name = @"The Great Gastby";
book1.price = 10;
Book *book2 = [Book new];
book2.name = @"Time History";
book2.price = 20;
Book *book3 = [Book new];
book3.name = @"Wrong Hole";
book3.price = 30;
Book *book4 = [Book new];
book4.name = @"Wrong Hole";
book4.price = 40;
NSArray *arrBooks = @[book1,book2,book3,book4];
NSNumber *sum = [arrBooks valueForKeyPath:@"@sum.price"];
NSLog(@"sum:%f",sum.floatValue);
NSNumber *avg = [arrBooks valueForKeyPath:@"@avg.price"];
NSLog(@"avg:%f",avg.floatValue);
NSNumber *count = [arrBooks valueForKeyPath:@"@count"];
NSLog(@"count:%f",count.floatValue);
NSNumber *min = [arrBooks valueForKeyPath:@"@min.price"];
NSLog(@"min:%f",min.floatValue);
NSNumber *max = [arrBooks valueForKeyPath:@"@max.price"];
NSLog(@"max:%f",max.floatValue);
}
return 0;
}
打印結(jié)果:
sum:100.000000
avg:25.000000
count:4.000000
min:10.000000
max:40.000000
對(duì)象運(yùn)算符
想比較集合運(yùn)算符稍微復(fù)雜因妙,能以數(shù)組的方式返回指定的內(nèi)容
- @distinctUnionOfObjects
- @unionOfObjects
它們的返回值都是NSArray
痰憎,區(qū)別是前者返回的元素都是唯一的,是去重以后的結(jié)果攀涵;后者返回的元素是全集铣耘。
#import <Foundation/Foundation.h>
@interface Book : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger price;
@end
@implementation Book
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Book *book1 = [Book new];
book1.name = @"The Great Gastby";
book1.price = 40;
Book *book2 = [Book new];
book2.name = @"Time History";
book2.price = 20;
Book *book3 = [Book new];
book3.name = @"Wrong Hole";
book3.price = 30;
Book *book4 = [Book new];
book4.name = @"Wrong Hole";
book4.price = 10;
Book *book5 = [Book new];
book5.name = @"Wrong Hole1";
book5.price = 20;
NSArray* arrBooks = @[book1, book2, book3, book4, book5];
NSLog(@"distinctUnionOfObjects");
NSArray *arrDistinct = [arrBooks valueForKeyPath:@"@distinctUnionOfObjects.price"];
for (NSNumber *price in arrDistinct) {
NSLog(@"%f", price.floatValue);
}
NSLog(@"unionOfObjects");
NSArray *arrUnion = [arrBooks valueForKeyPath:@"@unionOfObjects.price"];
for (NSNumber *price in arrUnion) {
NSLog(@"%f", price.floatValue);
}
}
return 0;
}
打印結(jié)果:
distinctUnionOfObjects
10.000000
20.000000
30.000000
40.000000
unionOfObjects
10.000000
20.000000
30.000000
40.000000
20.000000
KVC處理字典
當(dāng)對(duì)NSDictionary
對(duì)象使用KVC
時(shí),valueForKey
的表現(xiàn)行為和objectForKey
一樣以故。所以使用valueForKeyPath
用來(lái)訪(fǎng)問(wèn)多層嵌套的字典比較方便蜗细。
KVC
里面還有兩個(gè)關(guān)于NSDictionary
的方法:
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
dictionaryWithValuesForKeys:
是指輸入一組key
,返回這組key
對(duì)應(yīng)的屬性怒详,在組成一個(gè)字典炉媒。
setValuesForKeysWithDictionary:
是用來(lái)修改model
種對(duì)應(yīng)key
的屬性。
看代碼:
#import <Foundation/Foundation.h>
@interface Address : NSObject
@end
@interface Address()
@property (nonatomic, copy)NSString *country;
@property (nonatomic, copy)NSString *province;
@property (nonatomic, copy)NSString *city;
@property (nonatomic, copy)NSString *district;
@end
@implementation Address
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
//模型轉(zhuǎn)字典
Address *add = [Address new];
add.country = @"China";
add.province = @"Guang Dong";
add.city = @"Shen Zhen";
add.district = @"Nan Shan";
NSArray *arr = @[@"country",@"province",@"city",@"district"];
NSDictionary *dict = [add dictionaryWithValuesForKeys:arr]; //把對(duì)應(yīng)key所有的屬性全部取出來(lái)
NSLog(@"%@",dict);
//字典轉(zhuǎn)模型
NSDictionary *modifyDict = @{@"country" : @"USA",
@"province" : @"california",
@"city" : @"Los angle"
};
[add setValuesForKeysWithDictionary:modifyDict]; //用key Value來(lái)修改Model的屬性
NSLog(@"country:%@ province:%@ city:%@", add.country, add.province, add.city);
}
return 0;
}
打印結(jié)果:
2018-05-05 17:08:48.824653+0800 KVCKVO[35807:6368235] {
city = "Shen Zhen";
country = China;
district = "Nan Shan";
province = "Guang Dong";
}
2018-05-05 17:08:48.825075+0800 KVCKVO[35807:6368235]
country:USA province:california city:Los angle
KVC的使用
動(dòng)態(tài)的取值和設(shè)值
利用KVC
動(dòng)態(tài)的取值和設(shè)值是最基本的用途
用KVC
來(lái)訪(fǎng)問(wèn)和修改私有變量
對(duì)于類(lèi)里的私有屬性昆烁,Objective-C
是無(wú)法直接訪(fǎng)問(wèn)的吊骤,但是KVC
是可以的。
Model和字典轉(zhuǎn)換
這是KVC強(qiáng)大作用的又一次體現(xiàn)静尼,KVC和Objc的runtime組合可以很容易的實(shí)現(xiàn)Model和字典的轉(zhuǎn)換白粉。
修改一些控件的內(nèi)部屬性
這也是iOS開(kāi)發(fā)中必不可少的小技巧。眾所周知很多UI控件都由很多內(nèi)部UI控件組合而成的鼠渺,但是Apple度沒(méi)有提供這訪(fǎng)問(wèn)這些控件的API鸭巴,這樣我們就無(wú)法正常地訪(fǎng)問(wèn)和修改這些控件的樣式。
而KVC在大多數(shù)情況可下可以解決這個(gè)問(wèn)題拦盹。最常用的就是個(gè)性化UITextField中的placeHolderText了鹃祖。
操作集合
Apple對(duì)KVC的valueForKey:方法作了一些特殊的實(shí)現(xiàn),比如說(shuō)NSArray和NSSet這樣的容器類(lèi)就實(shí)現(xiàn)了這些方法普舆。所以可以用KVC很方便地操作集合惯豆。
用KVC實(shí)現(xiàn)高階消息傳遞
當(dāng)對(duì)容器類(lèi)使用KVC時(shí)池磁,valueForKey:將會(huì)被傳遞給容器中的每一個(gè)對(duì)象,而不是容器本身進(jìn)行操作楷兽。結(jié)果會(huì)被添加進(jìn)返回的容器中地熄,這樣,開(kāi)發(fā)者可以很方便的操作集合來(lái)返回另一個(gè)集合芯杀。
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSArray *arrStr = @[@"english", @"franch", @"chinese"];
NSArray *arrCapStr = [arrStr valueForKey:@"capitalizedString"];
for (NSString *str in arrCapStr) {
NSLog(@"%@", str);
}
NSArray *arrCapStrLength = [arrStr valueForKeyPath:@"capitalizedString.length"];
for (NSNumber *length in arrCapStrLength) {
NSLog(@"%ld", (long)length.integerValue);
}
}
return 0;
}
打印結(jié)果:
English
Franch
Chinese
7
6
7
方法capitalizedString
被傳遞到NSArray
中的每一項(xiàng)端考,這樣,NSArray
的每一員都會(huì)執(zhí)行capitalizedString
并返回一個(gè)包含結(jié)果的新的NSArray
揭厚。
從打印結(jié)果可以看出却特,所有String
的首字母都成功以轉(zhuǎn)成了大寫(xiě)。
同樣如果要執(zhí)行多個(gè)方法也可以用valueForKeyPath:
方法筛圆。它先會(huì)對(duì)每一個(gè)成員調(diào)用 capitalizedString
方法裂明,然后再調(diào)用length
,因?yàn)?code>length方法返回是一個(gè)數(shù)字太援,所以返回結(jié)果以NSNumber
的形式保存在新數(shù)組里闽晦。
實(shí)現(xiàn)KVO
可參考KVO
參考博客:jackyshan