前言:
一個Module是機器代碼和數(shù)據(jù)的最小單元,可以獨立于其他代碼單元進行鏈接,
通常,Module是通過編譯單個源文件生成的目標文件边琉。例如,當前的test.m被編譯成目標文件test.o時记劝,當前的目標文件就代表一個Module
但是变姨,有一個問題,Module在調用的時候會產生開銷厌丑,比如我們在使用一個靜態(tài)庫的時候定欧。
一. Module
1.1 #include 與 #import 區(qū)別
舉例 use.c 中引入 A.h B.h文件,那么編譯use.c的時候怒竿,A.h B.h會一塊進行編譯砍鸠,如果現(xiàn)在 use1.c 中也引入 A.h B.h,那么A.h B.h又會進行一次編譯耕驰,這就是傳統(tǒng)#include的編譯方式
這個時候就體現(xiàn)出了module的作用爷辱,也就是#import,會提前把A.h B.h編譯成二進制文件耍属,需要的時候拿來使用托嚣,再有文件導入時就不會重新編譯。
/* A.h */
#ifdef ENABLE_A
void a() {}
#endif
/* B.h */
#import "A.h"
/* use.c */
#import "B.h"
void use() {
#ifdef ENABLE_A
a();
#endif
}
/* module.modulemap文件 */
/* modulemap 用來描述頭文件與module之間映射的關系 */
/* A代表A.h B代表B.h */
module A {
header "A.h"
}
module B {
header "B.h"
// 導出厚骗,把B.h引用的頭文件也一并導出示启,也可以用 export *,把所有引用的頭文件一并導出
export A
}
// 進入Cat文件夾领舰,把use.c文件編譯成use.o文件
# -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
A.h B.h 編譯之后的產物,這兩個文件就是預編譯好的冲秽,如果其他文件再引入A和B就不用重新編譯了舍咖,如下圖所示
framework module AFNetworking { //聲明framework的module名稱為AFNetworking
//導入文件的集合
umbrella header "AFNetworking-umbrella.h"
export * //把引入的頭文件重新導出。
module * { export * } //把導入頭文件修飾成子module锉桑,并把符號全部導出
}
其他module的操作點這里
開啟module之后無論我們使用#include排霉,#import,@import民轴,最終都會被轉換成@import寫法攻柠,編譯時都會被優(yōu)化成module形式球订,就是同一個文件只會被編譯一次。
1.3 module操作
創(chuàng)建MulitProject.xcworkspace如下圖所示
framework module LGOCFramework {
// umbrella<目錄>
umbrella header "LGOCFramework.h"
explicit module LGTeacher {
header "LGTeacher.h"
export *
}
explicit module LGStudent {
header "LGStudent.h"
export *
}
}
編譯成功,并在framework文件中看到module.modulemap文件
二. Swift庫使用OC代碼
在framework中沒有橋接文件浪谴,所以swift代碼沒法直接調用oc开睡,我們要使用module,build setting中配置Module Map File路徑即可使用
自定義LGSwiftFramework.modulemap內容如下
framework module LGSwiftFramework {
umbrella "Headers"
export *
}
我們可以在swift代碼中直接使用oc類苟耻,如果我們想在oc類中調用swift代碼篇恒,我們需要通過module指定頭文件#import <項目/項目-Swift.h>
如果我們不想對外暴漏我們的OC類,我們可以創(chuàng)建LGSwiftFramework.private.modulemap梁呈,內容如下
framework module LGSwiftFramework_Private {
module LGOCStudent {
header "LGOCStudent.h"
export *
}
}
然后在Private Module Map File 中指定路徑婚度。
我們不能通過LGSwiftFramework 的module 來訪問LGOCStudent,但是我們可以通過
LGSwiftFramework_Private來訪問LGOCStudent官卡。
Private Module不是真正意義上的私有,我們可以通過LGSwiftFramework_Private可以訪問醋虏,只是供開發(fā)者區(qū)分
小結
swift調用oc方法有3種方式
第一種:直接配置LGSwiftFramework.modulemap來使用#import <LGSwiftFramework/LGOCStudent>
第二種:配置LGSwiftFramework.private.modulemap來使用 @import LGSwiftFramework_Private.LGOCStudent
第三種:swift與oc約定協(xié)議寻咒,swift調用協(xié)議,協(xié)議再調用oc颈嚼。這里把協(xié)議暴露出來毛秘,達到oc隱藏的目的
三. Swift靜態(tài)庫合并
在Xcode 9.0之后,swift開始支持靜態(tài)庫
swift沒有頭文件的概念阻课,那么我們外界使用swift中的public修飾的類和函數(shù)怎么辦呢叫挟?Swift庫引入了一個全新的文件.swiftModule
.swiftModule包含序列化過的AST(抽象語法樹),也包含SIL(Swift中間語言限煞,Swift Intermediate Language)抹恳。
創(chuàng)建swiftFramework,編譯之后 show in finder署驻,可以看到Modules中多生成一個LGSwiftFramework.swiftmodule目錄奋献,這個目錄下多生成一個x86_64.swiftmodule文件
x86_64-apple-ios-simulator.swiftdoc:這個文檔可以刪除
創(chuàng)建兩個framework庫,分別為LGSwiftA和LGSwiftB
兩個庫里有一個相同的類
@objc open class LGSwiftTeacher: NSObject {
public func speek() {
print("speek!")
}
@objc public func walk() {
print("walk!")
}
}
使用腳本把兩個靜態(tài)庫編譯后的framework放到products目錄下
cp -Rv -- "${BUILT_PRODUCTS_DIR}/" "${SOURCE_ROOT}/../Products"
進入products目錄下旺上,合并兩個靜態(tài)庫
libtool -static LGSwiftA LGSwiftB -o libLGSwiftC.a
//日志警告瓶蚂,兩個靜態(tài)庫都包含LGSwiftTeacher.o
查看libLGSwiftC.a中的目標文件
$ ar -t libLGSwiftC.a
__.SYMDEF
LGSwiftA_vers.o
LGSwiftTeacher.o
LGSwiftB_vers.o
LGSwiftTeacher.o
我們手動組合LGSwiftC庫,因為文件有沖突宣吱,使用這種目錄結構窃这,可是防止Headers文件內部存在的沖突創(chuàng)建新工程LGApp 使用上面的LGSwiftC庫,LGApp工程配置LGApp.Debug.xcconfig文件
HEADER_SEARCH_PATHS = $(inherited) "${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework/Headers" "${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework/Headers"
// OTHER_CFLAGS:傳遞給用來編譯C或者OC的編譯器征候,當前就是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"
并且手動組合LGSwiftC庫如下
在一個文件類中導入#import <LGSwiftA.h> 就會編譯LGSwiftA靜態(tài)庫以及里面的相同類LGSwiftTeacher洒试,導入#import <LGSwiftB.h>就會編譯LGSwiftB靜態(tài)庫
四. OC映射到Swift方式
讓oc代碼在swift使用中規(guī)范
4.1使用宏
NS_SWIFT_NAME(<#name#>)
NS_REFINED_FOR_SWIFT 在swift方法中, 編譯器會在名稱前加上_
4.2.使用apinotes文件
參考文檔
命名規(guī)則:前面是項目或者sdk名稱朴上,后綴是apinotes
.apinotes文件創(chuàng)建好一定要放到根目錄下
#yaml 類似于 json格式
---
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
總結:
module:定義一個module
export:導出當前代表的頭文件使用的頭文件
export * :匹配目錄下所有的頭文件
module * :目錄下所有的頭文件都當作一個子module
explicit : 顯式聲明一個module的名稱