以下所有類和對象的描述均以objective-c為參考, cpu架構為arm64
類本身是一個描述, 描述裡包含執行個體化這個類需要多大的記憶體, 以及記憶體的每個byte<code>是</code>什麼内容, 這個内容的頭部是一個isa, 其他内容是ivar的值或指針.
對象是按類的描述所從記憶體空間裡面開辟出對應大小的空間并填充isa指針(alloc), 類的初始化方法往這個空間裡的byte裡面存初始化的内容.
舉個例子:
這個類被編譯之後變成一個描述(arm64), aclass占用24個位元組, 前8個位元組是isa指針, 中間八個位元組是nsstring的指針, 後八個位元組是一個nsinteger的值.:
調用<code>[[aclass alloc] init]</code>在alloc的時候, 會配置設定24個位元組的記憶體出來:
然後往前8個位元組放isa的位址(mask之後的), alloc完成後的記憶體長這樣:
然後調用alloc出來的這個對象的init方法, init方法會把aint的位置設為1, 而astring的位置沒有做初始化, 是以還是0:
isa指向了這個類的meta, meta裡面存了父類/ivar結構/方法等内容, 後續再做詳解.
代碼貼到xcode裡面, 然後xcode -> debug ->debug workflow -> always show disassembly
運作斷點會斷在這裡:
通過lldb的register read指令讀出目前的寄存器的值:
可以看到x8是alloc方法, x9是aclass. 斷點下面兩行指令會分别把aclass和alloc方法分别作為self和selector放到x0/x1寄存器作為objc_msgsend的第一個(self)和第二個參數(cmd).
把斷點斷在16行位置, 看看objc_msgsend調完之後x0的值:
得到的結果是:
這已經是aclass的一個對象了, 因為這裡并沒有調用init, 是以這個對象的狀态應該是:
通過 <code>shift + command + m</code>看看記憶體<code>0x15d602780</code>:
前8個byte是isa, 中8個和後8個byte都是0, 跟第一節中的描述一緻.
在把斷點斷到第20行(第19行是調用init方法, 大家可以自己嘗試), 看看init調用之後的記憶體内容:
前8個byte是isa, 中8個byte是0, 後8個byte的内容是數字1, 跟第一節中的描述一緻.
@property實際上是一個編譯器指令, 在編譯器會根據指令後的參數自動生成ivar和ivar的getter和setter.
把示例代碼做一個修改, 把ivar改為property, 再看看不同屬性下自定義getter和setter的不同實作.
main實作改為如下代碼, 為了直覺表示代碼直接用getter和setter通路和指派:
前置知識: 在oc發消息對應的方法的實作進行調用時, x0是調用方法的對象, x1是selector, 之後的x2/x3/x4...是傳進來的參數(如果有參數). 方法調用完成後, 傳回值放在x0(如果有傳回值). 注意, 這段描述并不完全準确也不完整, 具體請參考蘋果官方文檔[1].
在用hopper disassembler分析下編譯産物, 先來setter'-[aclass setastring:]'.
上面代碼幹的事情, 就是把要指派的對象存一份到對象的ivar偏移量對應的位置. 同時strong屬性這裡會通過objc_storestrong[2]把引用計數+1.
再來setter<code>-[aclass astring]</code>:
上面代碼幹的事情, 就是把内容從對象的ivar偏移量所在的位置取出來.
在來看看另一個屬性, 還是先來setter<code>-[aclass setaint:]</code>:
由于nsinteger本身不是對象沒有引用計數等操作, 這裡的代碼比較簡單, getter與上面的getter也類似就不做額外解析了.
strong和weak和assign的差別在于objc_storestrong和objc_storeweak和直接指派.