Swift

Swift -- 12.Swift混编(上)

2022-03-02  本文已影响0人  MissStitch丶

一..swiftmodule

相当于就是Swift的头文件,通过.swiftmodule外界访问framework中的类/函数

我们都知道OC代码调用Swift代码需要使用<ProjectName>-swift.h
Swift代码调用OC代码需要使用<ProjectName>.Bridging-Header.h

此时出现了一个问题,我们在创建Swift framework的时候,此时就使用不了桥接文件了。这就跟我们的混编带来了问题

我们创建一个Swift Framework,添加一些代码,编译后,查看framework

framework_Headers
Headers里面存放的是暴露给外界使用的头文件 framework_Modules

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环境变量

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'

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

//对外界暴露这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
}

四.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 ()

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文件来存放头文件

模块探究修改为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

加入关键字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与项目关联

Config在工程下的配置

3.配置xcconfig文件

比如我们想要配置Other Linker Flgas

Other Linker Flgas
// key -> 配置Build Setting的选项
// value -> 值是什么
OTHER_LDFLAGS = -framework "Foundation"

当我们写完后,再返回Build Setting后,发现我们配置的数据已经生效的

注意:手动配置Build Setting的优先级最高。

优先级(由高到低)

当手动修改了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
上一篇下一篇

猜你喜欢

热点阅读