美文网首页Swift
Swift -- 12.Swift混编(上)

Swift -- 12.Swift混编(上)

作者: MissStitch丶 | 来源:发表于2022-03-02 23:42 被阅读0次

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

    相关文章

      网友评论

        本文标题:Swift -- 12.Swift混编(上)

        本文链接:https://www.haomeiwen.com/subject/ujnxrrtx.html