OC做爲一門萬物皆對象的語言,那麼對於對象建立、開闢內存的瞭解必不可少,本文就將探索一下alloc在底層的具體步驟c++
官方源碼算法
Cooci司機objc4-756.2調試方案(Xcode11暫時沒法斷點進源碼)設計模式
在源碼中,咱們能夠經過Command+單擊/右擊->Jump to Defintion
的方式進入alloc
調用方法,脫離了源碼咱們又該如何知道它調用了什麼底層方法呢?sass
在對象建立的代碼處下個斷點,等執行到斷點處使用如下方法:bash
這三種方法都能得出調用了objc_alloc
方法app
//
// main.m
// FXTest
//
// Created by mac on 2019/12/19.
//
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "FXPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *object1 = [NSObject alloc];
FXPerson *object2 = [FXPerson alloc];
}
return 0;
}
複製代碼
不出意料,各位都能來到以下方法函數
+ (id)alloc {
return _objc_rootAlloc(self);
}
複製代碼
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
複製代碼
可是接下來的源碼就會讓你頭暈目眩,不想看了post
// 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];
}
複製代碼
原本看源碼就枯燥,還有這麼多if-else邏輯岔路口,就會有不少人關閉了Xcode學習
看啥很差看源碼,是嫌本身頭髮太旺盛嗎?優化
別急,我這裏已經幫你掉過頭髮了(捋過思路了)
這個坑無傷大雅,瞭解便可;能夠簡單理解爲
OC對象alloc->alloc
不知道你有沒有發現奇怪的一點,第二節探索方向中明明調用的是底層objc_alloc
方法,爲何在建立對象處跟進源碼會來到alloc
方法呢?
函數調用棧也略有問題
這個問題還與Xcode版本有關,Xcode11->objc_alloc,Xcode10->alloc,實在使人百思不得其解
對於這個問題,目前的說法是源碼開源得不夠充分。如下這段代碼雖然未調用到,但其邏輯也是回味無窮
大體猜想是這個方法等於交換單次的Method Swizzling(既然官方不開源,說明無關痛癢)
複製代碼
前面說起過的兩個方法
+ (id)alloc {
return _objc_rootAlloc(self);
}
複製代碼
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
複製代碼
// 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];
}
複製代碼
①if (slowpath(checkNil && !cls))判斷
fastpath(x)
表示x極可能不爲0,但願編譯器進行優化;slowpath(x)
表示x極可能爲0,但願編譯器進行優化——這裏表示cls大機率是有值的,編譯器能夠不用每次都讀取 return nil 指令
②if (fastpath(!cls->ISA()->hasCustomAWZ()))判斷
hasCustomAWZ
實際意義是hasCustomAllocWithZone
——這裏表示有沒有alloc / allocWithZone
的實現(只有不是繼承NSObject/NSProxy
的類才爲true)
③if (fastpath(cls->canAllocFast()))判斷
內部調用了bits.canAllocFast
默認爲false
④id obj = class_createInstance(cls, 0)
內部調用了_class_createInstanceFromZone(cls, extraBytes, nil)
這裏有個id obj
,嘗試着控制檯打印一下
咱們已經找到了咱們想要的結果,接下來咱們探索下_class_createInstanceFromZone方法是怎麼將obj建立出來的
/*********************************************************************** * class_createInstance * fixme * Locking: none **********************************************************************/
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;
}
複製代碼
①hasCxxCtor()
hasCxxCtor()是判斷當前class或者superclass是否有.cxx_construct
構造方法的實現
②hasCxxDtor()
hasCxxDtor()是判斷判斷當前class或者superclass是否有.cxx_destruct
析構方法的實現
③canAllocNonpointer()
anAllocNonpointer()是具體標記某個類是否支持優化的isa
④instanceSize()
instanceSize()獲取類的大小(傳入額外字節的大小)
已知zone=false,fast=true,則(!zone && fast)=true
⑤calloc()
用來動態開闢內存,沒有具體實現代碼,接下來的文章會講到malloc源碼
⑥initInstanceIsa()
內部調用initIsa(cls, true, hasCxxDtor)
初始化isa
這一步已經完成了初始化isa並開闢內存空間,那咱們來看看
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;
}
// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() {
assert(isRealized());
return data()->ro->instanceSize;
}
// 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;
}
複製代碼
下面按調用順序講解
①size_t instanceSize(size_t extraBytes)
前面講過——獲取類的大小
②alignedInstanceSize()
獲取類所須要的內存大小
③unalignedInstanceSize()
data()->ro->instanceSize
就是獲取這個類全部屬性內存的大小。這裏只有繼承NSObject
的一個屬性isa
——返回8字節
④word_align
顧名思義,字節對齊——64位系統下,對象大小採用8字節對齊
⑤if (size < 16) size = 16
CoreFoundation須要全部對象之和至少是16字節
假如: 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的倍數對齊,即 8 字節對齊
複製代碼
這裏有個疑問:爲何要使用8字節對齊算法呢?
簡單畫了個示意圖,上邊是牢牢挨着,下面是8字節爲一格。若是cpu存數據的時候牢牢挨着,讀取的時候要不斷變化讀取長度,因此這時間就採用了空間換時間
的作法
那爲何是8字節?不是4字節或是16字節?
——由於內存中8字節的指針比較多
instanceSize
計算內存大小——量房子
calloc
申請開闢內存——造房子
initInstanceIsa
指針關聯對象——房子寫下名字
init什麼也不作,就是給開發者使用工廠設計模式提供一個接口
// 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;
}
複製代碼
補充:關於子類中if (self == [super init])
爲何要這麼寫——子類先繼承父類的屬性,再判斷是否爲空,如若爲空不必進行一系列操做了直接返回nil
new等於先調用alloc,再init
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
複製代碼
研究源碼必然是枯燥的,可是面對源碼不用懼怕,一步步把它拆分開來研究,多利用官方給的註釋/Github大神的註釋,慢慢也就啃下來了。
看別人學習津津有味,不如本身上手實際玩一下會更有收穫;只看不練永遠學不會,或許你學的正是別人錯誤的理論呢