寫在前面
? iOS中內(nèi)存空間創(chuàng)建哼丈,對象的創(chuàng)建會使用到alloc;今天我們來探索一下alloc的底層步驟。
? 源碼
? Cooci司機objc4-756.2調試方案(Xcode11暫時無法斷點進源碼)
一.準備工作
? 下載好源碼,經(jīng)過一輪輪運行Carsh調試之后牺堰,可以通過 common+control+單擊 alloc 看到底層源碼的調用;
? 對于查看調用alloc具體源碼颅围,我們可以使用斷點來分析:
? · 符號斷點
? · 調試欄:step into
? ·顯示匯編代碼:菜單欄Debug->Debug Workflow->Always Show Disassembly
上面三種可以斷到 objc_alloc 方法中
二.實際操作
//
// main.m
// objc-debug
//
// Created by mark on 2020/03/9.
//
?
#import <Foundation/Foundation.h>
?
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSObject *object = [NSObject alloc];
NSLog(@"====== %@",object);
}
return 0;
}
? 不出意外大家都可以來到這邊
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
? 然后下面到了源碼部分伟葫,看到是不是開始抓頭了,按住續(xù)命穴我們繼續(xù)
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
if (slowpath(checkNil && !cls)) return nil;
?
#if __OBJC2__
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
// No alloc/allocWithZone implementation. Go straight to the allocator.
// fixme store hasCustomAWZ in the non-meta class and
// add it to canAllocFast's summary
if (fastpath(cls->canAllocFast())) {
// No ctors, raw isa, etc. Go straight to the metal.
bool dtor = cls->hasCxxDtor();
id obj = (id)calloc(1, cls->bits.fastInstanceSize());
if (slowpath(!obj)) return callBadAllocHandler(cls);
obj->initInstanceIsa(cls, dtor);
return obj;
}
else {
// Has ctor or raw isa or something. Use the slower path.
id obj = class_createInstance(cls, 0);
if (slowpath(!obj)) return callBadAllocHandler(cls);
return obj;
}
}
#endif
?
// No shortcuts available.
if (allocWithZone) return [cls allocWithZone:nil];
return [cls alloc];
}
源碼比較多的修飾符和轉義字符院促,看起來是會比較枯燥筏养,不然頭發(fā)為啥越來越少了呢(頭發(fā)旺盛的略過),下面我們來分析一下
三.alloc 流程圖
1.alloc,objc_alloc 區(qū)分
從函數(shù)棧調用分析常拓,走的是alloc方法渐溶。
xcode10 -> alloc,xcode11 -> objc_alloc 弄抬;(使用MachOView查看兩種編譯下的Mach-O文件茎辐,在_Data段__la_symbol_ptr 節(jié)中,我們可以看出在Xcode11下alloc的符號會被設置為objc_alloc,而xcode10卻沒有)
2.callAlloc方法
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
if (slowpath(checkNil && !cls)) return nil;
?
#if __OBJC2__
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
// No alloc/allocWithZone implementation. Go straight to the allocator.
// fixme store hasCustomAWZ in the non-meta class and
// add it to canAllocFast's summary
if (fastpath(cls->canAllocFast())) {
// No ctors, raw isa, etc. Go straight to the metal.
bool dtor = cls->hasCxxDtor();
id obj = (id)calloc(1, cls->bits.fastInstanceSize());
if (slowpath(!obj)) return callBadAllocHandler(cls);
obj->initInstanceIsa(cls, dtor);
return obj;
}
else {
// Has ctor or raw isa or something. Use the slower path.
id obj = class_createInstance(cls, 0);
if (slowpath(!obj)) return callBadAllocHandler(cls);
return obj;
}
}
#endif
?
// No shortcuts available.
if (allocWithZone) return [cls allocWithZone:nil];
return [cls alloc];
}
1> slowpath(checkNil && !cls)
兩個優(yōu)化比較:slowpath(x)眉睹,fastpath(x); slowpath(x) :x為0荔茬,希望編譯器優(yōu)化;x大概率是有值的竹海,不用每次都讀取。fastpath(x):表示x很可能不為0丐黄,希望編譯器進行優(yōu)化;
2> fastpath(!cls->ISA()->hasCustomAWZ())
hasCustomAWZ()方法表示 hasCustomAllocWithZone斋配,這里表示沒有alloc/allocWithZone的實現(xiàn)
3> fastpath(cls->canAllocFast())
里面調用了bit.canAllocFast 默認返回false
4> id obj = class_createInstance(cls, 0)
內(nèi)部調用 _class_createInstanceFromZone(cls, extraBytes, nil)
3._class_createInstanceFromZone方法
static __attribute__((always_inline))
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
if (!cls) return nil;
?
assert(cls->isRealized());
?
// Read class's info bits all at once for performance
bool hasCxxCtor = cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
?
size_t size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
?
id obj;
if (!zone && fast) {
obj = (id)calloc(1, size);
if (!obj) return nil;
obj->initInstanceIsa(cls, hasCxxDtor);
}
else {
if (zone) {
obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
if (!obj) return nil;
?
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
?
if (cxxConstruct && hasCxxCtor) {
obj = _objc_constructOrFree(obj, cls);
}
?
return obj;
}
1>hasCxxCtor()
addSubclass() propagates this flag from the superclass. 判斷當前class或者superclass是否有.cxx_construct
構造方法的實現(xiàn)
2>hasCxxDtor()
hasCxxDtor()是判斷判斷當前class或者superclass是否有.cxx_destruct
析構方法的實現(xiàn)
3>canAllocNonpointer()
anAllocNonpointer()是具體標記某個類是否支持優(yōu)化的isa
4>cls->instanceSize(extraBytes)
instanceSize 獲取類的大小(傳入額外字節(jié)的大泄喙搿)傳入值為zone= false,fast = true艰争,則(!zone && fast) = true
5>calloc()
用于動態(tài)開辟內(nèi)存,沒有具體實踐代碼桂对。在接下來的文章里面會講到malloc源碼
6>initInstanceIsa()
內(nèi)部調用initIsa(cls, true, hasCxxDtor)
初始化isa
這一步已經(jīng)完成了初始化isa并開辟內(nèi)存空間甩卓,那我們來看看
instanceSize
做了什么
4.字節(jié)對齊 - ( instanceSize探索)
#ifdef __LP64__
# define WORD_SHIFT 3UL
# define WORD_MASK 7UL
# define WORD_BITS 64
#else
# define WORD_SHIFT 2UL
# define WORD_MASK 3UL
# define WORD_BITS 32
#endif
?
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
static inline size_t word_align(size_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
?
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
?
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
我們來過下instanceSize調用順序
instanceSize(extraBytes) -> alignedInstanceSize ->word_align(unalignedInstanceSize())
1>instanceSize(extraBytes)
這個方法是獲取類大小
2>alignedInstanceSize()
獲取類所需要的內(nèi)存空間大小
3>unalignedInstanceSize()
data()->ro->instanceSize
就是獲取這個類所有屬性內(nèi)存的大小。這里只有繼承NSObject
的一個屬性isa
——返回8字節(jié)
4>word_align
字節(jié)對齊蕉斜,在64位系統(tǒng)下逾柿,對象大小采用8字節(jié)對齊法
5>if (size < 16) size = 16
CoreFoundation需要所有對象之和至少是16字節(jié)
5.字節(jié)對齊算法-(實現(xiàn))
假如: x = 9缀棍,已知WORD_MASK = 7
?
x + WORD_MASK = 9 + 7 = 16
WORD_MASK 二進制 :0000 0111 = 7 (4+2+1)
~WORD_MASK : 1111 1000
16二進制為 : 0001 0000
1111 1000
0001 0000
0001 0000 = 16
?
所以 x = 16 也就是 8的倍數(shù)對齊,即 8 字節(jié)對齊
總結:對象大小為16字節(jié)机错,必定是8的倍數(shù)
疑問:為什么要使用8字節(jié)對齊算法呢爬范?
簡單畫了個示意圖,上邊是緊緊挨著弱匪,下面是8字節(jié)為一格青瀑。如果cpu存數(shù)據(jù)的時候緊緊挨著,讀取的時候要不斷變化讀取長度萧诫,所以這時候就采用了空間換時間
的做法
那為什么是8字節(jié)斥难?不是4字節(jié)或是16字節(jié)?
——因為內(nèi)存中8字節(jié)的指針比較多
四.alloc 實際流程圖
instanceSize
計算內(nèi)存大小——量房子
calloc
申請開辟內(nèi)存——造房子
initInstanceIsa
指針關聯(lián)對象——房子寫下名字
五.init & new
init什么也不做帘饶,就是給開發(fā)者使用工廠設計模式提供一個接口
// Replaced by CF (throws an NSException)
+ (id)init {
return (id)self;
}
?
- (id)init {
return _objc_rootInit(self);
}
?
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;
}
new 相當于調用了alloc init
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
衍生:if(self = [super init])
在 返回是instanceType 初始化時蘸炸,常用到這種寫法,為什么這么寫呢尖奔?- 子類繼承于父類屬性搭儒,再判斷是否為空,為空則返回nil提茁。確保是子類調用的方法和父類對應
六 寫在后面
工欲善其事必先利其器淹禾。只有在理解底層源碼的同事,才有創(chuàng)新茴扁。從枯燥的源碼慢慢啃下來铃岔,通過大神文章和gitHub大神注釋理解,拆開一步步研究
實踐出真知峭火!
感謝以下大神的文章:
文章參考:iOS alloc流程