runtime---ivar值真正的內(nèi)存地址

前言

一提起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)手瘟斜,豐衣足食_.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末缸夹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子螺句,更是在濱河造成了極大的恐慌虽惭,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件壹蔓,死亡現(xiàn)場(chǎng)離奇詭異趟妥,居然都是意外死亡猫态,警方通過(guò)查閱死者的電腦和手機(jī)佣蓉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)亲雪,“玉大人勇凭,你說(shuō)我怎么就攤上這事∫逶” “怎么了虾标?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)灌砖。 經(jīng)常有香客問(wèn)我璧函,道長(zhǎng),這世上最難降的妖魔是什么基显? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任蘸吓,我火速辦了婚禮,結(jié)果婚禮上撩幽,老公的妹妹穿的比我還像新娘库继。我一直安慰自己箩艺,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布宪萄。 她就那樣靜靜地躺著艺谆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拜英。 梳的紋絲不亂的頭發(fā)上静汤,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音居凶,去河邊找鬼撒妈。 笑死,一個(gè)胖子當(dāng)著我的面吹牛排监,可吹牛的內(nèi)容都是我干的狰右。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼舆床,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼棋蚌!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起挨队,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤谷暮,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后盛垦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體湿弦,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年腾夯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了颊埃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蝶俱,死狀恐怖班利,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情榨呆,我是刑警寧澤罗标,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站积蜻,受9級(jí)特大地震影響闯割,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜竿拆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一宙拉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧如输,春花似錦鼓黔、人聲如沸央勒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)崔步。三九已至,卻和暖如春缎谷,著一層夾襖步出監(jiān)牢的瞬間井濒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工列林, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瑞你,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓希痴,卻偏偏與公主長(zhǎng)得像者甲,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子砌创,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容