GNUstep KVC/KVO探索(一):KVC的內(nèi)部實(shí)現(xiàn)
GNUstep KVC/KVO探索(二):KVO的內(nèi)部實(shí)現(xiàn)
KVC全稱是Key Value Coding,定義在NSKeyValueCoding.h文件中镇草,是一個非正式協(xié)議。KVC提供了一種間接訪問其屬性方法或成員變量的機(jī)制屁商,可以通過字符串來訪問對應(yīng)的屬性方法或成員變量。
在NSKeyValueCoding中提供了KVC通用的訪問方法颈墅,分別是getter方法valueForKey:和setter方法setValue:forKey:蜡镶,以及其衍生的keyPath方法雾袱,這兩個方法各個類通用的。并且由KVC提供默認(rèn)的實(shí)現(xiàn)官还。
GNUstep和Cocoa都是基于OpenStep演變而來芹橡,所以底層實(shí)現(xiàn)有很多相似之處,可以通過GNUstep源碼來分析KVC的底層實(shí)現(xiàn)望伦。
一林说、Set方法
- (void) setValue: (id)anObject forKey: (NSString*)aKey
{
unsigned size = [aKey length] * 8;
char key[size + 1];
....
[aKey getCString: key
maxLength: size + 1
encoding: NSUTF8StringEncoding];
size = strlen(key);
SetValueForKey(self, anObject, key, size);
}
- 將傳入的key轉(zhuǎn)成字符數(shù)組,然后調(diào)用
SetValueForKey()
屯伞,主要實(shí)現(xiàn)內(nèi)容在SetValueForKey()
- 中間省略的部分為老版本的兼容設(shè)置
static void
SetValueForKey(NSObject *self, id anObject, const char *key, unsigned size)
{
SEL sel = 0;
const char *type = 0;
int off = 0;
if (size > 0)
{
const char *name;
char buf[size + 6];
char lo;
char hi;
strncpy(buf, "_set", 4);
strncpy(&buf[4], key, size);
lo = buf[4];
hi = islower(lo) ? toupper(lo) : lo;
buf[4] = hi;
buf[size + 4] = ':';
buf[size + 5] = '\0';
name = &buf[1]; // setKey:
type = NULL;
sel = sel_getUid(name);
if (sel == 0 || [self respondsToSelector: sel] == NO)
{
name = buf; // _setKey:
sel = sel_getUid(name);
if (sel == 0 || [self respondsToSelector: sel] == NO)
{
sel = 0;
if ([[self class] accessInstanceVariablesDirectly] == YES)
{
buf[size + 4] = '\0';
buf[3] = '_';
buf[4] = lo;
name = &buf[3]; // _key
if (GSObjCFindVariable(self, name, &type, &size, &off) == NO)
{
buf[4] = hi;
buf[3] = 's';
buf[2] = 'i';
buf[1] = '_';
name = &buf[1]; // _isKey
if (GSObjCFindVariable(self,
name, &type, &size, &off) == NO)
{
buf[4] = lo;
name = &buf[4]; // key
if (GSObjCFindVariable(self,
name, &type, &size, &off) == NO)
{
buf[4] = hi;
buf[3] = 's';
buf[2] = 'i';
name = &buf[2]; // isKey
GSObjCFindVariable(self,
name, &type, &size, &off);
}
}
}
}
}
else
{
GSOnceFLog (@"Key-value access using _setKey: is deprecated:");
}
}
}
GSObjCSetVal(self, key, anObject, sel, type, size, off);
}
此方法的實(shí)現(xiàn)很簡單腿箩,主要是獲取key相關(guān)的方法或者變量,然后調(diào)用GSObjCSetVal()
進(jìn)行實(shí)際賦值操作
- 方法中利用傳進(jìn)來的字符數(shù)組劣摇,進(jìn)行拼接珠移,依次查詢
setKey
--->_setKey
--->_key
--->_isKey
--->key
--->isKey
-
accessInstanceVariablesDirectly
通過此參數(shù)判斷當(dāng)關(guān)聯(lián)方法找不到時,是否可以直接操作實(shí)例變量末融,默認(rèn)實(shí)現(xiàn)為YES - 最后
GSObjCSetVal()
傳入查詢到的方法或者變量剑梳,進(jìn)行實(shí)際賦值操作
void
GSObjCSetVal(NSObject *self, const char *key, id val, SEL sel,
const char *type, unsigned size, int offset)
{
static NSNull *null = nil;
NSMethodSignature *sig = nil;
if (null == nil)
{
null = [NSNull new];
}
if (sel != 0)
{
sig = [self methodSignatureForSelector: sel];
if ([sig numberOfArguments] != 3)
{
[NSException raise: NSInvalidArgumentException
format: @"key-value set method has wrong number of args"];
}
type = [sig getArgumentTypeAtIndex: 2];
}
if (type == NULL)
{
[self setValue: val forUndefinedKey:
[NSString stringWithUTF8String: key]];
}
else if ((val == nil || val == null) && *type != _C_ID && *type != _C_CLASS)
{
[self setNilValueForKey: [NSString stringWithUTF8String: key]];
}
else
{
switch (*type)
{
case _C_ID:
case _C_CLASS:
{
id v = val;
if (sel == 0)
{
id *ptr = (id *)((char *)self + offset);
ASSIGN(*ptr, v);
}
else
{
void (*imp)(id, SEL, id) =
(void (*)(id, SEL, id))[self methodForSelector: sel];
(*imp)(self, sel, val);
}
}
break;
case _C_CHR:
{
char v = [val charValue];
if (sel == 0)
{
char *ptr = (char *)((char *)self + offset);
*ptr = v;
}
else
{
void (*imp)(id, SEL, char) =
(void (*)(id, SEL, char))[self methodForSelector: sel];
(*imp)(self, sel, v);
}
}
break;
case _C_UCHR:
case _C_UCHR:
case _C_SHT:
case _C_USHT:
case _C_INT:
...
default:
[self setValue: val forUndefinedKey:
[NSString stringWithUTF8String: key]];
}
}
}
此方法為KVC中實(shí)際賦值方法,主要做了一下幾件事:
- 判斷方法是否存在滑潘,如果存在,判斷方法類型是否正確锨咙,set方法的參數(shù)應(yīng)該有三個
self _cmd value
, 不正確就拋出異常语卤, 如果方法正確,則通過getArgumentTypeAtIndex
獲取到方法中value
參數(shù)的類型 - 此時如果方法存在酪刀,則獲取到方法中參數(shù)的類型粹舵,如果方法不存在,則會使用實(shí)參傳遞進(jìn)來的實(shí)例變量的類型
- 判斷類型是否為空骂倘,如果為空眼滤,則代表沒有key相關(guān)的信息,調(diào)用
setValue:forUndefinedKey:
方法历涝,方法默認(rèn)實(shí)現(xiàn)為拋出異常诅需,子類實(shí)現(xiàn)此方法可避免崩潰 - 然后判斷要賦值的
value
如果為nil/null
,并且參數(shù)類型不是指針類型和類,則調(diào)用setNilValueForKey:
,默認(rèn)實(shí)現(xiàn)為拋出異常 - 此時已經(jīng)獲取了所有信息荧库,判斷參數(shù)類型堰塌,在根據(jù)獲取的相關(guān)聯(lián)信息,是方法還是實(shí)例變量進(jìn)行不同操作分衫,
如果是實(shí)例變量场刑,則將參數(shù)轉(zhuǎn)成相匹配類型,然后利用指針賦值
如果是方法蚪战,則根據(jù)methodForSelector:
獲取相應(yīng)類型的IMP
指針進(jìn)行調(diào)用牵现。 - 最后如果類型匹配不到铐懊,則調(diào)用
setValue:forUndefinedKey:
方法拋出異常
二、get方法
- (id) valueForKey: (NSString*)aKey
{
unsigned size = [aKey length] * 8;
char key[size + 1];
[aKey getCString: key
maxLength: size + 1
encoding: NSUTF8StringEncoding];
size = strlen(key);
return ValueForKey(self, key, size);
}
static id ValueForKey(NSObject *self, const char *key, unsigned size)
{
SEL sel = 0;
int off = 0;
const char *type = NULL;
if (size > 0)
{
const char *name;
char buf[size + 5];
char lo;
char hi;
strncpy(buf, "_get", 4);
strncpy(&buf[4], key, size);
buf[size + 4] = '\0';
lo = buf[4];
hi = islower(lo) ? toupper(lo) : lo;
buf[4] = hi;
name = &buf[1]; // getKey
sel = sel_getUid(name);
if (sel == 0 || [self respondsToSelector: sel] == NO)
{
buf[4] = lo;
name = &buf[4]; // key
sel = sel_getUid(name);
if (sel == 0 || [self respondsToSelector: sel] == NO)
{
buf[4] = hi;
buf[3] = 's';
buf[2] = 'i';
name = &buf[2]; // isKey
sel = sel_getUid(name);
if (sel == 0 || [self respondsToSelector: sel] == NO)
{
sel = 0;
}
}
}
if (sel == 0 && [[self class] accessInstanceVariablesDirectly] == YES)
{
buf[4] = hi;
name = buf; // _getKey
sel = sel_getUid(name);
if (sel == 0 || [self respondsToSelector: sel] == NO)
{
buf[4] = lo;
buf[3] = '_';
name = &buf[3]; // _key
sel = sel_getUid(name);
if (sel == 0 || [self respondsToSelector: sel] == NO)
{
sel = 0;
}
}
if (sel == 0)
{
if (GSObjCFindVariable(self, name, &type, &size, &off) == NO)
{
buf[4] = hi;
buf[3] = 's';
buf[2] = 'i';
buf[1] = '_';
name = &buf[1]; // _isKey
if (!GSObjCFindVariable(self, name, &type, &size, &off))
{
buf[4] = lo;
name = &buf[4]; // key
if (!GSObjCFindVariable(self, name, &type, &size, &off))
{
buf[4] = hi;
buf[3] = 's';
buf[2] = 'i';
name = &buf[2]; // isKey
GSObjCFindVariable(self, name, &type, &size, &off);
}
}
}
}
}
}
return GSObjCGetVal(self, key, sel, type, size, off);
}
id
GSObjCGetVal(NSObject *self, const char *key, SEL sel,
const char *type, unsigned size, int offset)
{
NSMethodSignature *sig = nil;
if (sel != 0)
{
sig = [self methodSignatureForSelector: sel];
if ([sig numberOfArguments] != 2)
{
[NSException raise: NSInvalidArgumentException
format: @"key-value get method has wrong number of args"];
}
type = [sig methodReturnType];
}
if (type == NULL)
{
return [self valueForUndefinedKey: [NSString stringWithUTF8String: key]];
}
else
{
id val = nil;
switch (*type)
{
case _C_ID:
case _C_CLASS:
{
id v;
if (sel == 0)
{
v = *(id *)((char *)self + offset);
}
else
{
id (*imp)(id, SEL) =
(id (*)(id, SEL))[self methodForSelector: sel];
v = (*imp)(self, sel);
}
val = v;
}
break;
case _C_UCHR:
case _C_UCHR:
case _C_SHT:
case _C_USHT:
case _C_INT:
....
default:
#ifdef __GNUSTEP_RUNTIME__
{
Class cls;
struct objc_slot *type_slot;
SEL typed;
struct objc_slot *slot;
cls = [self class];
type_slot = objc_get_slot(cls, @selector(retain));
typed = GSSelectorFromNameAndTypes(sel_getName(sel), NULL);
slot = objc_get_slot(cls, typed);
if (strcmp(slot->types, type_slot->types) == 0)
{
return slot->method(self, typed);
}
}
#endif
val = [self valueForUndefinedKey:
[NSString stringWithUTF8String: key]];
}
return val;
}
}
- get方法與set方法類似瞎疼,都是根據(jù)key一次查詢相關(guān)信息科乎,查詢順序?yàn)?code>getKey--->
key
--->isKey
--->_getKey
--->_key(方法)
--->_key(實(shí)例變量)
--->_isKey
--->key
--->isKey
, 前5個查詢?yōu)榉椒ǔ笊鳎?個為實(shí)例變量喜喂。 - 同樣根據(jù)
accessInstanceVariablesDirectly
判斷是否直接操作實(shí)例變量,其中包含_getKey _key
方法 - 然后根據(jù)獲取到的內(nèi)容調(diào)用
GSObjCGetVal()
獲取值 -
GSObjCGetVal()
和GSObjCSetVal()
邏輯相似竿裂,不過用來判斷的是方法返回值的類型進(jìn)行判斷玉吁。同樣,如果類型為空腻异,則調(diào)用valueForUndefinedKey:
方法拋出異常进副。 - 然后根據(jù)類型、方法/實(shí)例變量進(jìn)行取值悔常,如果是方法影斑,則獲取
IMP
指針調(diào)用,如果只實(shí)例變量机打,則直接根據(jù)指針取值矫户。 - 最后如果類型匹配不到,則調(diào)用
valueForUndefinedKey:
方法拋出異常残邀。
三皆辽、KeyPath 相關(guān)
KVC中可以使用點(diǎn)語法,進(jìn)行深層次的賦值芥挣、取值驱闷。
- (void) setValue: (id)anObject forKeyPath: (NSString*)aKey
{
NSRange r = [aKey rangeOfString: @"." options: NSLiteralSearch];
#ifdef WANT_DEPRECATED_KVC_COMPAT
IMP o = [self methodForSelector: @selector(takeValue:forKeyPath:)];
setupCompat();
if (o != takePath && o != takePathKVO)
{
(*o)(self, @selector(takeValue:forKeyPath:), anObject, aKey);
return;
}
#endif
if (r.length == 0)
{
[self setValue: anObject forKey: aKey];
}
else
{
NSString *key = [aKey substringToIndex: r.location];
NSString *path = [aKey substringFromIndex: NSMaxRange(r)];
[[self valueForKey: key] setValue: anObject forKeyPath: path];
}
}
- (id) valueForKeyPath: (NSString*)aKey
{
NSRange r = [aKey rangeOfString: @"." options: NSLiteralSearch];
if (r.length == 0)
{
return [self valueForKey: aKey];
}
else
{
NSString *key = [aKey substringToIndex: r.location];
NSString *path = [aKey substringFromIndex: NSMaxRange(r)];
return [[self valueForKey: key] valueForKeyPath: path];
}
}
兩個方法類似,都是利用遞歸逐級調(diào)用 valueForKey:
以及 setValue:forKey:
最后
- KVC 就是利用字符串空免,來查詢到和對象相關(guān)聯(lián)的方法空另、實(shí)例變量,來達(dá)到間接訪問屬性方法或成員變量蹋砚。
- 由于KVC在訪問屬性方法或成員變量前加了一層查詢機(jī)制扼菠,所以效率沒有直接訪問屬性方法或成員變量高。
- 由于KVC的使用是通過字符串, 所以極易出錯坝咐,推薦借鑒 RAC中的
@keyPath
宏娇豫, RAC宏分析8:@keypath - 由上面源碼得知,如果key相關(guān)聯(lián)信息找不到畅厢,獲取傳入?yún)?shù)類型不匹配都會導(dǎo)致異常冯痢,所以使用時,最后要重寫相應(yīng)的方法
- 容器類包含了很多便捷的
keyPath
,
@count
@avg
@max
@min
@sum
@distinctUnionOfArrays
@distinctUnionOfObjects
@distinctUnionOfSets
@unionOfArrays
@unionOfObjects
@unionOfSets
以上keyPath
都是通過容器里重寫valueForKeyPath:
實(shí)現(xiàn),而且還可以通過數(shù)組浦楣,一次性返回內(nèi)部對象的某個屬性集合袖肥。
由于這些方法都是通過KVC實(shí)現(xiàn),所以效率比較低振劳,而且由于返回值為對象椎组,所以還進(jìn)行了不必要的封裝