年轻人,听说你想使用Framework - 基礎觀念

Frameworks

本文為譯文,並已取得作者Nick Teissler同意。
原文鏈接:原文

Framework這小子問了幾個很好的問題

本文章適合初學Framework的讀者。

前言

Apple 已經將 iOS, macOS 的代碼分成 Modules, libraries, frameworks。

Frameworks 的設計不單單只是為了封裝資源跟模塊化代碼,更不只是為了減少代碼的重編譯時間而已。

要想減輕代碼量、加速Debug、增加代碼復用性,就不能只知道Framework是一個可以拖來拖去的工具箱,必須更近一步的了解這些:

  • 靜態庫 - Static Libraries
  • 動態庫 - Dynamic Libraries
  • Framework的結構
  • Linking 鏈接 與 Embed 嵌入的不同
  • Q & A

以下內容適用 macOS, tvOS, iOS. 可能會隨時間有改動。

靜態庫 Static Libraries

在靜態庫之前,我們要先從Object File說起,以基礎的角度來說,object files 就是個有結構的位元塊。這些位元塊包含著一些程序代碼、有些包含著準備給Linker跟Loader使用的資料結構。

可以試著在終端輸入以下指令,瞄一眼objectfile的樣子

1
objdump -macho -section-headers /bin/ls

Objectfiles通常以四種形式登場

  • Relocatable: 包含可以在編譯時可被其他Relocatable Link的代碼和數據,以共同製作成Executable。
  • Executable:準備好載入記憶體的可執行檔。
  • Shared: 一種特殊的Relocatable file,概念類似 動態庫。
  • Bundle: 我們不特別描述Bundle, 在macOS上通常當作插件使用。

譯文外補充:
Relocatable:包含著能與其他relocatable在編譯時進行Linked的代碼(執行代碼)以及數據(變量全局數據)。
Shared: 編譯時只依Header作api上的確認,不包進任一Executable,共享於Executable之間。

若你嘗試編譯一段沒有 main函數的 C 代碼,會得到一個 relocatable object file

fancy.c

1
2
3
4
5
6
void fancySwap(int *xp, int *yp) {
if (xp == yp) return; // try it!
*xp = *xp ^ *yp;
*yp = *xp ^ *yp;
*xp = *xp ^ *yp;
}

在Terminal輸入

1
cc -c fancy.c

產出的 fancy.o 是一種 objectFile ,包含著 fancySwap 符號以及對應的實現。為了更方便地處理 Relocatable object files,多個object files能被封裝成 .a (a 代表著 archive) .a 檔也被稱作 靜態庫(static library) 或 靜態歸檔(static archive)。

當executable中某function 或 數據來自於 .a,linker是能夠智能地只鏈接其對應的符號,但是,linker還是會將所有的這些.a裡的代碼給一個固定的load地址並包含(copies and relocates)進executable中,造成肥大的executable,且增長讀進memory的時間。

當你有10個應用使用此靜態庫,就需要個別有一份copy,也就是十個復本!而且若是靜態庫的開發者更新了代碼,則所有的應用都必須重新編譯一遍。而動態庫就是為了解決這一步便利性而誕生的。

動態庫 Dynamic Libraries

動態庫 (也稱作Shared Library, Shared object, 動態鏈接庫), 跟靜態庫一樣是多個object files封裝起來的,不一樣的是動態庫只有在程序的載入時間(load time)或運行時(Run time)會被載入,並且在memory隨機的配發一段地址。

以上行為是由動態鏈接器(Dynamic linker, macOS稱dyld)來完成,動態庫在:

  • macOS上 以 .dylib 存在
  • windows上 以 .DLL 存在
  • linux上 以 .so 存在

然而在運行時進行才做鏈接其實是一個笨重的負擔,應合理安排哪些庫需要Load以及時機。

在 Treminal輸入以下指令查看更多關於動態載入的內容

1
man dlopen

Shared object 解決了前面static object files的問題

  • 現在多個executable只動態鏈接到 動態庫的『唯一』拷貝。
  • 更新庫並不影響executable
  • 庫的依賴會被自動載入且鏈接(依賴庫必須在search path裡)

macOS 大規模的使用shared libraries,可以前往路徑 /usr/lib文件夾查看系統的動態庫。

如果你想要使用自己的動態庫,必須確保它被 embedded 進你的App裡。

看完上面這段,你可能會想 “太好了, 我不需要重編譯了,但我最好為我庫的使用者提供兼容性”。

是的沒錯,你的確需要考慮每當你update動態庫時,API的兼容性,或許這也是你正在看這篇文章的原因.. 學習一種具有地址兼容性的bundling格式 - Framework

Framework

一個Framework其實就是一個有著特定結構的文件夾裝著各種共享的資源。

這些資源通常是 圖片、Xibs、動態庫、靜態庫、文檔…等,他們被封裝成bundle存儲卻又不像其他的bundles( 像是 .app),Framework毫不掩飾的表明它純粹就是一個文件夾。

這裡指的Bundle並不是上面所提及的Bundle Object

Framework 以及其組成的子文件夾

Headers

這文件夾包含了Framework對外公開的C & Obj-C headers,Swift 並不會用到這些Headers,如果你的framework是用Swift寫的,Xcode會自動幫你創建這個文件夾以提供互用性。

若你有時不清楚在Swift代碼裡加 @objc, @objcMembers 的影響,可以查看 Build Settings 裡“SWIFT_OBJC_INTERFACE_HEADER_NAME”所指的文件,並試著改動@objc @objcMembers的宣告,看看這些改動對這文件的影響。

Modules

這文件夾包含了LLVM, Swift 的 Module信息。

.modulemap檔案是給Clang使用的。

關於Clang 可看譯者的另一篇文 LLVM前端Clang

.swiftmodule 文件夾下的檔案類似headers,但是不像是 headers, 這些檔案是二進制的且“無格式也有可能會改變”,在你Cmd-click 一個Swift函數時Xcode就是利用這些檔案去定位其所屬的module。

儘管這些都是二進制文件,但他們仍是一種叫 llvm bitcode 的結構,正因如此,我們能用llvm-bcanalyzer and llvm-strings取得相關信息。

MyCustomFramework

雖然他被finder標註成Unix executable,但他其實是一個 relocatable shared object file

Resources

本地化的資源, xibs, 檔案, 圖片… 跟其它資源存放在這

圖片裡的箭頭 (Framework版本)

這些箭頭都是 symlink 符號連結(捷徑)。

每當產生一個新版本時,會被放進 Versions/B. Current 的箭頭會被更新成指向 B。

與此同時,原本動態鏈接到A的程序仍會保持鏈接A而不是Current,怎麼辦到的?

每當executable 在編譯時, dynamic linker會紀錄與自己有兼容的Framework路徑。

But

現今Apple已鮮少使用這個功能了

Xcode 裡 Linking vs. Embedding Frameworks

這裡我們來討論Xcode裡一些常見的設定,Target的 general settings.

Xcode's Target General Settings View

通常需要使用Framework時,都會選擇 “Linked Frameworks and Libraries”,除非你打算在 runtime的時候才做鏈接跟載入(前面提及的dlopen)。

Linking 和 Embedding 的差別是什麼?

為什麼當你Embed一個Framework時,Xcode自動將Framework加到Link區?

想一下剛剛在讀過的,dylibs儲存在哪?當你想要用自己的dylib時該做什麼?


是的!Embedding 實際上將Framework拷貝一份到你的application bundle裡Frameworks文件夾下,

系統庫是 iOS 和 macOS 自帶的,你可以放心的鏈接他們,但是你自己的Framewroks並不是系統自帶的,因此你必須要embedded 到 application bundle裡。

而你的應用若是沒有Linking它也沒有任何用,所以Xcode自動幫你做了鏈接。

★★

Linking and embedding間接的暗示 動態 或是 靜態鏈接,

我們現在知道embedding一個靜態庫是不合理的,因為靜態庫的符號已經被編譯進executable,所以 Xcode不會應讓你將static library放到 Embed

(但其實Xcode會讓你放,然而這是很沒效率的使用,所以你不該這麼做)

技術上來講,macOS是可以讓你在runtime時去載入一個靜態的 .a file的,並標記一塊記憶體當作是 executable,

這樣做能有效的載入靜態庫,然而這技術是由 JIT compilers完成的,iOS並沒有標記記憶體的能力。

★★

聽說你想要使用 Framework

在你使用Framework或是將自己的代碼做成Framework時,有哪些因素要考慮的?

  • 你要開發一組應用嗎?如果是,這就是Framework本質的一部分 - 分享 Frameworks,你需要去建一個包安裝器把 shared frameworks放進 /Library/Frameworks or ~/Library/Frameworks.
  • 你的多個apps共享著些許的重複代碼嗎?使用framework或許不是最好的選擇,如果只是要共享一個非常小量的代碼,為了保持同步的 打包和版本控制反而會成為負擔。
  • 你想試著模塊化你的代碼嗎?如果你想要將代碼移進Framework並將它變成模組,最好注意 Swift’s access control.
  • 別將Framework當作能不重新編譯的萬靈丹,有些項目會將代碼中極大量快完成的部分移動到Framework以提升編譯速度,這樣做的確能降低時間,如我們所知Framework不需重新編譯,然而這樣的方式會使模塊更複雜,Debug, 測試難度提升。
  • 你要將代碼做成第三方包給其他人使用嗎?這情況下,你可以選擇使用動態庫 靜態庫 或Framework。macOS, iOS已逐漸習慣動態Framewrok了,因為他的輕便性、普遍以及易用性。

Sources

Comments

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×