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