一..swiftmodule
相當于就是Swift的頭文件,通過.swiftmodule
外界訪問framework
中的類/函數
我們都知道OC代碼
調用Swift代碼
需要使用<ProjectName>-swift.h
Swift代碼
調用OC代碼
需要使用<ProjectName>.Bridging-Header.h
此時出現了一個問題,我們在創建Swift framework
的時候,此時就使用不了橋接文件了。這就跟我們的混編帶來了問題
- Swift沒有頭文件,只有
.swiftmodelu
-
Swift Framework
不能使用<ProjectName>.Bridging-Header.h
我們創建一個Swift Framework
,添加一些代碼,編譯后,查看framework
Headers
里面存放的是暴露給外界使用的頭文件
-
Framework-Swift.h
給OC
使用的 -
Framework.h
給Swift
使用的
-
.swiftmodule
包含序列化過的ATS(抽象語法樹)
,也包含SIL(Swift中間語言)
-
.swiftdoc
用戶文檔 -
.swiftinterface
:Module stability
模塊穩定性
1.探究.swiftmodule是怎樣把Swift代碼暴露出去的
外界文件訪問到了.swiftmodule
,也就能訪問到里面的代碼
這里我們來探究一下x86_64-apple-macos.swiftmodule
怎么暴露出去的,里面的Swift文件
怎么根據它去訪問對應的模塊代碼的
1.添加一個腳本
//刪掉Products目錄
rm -rf "${SOURCE_ROOT}/Products"
//從BUILT_PRODUCTS_DIR里面拷貝到Products目錄
cp -Rv -- "${BUILT_PRODUCTS_DIR}/" "${SOURCE_ROOT}/Products"
Xcode
就是一個大型的shell
環境。里面可以調用很多工具clang
、swiftc
。
參數就是由Build Settings
來控制的。其實就在定義shell
環境變量
- 可以通過
Build Settings
來控制 - 可以通過
xcconfig
控制(沒有在Build Settings
暴露的)
2.Swift REPL
Swift解釋器,用來運行調試Swift代碼
可以使用REPL
顯示.swiftmodule
包含的內容
3.使用REPL
? swift -frontend -repl
<unknown>:0: error: unable to load standard library for target 'x86_64-apple-macosx12.0'
-
REPL
屬于Swift前端工具 - 我們終端里使用的
clang
、swift
實際實際上使用的都是Xcode
內置的
找到sdk
位置
? xcrun -show-sdk-path
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk
拼接我們的sdk
? swift -frontend -repl -sdk /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk
<unknown>:0: error: fatal error encountered during compilation; please submit a bug report (https://swift.org/contributing/#reporting-bugs) and include the project
<unknown>:0: note: Compiler-internal integrated REPL has been removed; use the LLDB-enhanced REPL instead.
Please submit a bug report (https://swift.org/contributing/#reporting-bugs) and include the project and the crash backtrace.
執行后報錯Compiler-internal integrated REPL has been removed; use the LLDB-enhanced REPL instead.
大概意思就是編譯器內部的REPL
已經被移除了,請使用LLDB的REPL
替代
終端上使用Xcode內置的命令行工具
出現的問題原因:
蘋果公司在LLVM
上拉了自己的分支,然后在原有的基礎上修改了一些東西,增加了自己的一些東西,屏蔽了一些東西。
所以說這里的REPL
被蘋果修改了,你使用不到REPL
。
使用LLDB
需要項目運行,并且比較復雜,所以此方式不好實現
4.LLVM編譯Swift
//修改項目中Swift編譯器,如果源碼版本與Xcode中Swift版本不一致時,需要將項目中的Swift修改為源碼版本的Swift編譯器
//SWIFT_EXEC = xxx/xxx.swiftc
//如果編譯源碼的macos版本與當前系統的macos版本不一致時,需要將SDK改為之前源碼版本的SDK
//open /Library/Developer/CommandLineTools/SDKs ---> SDK
//SDKROOT 在Building Setting中為Base SDK
//SDKROOT = /Library/Developer/CommandLineTools/SDKs/MacOSX12.sdk
這里由于我這源碼未編譯好,因此后續流程待續。
5.通過REPL解析出來的.swiftmodule
-
@_exported import Foundation
是Framework.h
引入的頭文件
//對外界暴露這2個頭文件
#import <Foundation/Foundation.h>
#import <CoreImage/CoreImage.h>
總結:.swiftmodule
實際上保存的是Framework
對代碼分析后的結果,將頭文件及分析后的代碼暴露出去
二..swiftinterface
Module Stability
Swift5.1支持,解決模塊間編譯器版本兼容問題。這意味著使用不同版本編譯器構建的Swift模塊可以在同一應用程序中一起使用。
注意:這里并不能保證里面的代碼能夠在不同版本下使用,Swift是一門靜態語言在編譯的時候就確定了內存分配等信息。在其它版本下編譯的結構還是不一樣的
。這里的穩定性只是能夠在不同版本下使用這個模塊
。如果需要保證二進制信息能夠正常的使用,就需要使用到Library Evolution
比如:我這里有一個通過Swift5.2.4
編譯出來的Framework
。并且我的項目中Swift版本為5.5.2
中使用這個Framework
,此時就通過.swiftinterface
來保證Framework
能夠正常的在5.5.2
下使用。
當我們的Framework中Swift版本
和工程中的Swift版本
一致時,優先去使用.swiftmodule
.swiftmodule
編譯時間比.swiftinterface
快,因為.swiftinterface
支持的功能更多,更復雜,編譯消耗的時間也就越多
默認編譯出的Framework是沒有.swiftinterface文件的,怎么開啟這項功能?
Building Setting
中Build Libraries for Distribution
開啟后,就有了.swiftinterface
文件。
注意:開啟后,Library Evolution
也會開啟。它們是一起使用的
三.Library Evolution
從Swift 5
開始,庫能夠聲明穩定的ABI
,允許庫二進制文件替換為更新版本,而無需重新編譯客戶端程序。
Library Evolution
對應Building Setting
中Build Libraries for Distribution
,默認為NO
當開啟時,Framework
中的代碼邏輯會推到運行時確定。
推到Runtime
,意味著性能的下降。本來Swift
就是一門靜態語言,這樣又要動態的確定代碼邏輯。感覺就有點自相矛盾了。
此時又引入了一個關鍵字@frozen
@frozen
關鍵字下的代碼就不會推到運行時去。
@frozen
public struct Teacher {
public init() {}
public var Kody = 1
public var Cat = 2
}
-
Teacher
結構體下的代碼不會推到運行時去
四.module
用來管理一組頭文件
優點:優化頭文件的查找,優化編譯時間(預編譯處理,會把頭文件編譯成二進制文件,省去頭文件編譯時間。避免重復編譯)
比如說:這里有一個OC工程,里面有一個LGCat
。此時我們想使用module
來管理我們的頭文件
1.創建一個空文件,名稱為module.modulemap
2.module關聯頭文件
//定義了LGCat的module
module LGCat {
//header -> 代表module管理的頭文件
header "LGCat.h"
}
3.在ViewController.m中使用module(LGCat)
@import LGCat;
@interface ViewController ()
- 此時我們發現,使用時會報錯。
Module 'LGCat' not found
- 那么此時我們需要讓
module(LGCat)
生效
4.使LGCat生效
創建module_oc.Debug.xcconfig
文件,并配置到工程中
//告訴我的clang編譯器我們自定義的module
//OTHER_CFLAGS ---> Other C Flags(傳遞添加的標志給C編譯器/OC文件)
OTHER_CFLAGS = "-fmodule-map-file=${SRCROOT}/module_OC/模塊探究/module.modulemap"
此時編譯后,發現依舊報錯。但是此時ViewController.m
中不報錯了,但是LGCat.m
報錯了。找不到NSLog
函數
Implicitly declaring library function 'NSLog' with type 'void (id, ...)'
此時是因為我們的module(LGCat)
并沒有引入Foundation
因此,我們去`module文件配置
//定義了LGCat的module
module LGCat {
//header -> 代表module管理的頭文件
header "LGCat.h"
//* 通配符,代表LGCat里面所使用到的模塊都導出。
//export *
//也可以指定只導出Foundation
export Foundation
}
再次編譯,此時編譯成功。此時的LGCat
就已經生效了
1.使用umbrella
管理頭文件
當我們的頭文件很多的時候,如果在module
里配置的話就很麻煩,此時可以使用umbrella
來管理頭文件
新建一個module_OC-umbrella.h
文件,里面的代碼為:
#import "LGCat.h"
module
配置傘頭文件
//定義了LGCat的module
module LGCat {
//header -> 代表module管理的頭文件
//header "LGCat.h"
//umbrella -> 管理一組頭文件,用一個頭文件映射一組頭文件
umbrella header "module_OC-umbrella.h"
//* 通配符,代表LGCat里面所使用到的模塊都導出。
export *
//也可以指定只導出Foundation
//export Foundation
}
2.將此時的module改為framework
一般我們訪問一個模塊的時候,有兩種方式。一種是使用@import LGCat;
。另一種是使用#import <LGCat/LGCat.h>
。
但是我們這里的LGCat是不能使用#import <LGCat/LGCat.h>
的。因此這種方式是framework
的專屬的方式。
那么我們該怎么修改呢?
framework
是一個特殊的module
創建Headers文件來存放頭文件
- 此時還是不能使用
#import <LGCat/LGCat.h>
。那是因為編譯器必須識別在framework目錄
下
模塊探究修改為LGCat.framework
同時,在module_oc.Debug.xcconfig
文件中的OTHER_CFLAGS
路徑也要更換成最新的
OTHER_CFLAGS = "-fmodule-map-file=${SRCROOT}/module_OC/LGCat.framework/module.modulemap"
如果鏈接報錯,刪除LGCat.framework
,改為LGCat
再拖入項目中再改為LGCat.framework
就可以了。鏈接錯誤就是找不到LGCat
信息。
你會發現,此時就編譯成功了。
3.子module
如果我們此時想通過@import Cat.LGCat;
來訪問我們的LGCat
framework module Cat {
//header -> 代表module管理的頭文件
//header "LGCat.h"
//umbrella -> 管理一組頭文件,用一個頭文件映射一組頭文件
umbrella header "module_OC-umbrella.h"
//* 通配符,代表LGCat里面所使用到的模塊都導出。
export *
//也可以指定只導出Foundation
//export Foundation
//1.此類子module寫法必須寫上子module需要的頭文件
// module LGCat {
// header "LGCat.h"
// export *
// }
//2.此類子module不用寫上頭文件,而是使用的通配符。將umbrella中的頭文件作用域子module上
module * {
export *
}
}
導入主module
@import Cat;
- 也會把
子module
給導入進去
導入主module時,不導入子module
加入關鍵字explicit
,表示只有顯式的引入該module
才能夠被使用
//explicit表示顯式的引入才能使用
explicit module LGCat {
header "LGCat.h"
export *
}
requires關鍵字
module Framework.Swift {
header "Framework-Swift.h"
requires objc
}
requires
表示使用模塊的源碼文件是一個OC文件
,這個模塊才能生效。也就是只能OC
才能訪問
五.xcconfig
通過xcconfig
可以修改Xcode
編譯時的環境變量
1.新建xcconfig文件
commond + n
然后搜索Config
,然后就可以新建一個Config.xcconfig
文件
2.xcconfig與項目關聯
- 1.進入
Target
- 2.然后找到左側
PROJECT
- 3.選擇
info
- 4.在
Configurations
配置xcconfig
文件
3.配置xcconfig文件
比如我們想要配置Other Linker Flgas
- 對應的
xcconfig
里的環境變量為OTHER_LDFLAGS
// key -> 配置Build Setting的選項
// value -> 值是什么
OTHER_LDFLAGS = -framework "Foundation"
當我們寫完后,再返回Build Setting
后,發現我們配置的數據已經生效的
注意:手動配置Build Setting
的優先級最高。
優先級(由高到低)
- 手動配置
Target Build Setting
-
Target
中配置的xcconfig
文件 - 手動配置
Project Build Setting
-
Project
中配置的xcconfig
文件
當手動修改了Build Setting
后導致xcconfig
數據不生效問題(沖突),添加${inherited}
繼承過來
4.xcconfig配置環境變量(多個文件多個值)
//導入Config1.xcconfig,其中設置了OTHER_LDFLAGS
#include "Config1.xcconfig"
//還可以這樣使用相對路徑,${SRCROOT}就是工程路徑,Framework和工程路徑為一級
//#include "Framework/Config1.xcconfig"
// Config1.xcconfig設置了
// OTHER_LDFLAGS = -framework "CoreFoundation"
// key -> 配置Build Setting的選項
// value -> 值是什么
// 多個OTHER_LDFLAGS值的時候,需要加上${inherited}
OTHER_LDFLAGS = ${inherited} -framework "Foundation"
// -framework "CoreFoundation" -framework "Foundation" -framework "coreImage"
5.條件變量
//條件變量
//config -> 指定Configration是Debug
//sdk -> 指定是模擬器,還有iphoneos*、macosx*等
//arch -> 指定生效架構為x86_64
OTHER_LDFLAGS[config=Debug][sdk=iphonesimulator*][arch=x86_64] = ${inherited} -framework "Foundation"
六.Swift源碼編譯
1.準備工作
新建一個swift-source
文件夾
拉取資源可能訪問外網
2.拉取對應Xcode版本的源碼
查詢源碼版本號
? xcrun swift -version
Apple Swift version 5.5.2 (swiftlang-1300.0.47.5 clang-1300.0.29.30)
Target: x86_64-apple-darwin21.2.0
git clone --branch swift-5.5.2-RELEASE https://github.com/apple/swift.git
由于clone
速度太慢,我這里是下載的Source code (tar.gz)
3.update-checkout
clone編譯swift相關的庫(過程很長,需要幾個小時)
./swift/utils/update-checkout --tag swift-5.5.2-RELEASE --clone
4.ninja編譯
./swift/utils/build-script -r --debug-swift-stdlib --lldb