(IOS)KVC

KVC 簡介

KVC全稱是Key Value Coding(鍵值編碼)秘蛔,是一個基于NSKeyValueCoding非正式協(xié)議實現(xiàn)的機制沽瘦,它可以直接通過key值對對象的屬性進行存取操作钻心,而不需通過調(diào)用明確的存取方法鳍鸵。這樣就可以在運行時動態(tài)在訪問和修改對象的屬性,而不是在編譯時確定奇徒。

KVC提供了一種間接訪問屬性方法或成員變量的機制雏亚,可以通過字符串來訪問對象的的屬性方法或成員變量。

在實現(xiàn)了訪問器方法的類中摩钙,使用點語法和KVC訪問對象其實差別不大罢低,二者可以任意混用。但是沒有訪問器方法的類中胖笛,點語法無法使用网持,這時KVC就有優(yōu)勢了。

KVC和KVO都是基于OC的動態(tài)特性和Runtime機制的长踊。

KVC 通用的訪問方法

1.通用的訪問方法:

getter方法:valueForKey:

setter方法:setValue:forKey:

2.衍生的keyPath方法功舀,用來進行深層訪問(key使用點語法),也可單層訪問:

keyPath的setter方法:setValue: forKeyPath:

keyPath的getter方法:valueForKeyPath:

示例:

Address.h:

#import <Foundation/Foundation.h>

@interface Address : NSObject

@property (copy, nonatomic) NSString *city;

@property (copy, nonatomic) NSString *street;

@end

Person.h:

#import <Foundation/Foundation.h>

#import "Address.h"

@interface Person : NSObject

@property (copy, nonatomic) NSString *name;

@property (assign, nonatomic) NSInteger *sex;

@property (strong, nonatomic) NSNumber *age;

@property (strong, nonatomic) Address *address;

@end

- (void)viewDidLoad {

? ? [super viewDidLoad];

? ? Person *myself = [[Person alloc] init];

? ? [myself setValue:@"xds" forKey:@"name"];

? ? NSLog(@"-------name = %@",myself.name);

? ? NSLog(@"-------name = %@",[myself valueForKey:@"name"]);

? ? /**

? ? keyPath的setter方法:setValue: forKeyPath:

? ? keyPath的getter方法:valueForKeyPath:

? ? keyPath為多級訪問之斯,使用點語法

? ? */

? ? //注意日杈,這里要想使用keypath對adress的屬性進行賦值,必須先給myself賦一個Address對象

? ? Address *myAddress = [[Address alloc] init];

? ? [myself setValue:myAddress forKey:@"address"];

? ? //KeyPath為多級訪問

? ? [myself setValue:@"rizhao" forKeyPath:@"address.city"];

? ? NSLog(@"-------city = %@",myself.address.city);

? ? NSLog(@"-------city = %@",[myself valueForKeyPath:@"address.city"]);

}

keypath

除了對當前對象的屬性進行賦值外佑刷,還可以對其更“深層”的對象進行訪問莉擒。

keypath可以訪問到array數(shù)組中所有存儲的對象的屬性,前提是對象類型是一樣的瘫絮。

例如下面例子中涨冀,通過valueForKeyPath:將數(shù)組中所有對象的name屬性值取出,并放入一個數(shù)組中返回麦萤。

NSArray *names = [array valueForKeyPath:@"name"];

KVC 的多值操作

批量取值操作

KVC還有更強大的功能鹿鳖,可以根據(jù)給定的一組key,獲取到一組value壮莹,并且以字典的形式返回翅帜。

- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;

批量賦值操作

同樣,也可以通過KVC進行批量賦值命满。使用對象調(diào)用setValuesForKeysWithDictionary:方法時涝滴,可以傳入一個包含key、value的字典進去胶台,KVC可以將所有數(shù)據(jù)按照屬性名和字典的key進行匹配歼疮,并將value給User對象的屬性賦值。

- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;

示例

示例如下:

//批量賦值NSDictionary *dic = @{

? ? ? ? ? ? ? ? ? ? ? @"name":@"xiaoMing",

? ? ? ? ? ? ? ? ? ? ? @"sex":@1,

? ? ? ? ? ? ? ? ? ? ? @"age":@12,

? ? ? ? ? ? ? ? ? ? ? @"address":myAddress

? ? ? ? ? ? ? ? ? ? ? };

[myself setValuesForKeysWithDictionary:dic];

//批量取值NSArray *keys = @[@"name",@"age",@"sex",@"address"];

NSDictionary *values = [myself dictionaryWithValuesForKeys:keys];

NSLog(@"%@",values);

輸出:

2018-08-25 15:45:57.316650+0800 KVC[1145:231458] {

? ? address = "<Address: 0x60400003cc40>";

? ? age = "<null>";

? ? name = xds;

? ? sex = 0;

}

使用 KVC 進行字典轉模型

可以使用setValuesForKeysWithDictionary: 進行字典轉模型诈唬。

假如傳過來的Jason數(shù)據(jù)如下所示韩脏,注意到age傳過來的的key為Age,這樣在字典轉模型時會報錯铸磅,因為這個Age在模型類里面并沒有定義赡矢。

JSON數(shù)據(jù):

{

? ? @"name":@"xiaoMing",

? ? @"sex":@1,

? ? @"Age":@12

}

我們可以在Person里重寫 setValue:(id)value forUndefinedKey: 方法,這個方法是針對碰到未定義的key時怎么辦的方法愚屁。

#import "Person.h"

@implementation Person

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

? ? if ([key isEqualToString:@"Age"]) {

? ? ? ? [self setValue:value forKey:@"age"];

? ? }

}

@end

注意點

Json里的字段數(shù)量和字段名字應該和model類所匹配济竹,Json少了字段不會出現(xiàn)問題,但是多了或者是字段名不對霎槐,就會崩潰送浊。

不需對基本數(shù)據(jù)類型做處理,例如int型轉NSNumber丘跌,內(nèi)部會自動作出處理

NSArray和NSDictionary等集合對象袭景,value都不能是nil,否則會導致Crash

異常信息和異常處理

當根據(jù)KVC搜索規(guī)則闭树,沒有搜索到對應的key或者keyPath耸棒,則會調(diào)用對應的異常方法。異常方法的默認實現(xiàn)报辱,在異常發(fā)生時會拋出一個NSUndefinedKeyException的異常与殃,并且應用程序Crash。

我們可以重寫下面兩個方法,根據(jù)業(yè)務需求合理的處理KVC導致的異常:

在取值時幅疼,未有對應的key:

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

在賦值時米奸,未有對應的key:

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

當通過 KVC 給某個非對象的屬性賦值為 nil 時,例如使用 setValue:forkey 給 int 或 float 類型屬性賦為 nil 時爽篷,會拋出 NSInvalidArgumentException 的異常并崩潰悴晰。

我們可以重寫下面的方法,進行處理:

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

示例:

當我們?yōu)镻erson類的NSInteger類型的sex屬性賦nil時逐工,會報錯

[myself setValue:nil forKey:@"sex"];

Person.m 重寫 setNilValueForKey: 方法

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

? ? if ([key isEqualToString:@"sex"]) {

? ? ? ? [self setValue:@1111 forKey:@"sex"];

? ? }else{

? ? ? ? [super setNilValueForKey:key];

? ? }

}

集合屬性操作

當我們要操作一個對象里的集合屬性時(NSArray铡溪、NSSet等),我們可以通過KVC方法取到集合屬性泪喊,然后通過集合屬性操作集合中的元素棕硫。實際開發(fā)中最常用的方法,叫做間接操作袒啼。

直接操作要實現(xiàn)下面的方法饲帅,但通常不會用到:

有序集合對應方法如下:

-countOf<Key>//必須實現(xiàn),對應于NSArray的基本方法count:2? -objectIn<Key>AtIndex:

-<key>AtIndexes://這兩個必須實現(xiàn)一個瘤泪,對應于 NSArray 的方法 objectAtIndex: 和 objectsAtIndexes:

-get<Key>:range://不是必須實現(xiàn)的灶泵,但實現(xiàn)后可以提高性能,其對應于 NSArray 方法 getObjects:range:

-insertObject:in<Key>AtIndex:

-insert<Key>:atIndexes://兩個必須實現(xiàn)一個对途,類似于 NSMutableArray 的方法 insertObject:atIndex: 和 insertObjects:atIndexes:

-removeObjectFrom<Key>AtIndex:

-remove<Key>AtIndexes://兩個必須實現(xiàn)一個赦邻,類似于 NSMutableArray 的方法 removeObjectAtIndex: 和 removeObjectsAtIndexes:

-replaceObjectIn<Key>AtIndex:withObject:

-replace<Key>AtIndexes:with<Key>://可選的,如果在此類操作上有性能問題实檀,就需要考慮實現(xiàn)之

無序集合對應方法如下:

-countOf<Key>//必須實現(xiàn)惶洲,對應于NSArray的基本方法count:

-objectIn<Key>AtIndex:

-<key>AtIndexes://這兩個必須實現(xiàn)一個,對應于 NSArray 的方法 objectAtIndex: 和 objectsAtIndexes:

-get<Key>:range://不是必須實現(xiàn)的膳犹,但實現(xiàn)后可以提高性能恬吕,其對應于 NSArray 方法 getObjects:range:

-insertObject:in<Key>AtIndex:

-insert<Key>:atIndexes://兩個必須實現(xiàn)一個,類似于 NSMutableArray 的方法 insertObject:atIndex: 和 insertObjects:atIndexes:

-removeObjectFrom<Key>AtIndex:

-remove<Key>AtIndexes://兩個必須實現(xiàn)一個须床,類似于 NSMutableArray 的方法 removeObjectAtIndex: 和 removeObjectsAtIndexes:

-replaceObjectIn<Key>AtIndex:withObject:

-replace<Key>AtIndexes:with<Key>://這兩個都是可選的铐料,如果在此類操作上有性能問題,就需要考慮實現(xiàn)之

使用 KVC 進行集合類的運算

KVC 提供的 valueForKeyPath: 方法非常強大豺旬,可以在keyPath中嵌套集合運算符對集合中的對象進行相關的運算钠惩,例如求一個數(shù)組中所有Person對象的age總和。集合對象主要指NSArray和NSSet族阅,不包括NSDictionary篓跛。

集合運算符的格式

keyPathToCollection.@collentionOperator.keyPathToproperty

keyPathToCollection:Left key path,要操作的集合對象坦刀,若調(diào)用 valueForKeyPath: 方法的對象本來就是集合對象愧沟,則可以省略蔬咬;

collentionOperator:Collection operator,集合操作符沐寺,一般以@開頭计盒;

keyPathToproperty:Right key path,要運算的屬性芽丹。

//Address.h

@interface Address : NSObject

@property (copy, nonatomic) NSString *city;

@property (copy, nonatomic) NSString *street;

@property (strong, nonatomic) NSNumber *cityNumber;

@property (assign, nonatomic) NSInteger streetNumber;

@end

- (void)viewDidLoad {

? ? [super viewDidLoad];

? ? NSMutableArray *array = [NSMutableArray array];

? ? for ( int i = 0 ; i < 5 ; i++ ) {

? ? ? ? Address *address = [[Address alloc] init];

? ? ? ? NSString *cityStr = [NSString stringWithFormat:@"city-%d",i];

? ? ? ? //批量賦值

? ? ? ? NSDictionary *dic = @{

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @"city":cityStr,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @"street":@"street",

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @"cityNumber":@(i),

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @"streetNumber":@101

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? };

? ? ? ? [address setValuesForKeysWithDictionary:dic];

? ? ? ? [array addObject:address];

? ? }

? ? //返回數(shù)組中保存的對象的屬性,返回值為數(shù)組

? ? NSArray *cityArray = [array valueForKeyPath:@"city"];

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

}

集合運算符的分類

集合運算符主要分為三類:

集合操作符:處理集合包含的對象卜朗,并根據(jù)操作符的不同返回不同的類型拔第,返回值以NSNumber為主。

數(shù)組操作符:根據(jù)操作符的條件场钉,將符合條件的對象包含在數(shù)組中返回蚊俺。

嵌套操作符:處理集合對象中嵌套其他集合對象的情況,返回結果也是一個集合對象逛万。

1. 集合操作符

集合操作符處理 NSArray和 NSSet 及其子類這樣的集合對象泳猬,并根據(jù)不同的操作符返回不同類型的對象,返回值一般都是 NSNumber宇植。

@avg 用來計算集合中 right keyPath 指定的屬性的平均值得封。

//@avg:平均值

NSNumber *avg = [array valueForKeyPath:@"@avg.streetNumber"];

NSLog(@"%@",avg);

@count 用來計算集合中對象的數(shù)量。備注:@count 操作符比較特殊指郁,它不需要寫 right keyPath忙上,即使寫了也會被忽略。

//@count:集合里對象的數(shù)量

NSNumber *count = [array valueForKeyPath:@"@count"];

NSLog(@"%@",count);

@sum 用來計算集合中 right keyPath 指定的屬性的總和闲坎。

//@sum:總和

NSNumber *sum = [array valueForKeyPath:@"@sum.streetNumber"];

NSLog(@"%@",sum);

@max 用來查找集合中 right keyPath 指定的屬性的最大值疫粥。

//@max:最大值

NSNumber *max = [array valueForKeyPath:@"@max.cityNumber"];

NSLog(@"%@",max);

@min 用來查找集合中 right keyPath 指定的屬性的最小值。

//@min:最小值

NSNumber *min = [array valueForKeyPath:@"@min.cityNumber"];

NSLog(@"%@",min);

備注:@max 和 @min 在進行判斷時腰懂,都是通過調(diào)用 compare: 方法進行判斷梗逮,所以可以通過重寫該方法對判斷過程進行控制。

2. 數(shù)組操作符(因為返回的是數(shù)組绣溜,所以叫數(shù)組操作符)

@unionOfObjects將集合中的所有對象的同一個屬性放在數(shù)組中返回慷彤。這個和直接寫屬性好像沒區(qū)別。

NSArray *city = [array valueForKeyPath:@"@unionOfObjects.city"];

NSLog(@"%@",city);

NSArray *cityArray = [array valueForKeyPath:@"city"];

NSLog(@"%@",cityArray);

@distinctUnionOfObjects將集合中對象的屬性進行去重并返回怖喻。

NSArray *streetNumberArr = [array valueForKeyPath:@"@distinctUnionOfObjects.streetNumber"];

NSLog(@"%@",streetNumberArr);

3. 嵌套操作符

嵌套操作符是對集合里的集合進行操作瞬欧,比如數(shù)組里面的數(shù)組。

NSMutableArray *array1 = [NSMutableArray array];

? ? for ( int i = 0 ; i < 5 ; i++ ) {

? ? ? ? Address *address = [[Address alloc] init];

? ? ? ? NSString *cityStr = [NSString stringWithFormat:@"city-%d",i];

? ? ? ? //批量賦值

? ? ? ? NSDictionary *dic = @{

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @"city":cityStr,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @"street":@"street",

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @"cityNumber":@(i),

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @"streetNumber":@101

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? };

? ? ? ? [address setValuesForKeysWithDictionary:dic];

? ? ? ? [array1 addObject:address];

? ? }

? ? NSMutableArray *array2 = [NSMutableArray array];

? ? for ( int i = 3 ; i < 9 ; i++ ) {

? ? ? ? Address *address = [[Address alloc] init];

? ? ? ? NSString *cityStr = [NSString stringWithFormat:@"city-%d",i];

? ? ? ? //批量賦值

? ? ? ? NSDictionary *dic = @{

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @"city":cityStr,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @"street":@"street",

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @"cityNumber":@(i),

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @"streetNumber":@101

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? };

? ? ? ? [address setValuesForKeysWithDictionary:dic];

? ? ? ? [array2 addObject:address];

? ? }

? ? NSArray *array = @[array1,array2];

@unionOfArrays 是用來操作集合內(nèi)部的集合罢防,將所有right keyPath對應的屬性放在一個數(shù)組中返回艘虎。

NSArray *result = [array valueForKeyPath:@"@unionOfArrays.city"];

@distinctUnionOfArrays 是用來操作集合內(nèi)部的集合對象,將所有right keyPath對應的對象放在一個數(shù)組中咒吐,并進行排重野建。

? ? NSArray *result = [array valueForKeyPath:@"@distinctUnionOfArrays.city"];

@distinctUnionOfSets 是用來操作集合內(nèi)部的集合對象属划,將所有right keyPath對應的對象放在一個set中,并進行排重候生。

? ? NSSet *result = [array valueForKeyPath:@"@distinctUnionOfSets .city"];

4. 小技巧

如果你想對集合里裝的對象直接進行操作同眯,可以將 right keyPath 直接寫為 self。比如集合里裝的是NSNumber對象唯鸭,如下:

? ? NSArray *numbers = @[@1,@1,@3,@5];

? ? NSNumber *sum = [numbers valueForKeyPath:@"@distinctUnionOfObjects.self"];

? ? NSNumber *avg = [numbers valueForKeyPath:@"@avg.self"];

KVC對數(shù)值和結構體型屬性的支持

KVC可以自動的將數(shù)值或結構體型的數(shù)據(jù)打包或解包成NSNumber或NSValue對象须蜗,以達到適配的目的。

舉個例子目溉,Person類有個NSInteger類型的age屬性明肮,如下:

//? Person.m

#import "Person.h"

@interface Person ()

@property (nonatomic,assign) NSInteger age;

@end

@implementation Person

@end

//? Person.m

#import "Person.h"

@interface Person ()

@property (nonatomic,assign) NSInteger age;

@end

@implementation Person

@end

1. 修改值

我們通過KVC技術使用如下方式設置age屬性的值:

[[person setValue:[NSNumber numberWithInteger:5] forKey:@"age"];

我們賦給age的是一個NSNumber對象,KVC會自動的將NSNumber對象轉換成NSInteger對象缭付,然后再調(diào)用相應的訪問器方法設置age的值柿估。

2. 獲取值

同樣,以如下方式獲取age屬性值:

NSNumber *age = [person valueForKey:@"age"];

這時陷猫,會以NSNumber的形式返回age的值秫舌。

3. 注意點

我們不能直接將基本數(shù)據(jù)類型通過KVC賦值,需要把數(shù)據(jù)轉成NSNumber或NSValue類型傳入绣檬。

可以使用NSNumber的數(shù)據(jù)類型有:

+ (NSNumber*)numberWithChar:(char)value;

+ (NSNumber*)numberWithUnsignedChar:(unsignedchar)value;

+ (NSNumber*)numberWithShort:(short)value;

+ (NSNumber*)numberWithUnsignedShort:(unsignedshort)value;

+ (NSNumber*)numberWithInt:(int)value;

+ (NSNumber*)numberWithUnsignedInt:(unsignedint)value;

+ (NSNumber*)numberWithLong:(long)value;

+ (NSNumber*)numberWithUnsignedLong:(unsignedlong)value;

+ (NSNumber*)numberWithLongLong:(longlong)value;

+ (NSNumber*)numberWithUnsignedLongLong:(unsignedlonglong)value;

+ (NSNumber*)numberWithFloat:(float)value;

+ (NSNumber*)numberWithDouble:(double)value;

+ (NSNumber*)numberWithBool:(BOOL)value;

+ (NSNumber*)numberWithInteger:(NSInteger)valueNS_AVAILABLE(10_5,2_0);

+ (NSNumber*)numberWithUnsignedInteger:(NSUInteger)valueNS_AVAILABLE(10_5,2_0);

可以使用NSValue的數(shù)據(jù)類型有:

+ (NSValue*)valueWithCGPoint:(CGPoint)point;

+ (NSValue*)valueWithCGSize:(CGSize)size;

+ (NSValue*)valueWithCGRect:(CGRect)rect;

+ (NSValue*)valueWithCGAffineTransform:(CGAffineTransform)transform;

+ (NSValue*)valueWithUIEdgeInsets:(UIEdgeInsets)insets;

+ (NSValue*)valueWithUIOffset:(UIOffset)insets;

NSValue主要用于處理結構體型的數(shù)據(jù)足陨,任何結構體都是可以轉化成NSValue對象的,包括其它自定義的結構體娇未。

屬性驗證

在調(diào)用 KVC 前可以先進行驗證 key(keyPath) 和 value 的正確性钠右,驗證通過下面兩個方法進行。驗證方法默認實現(xiàn)返回 YES忘蟹,可以通過重寫對應的方法修改驗證邏輯飒房。

注意:驗證方法需要我們手動調(diào)用,并不會在進行 KVC 的過程中自動調(diào)用媚值。

//key方法

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

//keyPath方法

- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue

forKeyPath:(NSString *)inKeyPath error:(out NSError **)outError;

主動去調(diào)用驗證方法:

Person *person = [[Person alloc] init];

NSError *error;

NSString *name = @"xds";

if (![person validateValue:&name forKey:@"name" error:&error]) {

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

}

單獨驗證

KVC還支持對單獨屬性做驗證狠毯,可以通過定義validate<Key>:error:格式的方法,并在方法內(nèi)部實現(xiàn)驗證代碼褥芒。在編寫KVC驗證代碼的時候嚼松,應該先查找屬性有沒有自定義validate方法,然后再查找validateValue:方法锰扶,如果有則調(diào)用自己實現(xiàn)的方法献酗,如果兩個方法都沒有實現(xiàn)則默認返回YES。

- (BOOL)validateName:(id *)ioValue error:(NSError * __autoreleasing *)outError{

? ? if ((*ioValue ** nil) || ([(NSString *)*ioValue length] < 2)) {

? ? ? ? if (outError != NULL) {

? ? ? ? ? ? *outError = [NSError errorWithDomain:PersonErrorDomain

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? code:PersonInvalidNameCode

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? userInfo:@{ NSLocalizedDescriptionKey

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? : @"Name too short" }];

? ? ? ? }

? ? ? ? return NO;

? ? }

? ? return YES;

}

注意:這里 validateName 是 validate + 屬性名稱 組合而來的坷牛。

KVC 的搜索規(guī)則

在學習KVC的搜索規(guī)則前罕偎,要先弄明白一個屬性的作用,這個屬性在搜索過程中起到很重要的作用京闰。這個屬性表示是否允許讀取實例變量的值颜及,如果為YES則在KVC查找的過程中甩苛,從內(nèi)存中讀取屬性實例變量的值。

@property (class, readonly) BOOL accessInstanceVariablesDirectly;

在KVC的實現(xiàn)中俏站,依賴setter和getter的方法實現(xiàn)讯蒲,所以方法命名應該符合蘋果要求的規(guī)范,否則會導致KVC失敗肄扎。

基礎Getter搜索模式(valueForKey:/valueForKeyPath:)

1.首先按get<Key>墨林、<key>、is<Key>的順序查找getter方法犯祠,找到直接調(diào)用旭等。

若方法的返回結果類型為是一個對象指針,則直接返回結果雷则;

若類型為能夠轉化為NSNumber的基本數(shù)據(jù)類型,轉換為NSNumber后返回肪笋;

否則月劈,轉換為NSValue返回。

2.上面的getter沒有找到藤乙,查找countOf<Key>猜揪、objectIn<Key>AtIndex:、<Key>AtIndexes格式的方法坛梁。

如果countOf<Key>和另外兩個方法中的一個找到而姐,那么就會返回一個可以響應NSArray所有方法的集合代理(collection proxy object)。發(fā)送給這個代理集合(collection proxy object)的NSArray消息方法划咐,就會以countOf<Key>拴念、objectIn<Key>AtIndex:、<Key>AtIndexes這幾個方法組合的形式調(diào)用褐缠。如果receiver的類實現(xiàn)了get<Key>:range:方法政鼠,給方法也會用于性能優(yōu)化。

3.還沒查到队魏,那么查找countOf<Key>公般、enumeratorOf<Key>、memberOf<Key>:格式的方法胡桨。如果這三個方法都找到官帘,那么就返回一個可以響應NSSet所有方法的集合代理(collection proxy object)。發(fā)送給這個代理集合(collection proxy object)的NSSet消息方法昧谊,就會以countOf<Key>刽虹、enumeratorOf<Key>、memberOf<Key>:組合的形式調(diào)用呢诬。

4.還是沒查到状婶,那么如果類方法accessInstanceVariablesDirectly返回YES意敛,那么按_<key>,_is<Key>膛虫,<key>草姻,is<Key>(注意大小寫)的順序直接搜索實例變量。如果搜索到了稍刀,則返回receiver相應實例變量的值撩独。返回結果的處理見步驟1。

5.再沒查到账月,調(diào)用valueForUndefinedKey:方法综膀,報出異常。

總結一下:

先找相應的 getter 方法(get<Key>, <key>, is<Key>, 或者 _<key>)局齿,找到了則返回(對象類型直接返回剧劝,其它類型進行轉換);

沒有找到抓歼,則尋找 NSArray 相應的方法讥此;

沒有找到,則尋找 NSSet 相應的方法谣妻;

如果還沒有萄喳,且 accessInstanceVariablesDirectly 類屬性返回的是 YES,則去搜索實例變量(_<key>蹋半、_is<Key>他巨、<key>、is<Key>)减江。如果發(fā)現(xiàn)了染突,則返回;

還沒有辈灼,則轉到 valueForUndefinedKey: 方法并拋出異常觉痛。

基礎Setter搜索模式

這是setValue:forKey:的默認實現(xiàn),給定輸入?yún)?shù)value和key茵休。試圖在接收調(diào)用對象的內(nèi)部薪棒,設置屬性名為key的value,通過下面的步驟:

查找set<Key>:或_set<Key>命名的setter榕莺,按照這個順序俐芯,如果找到的話,調(diào)用這個方法并將值傳進去(根據(jù)需要進行對象轉換)钉鸯。

如果沒有發(fā)現(xiàn)一個簡單的setter吧史,但是 accessInstanceVariablesDirectly 類屬性返回YES,則查找一個命名規(guī)則為_<key>唠雕、_is<Key>贸营、<key>吨述、is<Key>的實例變量。根據(jù)這個順序钞脂,如果發(fā)現(xiàn)則將value賦值給實例變量揣云。

如果沒有發(fā)現(xiàn)setter或?qū)嵗兞浚瑒t調(diào)用setValue:forUndefinedKey:方法冰啃,并默認提出一個異常邓夕,但是一個NSObject的子類可以提出合適的行為。

總結:

先找 setter 方法(set<Key>:或_set<Key>)阎毅;

沒找到焚刚,如果則 accessInstanceVariablesDirectly 類屬性返回的是 YES,則去查找實例變量(_<key>扇调、_is<Key>矿咕、<key>、is<Key>)狼钮,若找到碳柱,則賦值;

沒有找到 setter 方法和實例變量燃领,則轉到 setValue:forUndefinedKey: 方法士聪,并拋出異常锦援。

KVC性能

根據(jù)上面KVC的實現(xiàn)原理猛蔽,我們可以看出KVC的性能并不如直接訪問屬性快,雖然這個性能消耗是微乎其微的灵寺。所以在使用KVC的時候曼库,建議最好不要手動設置屬性的setter、getter略板,這樣會導致搜索步驟變長毁枯。

而且盡量不要用KVC進行集合操作,例如NSArray叮称、NSSet之類的种玛,集合操作的性能消耗更大,而且還會創(chuàng)建不必要的對象瓤檐。

私有訪問

根據(jù)上面的實現(xiàn)原理我們知道赂韵,KVC本質(zhì)上是操作方法列表以及在內(nèi)存中查找實例變量。我們可以利用這個特性訪問類的私有變量挠蛉,例如下面在.m中定義的私有成員變量和屬性祭示,都可以通過KVC的方式訪問。

這個操作對readonly的屬性谴古,@protected的成員變量质涛,都可以正常訪問稠歉。如果不想讓外界訪問類的成員變量,則可以將accessInstanceVariablesDirectlygetter方法返回為NO汇陆。

Person.m文件

@interface Person(){

? ? NSString *str;

}

@property (strong, nonatomic) NSString *testStr;

@end

testStr是私有屬性巧娱,str是私有成員變量。使用KVC都可以訪問到這兩個端蛆。如果不想讓外界訪問到str成員變量床三,可以這樣做:

@implementation Person

+ (BOOL)accessInstanceVariablesDirectly{

? ? return NO;

}

@end

這樣就訪問不到私有的成員變量了。但是還是能訪問到私有的屬性月趟,因為屬性有getter和setter方法灯蝴,KVC會先搜索訪問方法,再去看accessInstanceVariablesDirectlygetter能不能訪問實例變量孝宗。

KVC 的實踐

KVC在實踐中也有很多用處穷躁,例如UITabbar或UIPageControl這樣的控件,系統(tǒng)已經(jīng)為我們封裝好了因妇,但是對于一些樣式的改變并沒有提供足夠的API问潭,這種情況就需要我們用KVC進行操作了』楸唬可以自定義一個UITabbar對象狡忙,然后在內(nèi)部創(chuàng)建自己想要的視圖,并通過layoutSubviews方法在內(nèi)部進行重新布局址芯。然后通過KVC的方式灾茁,將UITabbarController的tabbar屬性替換為自定義的類即可。

安全性檢查

KVC存在一個問題在于谷炸,因為傳入的key或keyPath是一個字符串北专,這樣很容易寫錯或者屬性自身修改后字符串忘記修改,這樣會導致Crash旬陡。

可以利用iOS的反射機制來規(guī)避這個問題拓颓,通過@selector()獲取到方法的SEL,然后通過NSStringFromSelector()將SEL反射為字符串描孟。這樣在@selector()中傳入方法名的過程中驶睦,編譯器會有合法性檢查,如果方法不存在或未實現(xiàn)會報黃色警告匿醒。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末场航,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子青抛,更是在濱河造成了極大的恐慌旗闽,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異适室,居然都是意外死亡嫡意,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進店門捣辆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蔬螟,“玉大人,你說我怎么就攤上這事汽畴【山恚” “怎么了?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵忍些,是天一觀的道長鲁猩。 經(jīng)常有香客問我,道長罢坝,這世上最難降的妖魔是什么廓握? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮嘁酿,結果婚禮上隙券,老公的妹妹穿的比我還像新娘。我一直安慰自己闹司,他們只是感情好娱仔,可當我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著游桩,像睡著了一般牲迫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上众弓,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天恩溅,我揣著相機與錄音隔箍,去河邊找鬼谓娃。 笑死,一個胖子當著我的面吹牛蜒滩,可吹牛的內(nèi)容都是我干的滨达。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼俯艰,長吁一口氣:“原來是場噩夢啊……” “哼捡遍!你這毒婦竟也來了?” 一聲冷哼從身側響起竹握,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤画株,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谓传,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡蜈项,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了续挟。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片紧卒。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖诗祸,靈堂內(nèi)的尸體忽然破棺而出跑芳,到底是詐尸還是另有隱情,我是刑警寧澤直颅,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布博个,位于F島的核電站,受9級特大地震影響功偿,放射性物質(zhì)發(fā)生泄漏坡倔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一脖含、第九天 我趴在偏房一處隱蔽的房頂上張望罪塔。 院中可真熱鬧,春花似錦养葵、人聲如沸征堪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽佃蚜。三九已至,卻和暖如春着绊,著一層夾襖步出監(jiān)牢的瞬間谐算,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工归露, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留洲脂,地道東北人。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓剧包,卻偏偏與公主長得像恐锦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子疆液,可洞房花燭夜當晚...
    茶點故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內(nèi)容

  • KVC KVC定義 KVC(Key-value coding)鍵值編碼一铅,就是指iOS的開發(fā)中,可以允許開發(fā)者通過K...
    暮年古稀ZC閱讀 2,137評論 2 9
  • 前言: 本文基本不講KVC/KVO的用法堕油,只結合網(wǎng)上的資料說說對這種技術的理解潘飘。 由于KVO內(nèi)容較少肮之,而且是以KV...
    土b蘭博王閱讀 3,055評論 0 33
  • 前言:往往會某項工具WORK,就想究其原理卜录。本文先簡單介紹KVC 一局骤、KVC 簡介 1.1 KVC 概述 1.KV...
    夢蕊dream閱讀 911評論 0 2
  • 本文參考: KVC官方文檔 KVC原理剖析 iOS KVC詳解 KVC 簡介 KVC全稱是Key Value Co...
    擰發(fā)條鳥xds閱讀 5,284評論 6 23
  • KVC(Key-valuecoding)鍵值編碼,單看這個名字可能不太好理解暴凑。其實翻譯一下就很簡單了峦甩,就是指iOS...
    榕樹頭閱讀 702評論 0 2