1. Basic methods
KVC(Key-value coding)鍵值編碼,就是指iOS的開(kāi)發(fā)中,可以允許開(kāi)發(fā)者通過(guò)Key名直接訪問(wèn)對(duì)象的屬性,或者給對(duì)象的屬性賦值。而不需要調(diào)用明確的存取方法。這樣就可以在運(yùn)行時(shí)動(dòng)態(tài)地訪問(wèn)和修改對(duì)象的屬性。而不是在編譯時(shí)確定,這也是iOS開(kāi)發(fā)中的黑魔法之一。很多高級(jí)的iOS開(kāi)發(fā)技巧都是基于KVC實(shí)現(xiàn)的。
KVC的定義都是對(duì)NSObject的擴(kuò)展來(lái)實(shí)現(xiàn)的,對(duì)于所有繼承了NSObject的類(lèi)型,都能使用KVC(一些純Swift類(lèi)和結(jié)構(gòu)體是不支持KVC的,因?yàn)闆](méi)有繼承NSObject),下面是valueForKey的definition文件:
@interface NSObject(NSKeyValueCoding)
- (nullable id)valueForKey:(NSString *)key;
下面是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不存在,且沒(méi)有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í)dictionaryWithValuesForKeys感覺(jué)還挺好的,可以把程序里面的model類(lèi)直接轉(zhuǎn)成dict,比如上傳給服務(wù)器的時(shí)候比較方便啊~
2. setValueForKey過(guò)程
KVC要設(shè)值,那么就要對(duì)象中對(duì)應(yīng)的key,KVC在內(nèi)部是按什么樣的順序來(lái)尋找key的。當(dāng)調(diào)用[test1 setValue:@"ying" forKey:@"name"];
的代碼時(shí),底層的執(zhí)行機(jī)制如下:
- (1) 程序優(yōu)先調(diào)用set<Key>:屬性值方法,代碼通過(guò)setter方法完成設(shè)置。
但是蘋(píng)果默認(rèn)的setter是會(huì)大寫(xiě)變量的首字母的,類(lèi)似于如果有個(gè)變量是name,那么setter的方法名是setName。
故而在KVC中key的大小寫(xiě)不同可能會(huì)造成不一樣的結(jié)果哦。
如果你只有一個(gè)setter是setname
(沒(méi)有name實(shí)例變量只定義了set方法),那么當(dāng)你通過(guò)KVC給key為name設(shè)value的時(shí)候會(huì)crash的,當(dāng)然如果你給Name設(shè)值也會(huì)crash;如果你的setter是setName
,那么無(wú)論你設(shè)值的key是name還是Name都不會(huì)crash的~
舉個(gè)例子~
// .h文件
@interface KVOTest : NSObject
@end
// .m文件
#import "KVOTest.h"
@interface KVOTest() {
NSString *name;
}
@end
@implementation KVOTest
- (instancetype)init {
if (self = [super init]) {
}
return self;
}
- (void)setLastName:(NSString *)name {
self->name = name;
NSLog(@"name: %@", name);
}
@end
======
KVOTest *test1 = [[KVOTest alloc] init];
不crash:
[test1 setValue:@"ying" forKey:@"LastName"];
[test1 setValue:@"ying" forKey:@"lastName"];
如果將setLastName改成setlastName,那么無(wú)論是設(shè)值LastName或者lastName都會(huì)crash……
也就是說(shuō),請(qǐng)大寫(xiě)你的setter方法setXXX首字母(例如setName)~
- (2) 如果沒(méi)有找到
setName:
方法,KVC機(jī)制會(huì)檢查+ (BOOL)accessInstanceVariablesDirectly
方法有沒(méi)有返回YES,默認(rèn)該方法會(huì)返回YES,如果你重寫(xiě)了該方法讓其返回NO的話(huà),那么在這一步KVC會(huì)執(zhí)行setValue:forUndefinedKey:
方法。
只要accessInstanceVariablesDirectly
不是NO,KVC機(jī)制會(huì)搜索該類(lèi)里面有沒(méi)有名為<key>的成員變量,無(wú)論該變量是在類(lèi)接口處定義,還是在類(lèi)實(shí)現(xiàn)處定義,也無(wú)論用了什么樣的訪問(wèn)修飾符,只在存在以<key>命名的變量,KVC都可以對(duì)該成員變量賦值。
再來(lái)舉個(gè)例子~
如果KVOTest有個(gè)property是name,還有個(gè)實(shí)例變量是name,那么其實(shí)他有兩個(gè)實(shí)例變量分別是name和_name,那么當(dāng)我們?cè)O(shè)值key為name的時(shí)候,究竟設(shè)值的是哪個(gè)呢?
// .h文件
@interface KVOTest : NSObject
@property (nonatomic, copy) NSString * name;
@end
// .m
@interface KVOTest() {
NSString *name;
}
@end
// 設(shè)值
[test1 setValue:@"ying" forKey:@"_name"];
[test1 setValue:@"ying" forKey:@"name"];
以上兩種設(shè)值的方法最后看test1都是_name
被設(shè)為了ying,但是name都是nil。
如果該類(lèi)即沒(méi)有set<key>:方法,也沒(méi)有_<key>成員變量,KVC機(jī)制會(huì)搜索_is<Key>的成員變量。
// .h文件
@interface KVOTest : NSObject
@property (nonatomic, copy) NSString * isName;
@end
// .m文件
@interface KVOTest() {
NSString *isName;
}
@end
// 使用
[test1 setValue:@"ying" forKey:@"name"];
[test1 setValue:@"ying" forKey:@"Name"];
同理,上面兩種最后都是_isName __NSCFConstantString * @"ying" 0x00000001038ddc08
,當(dāng)然如果你注釋掉.h里面的property,就會(huì)發(fā)現(xiàn)isName被成功賦值了~
但是哦,和setter有同樣的一個(gè)問(wèn)題,就是isXXX也是蘋(píng)果默認(rèn)會(huì)首字母大寫(xiě),于是當(dāng)你找key name的時(shí)候,它會(huì)找isName,如果你只有一個(gè)isname變量,那么你是無(wú)法set值給key是name或者Name的。
所以屬性isXXX也要首字母大寫(xiě)啊~如isName
但是很神奇的是,如果變量是name或者Name,那么你set key的時(shí)候無(wú)論是name還是Name都是可以正常運(yùn)行不會(huì)crash的。但是如果你給key nAme設(shè)值還是會(huì)crash哦。
也就是說(shuō),對(duì)于正常字母一致的變量,不會(huì)檢查首字符大小寫(xiě)的一致性,例如變量是name但是可以給key Name設(shè)值。但是對(duì)于isXXX的變量以及setter方法,由于蘋(píng)果默認(rèn)是首字母大寫(xiě)的格式生成的,也就是例如key是name,setter就應(yīng)該是setName,故而會(huì)檢查大小寫(xiě),例如如果給只有isname變量的對(duì)象設(shè)值key為Name的value就會(huì)crash啦。
我們?cè)僭囈幌?_isKey和key的成員變量哪個(gè)優(yōu)先級(jí)更高~
// .h文件
@interface KVOTest : NSObject
@property (nonatomic, copy) NSString * isName;
@end
// .m文件
@interface KVOTest() {
NSString *name;
}
@end
// 使用
[test1 setValue:@"ying" forKey:@"name"];
結(jié)果是_isName __NSCFConstantString * @"ying" 0x0000000101413c08
故而,總體查找變量的順序是 _key > _isKey > key > isKey
- (3) 如果上面列出的方法或者成員變量都不存在,系統(tǒng)將會(huì)執(zhí)行該對(duì)象的
setValue:forUndefinedKey:
方法,默認(rèn)是拋出異常。
如果開(kāi)發(fā)者想讓這個(gè)類(lèi)禁用KVC里,那么重寫(xiě)
+ (BOOL)accessInstanceVariablesDirectly
方法讓其返回NO即可,這樣的話(huà)如果KVC沒(méi)有找到set<Key>:屬性名時(shí),會(huì)直接用setValue:forUndefinedKey:
方法。
如果你重寫(xiě)了setValue:forUndefinedKey:
就可以不拋出異常啦,只是不建議這么做。
@implementation KVOTest
-(void)setValue:(id)value forUndefinedKey:(NSString *)key {
NSLog(@"forUndefinedKey: %@", key);
}
@end
使用:
[test1 setValue:@"ying" forKey:@"nname"];
輸出:
2019-12-08 19:55:58.123500+0800 Example1[2597:78836] forUndefinedKey: nname
3. valueForKey過(guò)程
當(dāng)調(diào)用valueForKey:@"name"
的代碼時(shí),KVC對(duì)key的搜索方式不同于setValueforKey,其搜索方式如下:
(1) 首先按get<Key>,<key>,is<Key>的順序方法查找getter方法,找到的話(huà)會(huì)直接調(diào)用。如果是BOOL或者Int等值類(lèi)型, 會(huì)將其包裝成一個(gè)NSNumber對(duì)象。
(2) 如果上面的getter沒(méi)有找到,KVC則會(huì)查找countOf<Key>,objectIn<Key>AtIndex或<Key>AtIndexes格式的方法。如果countOf<Key>方法和另外兩個(gè)方法中的一個(gè)被找到,那么就會(huì)返回一個(gè)可以響應(yīng)NSArray所有方法的代理集合(它是NSKeyValueArray,是NSArray的子類(lèi)),調(diào)用這個(gè)代理集合的方法,或者說(shuō)給這個(gè)代理集合發(fā)送屬于NSArray的方法,就會(huì)以countOf<Key>,objectIn<Key>AtIndex或<Key>AtIndexes這幾個(gè)方法組合的形式調(diào)用。還有一個(gè)可選的get<Key>:range:方法。所以你想重新定義KVC的一些功能,你可以添加這些方法,需要注意的是你的方法名要符合KVC的標(biāo)準(zhǔn)命名方法,包括方法簽名。
舉個(gè)例子~
#import "KVOTest.h"
@interface KVOTest()
@end
@implementation KVOTest
- (NSUInteger)countOfNames {
return 1;
}
-(id)objectInNamesAtIndex:(NSUInteger)index {
return @"yy";
}
@end
=====
使用:
KVOTest *test1 = [[KVOTest alloc] init];
id name = [test1 valueForKey:@"names"];
打斷點(diǎn)看下name就是醬紫的:
name NSKeyValueArray * 0x6000039a5f20 0x00006000039a5f20
(lldb) po name
<NSKeyValueArray 0x6000039a5f20>(
yy
)
(3) 如果上面的方法沒(méi)有找到,那么會(huì)同時(shí)查找countOf<Key>,enumeratorOf<Key>,memberOf<Key>格式的方法。如果這三個(gè)方法都找到,那么就返回一個(gè)可以響應(yīng)NSSet所的方法的代理集合,和上面一樣,給這個(gè)代理集合發(fā)NSSet的消息,就會(huì)以countOf<Key>,enumeratorOf<Key>,memberOf<Key>組合的形式調(diào)用。
(4) 如果還沒(méi)有找到,再檢查類(lèi)方法
+ (BOOL)accessInstanceVariablesDirectly
,如果返回YES(默認(rèn)行為),那么和先前的設(shè)值一樣,會(huì)按_<key>,_is<Key>,<key>,is<Key>的順序搜索成員變量名,這里不推薦這么做,因?yàn)檫@樣直接訪問(wèn)實(shí)例變量破壞了封裝性,使代碼更脆弱。如果重寫(xiě)了類(lèi)方法+ (BOOL)accessInstanceVariablesDirectly
返回NO的話(huà),那么會(huì)直接調(diào)用valueForUndefinedKey:
方法,默認(rèn)是拋出異常。
4. 基本類(lèi)型的讀取和設(shè)置
不是每一個(gè)方法都返回對(duì)象,但是valueForKey:
總是返回一個(gè)id對(duì)象,如果原本的變量類(lèi)型是值類(lèi)型或者結(jié)構(gòu)體,返回值會(huì)封裝成NSNumber或者NSValue對(duì)象。
這兩個(gè)類(lèi)會(huì)處理從數(shù)字,布爾值到指針和結(jié)構(gòu)體任何類(lèi)型。然后需要我們手動(dòng)轉(zhuǎn)換成原來(lái)的類(lèi)型。
盡管valueForKey:
會(huì)自動(dòng)將值類(lèi)型封裝成對(duì)象,但是setValueforKey:
卻不行。你必須手動(dòng)將值類(lèi)型轉(zhuǎn)換成NSNumber或者NSValue類(lèi)型,才能傳遞過(guò)去。
例如對(duì):
@interface KVOTest() {
int isName;
}
使用:
KVOTest *test1 = [[KVOTest alloc] init];
[test1 setValue:@10 forKey:@"name"];
結(jié)果:
isName int 10
也就是說(shuō)雖然傳入的value是NSNumber,但是賦值給int屬性的還是int哦~ 但valueForKey拿到的就是id(NSNumber)啦~
數(shù)值類(lèi)基本類(lèi)型我們都知道會(huì)用NSNumber封裝,那么CGPoint這種struct呢?答案是NSValue
可以使用NSValue的數(shù)據(jù)類(lèi)型有:
+ (NSValue*)valueWithCGPoint:(CGPoint)point;
+ (NSValue*)valueWithCGSize:(CGSize)size;
+ (NSValue*)valueWithCGRect:(CGRect)rect;
+ (NSValue*)valueWithCGAffineTransform:(CGAffineTransform)transform;
+ (NSValue*)valueWithUIEdgeInsets:(UIEdgeInsets)insets;
+ (NSValue*)valueWithUIOffset:(UIOffset)insetsNS_AVAILABLE_IOS(5_0);
NSValue主要用于處理結(jié)構(gòu)體型的數(shù)據(jù),它本身提供了如上集中結(jié)構(gòu)的支持。任何結(jié)構(gòu)體都是可以轉(zhuǎn)化成NSValue對(duì)象的,包括其它自定義的結(jié)構(gòu)體。
struct aStruct
{
int a;
int b;
};
typedef struct aStruct aStruct;
aStruct struct;
struct.a = 0;
struct.b = 0;
NSValue *anObj = [NSValue value:&struct withObjCType:@encode(aStruct)];
5. keyPath
在開(kāi)發(fā)過(guò)程中,一個(gè)類(lèi)的成員變量有可能是自定義類(lèi)或其他的復(fù)雜數(shù)據(jù)類(lèi)型,你可以先用KVC獲取該屬性,然后再次用KVC來(lái)獲取這個(gè)自定義類(lèi)的屬性,
但這樣是比較繁瑣的,對(duì)此,KVC提供了一個(gè)解決方案,那就是鍵路徑keyPath。顧名思義,就是按照路徑尋找key。
其實(shí)keyPath就是按照順序一層一層的用key value取值/賦值
犯懶偷來(lái)一個(gè)例子~
#import <Foundation/Foundation.h>
@interface Test1: NSObject {
NSString *_name;
}
@end
@implementation Test1
@end
@interface Test: NSObject {
Test1 *_test1;
}
@end
@implementation Test
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
//Test生成對(duì)象
Test *test = [[Test alloc] init];
//Test1生成對(duì)象
Test1 *test1 = [[Test1 alloc] init];
//通過(guò)KVC設(shè)值test的"test1"
[test setValue:test1 forKey:@"test1"];
//通過(guò)KVC設(shè)值test的"test1的name"
[test setValue:@"xiaoming" forKeyPath:@"test1.name"];
//通過(guò)KVC取值age打印
NSLog(@"test的\"test1的name\"是%@", [test valueForKeyPath:@"test1.name"]);
}
return 0;
}
[test setValue:@"xiaoming" forKeyPath:@"test1.name"];
相當(dāng)于先取test1的value,然后再設(shè)置拿到的value的name。
6. 周邊方法
有幾個(gè)相關(guān)的方法其實(shí)名字就可以看出是干啥的~ 例如validateValue
,valueForUndefinedKey
,setValueforUndefinedKey:
,setNilValueForKey
。
valueForUndefinedKey
和setValueforUndefinedKey:
一個(gè)是get的時(shí)候沒(méi)有拿到,一個(gè)是set的時(shí)候找不到key,默認(rèn)都是拋出異常的,如果你覆寫(xiě)可以不拋出,但一般不建議覆寫(xiě)。
setNilValueForKey
是當(dāng)你給一個(gè)基本數(shù)據(jù)類(lèi)型賦值nil的時(shí)候會(huì)觸發(fā),如果變量是NSString這種對(duì)象類(lèi)型是不會(huì)的哦。默認(rèn)的setNilValueForKey
是會(huì)拋出異常的,一般會(huì)覆寫(xiě)讓他不拋出。
例如:
@interface KVOTest() {
int isName;
}
@end
@implementation KVOTest
- (void)setNilValueForKey:(NSString *)key {
NSLog(@"setNilValueForKey:%@", key);
}
@end
// 使用
KVOTest *test1 = [[KVOTest alloc] init];
[test1 setValue:nil forKey:@"name"];
// 輸出:
2019-12-08 21:00:55.785039+0800 Example1[3330:120630] setNilValueForKey:name
如果你把isName的類(lèi)型改成NSString或者NSNumber就都不會(huì)有上面的輸出了哦~
我在嘗試這個(gè)的時(shí)候還發(fā)現(xiàn),其實(shí)即使你的isName類(lèi)型定義的是NSString,還是KVC給它設(shè)置成@24這種number,感覺(jué)KVC真的很黑魔法了但非常的危險(xiǎn)啊類(lèi)型檢查都木有。
validateValue
聽(tīng)起來(lái)就知道是用于看value是不是可以賦值給key的一個(gè)驗(yàn)證,例如給之前的類(lèi)增加這個(gè)方法:
- (BOOL)validateValue:(inout id _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError *__autoreleasing _Nullable *)outError {
NSNumber *num = *ioValue;
if (num.intValue == 10) {
NSLog(@"validateValue no");
return NO;
}
NSLog(@"validateValue yes");
return YES;
}
注意這里面(inout id _Nullable __autoreleasing *)ioValue
是指向value的指針哦
調(diào)用的時(shí)候醬紫:
KVOTest *test1 = [[KVOTest alloc] init];
[test1 validateValue:&number forKey:@"name" error:NULL];
[test1 setValue:@10 forKey:@"name"];
輸出:
2019-12-08 21:16:00.689897+0800 Example1[3496:130130] validateValue yes
也就是其實(shí)[test1 setValue:@10 forKey:@"name"];
不會(huì)自動(dòng)觸發(fā)valid,無(wú)論是不是valid都會(huì)賦值給key,其實(shí)這個(gè)方法是讓我們自己賦值之前手動(dòng)調(diào)用的……
7. 容器集合類(lèi)
同時(shí)蘋(píng)果對(duì)一些容器類(lèi)比如NSArray或者NSSet等,KVC有著特殊的實(shí)現(xiàn)。
這一段其實(shí)我們?cè)趃et值得時(shí)候也看到過(guò)~
有序集合Array對(duì)應(yīng)方法如下:
-countOf<Key>//必須實(shí)現(xiàn),對(duì)應(yīng)于NSArray的基本方法count:2 -objectIn<Key>AtIndex:
-<key>AtIndexes://這兩個(gè)必須實(shí)現(xiàn)一個(gè),對(duì)應(yīng)于 NSArray 的方法 objectAtIndex: 和 objectsAtIndexes:
-get<Key>:range://不是必須實(shí)現(xiàn)的,但實(shí)現(xiàn)后可以提高性能,其對(duì)應(yīng)于 NSArray 方法 getObjects:range:
-insertObject:in<Key>AtIndex:
-insert<Key>:atIndexes://兩個(gè)必須實(shí)現(xiàn)一個(gè),類(lèi)似于 NSMutableArray 的方法 insertObject:atIndex: 和 insertObjects:atIndexes:
-removeObjectFrom<Key>AtIndex:
-remove<Key>AtIndexes://兩個(gè)必須實(shí)現(xiàn)一個(gè),類(lèi)似于 NSMutableArray 的方法 removeObjectAtIndex: 和 removeObjectsAtIndexes:
-replaceObjectIn<Key>AtIndex:withObject:
-replace<Key>AtIndexes:with<Key>://可選的,如果在此類(lèi)操作上有性能問(wèn)題,就需要考慮實(shí)現(xiàn)之
無(wú)序集合Set對(duì)應(yīng)方法如下:
-countOf<Key>//必須實(shí)現(xiàn),對(duì)應(yīng)于NSArray的基本方法count:
-objectIn<Key>AtIndex:
-<key>AtIndexes://這兩個(gè)必須實(shí)現(xiàn)一個(gè),對(duì)應(yīng)于 NSArray 的方法 objectAtIndex: 和 objectsAtIndexes:
-get<Key>:range://不是必須實(shí)現(xiàn)的,但實(shí)現(xiàn)后可以提高性能,其對(duì)應(yīng)于 NSArray 方法 getObjects:range:
-insertObject:in<Key>AtIndex:
-insert<Key>:atIndexes://兩個(gè)必須實(shí)現(xiàn)一個(gè),類(lèi)似于 NSMutableArray 的方法 insertObject:atIndex: 和 insertObjects:atIndexes:
-removeObjectFrom<Key>AtIndex:
-remove<Key>AtIndexes://兩個(gè)必須實(shí)現(xiàn)一個(gè),類(lèi)似于 NSMutableArray 的方法 removeObjectAtIndex: 和 removeObjectsAtIndexes:
-replaceObjectIn<Key>AtIndex:withObject:
-replace<Key>AtIndexes:with<Key>://這兩個(gè)都是可選的,如果在此類(lèi)操作上有性能問(wèn)題,就需要考慮實(shí)現(xiàn)之
比較神奇的是keyPath對(duì)集合有特殊操作哦!(不是valueForKey,必須是valueForKeyPath哦)
NSArray *numA = @[@20, @30, @40];
NSNumber *sum = [numA valueForKeyPath:@"@sum.intValue"];
例如上面醬紫的sum就是@90~
簡(jiǎn)單集合運(yùn)算符共有@avg, @count , @max , @min ,@sum5種,就是對(duì)集合內(nèi)的元素們的某個(gè)屬性做平均、求和等操作。
還有兩個(gè)神奇的運(yùn)算符,分別是@distinctUnionOfObjects和@unionOfObjects~
@distinctUnionOfObjects會(huì)返回去重后的集合,@unionOfObjects會(huì)返回全集
例如:
NSArray *numA = @[@20, @30, @40, @30];
NSArray *distinctA = [numA valueForKeyPath:@"@distinctUnionOfObjects.intValue"];
NSArray *unionA = [numA valueForKeyPath:@"@unionOfObjects.intValue"];
輸出:
Printing description of distinctA:
<__NSArrayI 0x6000028b52c0>(
20,
30,
40
)
Printing description of unionA:
<__NSArrayM 0x6000028b4450>(
20,
30,
40,
30
)
※ Dictionary的KVC and 通過(guò)字典批量操作
這段我也是懶了后面的就直接借別人的了,對(duì)dict的話(huà)其實(shí)用kvc和dict的key value取值是一樣的。。
當(dāng)對(duì)NSDictionary對(duì)象使用KVC時(shí),valueForKey:的表現(xiàn)行為和objectForKey:一樣。所以使用valueForKeyPath:用來(lái)訪問(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的屬性。
這倆方法說(shuō)白了就是對(duì)任何對(duì)象批量進(jìn)行key value操作。
下面直接用代碼會(huì)更直觀一點(diǎn):
#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
打印出來(lái)的結(jié)果完全符合預(yù)期。
8. 應(yīng)用場(chǎng)景
用KVC來(lái)訪問(wèn)和修改私有變量
對(duì)于類(lèi)里的私有屬性,Objective-C是無(wú)法直接訪問(wèn)的,但是KVC是可以的。Model和字典轉(zhuǎn)換
這是KVC強(qiáng)大作用的又一次體現(xiàn),KVC和Objc的runtime組合可以很容易的實(shí)現(xiàn)Model和字典的轉(zhuǎn)換。修改一些控件的內(nèi)部屬性
很多UI控件都由很多內(nèi)部UI控件組合而成的,但是Apple度沒(méi)有提供這訪問(wèn)這些控件的API,這樣我們就無(wú)法正常地訪問(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é)果:
2018-05-05 17:16:21.975983+0800 KVCKVO[35824:6395514] English
2018-05-05 17:16:21.976296+0800 KVCKVO[35824:6395514] Franch
2018-05-05 17:16:21.976312+0800 KVCKVO[35824:6395514] Chinese
2018-05-05 17:16:21.976508+0800 KVCKVO[35824:6395514] 7
2018-05-05 17:16:21.976533+0800 KVCKVO[35824:6395514] 6
2018-05-05 17:16:21.976550+0800 KVCKVO[35824:6395514] 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)閘enth方法返回是一個(gè)數(shù)字,所以返回結(jié)果以NSNumber的形式保存在新數(shù)組里。
所以當(dāng)對(duì)array取key是count的value的時(shí)候,它會(huì)取找每個(gè)元素的key有木有count,木有就會(huì)crash……而不是找array.count。
9. 獲取對(duì)象的屬性列表以及方法列表
KVC如果你想找到可以改啥,至少得先知道有什么ivar以及setter、getter。下面的方式可以讓你獲取一個(gè)現(xiàn)成類(lèi)的方法和實(shí)例變量~
unsigned int count;
Method *methods = class_copyMethodList([UITextView class], &count);
for (int i = 0; i < count; i++) {
Method method = methods[i];
SEL selector = method_getName(method);
NSString *name = NSStringFromSelector(selector);
NSLog(@"method_getName:%@",name);
}
unsigned int numIvars;
Ivar *vars = class_copyIvarList([UITextView class], &numIvars);
NSString *key=nil;
for(int i = 0; i < numIvars; i++) {
Ivar thisIvar = vars[i];
key = [NSString stringWithUTF8String:ivar_getName(thisIvar)];
NSLog(@"variable_name :%@", key);
}
free(vars);
輸出了400+行截取一下吧
2019-12-08 22:34:28.229672+0800 Example1[3815:169799] method_getName:_cnui_applyContactStyle
2019-12-08 22:34:28.229768+0800 Example1[3815:169799] method_getName:ab_text
2019-12-08 22:34:28.229854+0800 Example1[3815:169799] method_getName:setAb_text:
2019-12-08 22:34:28.229937+0800 Example1[3815:169799] method_getName:ab_textAttributes
2019-12-08 22:34:28.230014+0800 Example1[3815:169799] method_getName:setAb_textAttributes:
2019-12-08 22:34:28.230091+0800 Example1[3815:169799] method_getName:isLayoutSizeDependentOnPerpendicularAxis
2019-12-08 22:34:28.230170+0800 Example1[3815:169799] method_getName:_nui_additionalInsetsForBaselines
2019-12-08 22:34:28.230239+0800 Example1[3815:169799] method_getName:respondsToSelector:
2019-12-08 22:34:28.230309+0800 Example1[3815:169799] method_getName:.cxx_destruct
2019-12-08 22:34:28.230388+0800 Example1[3815:169799] method_getName:dealloc
2019-12-08 22:34:28.230460+0800 Example1[3815:169799] method_getName:encodeWithCoder:
2019-12-08 22:34:28.230534+0800 Example1[3815:169799] method_getName:initWithCoder:
……
2019-12-08 22:34:28.507996+0800 Example1[3815:169799] variable_name :_textStorage
2019-12-08 22:34:28.508084+0800 Example1[3815:169799] variable_name :_textContainer
2019-12-08 22:34:28.508349+0800 Example1[3815:169799] variable_name :_layoutManager
2019-12-08 22:34:28.508607+0800 Example1[3815:169799] variable_name :_containerView
2019-12-08 22:34:28.508853+0800 Example1[3815:169799] variable_name :_layoutView
2019-12-08 22:34:28.509114+0800 Example1[3815:169799] variable_name :_inputDelegate
2019-12-08 22:34:28.509537+0800 Example1[3815:169799] variable_name :_tokenizer
2019-12-08 22:34:28.509824+0800 Example1[3815:169799] variable_name :_inputController
2019-12-08 22:34:28.510100+0800 Example1[3815:169799] variable_name :_interactionAssistant
2019-12-08 22:34:28.510367+0800 Example1[3815:169799] variable_name :_textInputTraits
2019-12-08 22:34:28.510639+0800 Example1[3815:169799] variable_name :_autoscroll
2019-12-08 22:34:28.510907+0800 Example1[3815:169799] variable_name :_tvFlags
2019-12-08 22:34:28.511168+0800 Example1[3815:169799] variable_name :_contentSizeUpdateSeqNo
2019-12-08 22:34:28.511435+0800 Example1[3815:169799] variable_name :_scrollTarget
2019-12-08 22:34:28.511709+0800 Example1[3815:169799] variable_name :_scrollPositionDontRecordCount
2019-12-08 22:34:28.511986+0800 Example1[3815:169799] variable_name :_scrollPosition
我試了一下之前用的KVOTest類(lèi):
2019-12-08 22:37:43.043772+0800 Example1[3830:171932] method_getName:countOfNames
2019-12-08 22:37:43.043867+0800 Example1[3830:171932] method_getName:objectInNamesAtIndex:
2019-12-08 22:37:43.043951+0800 Example1[3830:171932] method_getName:init
2019-12-08 22:37:43.044030+0800 Example1[3830:171932] method_getName:setValue:forUndefinedKey:
2019-12-08 22:37:43.044113+0800 Example1[3830:171932] method_getName:setNilValueForKey:
2019-12-08 22:37:43.044187+0800 Example1[3830:171932] method_getName:setLastName:
2019-12-08 22:37:43.044263+0800 Example1[3830:171932] method_getName:validateValue:forKey:error:
2019-12-08 22:37:43.044343+0800 Example1[3830:171932] variable_name :isName
可以看到真的是只要是里面的方法都輸出了,無(wú)論是不是對(duì)外,屬性也是都有~ 父類(lèi)的應(yīng)該都不會(huì),畢竟NSObject也很多方法,這里也都沒(méi)輸出。
參考:
http://www.lxweimin.com/p/b9f020a8b4c9
http://www.lxweimin.com/p/797a99f30ff9