iOS動態庫和靜態庫的運用

奔跑的鴻發表於2022-03-04

概念認識

什麼是庫

  • 庫是共享程式程式碼的方式,庫從本質上來說是一種可執行程式碼的二進位制格式,可以被載入記憶體中執行。在開發過程中,一些核心技術或者常用框架,出於安全性和穩定性的考慮,不想被外界知道,所以會把核心程式碼打包成庫,只暴露出標頭檔案以供使用。
  • 庫分為靜態庫和動態庫兩種。
    • 靜態庫:連結時完整地拷貝至可執行檔案中,使可執行檔案體積變大。如果多個APP都使用了同一個靜態庫,那麼每個APP都會拷貝一份。
    • 動態庫:連結時不拷貝至可執行檔案中,可執行檔案只會儲存指向動態庫的引用。程式執行時由系統動態載入到記憶體中,系統只會載入一次, 多個APP共用一份。
  • 靜態庫的存在形式有兩種:.a靜態庫、.framework靜態庫
  • 動態庫的存在形式有兩種:.dylib動態庫、系統的.framework動態庫

系統的.framework是系統SDK庫,有Foundation.framework、UIKit.framework、MapKit.framework等。由於蘋果不開源,每個框架只提供了介面(.h檔案),所有實現(.m檔案或.c/.cpp檔案)編譯在一個.framework二進位制檔案中。

蘋果把所有系統的.framework二進位制檔案統一打包成dyld_shared_cache_arm64檔案和dyld_shared_cache_armv7檔案,儲存在手機路徑(需要越獄才能找到):/System/Library/Caches/com.apple.dyld目錄下。

動態連結器儲存在手機路徑:/usr/lib/dyld,用於在APP執行時,連結APP和系統的.framework。

請新增圖片描述

兩種.framework區別

  • 靜態庫的形式包含.a和.framework兩種形式,動態庫的形式包含.dylib和系統的.framework兩種形式。
    動態庫的.framework是系統SDK才有的庫,而我們自己建立出來的.framework都是靜態庫

靜態庫中.a與.framework的區別

  • .a是一個純二進位制檔案,.framework中除了有二進位制檔案之外還有資原始檔。
    .a檔案不能直接使用,需要.h檔案配合。
    .framework檔案可以直接使用,因為本身包含了.h檔案和資原始檔。
    .framework = .a + .h + sorrceFile(資原始檔)

靜態庫的優點

  • 實現程式的模組化,將固定的業務模組化成靜態庫。
  • 方便共享程式碼,即可以和別人分享你的程式碼庫,但別人又看不到你程式碼的實現。
  • 開發第三方sdk的需要,例如兩個公司之間業務交流,不可能把原始碼都傳送給另一個公司,這時候將私密內容打包成靜態庫,別人只能呼叫介面,而不能知道其中實現的細節。

編譯連結

  • 原始碼轉成目標檔案的過程:
    原始碼先經過前處理器進行巨集替換、刪除註釋等工作,得到.i檔案,然後編譯器對.i檔案進行編譯,編譯成組合語言程式(副檔名.s或.asm),之後再將組合語言程式編譯成機器指令(0和1組成),得到目標檔案(副檔名.o)。
  • 編譯器和連結器處理流程:
    原始碼(.c或.cpp)→編譯器→目標檔案(.object)→連結器(+靜態庫)→可執行檔案(.exe或Mach-O)
  • 連結器作用:
    對於靜態庫,在編譯連結時把多個目標檔案和所用到的庫檔案連結在一起形成一個完整的可執行檔案,可執行檔案會比較大,但程式執行時就不需要庫了。
    對於動態庫,在編譯連結時把多個目標檔案連結成可執行檔案,但不會把庫檔案加入到該可執行檔案中,而是在程式執行時連結庫檔案。

靜態庫製作

製作.a靜態庫

  • .a檔案是一種靜態庫,a是archive(歸檔)的縮寫,它是由多個.o檔案歸檔而成
  • 新建工程,iOS --> Framework & Library --> Cocoa Touch Static Library

請新增圖片描述

這裡注意由於建立的是Static Library,Build Settings搜尋linking,Mach-O Type預設為Static Library。

請新增圖片描述

  • 刪除不需要的類,如StaticLibraryTool類,建立我們所需的類,如下:

請新增圖片描述

  • 新增需要公開的標頭檔案

請新增圖片描述

  • 設定靜態庫需要支援的最低系統版本

請新增圖片描述

  • 修改配置 Active Architecture Only修改為NO,否則生成的靜態庫就只支援當前選擇裝置的架構。

請新增圖片描述

  • 接下來可以開始編譯生成.a檔案了。
    裝置選擇Generic iOS Device,即通用型真機,然後command+B編譯,在工程Products目錄下生成.a檔案,右鍵Show in Finder找到.a檔案所在路徑,圖示Products目錄下的libStaticLibraryTool.a從紅色(紅色表示檔案不存在)變成了黑色(黑色表示檔案存在),此時表示提供真機使用的靜態庫編譯成功。

請新增圖片描述

接著裝置切換選擇任一模擬器,command+B編譯,此時提供模擬器使用的靜態庫編譯成功,如下:

請新增圖片描述

這兩個.a檔案要麼只支援真機,要麼只支援模擬器,如果想真機和模擬器都能使用該靜態庫,需要將二者合成為一個靜態庫,可用終端命令實現:

lipo -create 第一個.a檔案路徑 第二個.a檔案路徑 -output 最終的.a檔案路徑

合成為一個靜態庫後,我們可檢視合成後的靜態庫支援的架構,用命令:lipo -info xxx.a,看真機和模擬器架構是否都支援。結果如下:

請新增圖片描述

“i386 x86_64”是模擬器架構,“armv7 arm64 ”是真機架構,說明該靜態庫兩者都支援。
靜態庫支援哪些架構?

模擬器架構
    iPhone4s ~ 5 : i386
    iPhone5s ~ 7Plus : x86_64
真機架構
    3GS~4s : armv7
    5/5c : armv7s(armv7相容armv7s)
    5s ~ 6sPlus : arm64
  • 如何使用.a靜態庫?
    把.a檔案和公開的標頭檔案放入一個資料夾,命名為libStaticLibraryTool。將libStaticLibraryTool資料夾新增到需要使用的工程中,如下:
iOS動態庫和靜態庫的運用

libStaticLibraryTool.a新增到工程後,
Build Pases->Link Binary With Libraries 和 General->Frameworks, Libraries, and Embedded Content都會自動引入libStaticLibraryTool.a。
Build Settings->Library Search Paths也會自動設定.a檔案路徑,如:$(PROJECT_DIR)/DemoA/libStaticLibraryTool。
由於靜態庫中使用了OC的分類(Category),OC連結器只會為類建立符號表,不會為分類建立符號表,導致函式呼叫時(執行時)找不到分類的符號表,無法獲取函式地址,函式呼叫失敗。
解決辦法:設定Other Linker Flags的值為-ObjC,為分類建立符號表。
注意:如果不做該設定,專案中引用該庫,訪問該庫的Category介面時會崩潰

請新增圖片描述

最後我們便能在專案中引入標頭檔案,使用.a靜態庫的功能了,如下:

請新增圖片描述

  • Xcode13製作靜態庫,左側的Products目錄不見了,其實Products是存在的,只是沒有展示出來。
    解決方式:對.xcodeproj右鍵“顯示包內容”,開啟project.pbxproj,command+f搜尋mainGroup,複製mainGroup的值,將其貼上到productRefGroup的值上,使兩者保持一致,這樣工程左側的Products目錄就顯示出來了。

製作.framework靜態庫

  • 新建工程時選擇Framework

請新增圖片描述

該Framework預設是Dynamic Library(動態庫),我們需要將其設定成靜態庫:Build Settings搜尋mach,Mach-O Type選擇Static Library。
往工程中新增Person類和UIImage+Extension類,StaticLibraryTool.h中引入Person類和UIImage+Extension類的標頭檔案,推薦用包含靜態庫名的尖括號,指明引入的是哪個靜態庫的標頭檔案:

請新增圖片描述

Person.h和UIImage+Extension.h需要暴露出來,這兩個標頭檔案預設在TARGETS->Build Phases->Headers的Project中,將它們拖拽到Headers的Public中,如下:

請新增圖片描述

設定Build Settings中的Active Architecture Only為NO,iOS Deployment Target的最低版本為iOS 9.0,然後command+B,編譯生成.framework靜態庫。

  • 如何使用.framework靜態庫?
    使用時,將StaticLibraryTool.framework拖入到需要使用的工程中。
    Build Settings->Other Linker Flags設定-ObjC,以便編譯器能載入OC的Category(若不配置這個,訪問Category的介面時程式會崩潰)。
    由於靜態庫StaticLibraryTool.h中引入的標頭檔案帶上了靜態庫名作為路徑,即如下方式
#import <StaticLibraryTool/Person.h>
#import <StaticLibraryTool/UIImage+Extension.h>

因此Build Settings->Header Search Paths可不用設定靜態庫StaticLibraryTool的路徑。使用靜態庫時,必須嚴格按照上述帶靜態庫名的方式引入標頭檔案。
如果想不帶靜態庫名引入,如#import "StaticLibraryTool.h"(不推薦,無法明確這是靜態庫的標頭檔案),則需要配置Header Search Paths,新增路徑:$(PROJECT_DIR)/DemoA/StaticLibraryTool.framework/Headers

實現靜態庫的注意事項

  • 無論是.a靜態庫還是.framework靜態庫,我們需要的都是二進位制檔案+.h檔案+資原始檔,不同的是,.a本身只是二進位制檔案,需要配上.h檔案和資原始檔才能使用,而.framework本身已經包含了二進位制檔案、.h和資原始檔,可以直接使用。
  • 圖片資源的處理:兩種靜態庫,一般都是把圖片檔案單獨的放在一個.bundle檔案中,一般.bundle的名字和.a或.framework的名字相同。新建一個資料夾,把它改名為.bundle,右鍵->顯示包內容,之後就可以向其中新增資原始檔。
  • 把category打成靜態庫,在使用靜態庫的工程中,呼叫category中的方法時會報找不到該方法的錯誤(selector not recognized),解決辦法是:在使用靜態庫的工程中配置Build Settings->Other Linker Flags的值為-ObjC。
  • 如果一個靜態庫很複雜,需要暴露的.h檔案比較多,則可以在靜態庫的內部建立一個.h檔案(這個.h檔案的名字一般和靜態庫的名字相同)統一管理,把所有需要暴露出來的.h檔案都集中放在這個.h檔案中,這樣使用時可以只引入該.h檔案。

相關文章