不少 iOS 項目里都有 C++ 代碼的痕跡,Objective-C 和 C++ 雖然都是 C 的 superset,但二者在語言特性上存在很大差異,Objective-C 的 runtime 使其語言的特性更豐富更易使用,但代價是會增加性能損耗以及編譯后的 binary size。
很多成熟項目開發(fā)到一定階段,會關(guān)注一些關(guān)鍵指標,比如 App size,現(xiàn)在超過 100 M 的 App 比比皆是,而 App Store 上超過 150 MB 的 App 只能通過 Wifi 下載,當常規(guī)的瘦身手段用盡之后,App size 每一個 MB 的減少都彌足珍貴,這篇文章向 iOS 開發(fā)者介紹 C++ 的 zero cost abstraction 特性,在特定的場景下使用能起到立竿見影的療效:減小 iOS App 的 binary size,給 App 瘦身。
zero-cost abstraction
Objective-C 和 C++ 同為面向?qū)ο笳Z言,我們通過對象來抽象世界中的概念,但 Objective-C 的抽象伴隨著代價,抽象越多,定義的類越多,最后編譯出的 binary size 也就越大,而 C++ 卻沒這方面的煩惱,無論你定義多少類,設(shè)計多少 component,采用多少設(shè)計模式,并不會增加最后的 binary size,也就是所謂的 zero-cost,對 iOS 開發(fā)者來說,這種理論聽起來可能有些反常識,但如果你是先學(xué)習(xí) C, C++,再接觸 Objective-C 的 runtime,理解起來再直白不過。
舉例來說,假設(shè)我們從服務(wù)器收到一段請求 user 信息的 response,一般我們會將 response 還原成一個業(yè)務(wù) model 對象,user 類的定義如下:
class User { int gender; int age };
如果使用 C++ 來定義這個類,在 C++ 編譯器的眼里,這個類的全部信息不過是兩個連續(xù)存在于內(nèi)存空間上的 4 個字節(jié)(假設(shè)一個 int 占 4 字節(jié))。我們訪問 user 對象的代碼:
int age = user->age;
會被編譯器翻譯成一段類似如下的內(nèi)存訪問代碼:
r11 = *(r10 + 0x4)
在編譯器眼里沒有類的概念,只有內(nèi)存地址,偏移量,以及讀寫操作。即使我們加入更多的抽象,比如把 User 類放進 Car 類里面,再把 Car 放進 City 類里,當我們使用 city->car->user->age 時,編譯器依舊會將代碼翻譯成直白的 memory access。
如果我們使用 Objective-C 來書寫上述代碼,情況就完全不一樣了,熟悉 Objective-C runtime 的同學(xué)明白接下來會發(fā)生一系列操作,編譯后的代碼里,Objective-C 的 runtime 會先嘗試給 user 對象發(fā)送 message(如果是通過 property 訪問),需要通過 user 對象的 isa 指針找到 User 類定義,再通過 selector 在 cache 里找到 IMP 地址,最后才從函數(shù)返回需要操作的目標內(nèi)存地址。我只列出了關(guān)鍵的幾步,中間其實省略了 n 個流程,類越多,抽象的層次越多,步驟也就越多,這是由于 Objective-C 需要將 class 的定義編譯進最后的 binary 里,需要依賴 class 的信息來實現(xiàn) runtime 的一些機制,class 越多,最后生成 binary 自然也就越大。
簡而言之,大部分編程語言和 Objective-C 類似,由于需要在 binary 中保存 class 的信息,而將抽象的成本帶入了編譯后的機器碼。通過上面的分析我們也不難發(fā)現(xiàn) zero-cost abstraction 的好處體現(xiàn)在兩方面,一是 binary 更小,二是運行時更高效(沒有一層層的中轉(zhuǎn))。
C++ 的 zero-cost 特性得益于編譯器的高效實現(xiàn),我們在代碼里定義的所有類,最后都會被編譯器降維,高樓被夷為平地,信息卻不會丟失,編譯器用一片二向箔將面向?qū)ο蟮氖澜鐗罕獬梢环嫞嬂锏臋C器碼仍然能嚴格準確的表達我們的意圖。
謹慎使用
使用 zero-cost abstraction 的代價即為使用 C++ 開發(fā)的代價,C++ 使用難度高于 Objective-C,過多引入 C++ 代碼可能會造成純 iOS 團隊的維護效率降低,在引入之前,最好有準確的評估和 demo 先行驗證。
By “zero-abstraction” I mean not a byte and not a cycle wasted compared to hand-crafted low-level alternatives. Often the overhead of a function call (and especially an indirect function call) is considered too much. Offering both hardware access and abstraction is the basis of C++. Doing it efficiently is what distinguishes it from other languages.
? -Bjarne Stroustrup