前言
相關文章:
iOS底層探索九(方法的本質下objc_msgSend慢速及方法轉發初探)
開發中我們經常會用到XZPerson *p= [[XZPerson alloc]init],我們只知道,這樣我們就新建了一個對象,可以直接使用這個對象,可以對這個對象進行賦值使用,但是我們沒有關心過alloc方法底層到底是通過什麼方式進行實現的;今天我準備對alloc方法進行一次嘗試性底層挖掘;首先我們需要先準備一份可編譯的objc4_752代碼,可以直接進行下載探索;
首先,先上一張我探索完成的alloc流程圖,供大家進行參考
然後我們直接使用能運行 objc-752
源碼後,在 main.m
中寫入如下代碼就開始了 alloc
的探索。
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
XZPerson *object = [XZPerson alloc];
NSLog(@"Hello, World! %@",object);
}
return 0;
}
打斷點直接這裏 alloc
點擊進入的爲 _objc_rootAlloc
其實我們斷點後發現進入的是 objc_alloc
。
這裏我們稍作解釋:我們可以看到源碼中有這麼一個方法fixupMessageRef,這裏應該是對進行符號綁定將alloc綁定爲objc_alloc方法,具體是通過LLVM期進行符號綁定,我們這篇文章先不進行詳細介紹;
fixupMessageRef(message_ref_t *msg)
{
msg->sel = sel_registerName((const char *)msg->sel);
if (msg->imp == &objc_msgSend_fixup) {
if (msg->sel == SEL_alloc) {
msg->imp = (IMP)&objc_alloc;
} else if (msg->sel == SEL_allocWithZone) {
msg->imp = (IMP)&objc_allocWithZone;
} else if (msg->sel == SEL_retain) {
msg->imp = (IMP)&objc_retain;
} else if (msg->sel == SEL_release) {
msg->imp = (IMP)&objc_release;
} else if (msg->sel == SEL_autorelease) {
msg->imp = (IMP)&objc_autorelease;
} else {
msg->imp = &objc_msgSend_fixedup;
}
}
else if (msg->imp == &objc_msgSendSuper2_fixup) {
msg->imp = &objc_msgSendSuper2_fixedup;
}
else if (msg->imp == &objc_msgSend_stret_fixup) {
msg->imp = &objc_msgSend_stret_fixedup;
}
else if (msg->imp == &objc_msgSendSuper2_stret_fixup) {
msg->imp = &objc_msgSendSuper2_stret_fixedup;
}
#if defined(__i386__) || defined(__x86_64__)
else if (msg->imp == &objc_msgSend_fpret_fixup) {
msg->imp = &objc_msgSend_fpret_fixedup;
}
#endif
#if defined(__x86_64__)
else if (msg->imp == &objc_msgSend_fp2ret_fixup) {
msg->imp = &objc_msgSend_fp2ret_fixedup;
}
#endif
}
1.首先我們可以看到進入的是 alloc 內部調用_objc_rootAlloc方法績效調用callAlloc方法
+ (id)alloc {
return _objc_rootAlloc(self);
}
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
這裏我們可以看到進入_objcrootAlloc方法裏面有參數爲Class的一個參數
Class 類底層其實是objc_class類結構體
typedef struct objc_class *Class;
objc_class結構體中
struct objc_class : objc_object {
// Class ISA; //isa指針
Class superclass; //父類
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
//...下面還有很多方法就不在這裏貼了,可以自行下代碼進行查看
objc_class
又繼承於 objc_object
,objc_object
存儲了 isa
的一個 Class
結構體
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
這裏如果有使用過RunTime的同學就可以看到一些熟悉的東西 ivars, methodLists ,protocols,這個後期文章我們再進行贅述,此篇文章先進行稍微引入
2.當進入callAlloc方法中
這裏我都有備註判斷的原因,如有不對還請看到的各位,評論留言我們可以一起探討,這裏如果我們自己打斷點的話,可以看到大部分會直接走到class_createInstance方法中;
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
/**
fastpath(x)表示x很可能不爲0,希望編譯器進行優化;
slowpath(x)表示x很可能爲0,希望編譯器進行優化——這裏表示cls大概率是有值的,編譯器可以不用每次都讀取 return nil 指令
*/
if (slowpath(checkNil && !cls)) return nil;
#if __OBJC2__
/**
實際意義是hasCustomAllocWithZone——這裏表示有沒有alloc / allocWithZone的實現(只有不是繼承NSObject/NSProxy的類才爲true)
*/
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
/**
內部調用了bits.canAllocFast後分爲2種情況
if(FAST_ALLOC){
}else{
默認爲false,
}
當我們查看FAST_ALLOC宏時,發現它的上方有個else 1 之後纔是else中定義這個宏,
也就是說這個宏一直是不存在的
可以自行點擊進入查看
*/
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];
}
3.class_createInstance方法中,跟蹤class_createInstance(cls, 0)方法,其內部調用了_class_createInstanceFromZone方法,並在其中進行size計算,內存申請,以及isa初始化
class_createInstance(Class cls, size_t extraBytes)
{
return _class_createInstanceFromZone(cls, extraBytes, nil);
}
_class_createInstanceFromZone這個方法是核心方法,進行了開闢內存和關聯對象
_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) {
//這裏是真正的創建類的方法但是這個calloc源碼是在malloc中
obj = (id)calloc(1, size);
if (!obj) return nil;
//將內存指向給objc進行關聯
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;
}
其中cls->instanceSize(extraBytes)此方法爲計算應該開闢多大內存方法,計算出size,
其中64位系統下,對象大小採用8字節對齊,但是實際申請的內存最低爲16字節,也就是說系統分配內存按照16字節對齊分配
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;//最小返回16字節
return size;
}
uint32_t alignedInstanceSize() {
//字節對齊
return word_align(unalignedInstanceSize());
}
# define WORD_MASK 7UL //這個宏標示數字7
static inline uint32_t word_align(uint32_t x) {
//WORD_MASK 7
//7+8 = 15
//0000 0111 2進製表示7
//0000 1111 2進製表示15
//1111 1000 2進製表示~7
//0000 1000 15 & ~7 值 16
//這裏主要就是做8進制對齊,這裏相當於8倍數
//x + 7
//x=1 8
//x=2 16
return (x + WORD_MASK) & ~WORD_MASK;
}
字節對齊核心算法:
例如傳值進來爲7 已知 WORD_MASK爲7
//WORD_MASK 7
7+8 = 15
0000 0111 2進製表示7
0000 1111 2進製表示15
1111 1000 2進製表示~7
---------------------------------
/0000 1000 (15 & ~7 ) 值 16
這裏主要就是做8進制對齊,這裏相當於8倍數,當然這裏的WORD_MASK如果是15那麼這個就是16字節對齊
根據不同的條件,使用calloc或者malloc_zone_calloc進行內存申請,並且初始化isa指針,至此size大小的對象obj已經申請完成,並且返回;
4.這就OC中alloc底層的調用方式,我們都看了alloc的源碼了,順便看下init方法吧;
- (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;
}
我們可以看出來,在init方法中蘋果開發人員沒有做任何東西,只是將對象直接返回,說明這只是蘋果開發人員爲我們提供的一個工廠方法,讓我們方便進行重寫;
5.順便把New方法也看下
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
仔細看過alloc源碼的話,這裏就很清晰了,callAlloc不就是調用了下alloc方法的底層麼,順便也調用了下init,這就是說,new方法底層也是調用alloc的,
總結
這是個人的實際追蹤源碼得出的一些結論,源碼中還有一些分支代碼可以自行查看initInstanceIsa下篇文章在進行分析,如果有錯誤的地方還請指正,大家一起討論,開發水平一般,還望大家體諒,歡迎大家點贊,關注我的CSDN,我會定期做一些技術分享!
寫給自己:
研究源碼枯燥的,但是面對源碼不用害怕,一步步把它拆分開來研究,多看看官方給的註釋/Github大神的註釋,慢慢來,總有一自己也會厲害的。