Swift:module、modulemap、調(diào)用OC代碼、靜態(tài)庫合并、與OC相互映射

一、module與modulemap的作用

1、 module
  • Module(模塊)-最小的代碼單元。
    一個Module是機(jī)器代碼和數(shù)據(jù)的最小單位,可以獨(dú)立于其他代碼單位進(jìn)行鏈接。通常,Module是通過編譯單個源文件生成的目標(biāo)文件。例如,當(dāng)前的test.m被編譯成目標(biāo)文件test.o時,當(dāng)前的目標(biāo)文件就代表了一個Module。但是,有一個問題,Module在調(diào)用的時候會產(chǎn)生開銷,比如我們在使用一個靜態(tài)庫的時候,可以這樣使用。
//  -fmodules:允許使用module語言來表示頭文件
//  -fmodule-map-file:module map的路徑。如不指明默認(rèn)module.modulemap
//  -fmodules-cache-path:編譯后的module緩存路徑
clang  -fmodules -fmodule-map-file=Cat.modulemap -fmodules-cache-path=../prebuilt -c use.c -o use.o
2、modulemap
  • modulemap定義:用來描述頭文件與module之間映射的關(guān)系的文件。
//  AFNetworking的module.modulemap文件
// framework module 名稱 AFNetworking
framework module AFNetworking {
  // umbrella <目錄> 傘柄  <目錄>/.h
  // AFNetworking-umbrella.h 傘柄 AFNetworking-umbrella.h/.h 傘骨
  umbrella header "AFNetworking-umbrella.h"

  // 重新導(dǎo)出
  export *
  // module: 子module*
  module * { export * }
}
/*
  module:定義一個module
  export:導(dǎo)出當(dāng)前代表的頭文件使用的頭文件
  export * :匹配目錄下所有的頭文件
  module * :目錄下所有的頭文件都當(dāng)作一個子module
  explicit :顯式聲明一個子module的名稱
*/
3、module實操

默認(rèn)開啟module之后,在引入頭文件時使用include""、 import<>、@import ;這三種寫法,最終都會被轉(zhuǎn)化成@import。

  • @import TestStaticFramework;
    這個靜態(tài)庫中可能包含了許多的.o文件。豈不是要導(dǎo)入很多的Module。并不需要。在靜態(tài)鏈接的時候,也就是靜態(tài)庫鏈接到主項目或者動態(tài)庫時,最后生成可執(zhí)行文件或者動態(tài)庫時。靜態(tài)鏈接器可以把多個Module鏈接優(yōu)化成一個,來減少本來多個Module直接調(diào)用的問題。

  • 每次包含標(biāo)頭時,編譯器都必須可傳遞地預(yù)處理和解析該標(biāo)頭及其包含的每個標(biāo)頭中的文本。必須對應(yīng)用程序中的每個翻譯單元重復(fù)此過程,這涉及大量的冗余工作。

  • 在具有N個翻譯單元和每個翻譯單元中包含M個標(biāo)頭的項目中,即使M個標(biāo)頭中的大多數(shù)在多個翻譯單元之間共享,編譯器仍在執(zhí)行M x N個工作。

  • include偽指令被預(yù)處理程序視為文本包含,因此在包含時必須接受任何活動的宏定義。如果任何活動宏定義碰巧與庫中的名稱沖突,則可能會破壞庫API或?qū)е聨祛^本身的編譯失敗。

  • 此外,導(dǎo)入模塊時將自動提供使用該模塊所需的任何鏈接器標(biāo)志

  • std.io模塊僅編譯一次,并且將模塊導(dǎo)入轉(zhuǎn)換單元是恒定時間操作(與模塊系統(tǒng)無關(guān))。因此,每個軟件庫的API僅解析一次,從而將M x N編譯問題減少為M + N問題。

  • 每個模塊都被解析為一個獨(dú)立的實體,因此它具有一致的預(yù)處理器環(huán)境。

  • 此外,在遇到導(dǎo)入聲明時,當(dāng)前的預(yù)處理器定義將被忽略,

  • @import上面的聲明導(dǎo)入std模塊的全部內(nèi)容(其中將包含例如整個C或C ++標(biāo)準(zhǔn)庫),并在當(dāng)前翻譯單元中提供其API。要僅導(dǎo)入模塊的一部分,可以使用點語法來特定特定的子模塊

  • 模塊會自動將#include指令轉(zhuǎn)換為相應(yīng)的模塊導(dǎo)入
    APINotes官方鏈接:https://clang.llvm.org/docs/APINotes.html
    Modules官方鏈接:https://clang.llvm.org/docs/Modules.html#export-declaration


二、Swift庫使用OC代碼

Swift庫使用OC代碼:不能使用橋接文件
以下方式就可以使用了:1. oc的頭文件放到modulemap下;2. oc的頭文件放到私有的modulemap下; 3. oc語言特性 協(xié)議 -》swift-〉協(xié)議(暴露) -》 OC

  • 創(chuàng)建是私有的modulemap文件命令中必須含有 private 比如:LGSwiftFramework.private.modulemap。并且在該文件內(nèi)部LGSwiftFramework后面必須加上_Private,其中 P 必須大寫。這些都是規(guī)則。
  • private.modulemap文件并不是真正意義上的讓外部文件不能使用期私有的module,而是僅僅做了一個標(biāo)識,來區(qū)分與.modulemap文件的不同而已。

三、Swift頭文件

  • 在 Xcode 9 之后,Swift 開始?持靜態(tài)庫。
    Swift 沒有頭?件的概念,那么我們外界要使?Swift中?public修飾的類和函數(shù)怎么辦?
    Swift庫中引?了?個全新的?件.swiftmodule。
    .swiftmodule 包含序列化過的 AST(抽象語法樹,Abstract Syntax Tree),也包含 SIL(Swift 中間語?,Swift Intermediate Language)。

四、Swift靜態(tài)庫合并

  • cp -Rv -- "${BUILT_PRODUCTS_DIR}/" "${SOURCE_ROOT}/../Products"
    復(fù)制命令:cp(復(fù)制) -R(單獨(dú)復(fù)制文件)-v(使cp冗長,顯示文件的原樣)
    $(BUILT_PRODUCTS_DIR):build成功后的,最終產(chǎn)品路徑可以在Build Settings參數(shù)的Per-configuration Build Products Path項里設(shè)置
    $(TARGET_NAME):目標(biāo)工程名稱
    $(SRCROOT):工程文件(比如Nuno.xcodeproj)的路徑
    $(CURRENT_PROJECT_VERSION):當(dāng)前工程版本號
    ${SOURCE_ROOT}:項目根目錄(如果和項目根目錄平級或更高,就$(SOURCE_ROOT)/../../IBAForms/headers)

  • libtool -staticlibtool -static LGSwiftA LGSwiftB -o libLGSwiftC.a -o libLGSwiftC.a
    將 LGSwiftA 和 LGSwiftB 合并為libLGSwiftC.a文件
    報出的警告:合并的內(nèi)容中LGSwiftTeacher.o文件重復(fù),這里只是報警告,不會報錯。libtool 合并靜態(tài)庫本身,合并時會提示存在相同的文件,且不進(jìn)行合并。


    警告
  • ar -t libLGSwiftC.a 列出libLGSwiftC.a文件里面包含的文件。


    列出靜態(tài)庫文件內(nèi)容
  • 合并 Swift 靜態(tài)庫難題:靜態(tài)庫合并后還有 .swiftmodule文件沒有合并,所以將用到的頭文件和Swift頭文件和modulemap文件通過目錄的形式放到一起。如下圖:


    header

    swiftmodule
  • Swift 合并的靜態(tài)庫文件應(yīng)用,進(jìn)行配置
    OC要用合并的靜態(tài)庫:clang: other c flags :-fmodule-map-file <modulemap path>
    Swift要用合并的靜態(tài)庫 : SwiftC :other swift flags 顯式告訴SwiftC <modulemap dir>

HEADER_SEARCH_PATHS = $(inherited) "${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework/Headers" "${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework/Headers"
// OTHER_CFLAGS:傳遞給用來編譯C或者OC的編譯器,當(dāng)前就是clang
OTHER_CFLAGS="-fmodule-map-file=${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework/module.modulemap" "-fmodule-map-file=${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework/module.modulemap"

// SWIFT_INCLUDE_PATHS: 傳遞給SwiftC編譯器,告訴他去下面的路徑中查找module.file
SWIFT_INCLUDE_PATHS="${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework"  "${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework"

五、Swift與OC相互映射

  • OC映射到Swift方式:1. 宏;2. <工程名稱>.apinotes。
  • 宏配置的缺點:如果一個 SDK 使用 OC 來寫的,現(xiàn)在需要適配 Swift。這樣就需要給每一個方法或?qū)傩蕴砑雍陙磉m配,這樣就會導(dǎo)致有大量工作要做,費(fèi)時費(fèi)力。并且要修改原有代碼。
  • .apinotes:文件以.apinotes結(jié)尾,且該文件一定要放在 SDK 的目錄里。該文件是采用yaml格式書寫。官方地址:https://clang.llvm.org/docs/APINotes.html
---
Name: OCFramework
Classes:
- Name: LGToSwift
  SwiftName: ToSwift
  Methods:
  - Selector: "changeTeacherName:"
    Parameters:
    - Position: 0
      Nullability: O
    MethodKind: Instance
    SwiftPrivate: true
    Availability: nonswift
    AvailabilityMsg: "這個不能用"
  - Selector: "initWithName:"
    MethodKind: Instance
    DesignatedInit: true
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容