KVC詳解【轉】

KVC(Key-valuecoding)鍵值編碼炸卑,單看這個名字可能不太好理解爷辱。其實翻譯一下就很簡單了录豺,就是指iOS的開發(fā)中,可以允許開發(fā)者通過Key名直接訪問對象的屬性饭弓,或者給對象的屬性賦值双饥。而不需要調用明確的存取方法。這樣就可以在運行時動態(tài)在訪問和修改對象的屬性弟断。而不是在編譯時確定咏花,這也是iOS開發(fā)中的黑魔法之一。很多高級的iOS開發(fā)技巧都是基于KVC實現(xiàn)的阀趴。目前網(wǎng)上關于KVC的文章在非常多昏翰,有的只是簡單地說了下用法,有的講得深入但是在使用場景和最佳實踐沒有說明刘急,我寫下這遍文章就是給大家詳解一個最完整最詳細的KVC棚菊。

KVC在iOS中的定義

無論是Swift還是Objective-C,KVC的定義都是對NSObject的擴展來實現(xiàn)的(Objective-c中有個顯式的NSKeyValueCoding類別名叔汁,而Swift沒有统求,也不需要)所以對于所有繼承了NSObject在類型,都能使用KVC(一些純Swift類和結構體是不支持KVC的)据块,下面是KVC最為重要的四個方法

- (nullable id)valueForKey:(NSString *)key;??????????????????????????//直接通過Key來取值

- (void)setValue:(nullable id)value forKey:(NSString *)key;??????????//通過Key來設值

- (nullable id)valueForKeyPath:(NSString *)keyPath;??????????????????//通過KeyPath來取值

- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;??//通過KeyPath來設值

當然NSKeyValueCoding類別中還有其他的一些方法码邻,下面列舉一些

+ (BOOL)accessInstanceVariablesDirectly;

//默認返回YES,表示如果沒有找到Set方法的話另假,會按照_key冒滩,_iskey,key浪谴,iskey的順序搜索成員开睡,設置成NO就不這樣搜索

- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;

//KVC提供屬性值確認的API因苹,它可以用來檢查set的值是否正確、為不正確的值做一個替換值或者拒絕設置新值并返回錯誤原因篇恒。

- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;

//這是集合操作的API扶檐,里面還有一系列這樣的API,如果屬性是一個NSMutableArray胁艰,那么可以用這個方法來返回

- (nullable id)valueForUndefinedKey:(NSString *)key;

//如果Key不存在款筑,且沒有KVC無法搜索到任何和Key有關的字段或者屬性,則會調用這個方法腾么,默認是拋出異常

- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;

//和上一個方法一樣奈梳,只不過是設值。

- (void)setNilValueForKey:(NSString *)key;

//如果你在SetValue方法時面給Value傳nil解虱,則會調用這個方法

- (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;

//輸入一組key,返回該組key對應的Value攘须,再轉成字典返回,用于將Model轉到字典殴泰。

上面的這些方法在碰到特殊情況或者有特殊需求還是會用到的于宙,所以也是可以了解一下。后面的代碼示例會有講到其中的一些方法悍汛。

同時蘋果對一些容器類比如NSArray或者NSSet等捞魁,KVC有著特殊的實現(xiàn)。建議有基礎的或者英文好的開發(fā)者直接去看蘋果的官方文檔离咐,相信你會對KVC的理解更上一個臺階谱俭。

KVC是怎么尋找Key的

KVC是怎么使用的,我相信絕大多數(shù)的開發(fā)者都很清楚宵蛀,我在這里就不再寫簡單的使用KVC來設值和取值的代碼了昆著,首頁我們來探討KVC在內部是按什么樣的順序來尋找key的。

當調用setValue:屬性值 forKey:@”name“的代碼時糖埋,底層的執(zhí)行機制如下:

程序優(yōu)先調用set:屬性值方法宣吱,代碼通過setter方法完成設置。注意瞳别,這里的是指成員變量名征候,首字母大清寫要符合KVC的全名規(guī)則,下同

如果沒有找到setName:方法祟敛,KVC機制會檢查+ (BOOL)accessInstanceVariablesDirectly方法有沒有返回YES疤坝,默認該方法會返回YES,如果你重寫了該方法讓其返回NO的話馆铁,那么在這一步KVC會執(zhí)行setValue:forUNdefinedKey:方法跑揉,不過一般開發(fā)者不會這么做。所以KVC機制會搜索該類里面有沒有名為_的成員變量,無論該變量是在類接口部分定義历谍,還是在類實現(xiàn)部分定義现拒,也無論用了什么樣的訪問修飾符,只在存在以_命名的變量望侈,KVC都可以對該成員變量賦值印蔬。

如果該類即沒有set:方法,也沒有_成員變量脱衙,KVC機制會搜索_is的成員變量侥猬,

和上面一樣,如果該類即沒有set:方法捐韩,也沒有_和_is成員變量退唠,KVC機制再會繼續(xù)搜索和is的成員變量。再給它們賦值荤胁。

如果上面列出的方法或者成員變量都不存在瞧预,系統(tǒng)將會執(zhí)行該對象的setValue:forUNdefinedKey:方法,默認是拋出異常寨蹋。

如果開發(fā)者想讓這個類禁用KVC里松蒜,那么重寫+ (BOOL)accessInstanceVariablesDirectly方法讓其返回NO即可扔茅,這樣的話如果KVC沒有找到set:屬性名時已旧,會直接用setValue:forUNdefinedKey:方法。

下面我們來讓代碼來測試一下上面的KVC機制

@interface Dog : NSObject

@end

@implementation Dog

{

? ? NSString* toSetName;

? ? NSString* isName;

? ? //NSString* name;

? ? NSString* _name;

? ? NSString* _isName;

}

// -(void)setName:(NSString*)name{

//? ? toSetName = name;

// }

//-(NSString*)getName{

//? ? return toSetName;

//}

+(BOOL)accessInstanceVariablesDirectly{

? ? return NO;

}

-(id)valueForUndefinedKey:(NSString *)key{

? ? NSLog(@"出現(xiàn)異常召娜,該key不存在%@",key);

? ? return nil;

}

-(void)setValue:(id)value forUndefinedKey:(NSString *)key{

? ? NSLog(@"出現(xiàn)異常运褪,該key不存在%@",key);

}

@end

int main(int argc, const char * argv[]) {

? ? @autoreleasepool {

? ? ? ? // insert code here...

? ? ? ? Dog* dog = [Dog new];

? ? ? ? [dog setValue:@"newName" forKey:@"name"];

? ? ? ? NSString* name = [dog valueForKey:@"toSetName"];

? ? ? ? NSLog(@"%@",name);

? ? }

? ? return 0;

}

首先我們先重寫accessInstanceVariablesDirectly方法讓其返回NO,再運行代碼(注意上面注釋的部分)玖瘸,XCode直接打印出

2016-04-15 15:52:12.039 DemoKVC[9681:287627] 出現(xiàn)異常秸讹,該key不存在name

2016-04-15 15:52:12.040 DemoKVC[9681:287627] 出現(xiàn)異常,該key不存在toSetName

2016-04-15 15:52:12.040 DemoKVC[9681:287627] (null)

這說明了重寫+(BOOL)accessInstanceVariablesDirectly方法讓其返回NO后,KVC找不到SetName:方法后雅倒,不再去找name系列成員變量璃诀,而是直接調用forUndefinedKey方法

所以開發(fā)者如果不想讓自己的類實現(xiàn)KVC,就可以這么做蔑匣。

下面那兩個setter和gettr的注釋取消掉劣欢,再把

NSString* name = [dog valueForKey:@"toSetName"]; 換成 NSString* name = [dog valueForKey:@"name"];

XCode就可以正確地打印出正確的值了

2016-04-15 15:56:22.130 DemoKVC[9726:289258] newName

下面再注釋到accessInstanceVariablesDirectly方法,就能測試其他的key查找順序了裁良,為了節(jié)省篇幅凿将,剩下的的KVC對于key尋找機制就不在這里展示了,有興趣的讀者可以寫代碼去驗證价脾。

當調用ValueforKey:@”name“的代碼時牧抵,KVC對key的搜索方式不同于setValue:屬性值 forKey:@”name“,其搜索方式如下

首先按get,,is的順序方法查找getter方法,找到的話會直接調用犀变。如果是BOOL或者int等值類型妹孙, 會做NSNumber轉換

如果上面的getter沒有找到,KVC則會查找countOf,objectInAtIndex,AtIndex格式的方法获枝。如果countOf和另外兩個方法中的要個被找到涕蜂,那么就會返回一個可以響應NSArray所的方法的代理集合(它是NSKeyValueArray,是NSArray的子類)映琳,調用這個代理集合的方法机隙,或者說給這個代理集合發(fā)送NSArray的方法,就會以countOf,objectInAtIndex,AtIndex這幾個方法組合的形式調用萨西。還有一個可選的get:range:方法有鹿。所以你想重新定義KVC的一些功能,你可以添加這些方法谎脯,需要注意的是你的方法名要符合KVC的標準命名方法葱跋,包括方法簽名。

如果上面的方法沒有找到源梭,那么會查找countOf娱俺,enumeratorOf,memberOf格式的方法。如果這三個方法都找到废麻,那么就返回一個可以響應NSSet所的方法的代理集合荠卷,以送給這個代理集合消息方法,就會以countOf烛愧,enumeratorOf,memberOf組合的形式調用油宜。

如果還沒有找到,再檢查類方法+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默認行為)怜姿,那么和先前的設值一樣慎冤,會按_,_is,,is的順序搜索成員變量名,這里不推薦這么做沧卢,因為這樣直接訪問實例變量破壞了封裝性蚁堤,使代碼更脆弱。如果重寫了類方法+ (BOOL)accessInstanceVariablesDirectly返回NO的話但狭,那么會直接調用valueForUndefinedKey:

還沒有找到的話披诗,調用valueForUndefinedKey:

下面再上代碼測試

@interface TwoTimesArray : NSObject

-(void)incrementCount;

-(NSUInteger)countOfNumbers;

-(id)objectInNumbersAtIndex:(NSUInteger)index;

@end

@interface TwoTimesArray()

@property (nonatomic,readwrite,assign) NSUInteger count;

@property (nonatomic,copy) NSString* arrName;

@end

@implementation TwoTimesArray

-(void)incrementCount{

? ? self.count ++;

}

-(NSUInteger)countOfNumbers{

? ? return self.count;

-(id)objectInNumbersAtIndex:(NSUInteger)index{? ? //當key使用numbers時,KVC會找到這兩個方法熟空。

? ? return @(index * 2);

}

-(NSInteger)getNum{? ? ? ? ? ? ? ? //第一個,自己一個一個注釋試

? ? return 10;

}

-(NSInteger)num{? ? ? ? ? ? ? ? ? ? ? //第二個

? ? return 11;

}

-(NSInteger)isNum{? ? ? ? ? ? ? ? ? ? //第三個

? ? return 12;

}

@end

int main(int argc, const char * argv[]) {

? ? @autoreleasepool {

? ? ? ? TwoTimesArray* arr = [TwoTimesArray new];

? ? ? ? NSNumber* num =? [arr valueForKey:@"num"];

? ? ? ? NSLog(@"%@",num);

? ? ? ? id ar = [arr valueForKey:@"numbers"];

? ? ? ? NSLog(@"%@",NSStringFromClass([ar class]));

? ? ? ? NSLog(@"0:%@? ? 1:%@? ? 2:%@? ? 3:%@",ar[0],ar[1],ar[2],ar[3]);

? ? ? ? [arr incrementCount];? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //count加1

? ? ? ? NSLog(@"%lu",(unsigned long)[ar count]);? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //打印出1

? ? ? ? [arr incrementCount];? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //count再加1

? ? ? ? NSLog(@"%lu",(unsigned long)[ar count]);? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //打印出2

? ? ? ? [arr setValue:@"newName" forKey:@"arrName"];

? ? ? ? NSString* name = [arr valueForKey:@"arrName"];

? ? ? ? NSLog(@"%@",name);

? ? }

? ? return 0;

}

//打印結果

2016-04-17 15:39:42.214 KVCDemo[1088:74481] 10

2016-04-17 15:39:42.215 KVCDemo[1088:74481] NSKeyValueArray

2016-04-17 15:41:24.713 KVCDemo[1102:75424] 0:0? ? 1:2? ? 2:4? ? 3:6? ? ? ? ? ? ? ? //太明顯了藤巢,直接調用-(id)objectInNumbersAtIndex:(NSUInteger)index;方法

2016-04-17 15:39:42.215 KVCDemo[1088:74481] 1

2016-04-17 15:39:42.215 KVCDemo[1088:74481] 2

2016-04-17 15:39:42.215 KVCDemo[1088:74481] newName

很明顯,上面的代碼充分說明了說明了KVC在調用ValueforKey:@”name“時搜索key的機制息罗。不過還有些功能沒有全部列出掂咒,有興趣的讀者可以寫代碼去驗證。

在KVC中使用KeyPath

然而在開發(fā)過程中淮椰,一個類的成員變量有可能是其他的自定義類实柠,你可以先用KVC獲取出來再該屬性演怎,然后再次用KVC來獲取這個自定義類的屬性镀钓,但這樣是比較繁瑣的,對此蜓竹,KVC提供了一個解決方案术羔,那就是鍵路徑KeyPath姑蓝。

- (nullable id)valueForKeyPath:(NSString *)keyPath; //通過KeyPath來取值

- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;? //通過KeyPath來設值

@interface Address : NSObject

@end

@interface Address()

@property (nonatomic,copy)NSString* country;

@end

@implementation Address

@end

@interface People : NSObject

@end

@interface People()

@property (nonatomic,copy) NSString* name;

@property (nonatomic,strong) Address* address;

@property (nonatomic,assign) NSInteger age;

@end

@implementation People

@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;

}

//打印結果

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

上面的代碼簡單在展示了KeyPath是怎么用的膝蜈。如果你不小心錯誤的使用了key而非KeyPath的話锅移,KVC會直接查找address.country這個屬性,很明顯饱搏,這個屬性并不存在非剃,所以會再調用UndefinedKey相關方法。而KVC對于KeyPath是搜索機制第一步就是分離key推沸,用小數(shù)點.來分割key备绽,然后再像普通key一樣按照先前介紹的順序搜索下去。

KVC如何處理異常

KVC中最常見的異常就是不小心使用了錯誤的Key鬓催,或者在設值中不小心傳遞了nil的值肺素,KVC中有專門的方法來處理這些異常。

通常在用KVC操作Model時宇驾,拋出異常的那兩個方法是需要重寫的倍靡。雖然一般很小出現(xiàn)傳遞了錯誤的Key值這種情況,但是如果不小心出現(xiàn)了飞苇,直接拋出異常讓APP崩潰顯然是不合理的菌瘫。

一般在這里直接讓這個Key打印出來即可蜗顽,或者有些特殊情況需要特殊處理布卡。

通常情況下,KVC不允許你要在調用setValue:屬性值 forKey:@”name“(或者keyPath)時對非對象傳遞一個nil的值雇盖。很簡單忿等,因為值類型是不能為nil的。如果你不小心傳了崔挖,KVC會調用setNilValueForKey:方法贸街。這個方法默認是拋出異常,所以一般而言最好還是重寫這個方法狸相。

[people1 setValue:nil forKey:@"age"]

? *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '[ setNilValueForKey]: could not set nil as the value for the key age.' // 調用setNilValueForKey拋出異常

如果重寫setNilValueForKey:就沒問題了

@implementation People

-(void)setNilValueForKey:(NSString *)key{

? ? NSLog(@"不能將%@設成nil",key);

}

@end

//打印出

2016-04-17 16:19:55.298 KVCDemo[1304:92472] 不能將age設成nil

KVC處理非對象和自定義對象

不是每一個方法都返回對象薛匪,但是valueForKey:總是返回一個id對象,如果原本的變量類型是值類型或者結構體脓鹃,返回值會封裝成NSNumber或者NSValue對象逸尖。這兩個類會處理從數(shù)字,布爾值到指針和結構體任何類型。然后開以者需要手動轉換成原來的類型娇跟。盡管valueForKey:會自動將值類型封裝成對象岩齿,但是setValue:forKey:卻不行。你必須手動將值類型轉換成NSNumber或者NSValue類型苞俘,才能傳遞過去盹沈。

對于自定義對象,KVC也會正確以設值和取值吃谣。因為傳遞進去和取出來的都是id類型乞封,所以需要開發(fā)者自己擔保類型的正確性,運行時Objective-C在發(fā)送消息的會檢查類型岗憋,如果錯誤會直接拋出異常歌亲。

Address* add2 = [Address new];

add2.country = @"England";

[people1 setValue:add2 forKey:@"address"];

NSString* country1 = people1.address.country;

NSString * country2 = [people1 valueForKeyPath:@"address.country"];

NSLog(@"country1:%@? country2:%@",country1,country2);

//打印結果

2016-04-17 16:29:36.349 KVCDemo[1346:95910] country1:England? country2:England

KVC與容器類

對象的屬性可以是一對一的,也可以是一對多的澜驮。一對多的屬性要么是有序的(數(shù)組)陷揪,要么是無序的(數(shù)組)

不可變的有序容器屬性(NSArray)和無序容器屬性(NSSet)一般可以使用valueForKey:來獲取。比如有一個叫items的NSArray屬性杂穷,你可以用valurForKey:@"items"來獲取這個屬性悍缠。前面valueForKey:的key搜索模式中,我們發(fā)現(xiàn)其實KVC使用了一種更靈活的方式來管理容器類耐量。蘋果的官方文檔也推薦我們實現(xiàn)這些這些特殊的訪問器飞蚓。

而當對象的屬性是可變的容器時,對于有序的容器廊蜒,可以用下面的方法:

- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;

該方法返回一個可變有序數(shù)組趴拧,如果調用該方法,KVC的搜索順序如下

搜索insertObject:inAtIndex:,removeObjectFromAtIndex:或者insertAdIndexes,removeAtIndexes格式的方法

如果至少找到一個insert方法和一個remove方法山叮,那么同樣返回一個可以響應NSMutableArray所有方法代理集合(類名是NSKeyValueFastMutableArray2)著榴,那么給這個代理集合發(fā)送NSMutableArray的方法,以insertObject:inAtIndex:,removeObjectFromAtIndex:或者insertAdIndexes,removeAtIndexes組合的形式調用屁倔。還有兩個可選實現(xiàn)的接口:replaceOnjectAtIndex:withObject: , replaceAtIndexes:with: 脑又。

如果上步的方法沒有找到,則搜索set:格式的方法锐借,如果找到问麸,那么發(fā)送給代理集合的NSMutableArray最終都會調用set:方法。 也就是說钞翔,mutableArrayValueForKey:取出的代理集合修改后严卖,用·set:· 重新賦值回去去。這樣做效率會低很多布轿。所以推薦實現(xiàn)上面的方法哮笆。

如果上一步的方法還還沒有找到俺亮,再檢查類方法+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默認行為),會按_,,的順序搜索成員變量名疟呐,如果找到脚曾,那么發(fā)送的NSMutableArray消息方法直接交給這個成員變量處理。

如果還是找不到启具,調用valueForUndefinedKey:

關于mutableArrayValueForKey:的適用場景本讥,我在網(wǎng)上找了很多,發(fā)現(xiàn)其一般是用在對NSMutableArray添加Observer上鲁冯。

如果對象屬性是個NSMutableAArray拷沸、NSMutableSet、NSMutableDictionary等集合類型時薯演,你給它添加KVO時撞芍,你會發(fā)現(xiàn)當你添加或者移除元素時并不能接收到變化。因為KVO的本質是系統(tǒng)監(jiān)測到某個屬性的內存地址或常量改變時跨扮,會添加上- (void)willChangeValueForKey:(NSString *)key

和- (void)didChangeValueForKey:(NSString *)key方法來發(fā)送通知序无,所以一種解決方法是手動調用者兩個方法,但是并不推薦衡创,你永遠無法像系統(tǒng)一樣真正知道這個元素什么時候被改變帝嗡。另一種便是利用使用mutableArrayValueForKey:了。

@interface demo : NSObject

@property (nonatomic,strong) NSMutableArray* arr;

@end

@implementation demo

-(id)init{

? ? if (self == [super init]){

? ? ? ? _arr = [NSMutableArray new];

? ? ? ? [self addObserver:self forKeyPath:@"arr" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];

? ? }

? ? return self;

}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{

? ? NSLog(@"%@",change);

}

-(void)dealloc{

? ? [self removeObserver:self forKeyPath:@"arr"]; //一定要在dealloc里面移除觀察

}

-(void)addItem{

? ? [_arr addObject:@"1"];

}

-(void)addItemObserver{

? ? [[self mutableArrayValueForKey:@"arr"] addObject:@"1"];

}

-(void)removeItemObserver{

? ? [[self mutableArrayValueForKey:@"arr"] removeLastObject];

}

@end

然后再:

demo* d = [demo new];

[d addItem];

[d addItemObserver];

[d removeItemObserver];

打印結果

2016-04-18 17:48:22.675 KVCDemo[32647:505864] {

? ? indexes = "[number of indexes: 1 (in 1 ranges), indexes: (1)]";

? ? kind = 2;

? ? new =? ? (

? ? ? ? 1

? ? );

}

2016-04-18 17:48:22.677 KVCDemo[32647:505864] {

? ? indexes = "[number of indexes: 1 (in 1 ranges), indexes: (1)]";

? ? kind = 3;

? ? old =? ? (

? ? ? ? 1

? ? );

}

從上面的代碼可以看出璃氢,當只是普通地調用[_arr addObject:@"1"]時哟玷,Observer并不會回調,只有[[self mutableArrayValueForKey:@"arr"] addObject:@"1"];這樣寫時才能正確地觸發(fā)KVO一也。打印出來的數(shù)據(jù)中巢寡,可以看出這次操作的詳情,kind可能是指操作方法(我還不是很確認)椰苟,old和new并不是成對出現(xiàn)的抑月,當加添新數(shù)據(jù)時是new,刪除數(shù)據(jù)時是old

而對于無序的容器尊剔,可以用下面的方法:

- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;

該方法返回一個可變的無序數(shù)組如果調用該方法爪幻,KVC的搜索順序如下

搜索addObjectObject:,removeObject:或者add,remove格式的方法

如果至少找到一個insert方法和一個remove方法,那么同樣返回一個可以響應NSMutableSet所有方法代理集合(類名是NSKeyValueFastMutableSet2)须误,那么給這個代理集合發(fā)送NSMutableSet的方法,以addObjectObject:,removeObject:或者add,remove組合的形式調用仇轻。還有兩個可選實現(xiàn)的接口:intersect , set:京痢。

如果reciever是ManagedObject,那么就不會繼續(xù)搜索篷店。

如果上步的方法沒有找到祭椰,則搜索set: 格式的方法臭家,如果找到,那么發(fā)送給代理集合的NSMutableSet最終都會調用set:方法方淤。 也就是說钉赁,mutableSetValueForKey取出的代理集合修改后,用set:重新賦值回去去携茂。這樣做效率會低很多你踩。所以推薦實現(xiàn)上面的方法。

如果上一步的方法還還沒有找到讳苦,再檢查類方法+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默認行為)带膜,會按_,,的順序搜索成員變量名,如果找到鸳谜,那么發(fā)送的NSMutableSet消息方法直接交給這個成員變量處理膝藕。

如果還是找不到,調用valueForUndefinedKey:

可見咐扭,除了檢查reciever是ManagedObject以外芭挽,其搜索順序和mutableArrayValueForKey基本一至,

同樣蝗肪,它們也有對應的keyPath版本

- (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;

- (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;

iOS5和OSX10.7以后還有個mutableOrdered版本

- (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key

這兩種KVC的用法我還不是清楚览绿,目前只能找到用于KVO的例子。如果有讀者能在項目中用到穗慕,希望可以告訴我饿敲。

KVC和字典

當對NSDictionary對象使用KVC時,valueForKey:的表現(xiàn)行為和objectForKey:一樣逛绵。所以使用valueForKeyPath:用來訪問多層嵌套的字典是比較方便的怀各。

KVC里面還有兩個關于NSDictionary的方法

- (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;

- (void)setValuesForKeysWithDictionary:(NSDictionary *)keyedValues;

dictionaryWithValuesForKeys:是指輸入一組key,返回這組key對應的屬性术浪,再組成一個字典瓢对。

setValuesForKeysWithDictionary是用來修改Model中對應key的屬性。下面直接用代碼會更直觀一點

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]; //把對應key所有的屬性全部取出來

NSLog(@"%@",dict);

NSDictionary* modifyDict = @{@"country":@"USA",@"province":@"california",@"city":@"Los angle"};

[add setValuesForKeysWithDictionary:modifyDict];? ? ? ? ? ? //用key Value來修改Model的屬性

NSLog(@"country:%@? province:%@ city:%@",add.country,add.province,add.city);

//打印結果

2016-04-19 11:54:30.846 KVCDemo[6607:198900] {

? ? city = "Shen Zhen";

? ? country = China;

? ? district = "Nan Shan";

? ? province = "Guang Dong";

}

2016-04-19 11:54:30.847 KVCDemo[6607:198900] country:USA? province:california city:Los angle

打印出來的結果完全符合預期

KVC的內部實現(xiàn)機制

前面我們對析了KVC是怎么搜索key的胰苏。所以如果明白了key的搜索順序硕蛹,是可以自己寫代碼實現(xiàn)KVC的。在考慮到集合和keyPath的情況下硕并,KVC的實現(xiàn)會比較復雜法焰,我們只寫代碼實現(xiàn)最普通的取值和設值即可。

@interface NSObject(MYKVC)

-(void)setMyValue:(id)value forKey:(NSString*)key;

-(id)myValueforKey:(NSString*)key;

@end

@implementation NSObject(MYKVC)

-(void)setMyValue:(id)value forKey:(NSString *)key{

? ? if (key == nil || key.length == 0) {

? ? ? ? return;

? ? }

? ? if ([value isKindOfClass:[NSNull class]]) {

? ? ? ? [self setNilValueForKey:key]; //如果需要完全自定義倔毙,那么這里需要寫一個setMyNilValueForKey埃仪,但是必要性不是很大,就省略了

? ? ? ? return;

? ? }

? ? if (![value isKindOfClass:[NSObject class]]) {

? ? ? ? @throw @"must be s NSobject type";

? ? ? ? return;

? ? }

? ? NSString* funcName = [NSString stringWithFormat:@"set%@:",key.capitalizedString];

? ? if ([self respondsToSelector:NSSelectorFromString(funcName)]) {

? ? ? ? [self performSelector:NSSelectorFromString(funcName) withObject:value];

? ? ? ? return;

? ? }

? ? unsigned int count;

? ? BOOL flag = false;

? ? Ivar* vars = class_copyIvarList([self class], &count);

? ? for (NSInteger i = 0; i

上面就是自己寫代碼實現(xiàn)KVC的部分功能陕赃。其中我省略了自定義KVC錯誤方法卵蛉,省略了部分KVC搜索key的步驟颁股,但是邏輯是很清晰明了的,后面的測試也符合預期傻丝。當然這只是我自己實現(xiàn)KVC的思路甘有,Apple也許并不是這么做的。

KVC的正確性驗證

KVC提供了屬性值,用來驗證key對應的Value是否可用的方法

- (BOOL)validateValue:(inout id __nullable * __nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;

這個方法的默認實現(xiàn)是去探索類里面是否有一個這樣的方法:-(BOOL)validate:error:如果有這個方法葡缰,就調用這個方法來返回亏掀,沒有的話就直接返回YES

@implementation Address

-(BOOL)validateCountry:(id *)value error:(out NSError * _Nullable __autoreleasing *)outError{? //在implementation里面加這個方法,它會驗證是否設了非法的value

? ? NSString* country = *value;

? ? country = country.capitalizedString;

? ? if ([country isEqualToString:@"Japan"]) {

? ? ? ? return NO;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //如果國家是日本运准,就返回NO幌氮,這里省略了錯誤提示,

? ? }

? ? return YES;

}

@end

NSError* error;

id value = @"japan";

NSString* key = @"country";

BOOL result = [add validateValue:&value forKey:key error:&error]; //如果沒有重寫-(BOOL)-validate:error:胁澳,默認返回Yes

if (result) {

? ? NSLog(@"鍵值匹配");

? ? [add setValue:value forKey:key];

}

else{

? ? NSLog(@"鍵值不匹配"); //不能設為日本该互,基他國家都行

}

NSString* country = [add valueForKey:@"country"];

NSLog(@"country:%@",country);

//打印結果

2016-04-20 14:55:12.055 KVCDemo[867:58871] 鍵值不匹配

2016-04-20 14:55:12.056 KVCDemo[867:58871] country:China

如上面的代碼,當開發(fā)者需要驗證能不能用KVC設定某個值時韭畸,可以調用validateValue: forKey:這個方法來驗證宇智,如果這個類的開發(fā)者實現(xiàn)了-(BOOL)validate:error:這個方法,那么KVC就會直接調用這個方法來返回胰丁,如果沒有随橘,就直接返回YES,注意锦庸,KVC在設值時不會主動去做驗證机蔗,需要開發(fā)者手動去驗證。所以即使你在類里面寫了驗證方法甘萧,但是KVC因為不會去主動驗證萝嘁,所以還是能夠設值成功。

KVC在iOS開發(fā)中是絕不可少的利器扬卷,這種基于運行時的編程方式極大地提高了靈活性牙言,簡化了代碼,甚至實現(xiàn)很多難以想像的功能怪得,KVC也是許多iOS開發(fā)黑魔法的基礎咱枉。下面我來列舉iOS開發(fā)中KVC的使用場景

動態(tài)地取值和設值

利用KVC動態(tài)的取值和設值是最基本的用途了。相信每一個iOS開發(fā)者都能熟練掌握徒恋,

用KVC來訪問和修改私有變量

對于類里的私有屬性蚕断,Objective-C是無法直接訪問的,但是KVC是可以的因谎,請參考本文前面的Dog類的例子基括。

Model和字典轉換

這是KVC強大作用的又一次體現(xiàn),請參考我寫的iOS開發(fā)技巧系列—打造強大的BaseMod系列文章财岔,里面

充分地運用了KVC和Objc的runtime組合的技巧风皿,只用了短短數(shù)行代碼就是完成了很多功能。

修改一些控件的內部屬性

這也是iOS開發(fā)中必不可少的小技巧匠璧。眾所周知很多UI控件都由很多內部UI控件組合而成的桐款,但是Apple度沒有提供這訪問這些空間的API,這樣我們就無法正常地訪問和修改這些控件的樣式夷恍。而KVC在大多數(shù)情況可下可以解決這個問題魔眨。最常用的就是個性化UITextField中的placeHolderText了。

下面演示如果修改placeHolder的文字樣式酿雪。這里的關鍵點是如果獲取你要修改的樣式的屬性名遏暴,也就是key或者keyPath名。

img

一般情況下可以運用runtime來獲取Apple不想開放的屬性名

let count:UnsafeMutablePointer = UnsafeMutablePointer()

var properties = class_copyIvarList(UITextField.self, count)

while properties.memory.debugDescription !=? "0x0000000000000000"{

? ? let t = ivar_getName(properties.memory)

? ? let n = NSString(CString: t, encoding: NSUTF8StringEncoding)

? ? print(n)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //打印出所有屬性指黎,這里我用了Swift語言

? ? properties = properties.successor()

}

//上面省略了部分屬性

Optional(_disabledBackgroundView)

Optional(_systemBackgroundView)

Optional(_floatingContentView)

Optional(_contentBackdropView)

Optional(_fieldEditorBackgroundView)

Optional(_fieldEditorEffectView)

Optional(_displayLabel)

Optional(_placeholderLabel)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //這個正是我想要修改的屬性朋凉。

Optional(_dictationLabel)

Optional(_suffixLabel)

Optional(_prefixLabel)

Optional(_iconView)

//下面省略了部分屬性

可以從里面看到其他還有很多東西可以修改,運用KVC設值可以獲得自己想要的效果醋安。

操作集合

Apple對KVC的valueForKey:方法作了一些特殊的實現(xiàn)杂彭,比如說NSArray和NSSet這樣的容器類就實現(xiàn)了這些方法。所以可以用KVC很方便地操作集合

用KVC實現(xiàn)高階消息傳遞

當對容器類使用KVC時吓揪,valueForKey:將會被傳遞給容器中的每一個對象亲怠,而不是容器本身進行操作。結果會被添加進返回的容器中柠辞,這樣团秽,開發(fā)者可以很方便的操作集合來返回另一個集合。

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);

}

打印結果

2016-04-20 16:29:14.239 KVCDemo[1356:118667] English

2016-04-20 16:29:14.240 KVCDemo[1356:118667] Franch

2016-04-20 16:29:14.240 KVCDemo[1356:118667] Chinese

2016-04-20 16:29:14.240 KVCDemo[1356:118667] 7

2016-04-20 16:29:14.241 KVCDemo[1356:118667] 6

2016-04-20 16:29:14.241 KVCDemo[1356:118667] 7

方法capitalizedString被傳遞到NSArray中的每一項叭首,這樣习勤,NSArray的每一員都會執(zhí)行capitalizedString并返回一個包含結果的新的NSArray。從打印結果可以看出放棒,所有String都成功以轉成了大寫姻报。

同樣如果要執(zhí)行多個方法也可以用valueForKeyPath:方法。它先會對每一個成員調用capitalizedString方法间螟,然后再調用length吴旋,因為lenth方法返回是一個數(shù)字,所以返回結果以NSNumber的形式保存在新數(shù)組里厢破。

用KVC中的函數(shù)操作集合

KVC同時還提供了很復雜的函數(shù)荣瑟,主要有下面這些

①簡單集合運算符

簡單集合運算符共有@avg, @count 摩泪,@max笆焰,@min,@sum5種见坑,都表示啥不用我說了吧嚷掠, 目前還不支持自定義捏检。

@interface Book : NSObject

@property (nonatomic,copy)? NSString* name;

@property (nonatomic,assign)? CGFloat price;

@end

@implementation Book

@end

Book *book1 = [Book new];

book1.name = @"The Great Gastby";

book1.price = 22;

Book *book2 = [Book new];

book2.name = @"Time History";

book2.price = 12;

Book *book3 = [Book new];

book3.name = @"Wrong Hole";

book3.price = 111;

Book *book4 = [Book new];

book4.name = @"Wrong Hole";

book4.price = 111;

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);

打印結果

2016-04-20 16:45:54.696 KVCDemo[1484:127089] sum:256.000000

2016-04-20 16:45:54.697 KVCDemo[1484:127089] avg:64.000000

2016-04-20 16:45:54.697 KVCDemo[1484:127089] count:4.000000

2016-04-20 16:45:54.697 KVCDemo[1484:127089] min:12.000000

2016-04-20 16:45:54.697 KVCDemo[1484:127089] max:111.000000

②對象運算符

比集合運算符稍微復雜,能以數(shù)組的方式返回指定的內容不皆,一共有兩種:

@distinctUnionOfObjects

@unionOfObjects

它們的返回值都是NSArray贯城,區(qū)別是前者返回的元素都是唯一的,是去重以后的結果霹娄;后者返回的元素是全集能犯。

用法如下:

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);

}

2016-04-20 16:47:34.490 KVCDemo[1522:128840] distinctUnionOfObjects

2016-04-20 16:47:34.490 KVCDemo[1522:128840] 111.000000

2016-04-20 16:47:34.490 KVCDemo[1522:128840] 12.000000

2016-04-20 16:47:34.490 KVCDemo[1522:128840] 22.000000

2016-04-20 16:47:34.490 KVCDemo[1522:128840] unionOfObjects

2016-04-20 16:47:34.490 KVCDemo[1522:128840] 22.000000

2016-04-20 16:47:34.490 KVCDemo[1522:128840] 12.000000

2016-04-20 16:47:34.490 KVCDemo[1522:128840] 111.000000

2016-04-20 16:47:34.490 KVCDemo[1522:128840] 111.000000

前者會將重復的價格去除后返回所有價格,后者直接返回所有的圖書價格犬耻。(因為只返回價格踩晶,沒有返回圖書,感覺用處不大枕磁。)

③Array和Set操作符

這種情況更復雜了渡蜻,說的是集合中包含集合的情況,我們執(zhí)行了如下的一段代碼:

@distinctUnionOfArrays

@unionOfArrays

@distinctUnionOfSets

@distinctUnionOfArrays:該操作會返回一個數(shù)組透典,這個數(shù)組包含不同的對象晴楔,不同的對象是在從關鍵路徑到操作器右邊的被指定的屬性里

@unionOfArrays該操作會返回一個數(shù)組,這個數(shù)組包含的對象是在從關鍵路徑到操作器右邊的被指定的屬性里和@distinctUnionOfArrays不一樣峭咒,重復的對象不會被移除

@distinctUnionOfSets和@distinctUnionOfArrays類似税弃。因為Set本身就不支持重復。

轉自:http://ios.jobbole.com/84954/

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末凑队,一起剝皮案震驚了整個濱河市则果,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌漩氨,老刑警劉巖西壮,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異叫惊,居然都是意外死亡款青,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門霍狰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來抡草,“玉大人,你說我怎么就攤上這事蔗坯】嫡穑” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵宾濒,是天一觀的道長腿短。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么橘忱? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任赴魁,我火速辦了婚禮,結果婚禮上鹦付,老公的妹妹穿的比我還像新娘尚粘。我一直安慰自己择卦,他們只是感情好敲长,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著秉继,像睡著了一般祈噪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上尚辑,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天辑鲤,我揣著相機與錄音,去河邊找鬼杠茬。 笑死月褥,一個胖子當著我的面吹牛,可吹牛的內容都是我干的瓢喉。 我是一名探鬼主播宁赤,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼栓票!你這毒婦竟也來了决左?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤走贪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體酷窥,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡口猜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了逃沿。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片婴渡。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖感挥,靈堂內的尸體忽然破棺而出缩搅,到底是詐尸還是另有隱情,我是刑警寧澤触幼,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布硼瓣,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏堂鲤。R本人自食惡果不足惜亿傅,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瘟栖。 院中可真熱鬧葵擎,春花似錦、人聲如沸半哟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寓涨。三九已至盯串,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間戒良,已是汗流浹背体捏。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留糯崎,地道東北人几缭。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像沃呢,于是被迫代替她去往敵國和親年栓。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內容

  • KVC(Key-value coding)鍵值編碼樟插,單看這個名字可能不太好理解韵洋。其實翻譯一下就很簡單了,就是指iO...
    朽木自雕也閱讀 1,560評論 6 1
  • KVC(Key-value coding)鍵值編碼黄锤,單看這個名字可能不太好理解搪缨。其實翻譯一下就很簡單了,就是指iO...
    黑暗中的孤影閱讀 49,723評論 74 441
  • KVC(Key-value coding)鍵值編碼鸵熟,單看這個名字可能不太好理解副编。其實翻譯一下就很簡單了,就是指iO...
    Fendouzhe閱讀 674評論 0 6
  • KVC簡單介紹 KVC(Key-value coding)鍵值編碼流强,就是指iOS的開發(fā)中痹届,可以允許開發(fā)者通過Key...
    公子無禮閱讀 1,389評論 0 6
  • KVC(Key-value coding)鍵值編碼,iOS的開發(fā)中打月,可以允許開發(fā)者通過Key名直接訪問對象的屬性队腐,...
    CALayer_Sai閱讀 2,523評論 0 4