Swift -- 12.Swift混編(上)

一..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

framework_Headers

Headers里面存放的是暴露給外界使用的頭文件

  • Framework-Swift.hOC使用的
  • Framework.hSwift使用的
framework_Modules
  • .swiftmodule包含序列化過的ATS(抽象語法樹),也包含SIL(Swift中間語言)
  • .swiftdoc用戶文檔
  • .swiftinterfaceModule 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環境。里面可以調用很多工具clangswiftc

參數就是由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前端工具
  • 我們終端里使用的clangswift實際實際上使用的都是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 FoundationFramework.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 SettingBuild Libraries for Distribution開啟后,就有了.swiftinterface文件。

注意:開啟后,Library Evolution也會開啟。它們是一起使用的

三.Library Evolution

Swift 5開始,庫能夠聲明穩定的ABI,允許庫二進制文件替換為更新版本,而無需重新編譯客戶端程序。

Library Evolution對應Building SettingBuild 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

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文件
Config在工程下的配置

3.配置xcconfig文件

比如我們想要配置Other Linker Flgas

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
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容