前言:想必大家對于[xxx alloc] init]
非常熟悉了坊萝,都知道是創(chuàng)建一個(gè)xxx的對象,但是OC底層到底做了什么许起?
首先看下方代碼:
HRTest * t = [HRTest alloc];
HRTest * tt = [t init];
HRTest * ttt = [t init];
HRTest * tttt = [HRTest alloc];
NSLog(@"%@---%p---%p",t,t,&t);
NSLog(@"%@---%p---%p",tt,tt,&tt);
NSLog(@"%@---%p---%p",ttt,ttt,&ttt);
NSLog(@"%@---%p---%p",tttt,tttt,&tttt);
-
輸出結(jié)果:
常識:
- %p/t :是指向?qū)ο蟮闹羔? %p/&t :是指向?qū)ο笾羔樀闹羔?/li>
- 棧區(qū)存放原則:從
高位到低位
屹堰; 堆區(qū)存放原則:從低位到高位
- 內(nèi)存地址是連續(xù)的
- 根據(jù)觀察得出若干結(jié)論:
- 經(jīng)過
alloc
之后獲得了不同的內(nèi)存空間
,經(jīng)過init
之后內(nèi)存空間
相同街氢。
推斷:內(nèi)存空間是由alloc負(fù)責(zé)申請扯键,從這個(gè)角度看init并沒處理任何動(dòng)作 - 對象是存放在
堆區(qū)
; 對象的指針存放在棧區(qū)
- 經(jīng)過
對象的存儲(chǔ)位置
用一張圖來解釋:
alloc
alloc
想要一探alloc
是如何申請了內(nèi)存空間的,就需要使用上篇中提到的objc源碼了珊肃。廢話不多說荣刑,打開源碼,加上斷點(diǎn)伦乔,一步步開始調(diào)試:
此處有兩種可能厉亏,簡述流程省略代碼:
- 創(chuàng)建
NSObjec
直接進(jìn)入alloc
流程 - 創(chuàng)建繼承自NSObject的自定義類
先進(jìn)入_objc_alloc
->callAlloc
->alloc
,為什么會(huì)進(jìn)入_objc_alloc而不是調(diào)用的alloc這就要涉及到llvm
中的知識,后續(xù)有機(jī)會(huì)再來解釋烈和,可以簡單理解為llvm做了一次類似于hook
的操作爱只,將alloc
轉(zhuǎn)為_objc_alloc
- 最終到達(dá)了
_class_createInstanceFromZone
,這個(gè)方法是正在來進(jìn)行內(nèi)存申請操作的地方
alloc流程圖
_class_createInstanceFromZone
_class_createInstanceFromZone流程圖
init
//此處只放出最核心代碼
_class_createInstanceFromZone(...){
...
size_t size;
size = cls->instanceSize(extraBytes);
}
size_t instanceSize(size_t extraBytes) const {
//編譯快速計(jì)算所占內(nèi)存大小
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
if (size < 16) size = 16;
return size;
}
size_t fastInstanceSize(size_t extra) const
{...
//計(jì)算實(shí)際內(nèi)存占用
size_t size = _flags & FAST_CACHE_ALLOC_MASK;
return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
}
static inline size_t align16(size_t x) {
//著名的字節(jié)對齊算法
return (x + size_t(15)) & ~size_t(15);
}
calloc
//此處只放出最核心代碼
_class_createInstanceFromZone(...){
...
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
// 申請1塊size大小的內(nèi)存空間
obj = (id)calloc(1, size);
}
}
-
zone
方式:在iOS8以后就基本不使用了 - 注意
calloc
返回的是一個(gè)id
招刹,表示當(dāng)前并無類型
initInstanceIsa
//此處只放出最核心代碼
_class_createInstanceFromZone(...){
...
id obj;
if (!zone && fast) {
//一般會(huì)進(jìn)入此判斷
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
obj->initIsa(cls);
}
}
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
...
//進(jìn)行類型賦值
isa = isa_t((uintptr_t)cls);
}
看完全部流程是不是感覺到流程索然繁雜恬试,但是本質(zhì)并不復(fù)雜,[狗頭]
核心步驟:計(jì)算內(nèi)存大小 - 申請內(nèi)存 - 進(jìn)行類的關(guān)聯(lián)
fastpath疯暑、slowpath
在源碼中反復(fù)出現(xiàn)的這兩個(gè)宏定義训柴,我覺得有必要簡單解釋一下:
//x很可能為真, fastpath 可以簡稱為 真值判斷
#define fastpath(x) (__builtin_expect(bool(x), 1))
//x很可能為假妇拯,slowpath 可以簡稱為 假值判斷
#define slowpath(x) (__builtin_expect(bool(x), 0))
- 來源:__builtin_expect命令是由gcc引入的
- 目的:提醒編譯器可以對此處代碼進(jìn)行編譯優(yōu)化幻馁,以減少指令跳轉(zhuǎn)帶來的性能消耗
- 作用:在編譯過程中就允許程序員將最有可能執(zhí)行到的代碼分支告訴編譯器
1,自定義類第一次callAlloc時(shí)沒有找到默認(rèn)的allocWithZone,經(jīng)過objc_msgsend(alloc)之后越锈,第二次callAlloc時(shí)找到了默認(rèn)的allocWithZone仗嗦。allocWithZone是什么時(shí)候創(chuàng)建加載的呢?
init做了什么
- (id)init {
return _objc_rootInit(self);
}
id _objc_rootInit(id obj)
{
return obj;
}
- 事實(shí)上init方法并沒有做任何事情甘凭,也應(yīng)證之前的猜想:
內(nèi)存空間是由alloc負(fù)責(zé)申請稀拐,從這個(gè)角度看init并沒處理任何動(dòng)作 - apple蘋果這樣設(shè)計(jì)的目的:類似工廠方法,為后續(xù)
自定義做一些處理提供一個(gè)入口
new做了什么
一般在開發(fā)中对蒲,初始化除了init钩蚊,還會(huì)使用new
贡翘,通過源碼來看兩者本質(zhì)上并沒有什么區(qū)別
+ (id)new {
retur [callAlloc(self, false/*checkNil*/) init];
}
但是在一般的開發(fā)中,如果使用自定的類砰逻,這里并不建議使用new
鸣驱,因?yàn)檫@里系統(tǒng)只會(huì)調(diào)用init方法,對于自定義的initWhitXXX
并不會(huì)調(diào)用蝠咆。但是系統(tǒng)自己類大可放心使用.
-
initWhitCustom
并沒有調(diào)用