你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 重識 Objective

重識 Objective

編輯:IOS開發基礎

這是重識 Objective-C Runtime系列文章的其中一篇:

  • 重識 Objective-C Runtime - Smalltalk 與 C 的融合

  • 重識 Objective-C Runtime - 看透 Type 與 Value

  • 重識 Objective-C Runtime - 何為對象何為類

  • 重識 Objective-C Runtime - Calling Conventions

  • 重識 Objective-C Runtime - 能寫完上面的就不錯了

看透 Type 與 Value

對於 C 語言來說,Type 就個比較虛幻的東西,它唯一的目的便是讓編譯器知道一段數據的長度,來決定如何存取,舉個例子:

int i = 123;char c = (char)i;

這段代碼聲明了一個 int 類型的變量和一個 char 類型的變量,有初始化和類型強轉過程,在 x86_64 架構下,這兩行代碼的匯編如下:

movl $123, -4(%rbp)movl -4(%rbp), %eaxmovb %al, %clmovb %cl, -5(%rbp)

匯編看起來混亂,但卻能最真實的反映出程序的運行過程,逐行解釋下:

movl $123, -4(%rbp)

move 指令就是簡單的值拷貝,這條指令中出現的 movl 表示按低 32 位的長度來拷貝(也就是一個 int 的長度),與之相似的還有 8 位的 movb(char)、16 位的 movw (short)、64 位的 movq (long in 64) 等;$123 即字面常量值;-4(%rbp) 代表 base pointer - 棧基地址寄存器,偏移 4 字節的位置。這個指令執行後內存如下所示:

1478480922648889.png

movl -4(%rbp), %eax

將剛才 4 字節長度內存賦值給 %eax 寄存器,它是最常用的通用寄存器之一,名為 accumulator,在 64 位架構下,rax 表示這個寄存器的完全體,eax 表示它的低 32 位,ax 表示低 16 位,ah 表示第 8~16 位,al 表示最低的 8 位。這樣摳門的設計一部分因為兼容歷史的 32 架構,一方面也是為了更充分利用寄存器這個寶貴的資源:

1478480923294523.png

movb %al, %cl

按 8 位長度 (char) 將 a 寄存器的最低 8 位移動到 c 寄存器(count register)的低 8 位。這一個指令就在做 int 到 char 的類型轉換,把 123 存在寄存器的低 32 位上,再把寄存器的最低 8 位取出來,相當於把 00000000000000000000000001111011 截斷成了 01111011。

movb %cl, -5(%rbp)

最後,再把剛才的結果按 8 字節的長度拷貝到 %rbp 偏移 5 的位置,完成這個 char 類型棧變量的賦值:

1478480923810031.png

因此,對於 C 這種靜態語言,Type 信息只用於編譯器解析,除了靜態檢查外還影響生成:

  1. 相應長度的指令 (是 movq、movl 還是 movb ?)

  2. 寄存器長度的選用(是 rax、eax 還是 al ?)

  3. 棧變量內存大小的確定,也可以說是 sp 的位置( sp 表示 Stack Pointer, 它和 Base Pointer 配合管理棧內存的分配與回收,所謂“分配”棧內存只是用如 subq $32, %rsp 的指令將 sp 向低地址移動)

然而,對於動態語言,Type 不僅在編譯期起到上述作用,還需要保留到運行時,讓動態調用得以實現,被稱作 Type Encodings,對於 Objective-C 所有 Type 的編碼,都可以在這個官方文檔中查到,裡面的編碼和用 @encode() 生成的一致,比如:

@encode(int) => "i"@encode(float) => "f"@encode(id) => "@"@encode(SEL) => ":"@encode(CGRect) => "{CGRect={CGPoint=dd}{CGSize=dd}}" // 64

Objective-C Class 中每個實例變量的 Type 信息全部被編碼,Runtime 也提供了 ivar_getTypeEncoding 來訪問。
同時,為支持消息的轉發和動態調用,Objective-C Method 的 Type 信息也被以 “返回值 Type + 參數 Types” 的形式組合編碼,還需要考慮到 self 和 _cmd 這兩個隱含參數:

- (void)foo; => "v@:"- (int)barWithBaz:(double)baz; => "iv@:d"

注:上面的方法的 Encoding 使用新的格式,舊的格式中包含調用棧大小和布局信息,如 i24@0:8i16i20,表示調用棧幀共 24 字節大小,後面每個參數跟著的數字表示該參數在調用棧的偏移值,在 x86_64 和 ARM 成為主流後,調用的 Calling Conventions 發生巨大變化,開始借助寄存器傳參,所以在“參數壓棧”時代的這種編碼方式逐漸被廢棄。

方法的編碼可以使用 method_getTypeEncoding 獲取,在 Cocoa 層,被 NSMethodSignature 封裝,並提供了一些便捷的解析方法。

多說一句,純 Swift 聲稱自己是靜態的語言,因為在編譯後,任何結構都會被 Name Mangling 壓縮成一個符號,比如下面的方法:

class Sark {    func foo(bar: Int) -> Int {        return bar;    }}

經過 Name Mangling 的符號是 _TFC12TestSwift4Sark3foofT3barSi_Si,雖然把結構都拍扁了,但該有的信息都在,Module、Class、Method、參數和返回值類型等,按照一定的格式進行了編碼,感興趣可以看這篇文章。

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved