iOS小技巧

iOS:Module

2021-01-30  本文已影响0人  MonKey_Money

1. Module-最小的代码单元

一个Module是机器代码和数据的最小单元,可以独立于其他代码单元进行链接,
通常,Module是通过编译单个源文件生成的目标文件。例如,当前的test.m被编译成目标文件test.o时,当前的目标文件就代表一个Module
但是,有一个问题,Module在调用的时候会产生开销,比如我们在使用一个静态库的时候。
导入文件时如果使用include的,每次编译的时候就会编译一个我们include的头文件,导入资源的浪费。我们现在使用的import(module)导入头文件,导入的头文件会预先编译成二进制,再有文件导入时就不会重新编译。

1.1,实测module

//A.h文件
#ifdef ENABLE_A
void a() {}
#endif
//B.h文件
#import "A.h"
//module.modulemap文件
module A {
  header "A.h"
}

module B {
  header "B.h"
  export A
}
//use.c文件
#import "B.h"
void use() {
#ifdef ENABLE_A
  a();
#endif
}

我们使用clang编译

// -fmodules:允许使用module语言来表示头文件
// -fmodule-map-file:module map的路径。如不指明默认module.modulemap
// -fmodules-cache-path:编译后的module缓存路径
clang  -fmodules -fmodule-map-file=module.modulemap -fmodules-cache-path=../prebuilt -c use.c -o use.o

我们查看prebuilt->2Q2IP2MFAAABM文件可以看到两个pcm文件,这两个文件就是预编译好的,如果其他文件再引入A和B就不用重新编译了。

1.2.查看AFNetworking文件的modulemap文件

framework module AFNetworking { //声明framework的module名称为AFNetworking
//导入文件的集合
  umbrella header "AFNetworking-umbrella.h"
  export * //把引入的头文件重新导出。
  module * { export * } //把导入头文件修饰成子module,并把符号全部导出
}

其他module的操作,点这里
我们开启module之后无论我们使用include,import或者@import,编译的使用都会被优化成module形式,就是同一个文件只会被编译一次。

1.3.实操

我们创建一个framework,名字为MyOCFramework,再创建一个主工程名字为MyTestApp,打开主工程,点击file->save as workspace,保存到主工程的同一级目录下。然后打来我们的workspace,在工程中,在没有文件被选的情况下,File->Add file to 到我们的workspace。选择我们的framework。
编译我们的framework,能看到会在framework下自动生成Modules 文件.
如果我们想自定义我们的module文件,我们创建modulemap文件,然后在build setting中设置module map file的路径。
我们创建ocmodule.modulemap文件文件内容如下

framework module MyOCFramework {
  umbrella "Headers"

  export *
  module * { export * }
}

module map file设置为MyOCFramework/ocmodule.modulemap,编译成功,并在framework文件中看到module.modulemap。

2.Swift的framework和OC混编

因为在framework中没有桥接文件,所以swift代码没法直接调用oc,我们要使用module,framework已经自动帮我们实现了。
我们可以在swift代码中直接使用oc类,如果我们想在oc类中调用swift代码,我们需要通过module指定头文件#import <项目/项目-Swift.h>
如果我们不想对外暴漏我们的OC类,我们可以创建swiftmodule.private.modulemap

framework module MySwiftFramework_Private {
  explicit module MyOCClass{
      header "MyOCClass.h"
      export *
  }
}

然后在Private Module Map File 中指定路径。
我们不能通过MySwiftFramework 的module 来访问MyOCClass,但是我们可以通过
MySwiftFramework_Private来访问MyOCClass。
Private Module不是真正意义上的私有,我们可以通过MySwiftFramework_Private可以访问,只是供开发者区分。

3.Swift静态库合并

在Xcode 9.0之后,swift开始支持静态库
swift没有头文件的概念,那么我们外界使用swift中的public修饰的类和函数怎么办呢?Swift库引入了一个全新的文件.swiftModule
.swiftModule包含序列化过的AST(抽象语法树),也包含SIL(Swift中间语言,Swift Intermediate Language)。
我们可以看一下我们的framework中,Module中有一个.swiftmodule文件。
创建两个framework库,分别为MySwiftA和MySwiftB
两个库里有一个相同的类

@objc open class MySwiftTeacher: NSObject {
    public func speek() {
        print("speek!")
    }
    @objc public func walk() {
        print("walk!")
    }
}

并把两个静态库编译后的framework放到products目录下脚本

cp -Rv -- "${BUILT_PRODUCTS_DIR}/" "${SOURCE_ROOT}/../Products"

合并两个静态库

libtool -static MySwiftA.framework/MySwiftA MySwiftB.framework/MySwiftB -o libMySwiftC.a
//日志警告,两个静态库都包含MySwiftTeacher.o

我们通过ar -t libMySwiftC.a查看libMySwiftC.a中的目标文件

__.SYMDEF
MySwiftA_vers.o
MySwiftTeacher.o
MySwiftB_vers.o
MySwiftTeacher.o

我们手动组合MySwiftC库


image.png

配置build setting文件

HEADER_SEARCH_PATHS = $(inherited) "${SRCROOT}/MySwiftC/MySwiftA/Headers" "${SRCROOT}/MySwiftC/MySwiftB/Headers"
OTHER_CFLAGS = $(inherited) "-fmodule-map-file=${SRCROOT}/MySwiftC/MySwiftA/module.modulemap" "-fmodule-map-file=${SRCROOT}/MySwiftC/MySwiftB/module.modulemap"
SWIFT_INCLUDE_PATHS = $(inherited) "${SRCROOT}/MySwiftC/MySwiftB" "${SRCROOT}/MySwiftC/MySwiftA"

4.OC映射到Swift方式

为了让oc代码在swift使用中规范,

4.1使用宏

NS_SWIFT_NAME(<#name#>)
NS_REFINED_FOR_SWIFT 在swift方法中, 编译器会在名称前加上
_

4.2.使用apinotes文件

官方文档
前面是项目或者sdk的名称后缀是apinotes,

---
Name: OCFramework
Classes:
- Name: LGToSwift
  SwiftName: ToSwift
  Methods:
  - Selector: "changeTeacherName:"
    Parameters:
    - Position: 0
      Nullability: O
    MethodKind: Instance
    SwiftPrivate: true
    # Availability: nonswift
    #AvailabilityMsg: "prefer 'deinit'"
  - Selector: "initWithName:"
    MethodKind: Instance
    DesignatedInit: true

5.module 相关的 build setting 参数

5.1对module自身的描述:

DEFINES_MODULE:YES/NO,module 化需要设置为 YES
MODULEMAP_FILE:指向 module.modulemap 路径
HEADER_SEARCH_PATHS:modulemap 内定义的 Objective-C 头文件,必须在 HEADER_SEARCH_PATHS 内能搜索到
PRODUCT_MODULE_NAME:module 名称,默认和 Target name 相同

5.2对外部module的引用

FRAMEWORK_SEARCH_PATHS:依赖的 Framework 搜索路径
OTHER_CFLAGS:编译选项,可配置依赖的其他 modulemap 文件路径 -fmodule-map-file={modulemap_path} HEADER_SEARCH_PATHS:头文件搜索路径,可用于配置源码中引用的其他 Library 的头文件 OTHER_LDFLAGS:依赖其他二进制的编译依赖选项 SWIFT_INCLUDE_PATHS:swiftmodule 搜索路径,可用于配置依赖的其他 swiftmodule OTHER_SWIFT_FLAGS:Swift 编译选项,可配置依赖的其他 modulemap 文件路径 -Xcc -fmodule-map-file={modulemap_path}

上一篇下一篇

猜你喜欢

热点阅读