module(模塊):最小的代碼單元一個(gè)
module是機(jī)器代碼和數(shù)據(jù)的最小單位,可以獨(dú)立于其他代碼單位進(jìn)行鏈接通常,
module是通過編譯單個(gè)源文件生成的目標(biāo)文件。例如:當(dāng)前的test.m被編譯成目標(biāo)文件test.o時(shí),當(dāng)前的目標(biāo)文件就代表了一個(gè)module
這里有一個(gè)問題,
module在調(diào)用的時(shí)候會(huì)產(chǎn)生開銷,當(dāng)使用一個(gè)靜態(tài)庫的時(shí):@import TestStaticFramework;如果靜態(tài)庫中包含許多
.o文件。這豈不是會(huì)導(dǎo)入很多module?當(dāng)然不會(huì)。在靜態(tài)鏈接的時(shí)候,也就是靜態(tài)庫鏈接到主項(xiàng)目或者動(dòng)態(tài)庫,最終生成可執(zhí)行文件或者動(dòng)態(tài)庫時(shí),靜態(tài)鏈接器可以把多個(gè)
module鏈接優(yōu)化成一個(gè),來減少本來多個(gè)module直接調(diào)用的問題
module原理
未開啟
module時(shí),當(dāng)B文件導(dǎo)入A.h,C文件又導(dǎo)入了A.h和B.h
#include:A.h會(huì)跟隨B文件和C文件編譯多次。使用#include造成C文件重復(fù)包含A.h,所以當(dāng)C文件編譯時(shí),A.h又會(huì)被編譯多次,相當(dāng)于編譯了N * M次#import:A.h依然會(huì)跟隨B文件和C文件編譯多次。但使用#import可以避免C文件重復(fù)包含A.h,此時(shí)C文件編譯,A.h只編譯一次,相當(dāng)于編譯了N + M次
開啟
module時(shí),頭文件會(huì)被預(yù)先編譯成二進(jìn)制文件,并且每個(gè)頭文件只會(huì)被編譯一次。此時(shí)無論有多少文件導(dǎo)入頭文件,都不會(huì)被重復(fù)編譯,只需要執(zhí)行N次即可
Cat目錄中,有A.h和B.h兩個(gè)頭文件,還有一個(gè)use.c代碼和一個(gè)module.modulemap文件。和Cat目錄平級(jí),創(chuàng)建prebuilt目錄,用來存儲(chǔ)編譯后的module緩存
打開
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 }
- 在
use.c文件中,使用了B.h,同時(shí)B.h使用了A.h打開
module.modulemap文件,寫入以下代碼:module A { header "A.h" } module B { header "B.h" export A }
module.modulemap文件的作用,它是用來描述頭文件與module之間映射的關(guān)系- 定義了名稱為
A和B的兩個(gè)module- 在
module A中,定義了header A.h,表示module A和A.h的映射關(guān)系- 在
module B中,定義了header B.h,和A同理。export A表示將B.h導(dǎo)入的A.h頭文件重新導(dǎo)出通過
clang命令,開啟module并將use.c編譯成目標(biāo)文件clang -fmodules -fmodule-map-file=module.modulemap -fmodules-cache-path=../prebuilt -c use.c -o use.o
-fmodules:允許使用module語言來表示頭文件-fmodule-map-file:module map的路徑。此參數(shù)缺失,默認(rèn)找module.modulemap文件。如果文件不存在,執(zhí)行會(huì)報(bào)錯(cuò)-fmodules-cache-path:編譯后的module緩存路徑打開
prebuilt目錄,兩個(gè).pcm文件,分別對(duì)應(yīng)A.h和B.h,它們就是預(yù)編譯頭文件后的產(chǎn)物
module在Xcode中是默認(rèn)開啟的
如果在
Build Settings中,將Enable Modules設(shè)置為NO,導(dǎo)入頭文件將不能使用@import方式
開啟
module后,項(xiàng)目中導(dǎo)入頭文件,無論使用#include、#import、@import中何種方式,最終都會(huì)映射為@import方式
module解讀
查看實(shí)際開發(fā)中使用的
.modulemap文件,例如:AFNetworking
打開
AFNetworking.framework中的module.modulemap文件framework module AFNetworking { umbrella header "AFNetworking-umbrella.h" export * module * { export * } }
- 定義
module名稱為AFNetworking,模塊是frameworkumbrella:可以理解為傘柄。一把雨傘的傘柄下有很多傘骨,umbrella的作用是指定一個(gè)目錄,這個(gè)目錄即為傘柄,目錄下所有.h頭文件即為傘骨umbrella header AFNetworking-umbrella.h:指定module AFNetworking映射AFNetworking-umbrella.h文件中所有.h頭文件export *:*表示通配符。將AFNetworking-umbrella.h文件中,所有.h頭文件重新導(dǎo)出module * { export * }:創(chuàng)建子module,使用*通配符,將AFNetworking-umbrella.h中導(dǎo)入的頭文件,按照頭文件名稱命名為子module名稱。再使用export *將子module中導(dǎo)入的頭文件重新導(dǎo)出打開
AFNetworking-umbrella.h文件#ifdef __OBJC__ #import <UIKit/UIKit.h> #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "AFNetworking.h" #import "AFHTTPSessionManager.h" #import "AFURLSessionManager.h" #import "AFCompatibilityMacros.h" #import "AFNetworkReachabilityManager.h" #import "AFSecurityPolicy.h" #import "AFURLRequestSerialization.h" #import "AFURLResponseSerialization.h" #import "AFAutoPurgingImageCache.h" #import "AFImageDownloader.h" #import "AFNetworkActivityIndicatorManager.h" #import "UIActivityIndicatorView+AFNetworking.h" #import "UIButton+AFNetworking.h" #import "UIImageView+AFNetworking.h" #import "UIKit+AFNetworking.h" #import "UIProgressView+AFNetworking.h" #import "UIRefreshControl+AFNetworking.h" #import "WKWebView+AFNetworking.h" FOUNDATION_EXPORT double AFNetworkingVersionNumber; FOUNDATION_EXPORT const unsigned char AFNetworkingVersionString[];
AFNetworking-umbrella.h文件,相當(dāng)于傘柄AFNetworking-umbrella.h文件中,導(dǎo)入的所有.h頭文件,相當(dāng)于傘骨項(xiàng)目中,使用
@import AFNetworking,可以.出一個(gè)子module列表,它對(duì)應(yīng)的也是傘柄下的傘骨列表
查看開源項(xiàng)目
AsyncDisplayKit中的module.modulemap打開
module.modulemap文件framework module AsyncDisplayKit { umbrella header "AsyncDisplayKit.h" export * module * { export * } explicit module ASControlNode_Subclasses { header "ASControlNode+Subclasses.h" export * } explicit module ASDisplayNode_Subclasses { header "ASDisplayNode+Subclasses.h" export * } }
- 定義
module名稱為AsyncDisplayKit,模塊是framework- 定義傘柄
AsyncDisplayKit.h- 將
AsyncDisplayKit.h文件中,所有.h頭文件重新導(dǎo)出- 創(chuàng)建子
module,使用*通配符,將AsyncDisplayKit.h中導(dǎo)入的頭文件,按照頭文件名稱命名為子module名稱。將子module中導(dǎo)入的頭文件重新導(dǎo)出explicit:顯示指明子module名稱
官方文檔
更多
API可查看 官方文檔
自定義module
搭建
LGOCFramework項(xiàng)目
LGOCFramework是一個(gè)動(dòng)態(tài)庫項(xiàng)目,創(chuàng)建項(xiàng)目后,系統(tǒng)默認(rèn)并不提供.modulemap文件
項(xiàng)目編譯后,在
LGOCFramework.framework中的Modules目錄下,會(huì)自動(dòng)生成module.modulemap文件
打開
module.modulemap文件,里面存儲(chǔ)了基本的頭文件與module之間映射的關(guān)系/* module.modulemap */ framework module LGOCFramework { // umbrella<目錄> umbrella header "LGOCFramework.h" explicit module LGTeacher { header "LGTeacher.h" export * } explicit module LGStudent { header "LGStudent.h" export * } }
如果想對(duì)
module進(jìn)行配置,例如:定義子module,此時(shí)需要自己創(chuàng)建modulemap文件在項(xiàng)目
LGOCFramework目錄下,創(chuàng)建LGOCFramework.modulemap文件
將
LGOCFramework.modulemap文件加入到項(xiàng)目中
在
BuildSetting中,修改Module Map File配置項(xiàng):
Module Map File:設(shè)置.modulemap文件路徑,填寫${SRCROOT}之后的路徑即可打開
LGOCFramework.modulemap文件,寫入以下代碼:framework module LGOCFramework { umbrella header "LGOCFramework.h" explicit module LGTeacher { header "LGTeacher.h" export * } explicit module LGStudent { header "LGStudent.h" export * } }
- 定義
module名稱為LGOCFramework,模塊是framework- 定義傘柄
LGOCFramework.h- 顯示指明子
module名稱為LGTeacher,映射LGTeacher.h,將LGTeacher.h中導(dǎo)入的頭文件重新導(dǎo)出- 顯示指明子
module名稱為LGStudent,映射LGStudent.h,將LGStudent.h中導(dǎo)入的頭文件重新導(dǎo)出項(xiàng)目編譯后,在
LGOCFramework.framework中的Modules目錄下,生成的依然是名稱為module.modulemap的文件
由于系統(tǒng)默認(rèn)識(shí)別
.modulemap文件的名稱是module.modulemap,所以自定義的LGOCFramework.modulemap文件在編譯后,名稱依然是module.modulemap,但里面的內(nèi)容已經(jīng)生效
搭建
LGApp項(xiàng)目
LGApp是一個(gè)App項(xiàng)目
創(chuàng)建
MulitProject.xcworkspace,加入LGOCFramework動(dòng)態(tài)庫項(xiàng)目。LGApp鏈接LGOCFramework動(dòng)態(tài)庫
打開
ViewController.m文件,導(dǎo)入LGOCFramework動(dòng)態(tài)庫的頭文件,和module中的配置完全一致
至此自定義
module成功
Swift庫使用OC代碼
module映射
搭建
LGSwiftFramework項(xiàng)目
LGSwiftFramework是一個(gè)Swift動(dòng)態(tài)庫項(xiàng)目
打開
LGOCStudent.h文件,寫入以下代碼:#import <Foundation/Foundation.h> @interface LGOCStudent : NSObject - (void)speek; @end打開
LGOCStudent.m文件,寫入以下代碼:#import "LGOCStudent.h" @implementation LGOCStudent - (void)speek { NSLog(@"LGOCStudent--speek"); } @end打開
LGSwiftTeacher.swift文件,寫入以下代碼:import Foundation @objc open class LGSwiftTeacher: NSObject { public func speek() { let s = LGOCStudent() s.speek() print("speek!") } @objc public func walk() { print("walk!") } }在
LGSwiftTeacher.swift文件中,調(diào)用了OC代碼。在日常項(xiàng)目中,使用橋接文件即可。但在Framework項(xiàng)目中,沒有橋接文件的概念,此時(shí)編譯報(bào)錯(cuò)
解決辦法:
創(chuàng)建
LGSwiftFramework.modulemap文件,寫入以下代碼:framework module LGSwiftFramework { umbrella "Headers" export * }
- 定義
module名稱為LGSwiftFramework,模塊是framework- 定義傘柄
Headers目錄- 將
Headers目錄下所有.h頭文件重新導(dǎo)出在
BuildSetting中,修改Module Map File配置項(xiàng):
Headers目錄下的.h頭文件
此時(shí)
LGSwiftTeacher.swift文件中,使用的OC代碼不再報(bào)錯(cuò),項(xiàng)目編譯成功
App使用Swift庫
承接
自定義module的案例打開
MulitProject.xcworkspace文件,加入LGSwiftFramework動(dòng)態(tài)庫項(xiàng)目。LGApp鏈接LGSwiftFramework動(dòng)態(tài)庫
在
LGApp中,打開ViewController.m文件,使用@import LGSwiftFramework導(dǎo)入頭文件,只能找到一個(gè).Swift
LGSwiftFramework項(xiàng)目在編譯時(shí),系統(tǒng)在.framework中生成的module.modulemap文件,會(huì)自動(dòng)生成以下代碼:framework module LGSwiftFramework { umbrella "Headers" export * } module LGSwiftFramework.Swift { header "LGSwiftFramework-Swift.h" requires objc }但這種導(dǎo)入方式,無法使用
LGOCStudent類
解決辦法:
使用
#import方式,也無法找到LGOCStudent.h頭文件
但
LGSwiftFramework中的.modulemap文件,將Headers目錄下所有.h文件全部重新導(dǎo)出。所以可以強(qiáng)行導(dǎo)入<LGSwiftFramework/LGOCStudent.h>,導(dǎo)入后LGOCStudent類可以正常使用#import "ViewController.h" #import <LGSwiftFramework/LGOCStudent.h> @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; LGOCStudent *student=[LGOCStudent new]; } @end
另一種解決辦法,通過
.modulemap文件,暴露出LGOCStudent:打開
LGSwiftFramework.modulemap文件,改為以下代碼:framework module LGSwiftFramework { umbrella "Headers" export * } module LGSwiftFramework.LGOCStudent { header "LGOCStudent.h" requires objc }再次編譯項(xiàng)目,使用
@import方式,此時(shí)可以找到LGOCStudent
導(dǎo)入
LGSwiftFramework.LGOCStudent后,LGOCStudent類可以正常使用#import "ViewController.h" @import LGSwiftFramework.LGOCStudent; @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; LGOCStudent *student=[LGOCStudent new]; } @end
私有module映射
在某些情況下,是否使用特定頭文件用于區(qū)分指定庫的
公共API和私有API例如:一個(gè)庫可能包含分別提供
公共API和私有API的頭文件LGOCStudent.h和LGOCStudent_Private.h。此外,LGOCStudent_Private.h可能僅在某些版本的庫中可用,而在其他版本庫中不可用。使用統(tǒng)一的module.modulemap文件無法表達(dá)這一點(diǎn)
LGSwiftFramework項(xiàng)目創(chuàng)建
LGOCStudent_Private.h文件,寫入以下代碼:#import <Foundation/Foundation.h> @interface LGOCStudent_Private : NSObject - (void)speek; @end創(chuàng)建
LGOCStudent_Private.m文件,寫入以下代碼:#import "LGOCStudent_Private.h" @implementation LGOCStudent_Private - (void)speek { NSLog(@"LGOCStudent_Private--speek"); } @end創(chuàng)建
LGSwiftFramework.private.modulemap文件,寫入以下代碼:framework module LGSwiftFramework_Private { module LGOCStudent { header "LGOCStudent_Private.h" export * } }
- 私有
.modulemap文件的名稱,中間的.private一定要加,這個(gè)是命名規(guī)則- 定義
module名稱為LGSwiftFramework_Private,模塊是framework- 定義私有
module名稱,后面一定要加Private后綴,并且首字母大寫- 定義
module名稱為LGOCStudent,映射LGOCStudent_Private.h- 將
LGOCStudent_Private.h中導(dǎo)入的頭文件重新導(dǎo)出在
BuildSetting中,修改Private Module Map File配置項(xiàng):
LGApp項(xiàng)目打開
ViewController.m文件,導(dǎo)入LGOCStudent.h和LGOCStudent_Private.h頭文件,此時(shí)它們被徹底分開了#import "ViewController.h" @import LGSwiftFramework.LGOCStudent; @import LGSwiftFramework_Private.LGOCStudent; @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; LGOCStudent *student=[LGOCStudent new]; LGOCStudent_Private *sp=[LGOCStudent_Private new]; } @end
Swift靜態(tài)庫
在
Xcode 9之后,Swift開始?持靜態(tài)庫
Swift沒有頭?件的概念,外界如何使?Swift中public修飾的類和函數(shù)?
Swift庫中引?了?個(gè)全新的?件.swiftmodule
.swiftmodule包含序列化過的AST(抽象語法樹,Abstract Syntax Tree),也包含SIL(Swift中間語?,Swift Intermediate Language)
Swift靜態(tài)庫合并
搭建
LGSwiftA項(xiàng)目
LGSwiftA是一個(gè)Swift靜態(tài)庫項(xiàng)目
打開
LGSwiftTeacher.swift文件,寫入以下代碼:import Foundation @objc open class LGSwiftTeacher: NSObject { public func speek() { print("speek!") } @objc public func walk() { print("walk!") } }
搭建
LGSwiftB項(xiàng)目
LGSwiftB是一個(gè)Swift靜態(tài)庫項(xiàng)目
打開
LGSwiftTeacher.swift文件,寫入以下代碼:import Foundation @objc open class LGSwiftTeacher: NSObject { public func speek() { print("speek!") } @objc public func walk() { print("walk!") } }
創(chuàng)建
MulitProject.xcworkspace,加入LGSwiftA、LGSwiftB兩個(gè)靜態(tài)庫項(xiàng)目
創(chuàng)建
Products目錄,和MuiltProject.xcworkspace平級(jí)
在
LGSwiftA、LGSwiftB項(xiàng)目中,選擇Build Phases,創(chuàng)建Run Script,寫入以下代碼:cp -Rv -- "${BUILT_PRODUCTS_DIR}/" "${SOURCE_ROOT}/../Products"
- 使用
cp命令,將編譯后的.framework文件拷貝到Products目錄編譯
LGSwiftA、LGSwiftB項(xiàng)目,打開Products目錄,.framework文件已成功拷貝
使用
libtool命令,合并LGSwiftA和LGSwiftB兩個(gè)靜態(tài)庫libtool -static \ -o \ libLGSwiftC.a \ LGSwiftA.framework/LGSwiftA \ LGSwiftB.framework/LGSwiftB由于
LGSwiftA、LGSwiftB項(xiàng)目中,都存在了相同的LGSwiftTeacher.swift文件,使用libtool命令合并后提示警告libtool: warning same member name (LGSwiftTeacher.o) in output file used for input files: LGSwiftA.framework/LGSwiftA(LGSwiftTeacher.o) and: LGSwiftB.framework/LGSwiftB(LGSwiftTeacher.o) due to use of basename, truncation and blank padding使用
ar -t libLGSwiftC.a命令,查看libLGSwiftC.a的文件列表__.SYMDEF LGSwiftA_vers.o LGSwiftTeacher.o LGSwiftB_vers.o LGSwiftTeacher.o如果是
OC動(dòng)態(tài)庫,.framework中可以舍棄Modules目錄,將兩個(gè)靜態(tài)庫的頭文件拷貝到一起即可但
Swift動(dòng)態(tài)庫,包含了x.swiftmodule目錄,里面的.swiftmodule文件不能舍棄,此時(shí)應(yīng)該如何處理?
解決辦法:
Products目錄下,創(chuàng)建LGSwiftC目錄,將庫文件libLGSwiftC.a拷貝到LGSwiftC目錄下
仿照
Cocoapods生成三方庫的目錄結(jié)構(gòu),在LGSwiftC目錄下,創(chuàng)建Public目錄,將LGSwiftA.framework和LGSwiftB.framework拷貝到Public目錄下
打開
LGSwiftA.framework和LGSwiftB.framework文件,將里面的庫文件、.plist文件、簽名等信息全部刪除,最終只保留Headers和Modules兩個(gè)目錄
雖然生成
.framework時(shí),自動(dòng)創(chuàng)建了Modules目錄。但編譯時(shí),.modulemap文件和x.swiftmodule目錄,應(yīng)該和Headers目錄平級(jí)將
.modulemap文件和x.swiftmodule目錄,從Modules目錄移動(dòng)到.framework文件下,和Headers目錄平級(jí)。然后刪除Modules目錄
此時(shí)靜態(tài)庫合并完成
App使用合并后的靜態(tài)庫
搭建
LGApp項(xiàng)目
LGApp是一個(gè)App項(xiàng)目
將
LGSwiftC目錄,拷貝到LGApp項(xiàng)目的根目錄下
將
libLGSwiftC.a庫文件,拖動(dòng)到項(xiàng)目中的Frameworks目錄
勾選
Copy items if needed,點(diǎn)擊Finish
創(chuàng)建
xcconfig文件,并配置到Tatget上,寫入以下代碼:HEADER_SEARCH_PATHS = $(inherited) '${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework/Headers' HEADER_SEARCH_PATHS = $(inherited) '${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework/Headers'
- 指定頭文件路徑
Header Search Paths在
ViewController.m中,使用module方式導(dǎo)入LGSwiftA,編譯報(bào)錯(cuò)
- 使用
module方式,還需要加載modulemap文件的路徑打開
xcconfig文件,改為以下代碼:HEADER_SEARCH_PATHS = $(inherited) '${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework/Headers' HEADER_SEARCH_PATHS = $(inherited) '${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework/Headers' OTHER_CFLAGS = $(inherited) '-fmodule-map-file=${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework/module.modulemap' OTHER_CFLAGS = $(inherited) '-fmodule-map-file=${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework/module.modulemap'
OTHER_CFLAGS:傳遞給用來編譯C或者OC的編譯器,當(dāng)前就是clang- 加載
modulemap文件的路徑- 對(duì)應(yīng)
Build Setting中的配置項(xiàng)
打開
ViewController.m,寫入以下代碼:#import "ViewController.h" @import LGSwiftA; @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; LGSwiftTeacher *teacher = [LGSwiftTeacher new]; } @end編譯成功,
Swift靜態(tài)庫中的LGSwiftTeacher類,可以在OC下正常使用
但此時(shí)還有另一個(gè)問題:
在
LGSwiftTest.swift中,使用import導(dǎo)入LGSwiftA,還是編譯報(bào)錯(cuò)
- 在
Swift中,還需要加載swiftmodule文件的路徑打開
xcconfig文件,改為以下代碼:HEADER_SEARCH_PATHS = $(inherited) '${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework/Headers' HEADER_SEARCH_PATHS = $(inherited) '${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework/Headers' OTHER_CFLAGS = $(inherited) '-fmodule-map-file=${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework/module.modulemap' OTHER_CFLAGS = $(inherited) '-fmodule-map-file=${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework/module.modulemap' SWIFT_INCLUDE_PATHS = $(inherited) '${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework' SWIFT_INCLUDE_PATHS = $(inherited) '${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework'
SWIFT_INCLUDE_PATHS:傳遞給SwiftC編譯器- 在指定路徑下查找
swiftmodule文件- 對(duì)應(yīng)
Build Setting中的配置項(xiàng)
打開
LGSwiftTest.swift文件,寫入以下代碼:import Foundation import LGSwiftA @objc open class LGSwiftTest: NSObject { public override init() { super.init() let t = LGSwiftTeacher() t.speek() } }編譯成功,
Swift靜態(tài)庫中的LGSwiftTeacher類,可以在Swift下正常使用
在
LGSwiftA.framework和LGSwiftB.framework兩個(gè)靜態(tài)庫中,都存在LGSwiftTeacher,有時(shí)甚至?xí)嬖陬^文件相同的情況。所以在案例中,手動(dòng)構(gòu)建的目錄結(jié)構(gòu),可以有效避免相同頭文件的沖突。并且在使用的時(shí)候,導(dǎo)入的頭文件是誰的,使用的LGSwiftTeacher對(duì)應(yīng)就是誰的鏈接靜態(tài)庫,只要沒指定
-all_load或-ObjC參數(shù),默認(rèn)會(huì)使用-noall_load參數(shù)。所以在同一個(gè)文件內(nèi),即使導(dǎo)入兩個(gè)頭文件,當(dāng)鏈接一個(gè)文件找到代碼后,就不會(huì)鏈接另一個(gè),因此也不會(huì)沖突
OC映射到Swift方式
搭建
OCFramework項(xiàng)目
OCFramework是一個(gè)OC動(dòng)態(tài)庫項(xiàng)目
打開
LGToSwift.h文件,寫入以下代碼:#import <Foundation/Foundation.h> typedef NS_ENUM(NSUInteger, LGTeacherName) { LGTeacherNameHank, LGTeacherNameCat, }; typedef NSString * LGTeacherNameString; extern NSString *getTeacherName(void); extern NSString * const LGTeacherCat; extern LGTeacherNameString const LGTeacherHank; @interface LGToSwift : NSObject - (nullable NSString *)teacherNameForIndex:(NSUInteger)index; - (BOOL)changeTeacherName:(nullable NSDictionary<NSString *, id> *)options; @end打開
LGToSwift.m文件,寫入以下代碼:#import "LGToSwift.h" NSString *getTeacherName(void) { return nil; } NSString * const LGTeacherCat = @"Cat"; LGTeacherNameString const LGTeacherHank = @"Hank"; @implementation LGToSwift - (nullable NSString *)teacherNameForIndex:(NSUInteger)pageIndex { return nil; } - (BOOL)changeTeacherName:(nullable NSDictionary<NSString *, id> >*)options { return NO; } @end
搭建
SwiftProject項(xiàng)目
SwiftProject是一個(gè)App項(xiàng)目
創(chuàng)建
MulitProject.xcworkspace,加入OCFramework動(dòng)態(tài)庫項(xiàng)目。SwiftProject鏈接OCFramework動(dòng)態(tài)庫
在
ViewController.swift中,使用OCFramework動(dòng)態(tài)庫的方法,出現(xiàn)以下問題:
- 無法對(duì)
LGTeacherNameString類型的屬性賦值枚舉值teacherName方法的命名,被改為teacherName(for:),但我們預(yù)期的是teacherName(forIndex:)changeTeacherName方法,我們希望它作為私有方法,并以雙下劃線字符__開頭
解決辦法:
可以使用特定宏,改變映射規(guī)則
在
OCFramework中,打開LGToSwift.h文件,改為以下代碼:#import <Foundation/Foundation.h> typedef NS_ENUM(NSUInteger, LGTeacherName) { LGTeacherNameHank, LGTeacherNameCat, }; typedef NSString * LGTeacherNameString NS_TYPED_EXTENSIBLE_ENUM; extern NSString *getTeacherName(void); extern NSString * const LGTeacherCat; extern LGTeacherNameString const LGTeacherHank; @interface LGToSwift : NSObject - (nullable NSString *)teacherNameForIndex:(NSUInteger)index NS_SWIFT_NAME(teacherName(forIndex:)); - (BOOL)changeTeacherName:(nullable NSDictionary<NSString *, id> *)options NS_REFINED_FOR_SWIFT; @end
NS_TYPED_EXTENSIBLE_ENUM:屬性指示編譯器,使用struct(swift_wrapper(struct)屬性),通過指定NS_TYPED_ENUM宏,編譯器被指示使用enum(swift_wrapper(enum)屬性)NS_SWIFT_NAME:通過指定NS_SWIFT_NAME宏,可以添加一些詳細(xì)信息以使函數(shù)清晰可見NS_REFINED_FOR_SWIFT:通過指定NS_REFINED_FOR_SWIFT宏,Swift的Clang Importer將做一些額外的工作,將該方法導(dǎo)入為私有方法,并以雙下劃線字符__開頭在
SwiftProject中,打開ViewController.swift文件,寫入以下代碼:import UIKit import OCFramework class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let Hank: LGTeacherNameString = .hank let teacher: LGToSwift = LGToSwift() teacher.teacherName(forIndex: 1) } } extension LGToSwift { func change() -> Bool { return __changeTeacherName(nil) } }問題解決,
OC中的方法和屬性,在Swift中使用符合預(yù)期
但另一個(gè)問題又出現(xiàn)了:
通過指定宏的方式,需要修改原有代碼。如果一個(gè)使用
OC開發(fā)的SDK需要適配Swift,需要為每一個(gè)方法或?qū)傩灾付ê?,這將是工程浩大且費(fèi)時(shí)費(fèi)力的事情
解決辦法:
使用
.apinotes文件,代替宏的方式在
OCFramework目錄下,創(chuàng)建OCFramework.apinotes文件
在
OCFramework中,將OCFramework.apinotes文件加入到項(xiàng)目
.apinotes文件必須要放在SDK的目錄中,采用yaml格式書寫,類似JSON格式打開
OCFramework.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'"
- 將
changeTeacherName:方法,在Swift中設(shè)置為不可用編譯項(xiàng)目,顯示自定義錯(cuò)誤提示:
prefer 'deinit'
.apinotes文件最終會(huì)被放入編譯后的.framework中
官方文檔
更多
API可查看 官方文檔
總結(jié)
module(模塊):最小的代碼單元,表示頭文件與目標(biāo)文件的關(guān)系
modulemap:最小的代碼單元,表示頭文件與目標(biāo)文件的映射定義一個(gè)
module:
export:導(dǎo)出當(dāng)前代表的頭文件使用的頭文件export *:匹配目錄下所有的頭文件module *:目錄下所有的頭文件都當(dāng)作一個(gè)子moduleexplicit *:顯式聲明一個(gè)module的名稱
Swift庫使用OC代碼:
- 不能使用橋接文件
OC的頭文件放到modulemap下- 使用私有
modulemap更好的表達(dá)公共API和私有API
Swift靜態(tài)庫合并
- 必須保留
.swiftmodule文件(Swift的頭文件)- 使用
libtool命令,合并靜態(tài)庫本身- 用到的頭文件、
Swift頭文件以及modulemap文件,通過目錄的形式放到一起OC要用合并的靜態(tài)庫:clang: other c flags:-fmodule-map-file <modulemap path>Swift要用合并的靜態(tài)庫:SwiftC :other swift flags顯式告訴SwiftC <modulemap dir>
OC映射到Swift方式
- 宏
- 使用
.apinotes文件:<工程名稱>.apinotes






















































