前言:本文章開始分析KVC相關~
一、Apple Developer Documentation
位址:傳送門
這裡就可以查找到kvc相關的蘋果官方文檔↓
二、KVC的設定
來結合代碼了解一下kvc的setter
- 1.看是否有set<Key> or _set<Key>
- (void)viewDidLoad {
[super viewDidLoad];
LGPerson *person = [[LGPerson alloc] init];
// 1: KVC - 設定值的過程 setValue 分析調用過程
[person setValue:@"Mcl" forKey:@"name"];
}
LGPerson.m中實作
- (void)setName:(NSString *)name{
NSLog(@"%s - %@",__func__,name);
}
輸出:
-[LGPerson setName:] - Mcl
注釋set<Key> 打開_set<Key>
//- (void)setName:(NSString *)name{
// NSLog(@"%s - %@",__func__,name);
//}
- (void)_setName:(NSString *)name{
NSLog(@"%s - %@",__func__,name);
}
輸出:
-[LGPerson _setName:] - Mcl
setName和_setName都打開注釋,輸出看看
-[LGPerson setName:] - Mcl
可以看出set<Key>優先級大于_set<Key>,也印證了文檔中第一條↓
1.查找第一個名為set<Key>:或_set<Key>的通路器,按此順序。如果找到,則使用輸入值(或根據需要取消包裝的值)調用它并完成
-
2.沒有簡單通路器如何執行
2.1 檢視類方法
accessInstanceVariablesDirectly
的傳回值是否為YES。
2.2 如果傳回YES,依次查找執行個體變量_<key>, _is< key>, <key>,或is< key>。
結合代碼看下
LGPerson.m中↓
/* Return YES if -valueForKey:, -setValue:forKey:, -mutableArrayValueForKey:,
-storedValueForKey:, -takeStoredValue:forKey:, and -takeValue:forKey:
may directly manipulate instance variables when sent to instances of the
receiving class, NO otherwise. The default implementation of this property
returns YES.
*/
+ (BOOL)accessInstanceVariablesDirectly{
return YES;
}
其實此方法可以不寫,因為注釋裡寫了預設為YES。
@interface LGPerson : NSObject{
@public
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
}
我們按順序定義對應的4個成員變量。在viewController裡的viewDidLoad方法中按順序依次列印各個成員變量的值。
NSLog(@"%@-%@-%@-%@",person->_name,person->_isName,person->name,person->isName);
列印結果:
Mcl-(null)-(null)-(null)
去掉LGPerson.h中的成員變量_name:
NSLog(@"%@-%@-%@",person->_isName,person->name,person->isName);
列印結果:
Mcl-(null)-(null)
去掉LGPerson.h中的成員變量_isName:
NSLog(@"%@-%@",person->name,person->isName);
列印結果:
Mcl-(null)
最後去掉LGPerson.h中的成員變量name:
NSLog(@"%@",person->isName);
列印結果:
Mcl
- 這裡有個小疑問,既然第一步會找
orset<Key>:
_set<Key>,第二部多了2個帶'is'的成員變量,那麼會不會找setIs<Key>和_setIs<Key>呢?
嘗試下在LGPerson實作類裡實作以下2個方法
- (void)setIsName:(NSString *)name{
NSLog(@"%s - %@",__func__,name);
}
// 沒有調用
- (void)_setIsName:(NSString *)name{
NSLog(@"%s - %@",__func__,name);
}
同時打開4個成員變量的注釋
NSString *_name;
NSString *_isName;
NSString *name;
NSString *isName;
viewController中
[person setValue:@"Mcl" forKey:@"name"];
NSLog(@"%@-%@-%@-%@",person->_name,person->_isName,person->name,person->isName);
運作檢視列印結果 :
隻打開setIsName:方法
-[LGPerson setIsName:] - Mcl
(null)-(null)-(null)-(null)
隻打開_setIsName:方法
Mcl-(null)-(null)-(null)
發現并沒有走 _setIsName:方法,是以實際上可以通過3種方法指派,而不是文檔裡寫的2種。
是以:雖然沒有寫- (void)setIsName:(NSString *)name 和 - (void)_setIsName:(NSString *)name
但是當我們實作了- (void)setIsName:(NSString *)name之後,列印4個成員變量的值都是空,說明生效了,并且優先級高于4個成員變量。
簡單總結就是:
2.如果沒有找到簡單的通路器,并且如果類方法accessinstancevariablesdirect傳回YES,請查找一個執行個體變量,其名稱依次為_<key>, _is< key>, <key>,或is< key>。如果找到,直接用輸入值(或取消包裝的值)設定變量并完成。
- 3.
在沒有找到通路器或執行個體變量時,調用setValue:forUndefinedKey:。預設情況下,這會引發一個異常,但是NSObject的子類可能提供特定于鍵的行為。
嘗試之後發現4個成員變量隻要存在任意一個就會正常列印,都注釋掉就會報錯。
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<LGPerson 0x600001a5bd80> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key name.'
三、KVC的取值
KVC的取值共有6點。
- 1.
在執行個體中搜尋第一個名稱為get<Key>、<Key>、is<Key>或_< Key>的通路器方法。如果找到了,就調用它,并使用結果繼續步驟5。否則執行下一步。
代碼驗證:
//viewController中viewDidLoad代碼:
LGPerson *person = [[LGPerson alloc] init];
//分别指派,便于區分
person->_name = @"_name";
person->_isName = @"_isName";
person->name = @"name";
person->isName = @"isName";
//這裡打斷點,輸出檢視
NSLog(@"取值:%@",[person valueForKey:@"name"]);
//LGPerson.m中加入下列代碼:
- (NSString *)getName{
return NSStringFromSelector(_cmd);
}
- (NSString *)name{
return NSStringFromSelector(_cmd);
}
- (NSString *)isName{
return NSStringFromSelector(_cmd);
}
- (NSString *)_name{
return NSStringFromSelector(_cmd);
}
_cmd是隐藏的參數,代表目前方法的selector。
上面4個方法都打開時,輸出結果為
取值:getName
依次注釋掉4個方法得到結論:
确實是按照蘋果文檔裡的順序取值的,而且如果4個方法都注釋掉,預設輸出代下劃線的_<Key>。
- 2.
如果沒有找到簡單的通路器方法,在執行個體中搜尋比對模式countOf<Key>和objectIn<Key>AtIndex:(對應于NSArray類定義的原始方法)和<Key> AtIndexes:(對應于NSArray方法objectsAtIndexes:)的方法。
如果找到了第一個和另外兩個中的至少一個,建立一個集合代理對象,響應所有NSArray方法并傳回它。否則,執行步驟3。
代理對象随後将它接收到的所有NSArray消息轉換為countOf<Key>, objectIn<Key>AtIndex:,和<Key> AtIndexes:消息的組合,并将其轉換為符合鍵值編碼的建立它的對象。如果原始對象還實作了一個名稱為get<Key>:range:的可選方法,那麼代理對象也會在适當的時候使用該方法。實際上,代理對象與符合鍵值編碼的對象一起工作,允許底層屬性像NSArray一樣行為,即使它不是NSArray。
- 3.
如果沒有找到簡單的通路方法或數組通路方法組,則查找名為countOf<Key>, enumeratorOf<Key>,和memberOf<Key>:(對應于NSSet類定義的基本方法)的三個方法。
如果找到了所有三個方法,建立一個集合代理對象響應所有的NSSet方法并傳回它。否則,執行步驟4。
這個代理對象随後将它接收到的任何NSSet消息轉換為countOf<Key>, enumeratorOf<Key>, memberOf<Key>: messages的組合,并将其轉換為建立它的對象。實際上,代理對象與符合鍵值編碼的對象一起工作,允許底層屬性的行為就像它是一個NSSet,即使它不是。
- 4.
如果沒有找到簡單的通路方法或一組集合通路方法,并且如果接收方的類方法accessinstancevariablesdirect傳回YES,則搜尋執行個體變量_<key>, _is< key>, <key>,或is< key>,依次。如果找到,直接擷取執行個體變量的值,然後繼續步驟5。否則,執行步驟6。
- 5.
如果檢索到的屬性值是對象指針,隻需傳回結果。
如果該值是NSNumber支援的标量類型,則将其存儲在NSNumber執行個體中并傳回該執行個體。
如果結果是NSNumber不支援的标量類型,則轉換為NSValue對象并傳回該對象。
- 6.
如果其他方法都失敗了,調用valueForUndefinedKey:。預設情況下,這會引發一個異常,但是NSObject的子類可能提供特定于鍵的行為。
這裡暫時不做舉例結合分析了。
四、KVC自定義實作
- set實作
大緻流程如下:
1.首先判斷key是否為空。
2.
2.1判斷accessInstanceVariablesDirectly類方法是否為YES。
2.2依次查找4個執行個體變量
_<key>
,
_is<Key>
,
<key>
, or
is<Key>。
3.抛出異常。
代碼:
建立一個NSObject的分類
@interface NSObject (LGKVC)
// LG KVC 自定義入口
- (void)lg_setValue:(nullable id)value forKey:(NSString *)key;
@end
@implementation NSObject (LGKVC)
- (void)lg_setValue:(nullable id)value forKey:(NSString *)key{
// 1: 判斷什麼 key
if (key == nil || key.length == 0) {
return;
}
// 2: setter set<Key>: or _set<Key>,
// key 要大寫
NSString *Key = key.capitalizedString;
// 拼接方法
NSString *setKey = [NSString stringWithFormat:@"set%@:",Key];
NSString *_setKey = [NSString stringWithFormat:@"_set%@:",Key];
NSString *setIsKey = [NSString stringWithFormat:@"setIs%@:",Key];
if ([self lg_performSelectorWithMethodName:setKey value:value]) {
NSLog(@"*********%@**********",setKey);
return;
}else if ([self lg_performSelectorWithMethodName:_setKey value:value]) {
NSLog(@"*********%@**********",_setKey);
return;
}else if ([self lg_performSelectorWithMethodName:setIsKey value:value]) {
NSLog(@"*********%@**********",setIsKey);
return;
}
// 3: 判斷是否響應 accessInstanceVariablesDirectly 傳回YES NO 奔潰
// 3:判斷是否能夠直接指派執行個體變量
if (![self.class accessInstanceVariablesDirectly] ) {
@throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
}
// 4: 間接變量
// 擷取 ivar -> 周遊 containsObjct -
// 4.1 定義一個收集執行個體變量的可變數組
NSMutableArray *mArray = [self getIvarListName];
// _<key> _is<Key> <key> is<Key>
NSString *_key = [NSString stringWithFormat:@"_%@",key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
if ([mArray containsObject:_key]) {
// 4.2 擷取相應的 ivar
Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
// 4.3 對相應的 ivar 設定值
object_setIvar(self , ivar, value);
return;
}else if ([mArray containsObject:_isKey]) {
Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
object_setIvar(self , ivar, value);
return;
}else if ([mArray containsObject:key]) {
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
object_setIvar(self , ivar, value);
return;
}else if ([mArray containsObject:isKey]) {
Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
object_setIvar(self , ivar, value);
return;
}
// 5:如果找不到相關執行個體
@throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ %@]: this class is not key value coding-compliant for the key name.****",self,NSStringFromSelector(_cmd)] userInfo:nil];
}
#pragma mark - 相關方法
- (BOOL)lg_performSelectorWithMethodName:(NSString *)methodName value:(id)value{
if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector:NSSelectorFromString(methodName) withObject:value];
#pragma clang diagnostic pop
return YES;
}
return NO;
}
- (NSMutableArray *)getIvarListName{
NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0; i<count; i++) {
Ivar ivar = ivars[i];
const char *ivarNameChar = ivar_getName(ivar);
NSString *ivarName = [NSString stringWithUTF8String:ivarNameChar];
NSLog(@"ivarName == %@",ivarName);
[mArray addObject:ivarName];
}
free(ivars);
return mArray;
}
@end
- get實作
類似set方法步驟按照文檔流程寫get實作
- (nullable id)lg_valueForKey:(NSString *)key{
// 1:刷選key 判斷非空
if (key == nil || key.length == 0) {
return nil;
}
// 2:找到相關方法 get<Key> <key> countOf<Key> objectIn<Key>AtIndex
// key 要大寫
NSString *Key = key.capitalizedString;
// 拼接方法
NSString *getKey = [NSString stringWithFormat:@"get%@",Key];
NSString *countOfKey = [NSString stringWithFormat:@"countOf%@",Key];
NSString *objectInKeyAtIndex = [NSString stringWithFormat:@"objectIn%@AtIndex:",Key];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
if ([self respondsToSelector:NSSelectorFromString(getKey)]) {
return [self performSelector:NSSelectorFromString(getKey)];
}else if ([self respondsToSelector:NSSelectorFromString(key)]){
return [self performSelector:NSSelectorFromString(key)];
}else if ([self respondsToSelector:NSSelectorFromString(countOfKey)]){
if ([self respondsToSelector:NSSelectorFromString(objectInKeyAtIndex)]) {
int num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
for (int i = 0; i<num-1; i++) {
num = (int)[self performSelector:NSSelectorFromString(countOfKey)];
}
for (int j = 0; j<num; j++) {
id objc = [self performSelector:NSSelectorFromString(objectInKeyAtIndex) withObject:@(num)];
[mArray addObject:objc];
}
return mArray;
}
}
#pragma clang diagnostic pop
// 3:判斷是否能夠直接指派執行個體變量
if (![self.class accessInstanceVariablesDirectly] ) {
@throw [NSException exceptionWithName:@"LGUnknownKeyException" reason:[NSString stringWithFormat:@"****[%@ valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
}
// 4.找相關執行個體變量進行指派
// 4.1 定義一個收集執行個體變量的可變數組
NSMutableArray *mArray = [self getIvarListName];
// _<key> _is<Key> <key> is<Key>
// _name -> _isName -> name -> isName
NSString *_key = [NSString stringWithFormat:@"_%@",key];
NSString *_isKey = [NSString stringWithFormat:@"_is%@",Key];
NSString *isKey = [NSString stringWithFormat:@"is%@",Key];
if ([mArray containsObject:_key]) {
Ivar ivar = class_getInstanceVariable([self class], _key.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:_isKey]) {
Ivar ivar = class_getInstanceVariable([self class], _isKey.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:key]) {
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
return object_getIvar(self, ivar);;
}else if ([mArray containsObject:isKey]) {
Ivar ivar = class_getInstanceVariable([self class], isKey.UTF8String);
return object_getIvar(self, ivar);;
}
return @"";
}
#pragma mark - 相關方法
- (id)performSelectorWithMethodName:(NSString *)methodName{
if ([self respondsToSelector:NSSelectorFromString(methodName)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
return [self performSelector:NSSelectorFromString(methodName) ];
#pragma clang diagnostic pop
}
return nil;
}