iOS底層原理探索 一 alloc&init探索

iOS底層原理探索篇 主要是圍繞底層進行源碼分析-LLDB調試-源碼斷點-彙編調試,讓本身之後回顧複習的😀😀設計模式

目錄以下:bash

iOS底層原理探索 一 開篇app

iOS底層原理探索 一 alloc&init探索函數

iOS底層原理探索 一 內存對齊&malloc源碼分析源碼分析

iOS底層原理探索 一 isa原理與對象的本質post

準備條件(以我當前的配置爲主)

  • macOS 10.15.2
  • Xcode 11.3
  • objc4-756.2 (若是你想本身配置可編譯objc-756.2的源碼,能夠看看Cooci帥哥的這篇文章)

alloc探索

咱們先來看下面這段代碼:ui

TCJPerson *p1 = [TCJPerson alloc];
    TCJPerson *p2 = [p1 init];
    TCJPerson *p3 = [p1 init];
複製代碼

看完代碼,有一個疑問就是:p一、p二、p3有什麼聯繫呢? 爲了弄清此問題,那就看運行結果唄: spa

經過運行結果,能夠知道p一、p二、p3 他們所指的內存空間是同樣的.也就是有三個不一樣指針地址指向同一個內存空間. 那爲何會是這樣的呢?上面的 allocinit到底作了什麼呢? 咱們先來看看 alloc的實現:下面介紹三種方式來查看他的實現.(查看過程需用真機查看,由於模擬器查找的是x86_64環境和arm64是不同的)

三種方式查看alloc的實現

第一種方式:直接下斷點.

來到研究對象斷點後(26行斷點處),接下來按住control鍵和箭頭所指的鍵會看到以下圖所示:
繼續以前的操做(按住control鍵和箭頭所指的鍵)以後會跳轉到以下圖所示:
到此咱們能夠看到 objc_alloclibobjc.A.dylib這個動態庫裏面.

第二種方式:下斷符號斷點.

第一步點左下角的**+號按鈕以後,在點擊Symbolic Breakpoint**設計

第二部添加alloc符號斷點:3d

以後過掉斷點以後會顯示以下:

第三種方式:經過彙編查看.

如何操做以下圖箭頭所示:

以後會顯示下圖所示:

這時咱們能夠看到在22行有objc_alloc,那在此處打下斷點按住control鍵和圖上鍵頭所指的鍵結果所下:

以後咱們繼續以前的操做(按住control鍵和箭頭所指的鍵)結果以下:

經過這三種方式咱們能夠知道objc_alloclibobjc.A.dylib這個動態庫裏面,那接下來咱們來經過alloc的源碼來分析.在這以前,咱們用寄存器來讀取一下:那麼什麼是寄存器呢? 寄存器就是應該存儲一些指針的一些東西,由於彙編它就是利用寄存器,用的妥妥的.過掉第一個斷點(37行斷點),來到alloc斷點以下:

其中 x0~x7用於程序調用的參數傳遞, x0是第一個參數的傳遞者也是返回的時候返回值的存儲地方.所以咱們通常讀x0就能夠了.以後過掉 alloc斷點來到 _objc_rootAlloc斷點:
objc_msgSend打上斷點,來到斷點處:
到此處發現彙編很難跟,一不當心就過了,爲此咱們仍是用源碼來跟吧.(嘿嘿彙編有點皮,搞不定😊😊)

alloc源碼分析

打開可編譯的objc756.2源碼,經過前面的探究咱們能夠看到,在調用alloc以前還調用了objc_alloc,咱們打下斷點一步一步去看,圖以下:

objc_alloc 方法:

咱們先來到這個斷點之處,而後全局搜索objc_alloc,以下圖打上斷點,以後咱們過掉上圖的斷點,回來到下面的斷點之處,這時咱們看到allocWithZone返回的是false.在下圖中我作了詳細的解釋.

那麼爲何objc_alloc這個流程只會走一次呢?請看下圖

從上圖中能夠看到符號綁定的操做是在fixupMessageRef這個方法裏面實現的.而fixupMessageRef的調用又是在_read_images裏面調用的.也就是dyld讀取咱們的鏡像文件的時候.然而,在咱們讀取鏡像文件的時候,系統會判斷是否須要FIXUP,若是須要的話,咱們就會調用fixupMessageRef,而後在fixupMessageRef內部判斷當前的消息sel是不是SEL_alloc,若是是的話就替換其IMP爲objc_alloc.其中FIXUP只會走一次,也就是說objc_alloc只會走一次. 以後會繼續走callAlloc方法:在這個方法中以下圖所示allocWithZone返回false,以後在走alloc方法.

補充另外一種方法說明objc_alloc只會走一次:

  1. 先說說爲何會走objc_alloc?由於在LLVM的底層會調用CGF.EmitCallOrInvoke

  2. 在說說objc_alloc只會走一次:

    • 正如前面說的第一次的時候call-objc_alloc->none沒有返回對象;經過LLVM源碼能夠看到:
      若是返回值爲none時,
    return CGF.EmitObjCAlloc(Receiver, CGF.ConvertType(ResultType))
    複製代碼

    也返回爲none,那麼就會進行下面的條件判斷

    if (Optional<llvm::Value *> SpecializedResult =
    tryGenerateSpecializedMessageSend(CGF, ResultType, Receiver, Args,Sel, Method, isClassMessage))
    複製代碼

    而此時並無進入

    return RValue::get(SpecializedResult.getValue())
    複製代碼

    可是此時調用了objc_alloc方法,以後會繼續走

    return GenerateMessageSend(CGF, Return, ResultType, Sel, Receiver, Args, OID, Method)
    複製代碼

    調用alloc方法.

    • 也能夠這樣說:走的是symbol符號綁定,沒有走objc_msgSend
    • 並非咱們調用的,而是系統調用符號的(編譯器調用)
alloc方法:

_objc_rootAlloc方法:

callAlloc方法:

此方法內部有一系列的判斷條件,其中因爲方法canAllocFast()的內部調用了bits.canAllocFast(),其返回值爲固定值false,因此能夠肯定以後建立對象會走class_createInstance方法

class_createInstance方法:

進入class_createInstance方法,其內部調用了_class_createInstanceFromZone方法,並在其中進行size計算,內存申請,以及isa初始化:

_class_createInstanceFromZone方法:

咱們先來看看對象size的計算,經過方法cls->instanceSize(extraBytes),計算出size,其中64位系統下,對象大小採用8字節對齊,可是實際申請的內存最低爲16字節,也就是說系統分配內存按照16字節對齊分配

在這過程當中還有兩個核心重點:

  • obj = (id)calloc(1, size)
  • obj->initInstanceIsa(cls, hasCxxDtor)
  • 這兩行代碼應該直接決定了alloc的做用 咱們來對這兩個核心內容分析一下: 建立指針,申請內存
    通過calloc函數建立了一個指針,這個指針是怎麼建立的,這個源碼在:libmalloc.(具體的分析在OC對象原理(二) 內存對齊探索&malloc源碼分析文中有寫) 最後根據不一樣的條件,使用calloc或者malloc_zone_calloc進行內存申請,而且初始化isa指針,至此size大小的對象obj已經申請完成,而且返回.

init源碼分析

進入init方法:

經過源碼能夠看到,其實 init方法什麼事情都沒有作。那爲何 init會什麼都不作呢? 其實這是一種設計模式,本身思考一下,平常開發過程當中,咱們會在什麼狀況下,進行 init方法的使用。 —— 重寫 在重寫默認初始化的時候,咱們能夠根據本身的需求,進行各類個性化的設置。 工廠設計, 父類沒有執行,交給子類去實現

至此,咱們在回到前面的問題?就很好的知道p一、p二、p3他們的內存地址爲何同樣了吧.

擴展new的底層實現

進入new方法:

經過源碼:在new方法裏面就是調用alloc的實現(callAlloc)後 進行了init操做,因而可知,[Class new] 徹底等價於 [[Class alloc] init].

總結

  • alloc建立了對象而且申請了一塊很多於16字節的內存控件.
  • init其實什麼也沒作,返回了當前的對象。其做用在於提供一個範式,方便開發者自定義.
  • new其實就是調用alloc的實現(callAlloc)後 進行了init操做.

最後附上alloc流程圖一張

相關文章
相關標籤/搜索