iOS中關於KVC使用的一些小技巧

KVC是iOS開發中常常會用到的技巧, 主要包括valueForKey:/setValue:ForKey:, valueForKeyPath:/setValue:forKeyPath:兩對組合方法. 最多見的理解和使用是:valueForKey:會首先查找以參數名命名的getter方法, 若是沒有找到, 則在對象內尋找名稱格式爲_key或key的實例變量. 另外還有dictionaryWithValuesForKeys:/setValuesForKeysWithDictionary方法用於獲取或設置指定的內容.git

這裏有兩個類:github

#pragma mark - Dog

@interface Dog : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, copy) NSString *city;

@end

@implementation Dog

@end



#pragma mark - Person

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;

@property (nonatomic, strong) Dog *dog;

@property (nonatomic, strong) NSArray<Dog *> *dogs;

@end

@implementation Person

@end
複製代碼

其中, Person類有一個dogs屬性. 下面將在這兩個類的基礎上展現一些小的demo.數組

基本使用

Dog *aDog = [[Dog alloc] init];
aDog.name = @"My Dog";
aDog.age = 2;
NSString *dogName = [aDog valueForKey:@"name"];
NSInteger dogAge = [[aDog valueForKey:@"age"] integerValue];
NSLog(@"dogName : %@, dogAge : %ld", dogName, dogAge);
複製代碼

鍵路徑

Person *aPerson = [[Person alloc] init];
aPerson.name = @"Chris";
aPerson.age = 18;
aPerson.dog = aDog;

NSString *name = [aPerson valueForKeyPath:@"name"];
NSInteger age = [[aPerson valueForKeyPath:@"age"] integerValue];
dogName = [aPerson valueForKeyPath:@"dog.name"];
dogAge = [[aPerson valueForKeyPath:@"dog.age"] integerValue];
NSLog(@"testKeyPath: name : %@, age : %ld", name, age);
NSLog(@"testKeyPath: dogName : %@, dogAge : %ld", dogName, dogAge);
複製代碼

注意, 鍵路徑的寫法遵循點語法.bash

批量操做

使用KVC, 有時候能夠很是方便地進行一些批量的操做.session

Dog *dog1 = [[Dog alloc] init];
dog1.name = @"Dog 1";
dog1.age = 1;
dog1.city = @"Shanghai";

Dog *dog2 = [[Dog alloc] init];
dog2.name = @"Dog 2";
dog2.age = 2;
dog2.city = @"Shanghai";

Dog *dog3 = [[Dog alloc] init];
dog3.name = @"Dog 3";
dog3.age = 3;
dog3.city = @"Beijing";

Person *aPerson = [[Person alloc] init];
aPerson.name = @"Chris";
aPerson.age = 18;
aPerson.dogs = @[dog1, dog2, dog3];
複製代碼

測試代碼以下:app

NSArray *dogNames = [aPerson valueForKeyPath:@"dogs.name"];
NSArray *dogAges = [aPerson valueForKeyPath:@"dogs.age"];
NSMutableString *str = [[NSMutableString alloc] init];
[str appendString:@"dogNames :"];
for (NSInteger i = 0; i < dogNames.count; i++) {
    [str appendFormat:@" %@, %ld years old. ", dogNames[i], [dogAges[i] integerValue]];
}
複製代碼

NSArray實現valueForKeyPath:的方法是循環遍歷它的內容並向每一個對象發送消息.測試

批量修改以下:ui

[aPerson setValue:@"Xiamen" forKeyPath:@"dogs.city"];

cities = [aPerson valueForKeyPath:@"dogs.@distinctUnionOfObjects.city"];
NSLog(@"cities : %@", cities);
複製代碼

快速運算

使用KVC進行快速運算的鍵路徑語法相似於 *** dogs.@count *** , *** dogs.@sum.age *** 等.atom

// dogs表示取出內容, @count即進行計算, 通知KVC機制進行鍵路徑左側值的對象總數
NSNumber *countOfDogs = [aPerson valueForKeyPath:@"dogs.@count"];
NSLog(@"count of dogs : %@", countOfDogs);

// 獲取@sum左側的集合, 對集合中的每一個對象執行右側操做age, 將結果組成一個集合並返回.
NSNumber *ageCountOfDogs = [aPerson valueForKeyPath:@"dogs.@sum.age"];
NSLog(@"ageCountOfDogs of dogs : %@", ageCountOfDogs);

NSNumber *ageAvgOfDogs = [aPerson valueForKeyPath:@"dogs.@avg.age"];
NSLog(@"age avg of dogs : %@", ageAvgOfDogs);
NSLog(@"age min : %@, max : %@", [aPerson valueForKeyPath:@"dogs.@min.age"], [aPerson valueForKeyPath:@"dogs.@max.age"]);

// 獲取惟一值
NSArray *cities = [aPerson valueForKeyPath:@"dogs.@distinctUnionOfObjects.city"];
NSLog(@"cities : %@", cities);
複製代碼

這裏就不做過多描述.spa

dictionaryWithValuesForKeys:/setValuesForKeysWithDictionary:

直接看代碼更直觀:

// 根據設置的key, 來進行組合結果.
Dog *lastDog = [[aPerson valueForKeyPath:@"dogs"] lastObject];
NSArray *keys = @[@"name"];
NSDictionary *values = [lastDog dictionaryWithValuesForKeys:keys];
NSLog(@"values : %@", values);

// 使用setValuesForKeysWithDictionary根據字典對Dog進行修改
NSDictionary *newValues = @{@"name": @"My Dog"};
[lastDog setValuesForKeysWithDictionary:newValues];
values = [lastDog dictionaryWithValuesForKeys:keys];
NSLog(@"values : %@", values);
複製代碼

總結

KVC的調用順序

KVC的valueForkey:如何找到對應的值呢?實際上會對使用vlaueForKey方法的實例對象嘗試調用以下方法,有返回值即中止並返回:

-get<Key>, -<key>, -is<Key>等getter方法,
-countOf<Key, -objectIn<Key>AtIndex:
_<key>, _is<Key>, <key>, is<Key>
複製代碼

對於結果,若是是引用對象,直接返回。若是是標量,使用NSNumber包裝並返回。不然,使用NSValue包裝並返回。 因此,使用KVC來操做實例對象的屬性和實例變量是很是容易的事。即,KVO與runtime(可以查詢到實例對象的全部實例變量和屬性等)結合起來,能夠作到對任意屬性和實例變量進行查詢和修改,儘管一些屬性和實例變量並未暴露出來。

其實,默認狀況下,KVC相關的方法有不少,其中有一些並非咱們熟知的。

-valueForKey:
-setValue:forKey:
-validateValue:forKey:error:
-mutableArrayValueForKey:
-mutableOrderedSetValueForKey:
-mutableSetValueForKey:

-valueForKeyPath:
-setValue:forKeyPath:
-validateValue:forKeyPath:error:
-mutableArrayValueForKeyPath:
-mutableOrderedSetValueForKeyPath:
-mutableSetValueForKeyPath:

-valueForUndefinedKey:
-setValue:forUndefinedKey:
-setNilValueForKey:
-dictionaryWithValuesForKeys:
-setValuesForKeysWithDictionary:
複製代碼

集合

NSArray

-valueForKey:方法會遍歷每一個元素,執行-valueForKey:,而且結果組合成一個新的數組並返回。對於返回nil的狀況,會使用NSNull來代替。

Return an array containing the results of invoking -valueForKey: on each of the receiver's elements. The returned array will contain NSNull elements for each instance of -valueForKey: returning nil.

-setValue:forKey:則會遍歷每一個元素,執行-setValue:forKey:方法。

NSSet

-valueForKey:方法會遍歷每一個元素,執行-valueForKey:,而且結果組合成一個新的set並返回。 注意,對於-valueForKey:的結果爲nil的元素,不會將其結果nil存入返回的set中。這一點與NSArray不一樣。

Return a set containing the results of invoking -valueForKey: on each of the receiver's members. The returned set might not have the same number of members as the receiver. The returned set will not contain any elements corresponding to instances of -valueForKey: returning nil (in contrast with -[NSArray(NSKeyValueCoding) valueForKey:], which may put NSNulls in the arrays it returns).

NSDictionary

這個沒什麼可說的,分別對應於NSDictionary的objectForKey:和setObject:forKey:方法。

Demo

Demo請參考:

DemoKVC 須要注意的是:

不要濫用KVC, KVC須要解析字符串來計算須要的結果, 所以速度較慢. 且沒法進行錯誤檢查.

最後補充一個KVC在AFNetworking中的使用案例

- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
    __block NSArray *tasks = nil;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
            tasks = dataTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
            tasks = uploadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
            tasks = downloadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
            tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
        }

        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    return tasks;
}
複製代碼

KVC若是使用得當,確實可以在代碼中起到畫龍點睛的做用。

相關文章
相關標籤/搜索