iOS-KVC
本文章屬於簡書-mr_young_原創,轉載請註明出處:
https://www.jianshu.com/p/23dce2ead05f
1.什麼是KVC?
KVC
:即key-value-coding
,鍵值編碼。
2.KVC應用場景
1> 通過鍵值路徑可為物件的屬性進行賦值,也可以設定物件的私有屬性。
Person *person = [Person alloc] init];
[person setValue:@"young" forKey:@"name"];
// 如果物件的某一屬性也是物件
[person setValue:@"doge" forKeyPath:@"dog.name"];
2> 通過鍵值路徑獲取物件某一屬性的值,也可以獲取私有屬性。
NSString *pName = [person valueForKey:@"name"];
// 也可以通過kvc獲取到dog的屬性
NSString *dogName = [person valueForKeyPath:@"dog.name"];
3> 簡單的字典-->模型
#import <Foundation/Foundation.h>
@class Dog;
@interface Person : NSObject
@property (nonatomic, assign) int p_id
@property (nonatomic, copy) NSString name;
@property (nonatomic, strong) Dog *dog;
+ (instancetype)personWithDict:(NSDictionary *)dict;
@end
#import "Person.h"
#import "Dog.h"
@implementation Person
+ (instancetype)personWithDict:(NSDictionary *)dict {
// dict: @{@"id":@(10), @"name":@"young", @"dog":@{@"name":@"doge", @"age":@(3)}}
Person *person = [Person alloc] init];
[person setValuesForKeysWithDictionary:dict];
return person;
}
// Person類中找不到key(此時找不到id)對應的屬性時,呼叫此方法
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
if ([key isEqualToString:@"id"]) {
self.p_id = [value intValue];
} else {
NSLog(@"Person找不到%@對應的屬性", key);
return nil;
}
}
@end
4> 用KVC獲取集合中的元素個數、最大值、最小值、平均值以及求和。⚠️注意:通過KVC來獲取集合中元素的個數時應當使用@"@count"
。
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Dog.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p1 = [[Person alloc] init];
Dog *dog1 = [[Dog alloc] init];
dog1.name = @"dog1";
dog1.age = 3;
p1.name = @"young";
p1.dog = dog1;
Person *p2 = [[Person alloc] init];
Dog *dog2 = [[Dog alloc] init];
dog2.name = @"dog2";
dog2.age = 2;
p2.name = @"wang";
p2.dog = dog2;
Person *p3 = [[Person alloc] init];
Dog *dog3 = [[Dog alloc] init];
dog3.name = @"dog3";
dog3.age = 6;
p3.name = @"zhang";
p3.dog = dog3;
NSArray *arr = [NSArray arrayWithObjects:p1, p2, p3, nil];
NSLog(@"\narr.count:%@\nmaxAge:%@\nminAg:%@\navgAge:%@\nsumAge:%@",
[arr valueForKey:@"@count"], [arr valueForKeyPath:@"@max.dog.age"],
[arr valueForKeyPath:@"@min.dog.age"], [arr valueForKeyPath:@"@avg.dog.age"],
[arr valueForKeyPath:@"@sum.dog.age"]);
}
return 0;
}
3.手擼KVC之前
3.1 首先,我們要知道在OC中一個屬性對應有四個成員變數,並且他們的優先順序依次為:_key
>_isKey
>key
>isKey
。
// Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject {
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
/**
NSString *_isName;
NSString *name;
NSString *isName;
*/
/**
NSString *name;
NSString *isName;
*/
/**
NSString *isName;
*/
}
@end
// Person.m
#import "Person.h"
@implementation Person
- (instancetype)init {
self = [super init];
if (self) {
_name = @"_name";
_isName = @"_isName";
name = @"name";
isName = @"isName";
// 執行結果: KVC-Test[1799:310739] name: _name
/**
// _name = @"_name";
_isName = @"_isName";
name = @"name";
isName = @"isName";
// 執行結果: KVC-Test[1799:310739] name: _isName
// _name = @"_name";
// _isName = @"_isName";
name = @"name";
isName = @"isName";
// 執行結果: KVC-Test[1799:310739] name: name
// _name = @"_name";
// _isName = @"_isName";
// name = @"name";
isName = @"isName";
// 執行結果: KVC-Test[1799:310739] name: isName
*/
}
return self;
}
@end
// main.m
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
NSLog(@"name: %@", [person valueForKey:@"name"]);
}
return 0;
}
@end
3.2 通過valueForKey:@"key"
獲取物件某一屬性時:
- 首先,呼叫getter方法,一個屬性對應有3個getter方法,並且它們的優先順序依次為:
getKey
>key
>isKey
。
// 還是用3.1的例子?,重寫Person類的3個getter方法
// Person.m
#import "Person.h"
@implementation Person
- (instancetype)init {
self = [super init];
if (self) {
_name = @"_name";
_isName = @"_isName";
name = @"name";
isName = @"isName";
}
return self;
}
- (NSString *)getName {
// 執行結果:KVC-Test[2115:403089] name: getter- getName
return @"getter- getName";
}
- (NSString *)name {
// 註釋getName方法後,執行結果:KVC-Test[2115:403089] name: getter - name
return @"getter - name";
}
- (NSString *)isName {
// 註釋getName和name方法後,執行結果:KVC-Test[2115:403089] name: getter - isName
return @"getter - isName";
}
@end
- 如果沒有getter方法,則會呼叫NSArry的兩個物件方法
- (NSInteger)countOfKey
和- (id)objectInKeyAtIndex:(NSInteger)index
,如果實現了這兩個方法則會返回一個陣列。
#import "Person.h"
@implementation Person
- (NSUInteger)countOfName {
return 10;
}
- (id)objectInNameAtIndex:(NSUInteger)index {
return [NSString stringWithFormat:@"name-%lu", (unsigned long)index];
}
// 列印結果為
//KVC-Test[2278:424945] name: (
// "name-0",
// "name-1",
// "name-2",
// "name-3",
// "name-4",
// "name-5",
// "name-6",
// "name-7",
// "name-8",
// "name-9"
// )
//Program ended with exit code: 0
@end
- 如果沒有NSArray的兩個物件方法,則會呼叫類方法
+(BOOL)accessInstanceVariablesDirectly
。返回NO,則不會查詢任何成員變數,並且報錯valueForUndefinedKey:]: this class is not key value coding-compliant for the key
。因此,該類方法預設返回YES。
- 只有
+ (BOOL)accessInstanceVariablesDirectly
返回YES時,KVC才會查詢Person
類中的4個成員變數:_key
、_isKey
、key
、isKey
(_name
、_isName
、name
、isName
)。它們優先順序依次為_key
>_isKey
>key
>isKey
, 當找到_key
時,即返回_key
的值,如果找不到_key
再查詢_isKey
...如果最後找不到isKey
的成員變數時,同樣報錯valueForUndefinedKey:]: this class is not key value coding-compliant for the key
。
- 如果類方法
+ (BOOL)accessInstanceVariablesDirectly
返回NO,或最後也沒又找到isKey
對應的成員變數時,系統還會給我們最後一次機會防止程式崩潰:就是實現- (id)valueForUndefinedKey:(NSString *)key
方法,該方法return nil;
時,會防止程式崩潰。
3.3 通過setValue:@"value" ForKey:@"key"
設定物件某一屬性時:
- 首先,呼叫setter方法。對於一個屬性,對應有2個setter方法,並且它們的優先順序依次為:
setKey
>setIsKey
。
// main.m
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person setValue:@"young" forKey:@"name"];
}
return 0;
}
@end
// Person.m
#import "Person.h"
@implementation Person
- (void)setName:(NSString *)name {
// 執行結果:KVC-Test[2315:444214] setter name - young
NSLog(@"setter name - %@", name);
}
- (void)setIsName:(NSString *)name {
// 註釋setName方法後,執行結果:KVC-Test[2315:444214] setter isName - young
NSLog(@"setter isName - %@", name);
}
@end
- 如果這個類沒有實現setter方法,則系統就會呼叫類方法:
+ (BOOL)accessInstanceVariablesDirectly
預設返回YES; 如果該類方法返回值為NO,便如方法名所言,不能訪問例項的所有成員變數。並且報錯:setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key name.'
- 當然,如果
+ (BOOL)accessInstanceVariablesDirectly``+ (BOOL)accessInstanceVariablesDirectly
類方法返回NO時,系統還給我們最後一次防止報錯的機會:實現- (void)setValue:(id)value forUndefinedKey:(NSString *)key
方法。
#import "Person.h"
@implementation Person
+ (BOOL)accessInstanceVariablesDirectly {
return NO;
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
// 執行結果:KVC-Test[2391:471126] Person類中沒有name屬性
NSLog(@"Person類中沒有%@屬性", key);
}
@end
3.4 將nil值通過KVC設定物件某一屬性時,該屬性不能為基本資料型別,否則報錯:
- (void)setNilValueForKey:(NSString *)key
方法,便可防止此異常的發生。
4.手擼KVC
// NSObject+MyKVX.h
#import <Foundation/Foundation.h>
@interface NSObject (MyKVC)
- (id)my_valueForKey:(NSString *)key;
- (void)my_setValue:(id)value forKey:(NSString *)key;
@end
// NSObject+MyKVX.m
#import "NSObject+MyKVC.h"
#import <objc/runtime.h>
@implementation NSObject (MyKVC)
- (id)my_valueForKey:(NSString *)key {
// key值需要合法
if (key == nil || key.length == 0) {
NSLog(@"key is nil!!!");
return nil;
}
// step 1: 呼叫getter方法: getKey、key、getIsKey
NSString *getKey = [NSString stringWithFormat:@"get%@", key.capitalizedString];
// 呼叫getKey方法
if ([self respondsToSelector:NSSelectorFromString(getKey)]) {
return [self performSelector:NSSelectorFromString(getKey)];
}
// 呼叫key方法
if ([self respondsToSelector:NSSelectorFromString(key)]) {
return [self performSelector:NSSelectorFromString(key)];
}
NSString *getIsKey = [NSString stringWithFormat:@"getIs%@", key.capitalizedString];
if ([self respondsToSelector:NSSelectorFromString(getIsKey)]) {
return [self performSelector:NSSelectorFromString(getIsKey)];
}
// step 2: 沒有getter方法,呼叫countOfKey和objectInKeyAtIndex:(NSInteger)index方法
NSString *countOfKey = [NSString stringWithFormat:@"countOf%@", key.capitalizedString];
// 注意⚠️:objectInKeyAtIndex:(NSInteger)index有引數,方法名要包含冒號
NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:", key.capitalizedString];
NSUInteger keyCount = 0;
if ([self respondsToSelector:NSSelectorFromString(countOfKey)] &&
[self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)]) {
keyCount = (NSUInteger)[self performSelector:NSSelectorFromString(countOfKey)];
NSMutableArray *keyValues = [NSMutableArray array];
for (int i = 0; i < keyCount; i++) {
[keyValues addObject:[self performSelector:NSSelectorFromString(objectInKeyAtIndex) withObject:@(i)]];
}
return keyValues;
}
// step 3: 沒有實現countOfKey和objectInKeyAtIndex方法
// 並且 accessInstanceVariablesDirectly類方法返回NO
if (![self.class accessInstanceVariablesDirectly]) { // 丟擲異常
NSException *exception = [NSException exceptionWithName:@"MyKVC Exception" reason:@"你不讓我訪問成員變數,我能怎麼辦!" userInfo:nil];
@throw exception;
}
// 獲取類中的所有成員變數
unsigned int ivarCount;
Ivar *ivars = class_copyIvarList(self.class, &ivarCount); // 拷貝一個類的成員變數列表到堆區域
// step 4: 根據成員變數的優先順序將獲取成員變數的值
Ivar _keyIvar = nil;
Ivar _isKeyIvar = nil;
Ivar keyIvar = nil;
Ivar isKeyIvar = nil;
for (int i = 0; i < ivarCount; i++) {
Ivar ivar = ivars[i];
NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
if ([name isEqualToString:[NSString stringWithFormat:@"_%@", key]]) {
_keyIvar = ivar;
} else if ([name isEqualToString:[NSString stringWithFormat:@"_is%@", key.capitalizedString]]) {
_isKeyIvar = ivar;
} else if ([name isEqualToString:[NSString stringWithFormat:@"%@", key]]) {
keyIvar = ivar;
} else if ([name isEqualToString:[NSString stringWithFormat:@"is%@", key.capitalizedString]]) {
isKeyIvar = ivar;
}
}
id obj = nil;
if (_keyIvar) {
obj = object_getIvar(self, _keyIvar);
free(ivars);
return obj;
} else if (_isKeyIvar) {
obj = object_getIvar(self, _isKeyIvar);
free(ivars);
return obj;
} else if (keyIvar) {
obj = object_getIvar(self, keyIvar);
free(ivars);
return obj;
} else if (isKeyIvar) {
obj = object_getIvar(self, isKeyIvar);
free(ivars);
return obj;
}
// step 5: 呼叫valueForUndefinedKey方法
free(ivars);
return [self valueForUndefinedKey:key];
}
- (void)my_setValue:(id)value forKey:(NSString *)key {
// key值需要合法
if (key == nil || key.length == 0) {
NSLog(@"key is nil!!!");
return;
}
// 第一步:呼叫setter方法:setKey、setIsKey; 注意⚠️:setter方法有引數,所以方法名後有冒號
// capitalizedString方法:字串首字母大寫
NSString *setKey = [NSString stringWithFormat:@"set%@:", key.capitalizedString];
// 呼叫setKey方法
if ([self respondsToSelector:NSSelectorFromString(setKey)]) {
[self performSelector:NSSelectorFromString(setKey) withObject:value];
return;
}
// 呼叫setIsKey
NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:", key.capitalizedString];
if ([self respondsToSelector:NSSelectorFromString(setIsKey)]) {
[self performSelector:NSSelectorFromString(setIsKey) withObject:value];
}
// 第二步:沒有setter方法 並且 accessInstanceVariablesDirectly類方法返回NO
if (![self.class accessInstanceVariablesDirectly]) { // 丟擲異常
NSException *exception = [NSException exceptionWithName:@"MyKVC Exception" reason:@"你不讓我訪問成員變數,我能怎麼辦!" userInfo:nil];
@throw exception;
return;
}
// 獲取類中的所有成員變數
unsigned int ivarCount;
Ivar *ivars = class_copyIvarList(self.class, &ivarCount); // 拷貝一個類的成員變數列表到堆區域
// 第三步:根據成員變數的優先順序將value賦值給成員變數
Ivar _keyIvar = nil;
Ivar _isKeyIvar = nil;
Ivar keyIvar = nil;
Ivar isKeyIvar = nil;
for (int i = 0; i < ivarCount; i++) {
Ivar ivar = ivars[i];
NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
if ([name isEqualToString:[NSString stringWithFormat:@"_%@", key]]) {
_keyIvar = ivar;
} else if ([name isEqualToString:[NSString stringWithFormat:@"_is%@", key.capitalizedString]]) {
_isKeyIvar = ivar;
} else if ([name isEqualToString:[NSString stringWithFormat:@"%@", key]]) {
keyIvar = ivar;
} else if ([name isEqualToString:[NSString stringWithFormat:@"is%@", key.capitalizedString]]) {
isKeyIvar = ivar;
}
}
if (_keyIvar) {
object_setIvar(self, _keyIvar, value);
free(ivars);
return;
} else if (_isKeyIvar) {
object_setIvar(self, _isKeyIvar, value);
free(ivars);
return;
} else if (keyIvar) {
object_setIvar(self, keyIvar, value);
free(ivars);
return;
} else if (isKeyIvar) {
object_setIvar(self, isKeyIvar, value);
free(ivars);
return;
}
// 異常處理方法
[self setValue:value forUndefinedKey:key];
free(ivars);
}
@end