前言
一提起runtime默刚,大家都會(huì)想起class , isa, metaclass, methodList, ivarList, propertyList等等帜消,網(wǎng)上這方面的介紹數(shù)不勝數(shù)溶握。并且大家都知道class里的ivarList存放著所有ivar的name卑惜、type等信息务豺。但是ivar的value放在哪呢棒旗?
我們經(jīng)常用class_copyIvarList
方法打印類的所有成員變量名稱痊乾,如下:
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList([Foo class], &count);
for (int i = 0; i < count; i++) {
Ivar ivar = ivarList[i];
const char *name = ivar_getName(ivar);
NSString *tName = @(name);
NSLog(@"ivar:%@", tName);
}
這些代碼給了我們一些錯(cuò)覺(jué)皮壁,所有ivar的名字和值都存在class里,都存在object的isa里哪审!下面我們用代碼驗(yàn)證一下蛾魄。
ivar值存在Class類里?
先定義一個(gè)Foo類:
@interface Foo : NSObject
@property(nonatomic, strong) NSString *name;
@end
然后打印Foo類的兩個(gè)實(shí)例的isa值:
Foo *foo1 = [[Foo alloc] init];
foo1.name = @"cat";
Foo *foo2 = [[Foo alloc] init];
foo2.name = @"dog";
NSLog(@"foo1->isa--%p", [foo1 class]);
NSLog(@"foo2->isa--%p", [foo2 class]);
日志:
foo1->isa--0x10ed00df0
foo2->isa--0x10ed00df0
object的定義
typedef struct objc_object {
Class isa;
} *id;
isa地址居然是相同的!可object的結(jié)構(gòu)體就一個(gè)isa變量滴须,如果isa都一樣舌狗,那兩個(gè)實(shí)例foo1和foo2怎么區(qū)分呢?foo1.name的cat和foo2.name的dog又存儲(chǔ)在哪了呢扔水?
唯一一種解釋Class里有一個(gè)map, 用來(lái)存儲(chǔ)每個(gè)對(duì)象的所有ivar值痛侍?
key | value |
---|---|
foo1 | {"name":"cat",...} |
foo2 | {"name":"dog",...} |
... | ... |
fooN | {"name":"nnn",...} |
但是仔細(xì)一想,不大可能魔市,感覺(jué)這種方式很蠢主届,需要把所有對(duì)象的ivar值都存到Class里,而且class的結(jié)構(gòu)體里并沒(méi)有類似的map待德。這個(gè)假設(shè)真?zhèn)涡陨院笤倩卮鹁。日勔幌耰sa混淆,它讓這個(gè)假設(shè)瞬間打臉磅网!
isa混淆---證明ivar值不在Class里
isa混淆就是將object的isa替換成它的子類谈截,在子類里添加我們自定義的方法來(lái)實(shí)現(xiàn)某些特定的功能筷屡。isa混淆與方法混淆最大的不同是涧偷,isa混淆只會(huì)影響一個(gè)對(duì)象,而不是所有的對(duì)象毙死。最常見的例子就是KVO
, 被監(jiān)聽的對(duì)象的isa全換成了帶有NSKVONotifying_
前綴的子類燎潮,然后替換了被監(jiān)聽屬性的setter
方法的IMP
。
isa混淆后對(duì)象仍有原來(lái)的所有功能扼倘,包括ivar值也沒(méi)有丟失确封。此時(shí)有沒(méi)有疑問(wèn)?為什么isa換成新的類了再菊,為什么之前存儲(chǔ)的ivar的值都可以繼續(xù)訪問(wèn)爪喘?難道它把ivar值都copy到新的類里了?好吧纠拔,我不想再騙自己了秉剑!事實(shí)證明ivar值并沒(méi)有在Class里。
object_getIvar找到了答案
id object_getIvar(id obj, Ivar ivar)
{
if (!obj || !ivar || obj->isTaggedPointer()) return nil;
ptrdiff_t offset;
objc_ivar_memory_management_t memoryManagement;
_class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement);
id *location = (id *)((char *)obj + offset);
if (memoryManagement == objc_ivar_memoryWeak) {
return objc_loadWeak(location);
} else {
return *location;
}
}
_class_lookUpIvar(Class cls, Ivar ivar, ptrdiff_t& ivarOffset,
objc_ivar_memory_management_t& memoryManagement)
{
ivarOffset = ivar_getOffset(ivar);
...
...
}
先通過(guò)ivar_getOffset取得ivar的offset,然后直接用obj+offset獲取ivar值稠诲,就這么簡(jiǎn)單侦鹏。
看到這時(shí),我想起了FLEX查找對(duì)象是被誰(shuí)引用的代碼臀叙,跟這個(gè)非常相似:
+ (instancetype)instancesTableViewControllerForInstancesReferencingObject:(id)object
{
NSMutableArray *instances = [NSMutableArray array];
NSMutableArray *fieldNames = [NSMutableArray array];
[FLEXHeapEnumerator enumerateLiveObjectsUsingBlock:^(__unsafe_unretained id tryObject, __unsafe_unretained Class actualClass) {
Class tryClass = actualClass;
while (tryClass) {
unsigned int ivarCount = 0;
Ivar *ivars = class_copyIvarList(tryClass, &ivarCount);
for (unsigned int ivarIndex = 0; ivarIndex < ivarCount; ivarIndex++) {
Ivar ivar = ivars[ivarIndex];
const char *typeEncoding = ivar_getTypeEncoding(ivar);
if (typeEncoding[0] == @encode(id)[0] || typeEncoding[0] == @encode(Class)[0]) {
ptrdiff_t offset = ivar_getOffset(ivar);//獲得ivar的偏移量
uintptr_t *fieldPointer = (__bridge void *)tryObject + offset;//通過(guò)偏移獲得對(duì)象
if (*fieldPointer == (uintptr_t)(__bridge void *)object) {
[instances addObject:tryObject];
[fieldNames addObject:@(ivar_getName(ivar))];
return;
}
}
}
tryClass = class_getSuperclass(tryClass);
}
}];
...
...
}
然后在mac os上調(diào)試得知offset是8字節(jié), 我們?cè)倏匆幌耰sa指針的大小
(lldb) p sizeof([Foo class])
(unsigned long) $0 = 8
isa也占8個(gè)字節(jié)略水,ivar恰好在isa后面,所以ivar值雖然沒(méi)在object的結(jié)構(gòu)體里劝萤,而是一直它的屁股后面渊涝。不同的object,雖然Isa是相同的,但是object的地址是不同的驶赏!所以Ivar值的地址也是不同的炸卑,這樣就可以解釋所有的問(wèn)題了。
所以真正的object結(jié)構(gòu)是這樣的:
struct objc_object struct Foo_instance
{ {
Class isa; Class isa;
id ivar1;
id ivar2;
…
} }
父類的Ivar值在哪煤傍?
經(jīng)過(guò)ivar_getoffset()方法測(cè)試盖文,在isa后面的先是父類的Ivar,再是子類的ivar
struct Foo_instance {
Class isa;
id superIvar1;
id superIvar2;
id ivar1;
id ivar2;
}
ivar賦值蚯姆,_name="xxx"五续,編譯器將它翻譯成了什么?
我本來(lái)以為系統(tǒng)會(huì)調(diào)用下面的方法實(shí)現(xiàn):
Ivar ivar = class_getInstanceVariable([Foo class], "_name");
object_setIvar(foo, ivar, @"xxx")
然而我在class_getInstanceVariable
方法這加了斷點(diǎn)龄恋,系統(tǒng)并沒(méi)有調(diào)用疙驾。那系統(tǒng)是怎么處理的呢?為了查原因郭毕,用clang命令查看c++代碼它碎,如下:
轉(zhuǎn)化前setter方法:
- (void)setName:(NSString *)name {
_name = @"haha";
}
轉(zhuǎn)化后c++代碼:
static void _I_Foo_setName_(Foo * self, SEL _cmd, NSString *name) {
(*(NSString **)((char *)self + OBJC_IVAR_$_Foo$_name)) = (NSString *)&__NSConstantStringImpl__var_folders_0p_tz5_9qns3vl5bztbvwv37m8c0000gn_T_Foo_39d932_mi_1;
}
static NSString * _I_Foo_name(Foo * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Foo$_name)); }
系統(tǒng)也是直接使用ivar_getoffset來(lái)set和get變量。
還發(fā)現(xiàn)一個(gè)意外收獲显押,代碼如下:
struct Foo_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
NSString *_password;
id _vc;
};
而那個(gè)NSObject_IMPL就是isa:
struct NSObject_IMPL {
Class isa;
};
這不就是真正的object的結(jié)構(gòu)體嗎扳肛。。自己繞了半天乘碑,原來(lái)c++源碼里都寫好了挖息!……
還是那句話---源碼下面無(wú)秘密
為什么category不能添加ivar,只能添加method呢兽肤?
通過(guò)上面的分析套腹,這個(gè)問(wèn)題的答案已經(jīng)很明顯了。但是這個(gè)問(wèn)題曾經(jīng)困擾了我久资铡,之前面試的時(shí)候也被問(wèn)題到過(guò)电禀,我回答:類的結(jié)構(gòu)空間已經(jīng)分配好了,不能改了笤休。但是再往下講就不會(huì)說(shuō)了尖飞,不知道了。宛官。很是尷尬『桑現(xiàn)在這個(gè)問(wèn)題不就迎刃而解了么?因?yàn)镕oo_IMPL結(jié)構(gòu)體已經(jīng)定義好了底洗,要想加ivar就得往結(jié)構(gòu)體里添加變量腋么,而這是不可能的,結(jié)構(gòu)體無(wú)法改變亥揖!而為什么添加mehtod就可以呢珊擂?因?yàn)閙ehtod是存在一個(gè)method_list數(shù)組里怎么加怎么加圣勒。。
結(jié)語(yǔ)
結(jié)論很簡(jiǎn)單摧扇,ivar放在object的后面圣贸。我主要是闡述了我找這個(gè)答案的過(guò)程和思路。其實(shí)我找答案花了非常多的時(shí)間扛稽。網(wǎng)上runtime的文章我都不知道看了幾百篇吁峻,都看爛了,最終也沒(méi)得到想要的東西在张。后來(lái)一想用含,我為什么總穿二手鞋?為什么總看別人總結(jié)的東西帮匾?我完全可以看源碼來(lái)了解其中的奧秘啄骇。我不要魚,而要漁~~~
自己動(dòng)手瘟斜,豐衣足食_.