iOS 開發中的『庫』(一)

發表於2016-10-16
  • 因為這篇文章有些問題,所以建議看完之後再看下iOS 開發中的『庫』(二)這篇文章

    看文章之前,你可以看下下面幾個問題,如果你都會了,或許可以不看。

  • .framework 是什麼?怎麼製作?
  • 談一談自己對動態庫和靜態庫的理解。
  • 在專案中如何使用動態framework的 APP ?使用了動態framework 的 APP 能上架 Appstore 麼?
  • 可以通過 framework 的方式實現 app 的熱修復麼?

我是前言

  • 最近發現很多人分不清 『.framework && .a 』、『動態庫 && 靜態庫』、『.tbd && .dylib』這幾個東西。甚至, 還有人一直以誤為 framework 就是動態庫!!鑑於網上許多文章都表述的含糊不清,再加上很多文章都比較老了,所以今天寫點東西總結一下。
  • 首先,看文章之前,你稍微瞭解這麼幾個東西:編譯過程、記憶體分割槽。下面開始!

理論篇

動態庫 VS. 靜態庫

Static frameworks are linked at compile time. Dynamic frameworks are linked at runtime

  • 首先你得搞清楚,這兩個東西都是編譯好的二進位制檔案。就是用法不同而已。為什麼要分為動態和靜態兩種庫呢?先看下圖:

11437742-9c0e23550300d1a3

靜態庫

12437742-a068e84eff67143c

動態庫
  • 我們可以很清楚的看到:
    • 對於靜態庫而言,在編譯連結的時候,會將靜態庫所有檔案都新增到 目標 app 可執行檔案中,並在程式執行之後,靜態庫app 可執行檔案 一起被載入到同一塊程式碼區中。
      • app 可執行檔案: 這個目標 app 可執行檔案就是 ipa解壓縮後,再顯示的包內容裡面與app同名的檔案。
    • 對於動態庫而言,在編譯連結的時候,只會將動態庫被引用的標頭檔案新增到目標 app 可執行檔案,區別於靜態庫動態庫 是在程式執行的時候被新增另外一塊記憶體區域。
    • 下面看下蘋果的官方文件中有兩句對動態庫靜態庫的解釋。
  • 舉個?:假設 UIKit 編譯成靜態庫和動態庫的大小都看成 1M , 載入到記憶體中花銷 1s . 現在又 app1 和 app2 兩個 app。倘若使用靜態庫的方式,那麼在 app1 啟動的時候, 需要花銷 2s 同時記憶體有 2M 分配給了 app1.同樣的道理 加上 app2 的啟動時間和記憶體消耗,採用靜態庫的方案,一共需要花銷 4s 啟動時間4M 記憶體大小4M 安裝包大小。那麼換成動態庫的時候,對於啟動和 app1 可能花費一樣的時間,但是在啟動 app2 的時候 不用再載入 UIKit 動態庫 了。減少了 UIKit 的重複 使用問題,一共花銷 3s啟動時間3M 記憶體大小4M 安裝包大小
  • 而很多 app 都會使用很多相同的庫,如 UIKit CFNetwork 等。所以,蘋果為了加快 app 啟動速度、減少記憶體花銷、減少安裝包體積大小,採用了大量 動態庫的形式 來優化系統。dyld 的共享快取 :在 OS X 和 iOS 上的動態連結器使用了共享快取,共享快取存於 /var/db/dyld/。對於每一種架構,作業系統都有一個單獨的檔案,檔案中包含了絕大多數的動態庫,這些庫都已經連結為一個檔案,並且已經處理好了它們之間的符號關係。當載入一個 Mach-O 檔案 (一個可執行檔案或者一個庫) 時,動態連結器首先會檢查 共享快取 看看是否存在其中,如果存在,那麼就直接從共享快取中拿出來使用。每一個程式都把這個共享快取對映到了自己的地址空間中。這個方法大大優化了 OS X 和 iOS 上程式的啟動時間。
  • 兩者都是由*.o目標檔案連結而成。都是二進位制檔案,閉源。

.framework VS .a

  • .a是一個純二進位制檔案,不能直接拿來使用,需要配合標頭檔案、資原始檔一起使用。在 iOS 中是作為靜態庫的檔名字尾。
  • .framework中除了有二進位制檔案之外還有資原始檔,可以拿來直接使用。
  • 在不能開發動態庫的時候,其實 『.framework = .a + .h + bundle』。而當 Xcode 6 出來以後,我們可以開發動態庫後『.framework = 靜態庫/動態庫 + .h + bundle』

.tbd VS .dylib

  • 對於靜態庫的字尾名是.a,那麼動態庫的字尾名是什麼呢?
  • 可以從 libsqlite3.dylib 這裡我們可以知道 .dylib 就是動態庫的檔案的字尾名。
  • 那麼 .tbd 又是什麼東西呢?其實,細心的朋友都早已發現了從 Xcode7 我們再匯入系統提供的動態庫的時候,不再有.dylib了,取而代之的是.tbd。而 .tbd 其實是一個YAML本文檔案,描述了需要連結的動態庫的資訊。主要目的是為了減少app 的下載大小。具體細節可以看這裡

小總結

  • 首先,相比較與靜態庫和動態庫,動態庫在包體積、啟動時間還有記憶體佔比上都是很有優勢的。
  • 為了解決 .a 的檔案不能直接用,還要配備 .h 和資原始檔,蘋果推出了一個叫做 .framework 的東西,而且還支援動態庫。

Embedded VS. Linked

Embedded frameworks are placed within an app’s sandbox and are only available to that app. System frameworks are stored at the system-level and are available to all apps.

  • OK,前面說了那麼多,那麼如果我們自己開發了一個動態framework 怎麼把它複製到 dyld 的共享快取 裡面呢?
  • 一般來說,用正常的方式是不能滴,蘋果也不允許你這麼做。(當然不排除一些搞逆向的大神通過一些 hack 手段達到目的)
  • 那麼,我們應該如何開發並使用我們自己開發的 動態framework 呢?
  • 那就是 Embedded Binaries。
  • Embedded 的意思是嵌入,但是這個嵌入並不是嵌入 app 可執行檔案,而是嵌入 app 的 bundle 檔案。當一個 app 通過 Embedded 的方式嵌入一個 app 後,在打包之後解壓 ipa 可以在包內看到一個 framework 的資料夾,下面都是與這個應用相關的動態framework。在 Xcode 可以在這裡設定,圖中紅色部分:

13437742-e051d0f7820b091e

Embedded && Link
  • 那麼問題又來了,下面的 linded feameworks and libraries 又是什麼呢?
  • 首先在 linded feameworks and libraries 這個下面我們可以連線系統的動態庫、自己開發的靜態庫、自己開發的動態庫。對於這裡的靜態庫而言,會在編譯連結階段連線到app可執行檔案中,而對這裡的動態庫而言,雖然不會連結到app可執行檔案中,但是會在啟動的時候就去載入這裡設定的所有動態庫。(ps.理論上應該是這樣,但是在我實際測試中似乎載入不載入都和這個沒關係。可能我的姿勢不對。?)
  • 如果你不想在啟動的時候載入動態庫,可以在 linded feameworks and libraries 刪除,並使用dlopen載入動態庫。(dlopen 不是私有 api。)

關於製作過程

  • 關於如何製作,大家可以看下raywenderlich家的經典教程《How to Create a Framework for iOS 》,中文可以看這裡《建立你自己的Framework》
  • 閱讀完這篇教程,我補充幾點。
    • 首先,framework 分為Thin and Fat Frameworks。Thin 的意思就是瘦,指的是單個架構。而 Fat 是胖,指的是多個架構。
    • 要開發一個真機和模擬器都可以除錯的 Frameworks 需要對Frameworks進行合併。合併命令是 lipolipo
    • 如果 app 要上架 appstore 在提交稽核之前需要把 Frameworks 中模擬器的架構給去除掉。
    • 個人理解,專案元件化或者做 SDK 的時候,最好以 framework 的形式來做。

實踐篇

framework 的方式實現 app 的熱修復

  • 由於 Apple 不希望開發者繞過 App Store 來更新 app,因此只有對於不需要上架的應用,才能以 framework 的方式實現 app 的更新。
  • 但是理論上只要保持簽名一致,在 dlopen 沒有被禁止的情況下應該是行的通的。(因為沒有去實踐,只能這樣 YY 了。)
  • 但是不論是哪種方式都得保證 伺服器上的 framework 與 app 的簽名要保持一致。

實現大致思路

  • 下載新版的 framework
  • 先到 document 下尋找 framework。然後根據條件載入 bundle or document 裡的 framework。

  • 再載入 framework

  • 載入類並做事情


番外篇

關於lipo

從原始碼到app

當我們點選了 build 之後,做了什麼事情呢?

  • 預處理(Pre-process):把巨集替換,刪除註釋,展開標頭檔案,產生 .i 檔案。
  • 編譯(Compliling):把之前的 .i 檔案轉換成組合語言,產生 .s檔案。
  • 彙編(Asembly):把組合語言檔案轉換為機器碼檔案,產生 .o 檔案。
  • 連結(Link):對.o檔案中的對於其他的庫的引用的地方進行引用,生成最後的可執行檔案(同時也包括多個 .o 檔案進行 link)。

ld && libtool

  • ld :用於產生可執行檔案。
  • libtool:產生 lib 的工具。

Build phases && Build rules && Build settings

  • Build phases: 主要是用來控制從原始檔到可執行檔案的整個過程的,所以應該說是面向原始檔的,包括編譯哪些檔案,以及在編譯過程中執行一些自定義的指令碼什麼的。
  • Build rules: 主要是用來控制如何編譯某種型別的原始檔的,假如說相對某種型別的原檔案進行特定的編譯,那麼就應該在這裡進行編輯了。同時這裡也會大量的運用一些 xcode 中的環境變數,完整的官方文件在這裡:Build Settings Reference
  • Build settings:則是對編譯工作的細節進行設定,在這個視窗裡可以看見大量的設定選項,從編譯到打包再到程式碼簽名都有,這裡要注意 settings 的 section 分類,同時一般通過右側的 inspector 就可以很好的理解選項的意義了。

談談 Mach-O

14437742-2b782afef864661e

Mach-O
  • 在製作 framework 的時候需要選擇這個 Mach-O Type.
  • 為Mach Object檔案格式的縮寫,它是一種用於可執行檔案,目的碼,動態庫,核心轉儲的檔案格式。作為a.out格式的替代,Mach-O提供了更強的擴充套件性,並提升了符號表中資訊的訪問速度。

參考資料


後記

  • 水平有限,若有錯誤,希望多多指正![coderonevv@gmail.com]

@我就叫Sunny怎麼了 提出的問題。

15437742-0c511f535fe71391

問題

更多

工作之餘,寫了點筆記,如果需要可以在我的 GitHub 看。

相關文章