OC對(duì)象的始源 - alloc
。
前言
我們都知道边酒,創(chuàng)建OC對(duì)象的2種方式: [[ClassName alloc]init]
或[ClassName new]
當(dāng)被問起他們的作用時(shí)赂摆,可能你的回答是: alloc + init
給對(duì)象開辟內(nèi)存空間并完成對(duì)象初始化,new
是類方法挟憔,實(shí)現(xiàn)的功能一樣。
這個(gè)描述沒有錯(cuò)烟号,但請(qǐng)?jiān)敿?xì)描述下他們的作用绊谭。
可能你一臉懵逼。 心里已經(jīng)開罵: 你丫有病吧汪拥,我已經(jīng)說完了呀4锎!
今天迫筑,我將幫你扯開alloc宪赶、init、new
的這塊遮羞布铣焊。 讓你深入了解
alloc、init罕伯、new曲伊。
前期準(zhǔn)備
方法一:github直接下載我編譯好的objc4-781編譯包
方法二: 參考上一章 手動(dòng)配置objc4-781編譯包
課前問題
打開objc4-781
包,在HTTest
文件夾中,創(chuàng)建HTPerson
測(cè)試文件(繼承自NSObject
),切換項(xiàng)目target為HTTest
,在main.m
文件中加入測(cè)試代碼追他。
%@
打印對(duì)象%p
打印地址&p
指針地址
問題:
-
p1
坟募、p2
、p3
對(duì)象和地址打印都一致邑狸, 為何&p
打印不一致 -
p4
的地址為什么和p1
懈糯、p2
、p3
都不一樣单雾。
學(xué)完本章赚哗,你就徹底懂了
本節(jié)內(nèi)容:
- alloc流程
- alloc核心函數(shù)
- alloc的地位(init她紫、new)
1. alloc流程
打開源碼工程,跟隨alloc函數(shù)屿储,一步步深入贿讹。流程如下:
當(dāng)出現(xiàn)分支時(shí),我們可以添加斷點(diǎn)
够掠,輔助查看主流程
是進(jìn)入哪個(gè)分支民褂。
不知道打斷點(diǎn),可參考OC底層原理一:定位源碼(歡迎來到底層世界)內(nèi)的三種斷點(diǎn)技巧疯潭。
在callAlloc
處出現(xiàn)了分支赊堪。斷點(diǎn)
后發(fā)現(xiàn)程序走向_objc_rootAllocWithZone
分支,繼而進(jìn)入_class_createInstanceFromZone
函數(shù)竖哩。
關(guān)于
fastpath
和slowpath
的作用哭廉,請(qǐng)移步OC底層原理四: 編譯器優(yōu)化
allocwithZone
: 和alloc
一樣,為對(duì)象分配
足夠的內(nèi)存
期丰, cocoa 會(huì)遍歷
該對(duì)象所有的成員變量
群叶,通過成員變量的類型來計(jì)算
所需占用的內(nèi)存
。從iOS8以后钝荡,Zone
的外層API已被廢棄
街立,僅底層源碼做兼容
處理。
_class_createInstanceFromZone
是最底層的包工頭埠通。?? 終于找到真正干活的人了赎离。它實(shí)現(xiàn)三大核心
方法,然后將成品obj
返回給外層端辱。
2.alloc核心函數(shù)
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
int construct_flags = OBJECT_CONSTRUCT_NONE,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
ASSERT(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size;
// 1. 計(jì)算開辟的內(nèi)存大小
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
// 2. 申請(qǐng)內(nèi)存空間
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
if (!zone && fast) {
// 初始化isa并與objc關(guān)聯(lián)
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (fastpath(!hasCxxCtor)) {
// 返回成品對(duì)象
return obj;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
- hasCxxCtor梁剔、hasCxxDtor、fast等 后續(xù)剖析isa會(huì)詳細(xì)講解
1. 計(jì)算內(nèi)存大小: instanceSize
size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
if (size < 16) size = 16
:做了小于16字節(jié)的判斷舞蔽。
跟斷點(diǎn)荣病,發(fā)現(xiàn)主流程進(jìn)入cache.fastInstanceSize(extraBytes)
:
size_t fastInstanceSize(size_t extra) const
{
ASSERT(hasFastInstanceSize(extra));
if (__builtin_constant_p(extra) && extra == 0) {
return _flags & FAST_CACHE_ALLOC_MASK16;
} else {
size_t size = _flags & FAST_CACHE_ALLOC_MASK;
// remove the FAST_CACHE_ALLOC_DELTA16 that was added
// by setFastInstanceSize
return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
}
}
繼續(xù)跟斷點(diǎn),進(jìn)入align16
:
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
align16
的實(shí)現(xiàn)渗柿,就是使用位運(yùn)算
算法完成16字節(jié)對(duì)齊
个盆。
算法
(x + size_t(15)) & ~size_t(15)
以x=8
為例,計(jì)算過程如下:
8 + size(15) = 23
二進(jìn)制->0000 0000 0001 0111
size_t(15)
二進(jìn)制->0000 0000 0000 1111
- 取反
~size_t(15)
二進(jìn)制->1111 1111 1111 0000
- 求交
&
:
0000 0000 0001 0111
&1111 1111 1111 0000
=0000 0000 0001 0000
- 結(jié)果表示為十進(jìn)制:
16
目的:
-
提高性能,加快存儲(chǔ)速度
通常內(nèi)存是由一個(gè)個(gè)字節(jié)組成朵栖,cpu在存儲(chǔ)數(shù)據(jù)時(shí)颊亮,是以固定字節(jié)塊
為單位進(jìn)行存取的。這是一個(gè)空間換時(shí)間
的優(yōu)化方式陨溅,這樣不用考慮字節(jié)未對(duì)齊的數(shù)據(jù)终惑,極大節(jié)省了計(jì)算資源,提升了存取速度门扇。 -
更安全
在一個(gè)對(duì)象中,isa
占8字節(jié)雹有,對(duì)象屬性
也占8字節(jié)偿渡。蘋果公司現(xiàn)在采用16字節(jié)對(duì)齊
,當(dāng)對(duì)象無屬性
是件舵,會(huì)預(yù)留8字節(jié)
卸察,即16字節(jié)對(duì)齊。 如果不預(yù)留铅祸,CPU存取時(shí)以16字節(jié)為單位長(zhǎng)度去訪問坑质,會(huì)訪問到相鄰對(duì)象,造成訪問混亂临梗。
執(zhí)行完后涡扼,回到上層函數(shù)size = cls->instanceSize(extraBytes)
可打印size值。
此時(shí)已完成內(nèi)存大小
的計(jì)算
盟庞。
2. 分配內(nèi)存 calloc
根據(jù)size
大小進(jìn)行內(nèi)存分配
執(zhí)行前打印
obj
只有cls類名
,執(zhí)行后打印吃沪,已成功申請(qǐng)內(nèi)存首地址
。但并不是我們想象中的格式
<HTPerson: 0x10069eff0>
,這是因?yàn)檫@一步只是單純的完成內(nèi)存申請(qǐng)什猖,返回首地址票彪。類和地址的綁定是下一步
initInstanceIsa
的工作
3. initInstanceIsa
初始化isa,完成與類的綁定
obj->initInstanceIsa(cls, hasCxxDtor);
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
具體的
isa
結(jié)構(gòu)和綁定關(guān)系不狮,后續(xù)會(huì)作為單獨(dú)章節(jié)
進(jìn)行講解
在isa
init之后加斷點(diǎn)降铸,打印obj
,此時(shí)發(fā)現(xiàn)地址與類完成綁定
總結(jié): 至此,我們已對(duì)alloc有了完整的認(rèn)知
3. alloc的地位(init摇零、new)
可能你有疑問推掸,alloc把活都干完了,init和new干啥驻仅?
init
進(jìn)入init
:
+ (id)init {
return (id)self;
}
- (id)init {
return _objc_rootInit(self);
}
進(jìn)入_objc_rootInit
id
_objc_rootInit(id obj)
{
// In practice, it will be hard to rely on this function.
// Many classes do not properly chain -init calls.
return obj;
}
- 我們發(fā)現(xiàn)
init
的類方法和對(duì)象方法返回的都是id
對(duì)象本身谅畅。 - 不同的是類方法返回了一個(gè)
id
類型的self
,這是為了可以給開發(fā)者提供自定義構(gòu)造方法
的入口噪服,通過id強(qiáng)轉(zhuǎn)類型
實(shí)現(xiàn)工廠設(shè)計(jì)
毡泻,返回我們定義的類型。
new
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
實(shí)際上就是完成調(diào)用了callAlloc
粘优,走的時(shí)alloc流程仇味。
唯一區(qū)別:
-
alloc + init
允許對(duì)init進(jìn)行重寫,可自定制init
完成工廠設(shè)計(jì) -
new
是完整封裝敬飒,無法在初始化這一步加入自定制需求
答案
- 問題1: p1邪铲、p2芬位、p3對(duì)象和地址打印都一致无拗, 為何&p打印不一致
alloc
是真正開辟內(nèi)存和綁定對(duì)象的,p1昧碉、p2英染、p3共用1個(gè)alloc揽惹,所以他們都是指向同一目的地址
。但是他們本身也是對(duì)象四康,在init
時(shí)傳入他們自身id
搪搏,&p
打印的是他們自身的地址。
通俗的說:
我有一個(gè)房子出售闪金,A疯溺、B、C三個(gè)都是我員工哎垦,他們都領(lǐng)著客戶來看我這套房子囱嫩。但是他們?nèi)齻€(gè)雖然都是我公司員工,但工號(hào)(id)不一樣漏设。
如果客戶問他們房子在哪(
等同于%@
和%p
打印)墨闲,他們都會(huì)告訴我房子的具體位置
(三人說的一定相同)。
如果顧客問他們是誰(shuí)
(等同于打印&p
),他們就會(huì)各自回答A郑口、B鸳碧、C。
- 問題2. p4的地址為什么和p1犬性、p2瞻离、p3都不一樣。
因?yàn)閜1仔夺、p2琐脏、p3是同一個(gè)alloc
打印的,而p4是new
出來的缸兔,new
會(huì)單獨(dú)調(diào)用alloc
日裙。 所以他們打印肯定不一樣
下一節(jié): OC底層原理五: NSObject的alloc分析