iOS:Module

1. Module-最小的代碼單元

一個Module是機器代碼和數(shù)據(jù)的最小單元,可以獨立于其他代碼單元進行鏈接,
通常,Module是通過編譯單個源文件生成的目標文件。例如,當前的test.m被編譯成目標文件test.o時,當前的目標文件就代表一個Module
但是,有一個問題,Module在調(diào)用的時候會產(chǎn)生開銷,比如我們在使用一個靜態(tài)庫的時候。
導入文件時如果使用include的,每次編譯的時候就會編譯一個我們include的頭文件,導入資源的浪費。我們現(xiàn)在使用的import(module)導入頭文件,導入的頭文件會預先編譯成二進制,再有文件導入時就不會重新編譯。

1.1,實測module

//A.h文件
#ifdef ENABLE_A
void a() {}
#endif
//B.h文件
#import "A.h"
//module.modulemap文件
module A {
  header "A.h"
}

module B {
  header "B.h"
  export A
}
//use.c文件
#import "B.h"
void use() {
#ifdef ENABLE_A
  a();
#endif
}

我們使用clang編譯

// -fmodules:允許使用module語言來表示頭文件
// -fmodule-map-file:module map的路徑。如不指明默認module.modulemap
// -fmodules-cache-path:編譯后的module緩存路徑
clang  -fmodules -fmodule-map-file=module.modulemap -fmodules-cache-path=../prebuilt -c use.c -o use.o

我們查看prebuilt->2Q2IP2MFAAABM文件可以看到兩個pcm文件,這兩個文件就是預編譯好的,如果其他文件再引入A和B就不用重新編譯了。

1.2.查看AFNetworking文件的modulemap文件

framework module AFNetworking { //聲明framework的module名稱為AFNetworking
//導入文件的集合
  umbrella header "AFNetworking-umbrella.h"
  export * //把引入的頭文件重新導出。
  module * { export * } //把導入頭文件修飾成子module,并把符號全部導出
}

其他module的操作,點這里
我們開啟module之后無論我們使用include,import或者@import,編譯的使用都會被優(yōu)化成module形式,就是同一個文件只會被編譯一次。

1.3.實操

我們創(chuàng)建一個framework,名字為MyOCFramework,再創(chuàng)建一個主工程名字為MyTestApp,打開主工程,點擊file->save as workspace,保存到主工程的同一級目錄下。然后打來我們的workspace,在工程中,在沒有文件被選的情況下,F(xiàn)ile->Add file to 到我們的workspace。選擇我們的framework。
編譯我們的framework,能看到會在framework下自動生成Modules 文件.
如果我們想自定義我們的module文件,我們創(chuàng)建modulemap文件,然后在build setting中設置module map file的路徑。
我們創(chuàng)建ocmodule.modulemap文件文件內(nèi)容如下

framework module MyOCFramework {
  umbrella "Headers"

  export *
  module * { export * }
}

module map file設置為MyOCFramework/ocmodule.modulemap,編譯成功,并在framework文件中看到module.modulemap。

2.Swift的framework和OC混編

因為在framework中沒有橋接文件,所以swift代碼沒法直接調(diào)用oc,我們要使用module,framework已經(jīng)自動幫我們實現(xiàn)了。
我們可以在swift代碼中直接使用oc類,如果我們想在oc類中調(diào)用swift代碼,我們需要通過module指定頭文件#import <項目/項目-Swift.h>
如果我們不想對外暴漏我們的OC類,我們可以創(chuàng)建swiftmodule.private.modulemap

framework module MySwiftFramework_Private {
  explicit module MyOCClass{
      header "MyOCClass.h"
      export *
  }
}

然后在Private Module Map File 中指定路徑。
我們不能通過MySwiftFramework 的module 來訪問MyOCClass,但是我們可以通過
MySwiftFramework_Private來訪問MyOCClass。
Private Module不是真正意義上的私有,我們可以通過MySwiftFramework_Private可以訪問,只是供開發(fā)者區(qū)分。

3.Swift靜態(tài)庫合并

在Xcode 9.0之后,swift開始支持靜態(tài)庫
swift沒有頭文件的概念,那么我們外界使用swift中的public修飾的類和函數(shù)怎么辦呢?Swift庫引入了一個全新的文件.swiftModule
.swiftModule包含序列化過的AST(抽象語法樹),也包含SIL(Swift中間語言,Swift Intermediate Language)。
我們可以看一下我們的framework中,Module中有一個.swiftmodule文件。
創(chuàng)建兩個framework庫,分別為MySwiftA和MySwiftB
兩個庫里有一個相同的類

@objc open class MySwiftTeacher: NSObject {
    public func speek() {
        print("speek!")
    }
    @objc public func walk() {
        print("walk!")
    }
}

并把兩個靜態(tài)庫編譯后的framework放到products目錄下腳本

cp -Rv -- "${BUILT_PRODUCTS_DIR}/" "${SOURCE_ROOT}/../Products"

合并兩個靜態(tài)庫

libtool -static MySwiftA.framework/MySwiftA MySwiftB.framework/MySwiftB -o libMySwiftC.a
//日志警告,兩個靜態(tài)庫都包含MySwiftTeacher.o

我們通過ar -t libMySwiftC.a查看libMySwiftC.a中的目標文件

__.SYMDEF
MySwiftA_vers.o
MySwiftTeacher.o
MySwiftB_vers.o
MySwiftTeacher.o

我們手動組合MySwiftC庫


image.png

配置build setting文件

HEADER_SEARCH_PATHS = $(inherited) "${SRCROOT}/MySwiftC/MySwiftA/Headers" "${SRCROOT}/MySwiftC/MySwiftB/Headers"
OTHER_CFLAGS = $(inherited) "-fmodule-map-file=${SRCROOT}/MySwiftC/MySwiftA/module.modulemap" "-fmodule-map-file=${SRCROOT}/MySwiftC/MySwiftB/module.modulemap"
SWIFT_INCLUDE_PATHS = $(inherited) "${SRCROOT}/MySwiftC/MySwiftB" "${SRCROOT}/MySwiftC/MySwiftA"

4.OC映射到Swift方式

為了讓oc代碼在swift使用中規(guī)范,

4.1使用宏

NS_SWIFT_NAME(<#name#>)
NS_REFINED_FOR_SWIFT 在swift方法中, 編譯器會在名稱前加上
_

4.2.使用apinotes文件

官方文檔
前面是項目或者sdk的名稱后綴是apinotes,

---
Name: OCFramework
Classes:
- Name: LGToSwift
  SwiftName: ToSwift
  Methods:
  - Selector: "changeTeacherName:"
    Parameters:
    - Position: 0
      Nullability: O
    MethodKind: Instance
    SwiftPrivate: true
    # Availability: nonswift
    #AvailabilityMsg: "prefer 'deinit'"
  - Selector: "initWithName:"
    MethodKind: Instance
    DesignatedInit: true

5.module 相關的 build setting 參數(shù)

5.1對module自身的描述:

DEFINES_MODULE:YES/NO,module 化需要設置為 YES
MODULEMAP_FILE:指向 module.modulemap 路徑
HEADER_SEARCH_PATHS:modulemap 內(nèi)定義的 Objective-C 頭文件,必須在 HEADER_SEARCH_PATHS 內(nèi)能搜索到
PRODUCT_MODULE_NAME:module 名稱,默認和 Target name 相同

5.2對外部module的引用

FRAMEWORK_SEARCH_PATHS:依賴的 Framework 搜索路徑
OTHER_CFLAGS:編譯選項,可配置依賴的其他 modulemap 文件路徑 -fmodule-map-file={modulemap_path} HEADER_SEARCH_PATHS:頭文件搜索路徑,可用于配置源碼中引用的其他 Library 的頭文件 OTHER_LDFLAGS:依賴其他二進制的編譯依賴選項 SWIFT_INCLUDE_PATHS:swiftmodule 搜索路徑,可用于配置依賴的其他 swiftmodule OTHER_SWIFT_FLAGS:Swift 編譯選項,可配置依賴的其他 modulemap 文件路徑 -Xcc -fmodule-map-file={modulemap_path}

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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

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