alloc
Object-C 是一門面向?qū)ο笳Z言,對象的設計在iOS編程中用到很多,比如ObjectA * objectA = [[ObjectA alloc]init]
,這就是一個類or對象的基本創(chuàng)建方式。那不禁要問這個alloc
在不為人知的背后做了什么呢?今天就來探索一下它
首先,先觀察這樣一段代碼,新建了一個書的類,然后創(chuàng)建它,打印它、以及它的地址
Book * book1 = [Book alloc];
Book * book2 = [book1 init];
Book * book3 = [book1 init];
NSLog(@"%@ - %p - %p",book1,book1,&book1);
NSLog(@"%@ - %p - %p",book2,book2,&book2);
NSLog(@"%@ - %p - %p",book3,book3,&book3);
下面是打印結(jié)果
<Book: 0x600003144690> - 0x600003144690 - 0x7ffeeb8db1b8
<Book: 0x600003144690> - 0x600003144690 - 0x7ffeeb8db1b0
<Book: 0x600003144690> - 0x600003144690 - 0x7ffeeb8db1a8
可以看到book1,book2,book3
它們打印出來的對象是一樣的,并且地址也是相同的,當取地址符時,打印出三個不一樣的地址。就代碼可以窺見一二,前兩個打印出相同結(jié)果,代表它們都指向了相同的內(nèi)存空間,最后一個取地址符時,相當于一個指針變量,存儲在棧區(qū),指向了開辟空間的對象的指針地址,可以理解為指針的指針,所以三個“變量“打印結(jié)果不一樣。由此我們可以猜測alloc
是申請了內(nèi)存空間(實際是如此,我們在學習OC的時候,資料中已經(jīng)說明,但卻沒有講解究竟在底層做了什么),而init
則是將內(nèi)存空間的地址返回給指向它的對象,并未對內(nèi)存空間做出修改。下面是簡單示意圖:
其中,一個對象占8個字節(jié),系統(tǒng)會為指向內(nèi)存空間的對象分配相近的指針地址,且其地址為連續(xù)棧地址,按十六進制計算 ,
a8 + 8 = b0,b0 + 8 = b8
alloc開辟內(nèi)存空間?
由上面的試驗我們初步猜測alloc是在做著申請開辟內(nèi)存空間的事,那我們就要去代碼底層一探究竟,當我們想要查看alloc定義的時候,蘋果卻沒有讓我們繼續(xù)的入口,它只是簡單給出了這樣一個定義+ (instancetype)alloc OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
我們可以通過這樣的兩個方式
- 1.查看蘋果關于objc的開源,下載一個相應的版本即可研究它;
- 2.在對象創(chuàng)建時,給
alloc
這一行下一個端點,按住鍵盤control
+斷點調(diào)試欄的Step into
按鈕,則會進入一個新的界面。
更深一步查看alloc
會出現(xiàn)類似以下的信息
xxproject`objc_alloc:
-> 0x10e747476 <+0>: jmpq *0x2b9c(%rip) ; (void *)0x000000010e7474cc
objc_alloc
就是我們需要繼續(xù)探索的地方。
為了方便分析,還是下載蘋果對應的源代碼 781,并對其進行工程編譯,參考這里,在工程代碼中搜索objc_alloc
,找到NSObject下,會出現(xiàn)
// Calls [cls alloc].
id
objc_alloc(Class cls)
{
return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
進入callAlloc
,就會出現(xiàn)下面的代碼
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
#if __OBJC2__
if (slowpath(checkNil && !cls)) return nil;
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
return _objc_rootAllocWithZone(cls, nil);
}
#endif
// No shortcuts available.
if (allocWithZone) {
return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
}
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}
_objc_rootAllocWithZone
繼續(xù)走會出現(xiàn)
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
// allocWithZone under __OBJC2__ ignores the zone parameter
return _class_createInstanceFromZone(cls, 0, nil,
OBJECT_CONSTRUCT_CALL_BADALLOC);
}
繼續(xù)往下面走就來到了_class_createInstanceFromZone
的實現(xiàn)
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;
// 計算開辟內(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 {
// alloc 開辟內(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) {
// 關聯(lián)cls類和obj指針地址
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)) {
return obj;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
由此得到了alloc的基本流程
關于fastpath 和 slowpath:
//x很可能為真, fastpath 可理解為 真值判斷
#define fastpath(x) (__builtin_expect(bool(x), 1))
//x很可能為假,slowpath 可理解為 假值判斷
#define slowpath(x) (__builtin_expect(bool(x), 0))
總結(jié)下來,alloc的步驟有
1.開辟多少內(nèi)存
2.開辟內(nèi)存,返回指針
3.關聯(lián)相應類
init做了什么?
在源碼工程中繼續(xù)查找init
,發(fā)現(xiàn)這樣的源碼
+ (id)alloc {
return _objc_rootAlloc(self);
}
// Replaced by ObjectAlloc
+ (id)allocWithZone:(struct _NSZone *)zone {
return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}
// Replaced by CF (throws an NSException)
+ (id)init {
return (id)self;
}
- (id)init {
return _objc_rootInit(self);
}
通過源碼發(fā)現(xiàn)這里的+init
方法是工廠設計模式下,以供開發(fā)者重寫的構(gòu)造方法。-init
實例方法依然是做了返回自身的操作,進入 _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;
}
它返回的就是obj
本身。
new又做了什么?
在平常的開發(fā)過程中,我們會不自覺的使用new
去新建對象,貌似這樣沒有什么問題,但是到底是不是都沒問題呢?進入new源碼中,new做了一個alloc + init
的操作,它一步到位的實現(xiàn)了對象的創(chuàng)建。但該init
是NSObject的init,對于未重寫init
的類時,使用new
是ok的,但如果子類重寫了init
,譬如:(id)initWithName:(NSString *)name
, 就不能再使用new
方法創(chuàng)建對象,否則它直接走父類的init
,重寫的init
的邏輯不會執(zhí)行。
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}