module
(模块):最小的代码单元一个
module
是机器代码和数据的最小单位,可以独立于其他代码单位进行链接通常,
module
是通过编译单个源文件生成的目标文件。例如:当前的test.m
被编译成目标文件test.o
时,当前的目标文件就代表了一个module
这里有一个问题,
module
在调用的时候会产生开销,当使用一个静态库的时:@import TestStaticFramework;
如果静态库中包含许多
.o
文件。这岂不是会导入很多module
?当然不会。在静态链接的时候,也就是静态库链接到主项目或者动态库,最终生成可执行文件或者动态库时,静态链接器可以把多个
module
链接优化成一个,来减少本来多个module
直接调用的问题
module原理
未开启
module
时,当B
文件导入A.h
,C
文件又导入了A.h
和B.h
#include
:A.h
会跟随B
文件和C
文件编译多次。使用#include
造成C
文件重复包含A.h
,所以当C
文件编译时,A.h
又会被编译多次,相当于编译了N * M
次#import
:A.h
依然会跟随B
文件和C
文件编译多次。但使用#import
可以避免C
文件重复包含A.h
,此时C
文件编译,A.h
只编译一次,相当于编译了N + M
次
开启
module
时,头文件会被预先编译成二进制文件,并且每个头文件只会被编译一次。此时无论有多少文件导入头文件,都不会被重复编译,只需要执行N
次即可
Cat
目录中,有A.h
和B.h
两个头文件,还有一个use.c
代码和一个module.modulemap
文件。和Cat
目录平级,创建prebuilt
目录,用来存储编译后的module
缓存
打开
A.h
文件,写入以下代码:#ifdef ENABLE_A void a() {} #endif
打开
B.h
文件,写入以下代码:#import "A.h"
打开
use.c
文件,写入以下代码:#import "B.h" void use() { #ifdef ENABLE_A a(); #endif }
- 在
use.c
文件中,使用了B.h
,同时B.h
使用了A.h
打开
module.modulemap
文件,写入以下代码:module A { header "A.h" } module B { header "B.h" export A }
module.modulemap
文件的作用,它是用来描述头文件
与module
之间映射的关系- 定义了名称为
A
和B
的两个module
- 在
module A
中,定义了header A.h
,表示module A
和A.h
的映射关系- 在
module B
中,定义了header B.h
,和A
同理。export A
表示将B.h
导入的A.h
头文件重新导出通过
clang
命令,开启module
并将use.c
编译成目标文件clang -fmodules -fmodule-map-file=module.modulemap -fmodules-cache-path=../prebuilt -c use.c -o use.o
-fmodules
:允许使用module
语言来表示头文件-fmodule-map-file
:module map
的路径。此参数缺失,默认找module.modulemap
文件。如果文件不存在,执行会报错-fmodules-cache-path
:编译后的module
缓存路径打开
prebuilt
目录,两个.pcm
文件,分别对应A.h
和B.h
,它们就是预编译头文件后的产物
module
在Xcode
中是默认开启的
如果在
Build Settings
中,将Enable Modules
设置为NO
,导入头文件将不能使用@import
方式
开启
module
后,项目中导入头文件,无论使用#include
、#import
、@import
中何种方式,最终都会映射为@import
方式
module解读
查看实际开发中使用的
.modulemap
文件,例如:AFNetworking
打开
AFNetworking.framework
中的module.modulemap
文件framework module AFNetworking { umbrella header "AFNetworking-umbrella.h" export * module * { export * } }
- 定义
module
名称为AFNetworking
,模块是framework
umbrella
:可以理解为伞柄
。一把雨伞的伞柄
下有很多伞骨
,umbrella
的作用是指定一个目录,这个目录即为伞柄
,目录下所有.h
头文件即为伞骨
umbrella header AFNetworking-umbrella.h
:指定module AFNetworking
映射AFNetworking-umbrella.h
文件中所有.h
头文件export *
:*
表示通配符。将AFNetworking-umbrella.h
文件中,所有.h
头文件重新导出module * { export * }
:创建子module
,使用*
通配符,将AFNetworking-umbrella.h
中导入的头文件,按照头文件名称命名为子module
名称。再使用export *
将子module
中导入的头文件重新导出打开
AFNetworking-umbrella.h
文件#ifdef __OBJC__ #import <UIKit/UIKit.h> #else #ifndef FOUNDATION_EXPORT #if defined(__cplusplus) #define FOUNDATION_EXPORT extern "C" #else #define FOUNDATION_EXPORT extern #endif #endif #endif #import "AFNetworking.h" #import "AFHTTPSessionManager.h" #import "AFURLSessionManager.h" #import "AFCompatibilityMacros.h" #import "AFNetworkReachabilityManager.h" #import "AFSecurityPolicy.h" #import "AFURLRequestSerialization.h" #import "AFURLResponseSerialization.h" #import "AFAutoPurgingImageCache.h" #import "AFImageDownloader.h" #import "AFNetworkActivityIndicatorManager.h" #import "UIActivityIndicatorView+AFNetworking.h" #import "UIButton+AFNetworking.h" #import "UIImageView+AFNetworking.h" #import "UIKit+AFNetworking.h" #import "UIProgressView+AFNetworking.h" #import "UIRefreshControl+AFNetworking.h" #import "WKWebView+AFNetworking.h" FOUNDATION_EXPORT double AFNetworkingVersionNumber; FOUNDATION_EXPORT const unsigned char AFNetworkingVersionString[];
AFNetworking-umbrella.h
文件,相当于伞柄
AFNetworking-umbrella.h
文件中,导入的所有.h
头文件,相当于伞骨
项目中,使用
@import AFNetworking
,可以.
出一个子module
列表,它对应的也是伞柄
下的伞骨
列表
查看开源项目
AsyncDisplayKit
中的module.modulemap
打开
module.modulemap
文件framework module AsyncDisplayKit { umbrella header "AsyncDisplayKit.h" export * module * { export * } explicit module ASControlNode_Subclasses { header "ASControlNode+Subclasses.h" export * } explicit module ASDisplayNode_Subclasses { header "ASDisplayNode+Subclasses.h" export * } }
- 定义
module
名称为AsyncDisplayKit
,模块是framework
- 定义伞柄
AsyncDisplayKit.h
- 将
AsyncDisplayKit.h
文件中,所有.h
头文件重新导出- 创建子
module
,使用*
通配符,将AsyncDisplayKit.h
中导入的头文件,按照头文件名称命名为子module
名称。将子module
中导入的头文件重新导出explicit
:显示指明子module
名称
官方文档
更多
API
可查看 官方文档
自定义module
搭建
LGOCFramework
项目
LGOCFramework
是一个动态库项目,创建项目后,系统默认并不提供.modulemap
文件
项目编译后,在
LGOCFramework.framework
中的Modules
目录下,会自动生成module.modulemap
文件
打开
module.modulemap
文件,里面存储了基本的头文件与module
之间映射的关系/* module.modulemap */ framework module LGOCFramework { // umbrella<目录> umbrella header "LGOCFramework.h" explicit module LGTeacher { header "LGTeacher.h" export * } explicit module LGStudent { header "LGStudent.h" export * } }
如果想对
module
进行配置,例如:定义子module
,此时需要自己创建modulemap
文件在项目
LGOCFramework
目录下,创建LGOCFramework.modulemap
文件
将
LGOCFramework.modulemap
文件加入到项目中
在
BuildSetting
中,修改Module Map File
配置项:
Module Map File
:设置.modulemap
文件路径,填写${SRCROOT}
之后的路径即可打开
LGOCFramework.modulemap
文件,写入以下代码:framework module LGOCFramework { umbrella header "LGOCFramework.h" explicit module LGTeacher { header "LGTeacher.h" export * } explicit module LGStudent { header "LGStudent.h" export * } }
- 定义
module
名称为LGOCFramework
,模块是framework
- 定义伞柄
LGOCFramework.h
- 显示指明子
module
名称为LGTeacher
,映射LGTeacher.h
,将LGTeacher.h
中导入的头文件重新导出- 显示指明子
module
名称为LGStudent
,映射LGStudent.h
,将LGStudent.h
中导入的头文件重新导出项目编译后,在
LGOCFramework.framework
中的Modules
目录下,生成的依然是名称为module.modulemap
的文件
由于系统默认识别
.modulemap
文件的名称是module.modulemap
,所以自定义的LGOCFramework.modulemap
文件在编译后,名称依然是module.modulemap
,但里面的内容已经生效
搭建
LGApp
项目
LGApp
是一个App
项目
创建
MulitProject.xcworkspace
,加入LGOCFramework
动态库项目。LGApp
链接LGOCFramework
动态库
打开
ViewController.m
文件,导入LGOCFramework
动态库的头文件,和module
中的配置完全一致
至此自定义
module
成功
Swift库使用OC代码
module映射
搭建
LGSwiftFramework
项目
LGSwiftFramework
是一个Swift
动态库项目
打开
LGOCStudent.h
文件,写入以下代码:#import <Foundation/Foundation.h> @interface LGOCStudent : NSObject - (void)speek; @end
打开
LGOCStudent.m
文件,写入以下代码:#import "LGOCStudent.h" @implementation LGOCStudent - (void)speek { NSLog(@"LGOCStudent--speek"); } @end
打开
LGSwiftTeacher.swift
文件,写入以下代码:import Foundation @objc open class LGSwiftTeacher: NSObject { public func speek() { let s = LGOCStudent() s.speek() print("speek!") } @objc public func walk() { print("walk!") } }
在
LGSwiftTeacher.swift
文件中,调用了OC
代码。在日常项目中,使用桥接文件即可。但在Framework
项目中,没有桥接文件的概念,此时编译报错
解决办法:
创建
LGSwiftFramework.modulemap
文件,写入以下代码:framework module LGSwiftFramework { umbrella "Headers" export * }
- 定义
module
名称为LGSwiftFramework
,模块是framework
- 定义伞柄
Headers
目录- 将
Headers
目录下所有.h
头文件重新导出在
BuildSetting
中,修改Module Map File
配置项:
Headers
目录下的.h
头文件
此时
LGSwiftTeacher.swift
文件中,使用的OC
代码不再报错,项目编译成功
App使用Swift库
承接
自定义module
的案例打开
MulitProject.xcworkspace
文件,加入LGSwiftFramework
动态库项目。LGApp
链接LGSwiftFramework
动态库
在
LGApp
中,打开ViewController.m
文件,使用@import LGSwiftFramework
导入头文件,只能找到一个.Swift
LGSwiftFramework
项目在编译时,系统在.framework
中生成的module.modulemap
文件,会自动生成以下代码:framework module LGSwiftFramework { umbrella "Headers" export * } module LGSwiftFramework.Swift { header "LGSwiftFramework-Swift.h" requires objc }
但这种导入方式,无法使用
LGOCStudent
类
解决办法:
使用
#import
方式,也无法找到LGOCStudent.h
头文件
但
LGSwiftFramework
中的.modulemap
文件,将Headers
目录下所有.h
文件全部重新导出。所以可以强行导入<LGSwiftFramework/LGOCStudent.h>
,导入后LGOCStudent
类可以正常使用#import "ViewController.h" #import <LGSwiftFramework/LGOCStudent.h> @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; LGOCStudent *student=[LGOCStudent new]; } @end
另一种解决办法,通过
.modulemap
文件,暴露出LGOCStudent
:打开
LGSwiftFramework.modulemap
文件,改为以下代码:framework module LGSwiftFramework { umbrella "Headers" export * } module LGSwiftFramework.LGOCStudent { header "LGOCStudent.h" requires objc }
再次编译项目,使用
@import
方式,此时可以找到LGOCStudent
导入
LGSwiftFramework.LGOCStudent
后,LGOCStudent
类可以正常使用#import "ViewController.h" @import LGSwiftFramework.LGOCStudent; @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; LGOCStudent *student=[LGOCStudent new]; } @end
私有module映射
在某些情况下,是否使用特定头文件用于区分指定库的
公共API
和私有API
例如:一个库可能包含分别提供
公共API
和私有API
的头文件LGOCStudent.h
和LGOCStudent_Private.h
。此外,LGOCStudent_Private.h
可能仅在某些版本的库中可用,而在其他版本库中不可用。使用统一的module.modulemap
文件无法表达这一点
LGSwiftFramework
项目创建
LGOCStudent_Private.h
文件,写入以下代码:#import <Foundation/Foundation.h> @interface LGOCStudent_Private : NSObject - (void)speek; @end
创建
LGOCStudent_Private.m
文件,写入以下代码:#import "LGOCStudent_Private.h" @implementation LGOCStudent_Private - (void)speek { NSLog(@"LGOCStudent_Private--speek"); } @end
创建
LGSwiftFramework.private.modulemap
文件,写入以下代码:framework module LGSwiftFramework_Private { module LGOCStudent { header "LGOCStudent_Private.h" export * } }
- 私有
.modulemap
文件的名称,中间的.private
一定要加,这个是命名规则- 定义
module
名称为LGSwiftFramework_Private
,模块是framework
- 定义私有
module
名称,后面一定要加Private
后缀,并且首字母大写- 定义
module
名称为LGOCStudent
,映射LGOCStudent_Private.h
- 将
LGOCStudent_Private.h
中导入的头文件重新导出在
BuildSetting
中,修改Private Module Map File
配置项:
LGApp
项目打开
ViewController.m
文件,导入LGOCStudent.h
和LGOCStudent_Private.h
头文件,此时它们被彻底分开了#import "ViewController.h" @import LGSwiftFramework.LGOCStudent; @import LGSwiftFramework_Private.LGOCStudent; @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; LGOCStudent *student=[LGOCStudent new]; LGOCStudent_Private *sp=[LGOCStudent_Private new]; } @end
Swift静态库
在
Xcode 9
之后,Swift
开始⽀持静态库
Swift
没有头⽂件的概念,外界如何使⽤Swift
中public
修饰的类和函数?
Swift
库中引⼊了⼀个全新的⽂件.swiftmodule
.swiftmodule
包含序列化过的AST
(抽象语法树,Abstract Syntax Tree
),也包含SIL
(Swift
中间语⾔,Swift Intermediate Language
)
Swift静态库合并
搭建
LGSwiftA
项目
LGSwiftA
是一个Swift
静态库项目
打开
LGSwiftTeacher.swift
文件,写入以下代码:import Foundation @objc open class LGSwiftTeacher: NSObject { public func speek() { print("speek!") } @objc public func walk() { print("walk!") } }
搭建
LGSwiftB
项目
LGSwiftB
是一个Swift
静态库项目
打开
LGSwiftTeacher.swift
文件,写入以下代码:import Foundation @objc open class LGSwiftTeacher: NSObject { public func speek() { print("speek!") } @objc public func walk() { print("walk!") } }
创建
MulitProject.xcworkspace
,加入LGSwiftA
、LGSwiftB
两个静态库项目
创建
Products
目录,和MuiltProject.xcworkspace
平级
在
LGSwiftA
、LGSwiftB
项目中,选择Build Phases
,创建Run Script
,写入以下代码:cp -Rv -- "${BUILT_PRODUCTS_DIR}/" "${SOURCE_ROOT}/../Products"
- 使用
cp
命令,将编译后的.framework
文件拷贝到Products
目录编译
LGSwiftA
、LGSwiftB
项目,打开Products
目录,.framework
文件已成功拷贝
使用
libtool
命令,合并LGSwiftA
和LGSwiftB
两个静态库libtool -static \ -o \ libLGSwiftC.a \ LGSwiftA.framework/LGSwiftA \ LGSwiftB.framework/LGSwiftB
由于
LGSwiftA
、LGSwiftB
项目中,都存在了相同的LGSwiftTeacher.swift
文件,使用libtool
命令合并后提示警告libtool: warning same member name (LGSwiftTeacher.o) in output file used for input files: LGSwiftA.framework/LGSwiftA(LGSwiftTeacher.o) and: LGSwiftB.framework/LGSwiftB(LGSwiftTeacher.o) due to use of basename, truncation and blank padding
使用
ar -t libLGSwiftC.a
命令,查看libLGSwiftC.a
的文件列表__.SYMDEF LGSwiftA_vers.o LGSwiftTeacher.o LGSwiftB_vers.o LGSwiftTeacher.o
如果是
OC
动态库,.framework
中可以舍弃Modules
目录,将两个静态库的头文件拷贝到一起即可但
Swift
动态库,包含了x.swiftmodule
目录,里面的.swiftmodule
文件不能舍弃,此时应该如何处理?
解决办法:
Products
目录下,创建LGSwiftC
目录,将库文件libLGSwiftC.a
拷贝到LGSwiftC
目录下
仿照
Cocoapods
生成三方库的目录结构,在LGSwiftC
目录下,创建Public
目录,将LGSwiftA.framework
和LGSwiftB.framework
拷贝到Public
目录下
打开
LGSwiftA.framework
和LGSwiftB.framework
文件,将里面的库文件、.plist
文件、签名等信息全部删除,最终只保留Headers
和Modules
两个目录
虽然生成
.framework
时,自动创建了Modules
目录。但编译时,.modulemap
文件和x.swiftmodule
目录,应该和Headers
目录平级将
.modulemap
文件和x.swiftmodule
目录,从Modules
目录移动到.framework
文件下,和Headers
目录平级。然后删除Modules
目录
此时静态库合并完成
App使用合并后的静态库
搭建
LGApp
项目
LGApp
是一个App
项目
将
LGSwiftC
目录,拷贝到LGApp
项目的根目录下
将
libLGSwiftC.a
库文件,拖动到项目中的Frameworks
目录
勾选
Copy items if needed
,点击Finish
创建
xcconfig
文件,并配置到Tatget
上,写入以下代码:HEADER_SEARCH_PATHS = $(inherited) '${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework/Headers' HEADER_SEARCH_PATHS = $(inherited) '${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework/Headers'
- 指定头文件路径
Header Search Paths
在
ViewController.m
中,使用module
方式导入LGSwiftA
,编译报错
- 使用
module
方式,还需要加载modulemap
文件的路径打开
xcconfig
文件,改为以下代码:HEADER_SEARCH_PATHS = $(inherited) '${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework/Headers' HEADER_SEARCH_PATHS = $(inherited) '${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework/Headers' OTHER_CFLAGS = $(inherited) '-fmodule-map-file=${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework/module.modulemap' OTHER_CFLAGS = $(inherited) '-fmodule-map-file=${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework/module.modulemap'
OTHER_CFLAGS
:传递给用来编译C
或者OC
的编译器,当前就是clang
- 加载
modulemap
文件的路径- 对应
Build Setting
中的配置项
打开
ViewController.m
,写入以下代码:#import "ViewController.h" @import LGSwiftA; @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; LGSwiftTeacher *teacher = [LGSwiftTeacher new]; } @end
编译成功,
Swift
静态库中的LGSwiftTeacher
类,可以在OC
下正常使用
但此时还有另一个问题:
在
LGSwiftTest.swift
中,使用import
导入LGSwiftA
,还是编译报错
- 在
Swift
中,还需要加载swiftmodule
文件的路径打开
xcconfig
文件,改为以下代码:HEADER_SEARCH_PATHS = $(inherited) '${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework/Headers' HEADER_SEARCH_PATHS = $(inherited) '${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework/Headers' OTHER_CFLAGS = $(inherited) '-fmodule-map-file=${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework/module.modulemap' OTHER_CFLAGS = $(inherited) '-fmodule-map-file=${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework/module.modulemap' SWIFT_INCLUDE_PATHS = $(inherited) '${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework' SWIFT_INCLUDE_PATHS = $(inherited) '${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework'
SWIFT_INCLUDE_PATHS
:传递给SwiftC
编译器- 在指定路径下查找
swiftmodule
文件- 对应
Build Setting
中的配置项
打开
LGSwiftTest.swift
文件,写入以下代码:import Foundation import LGSwiftA @objc open class LGSwiftTest: NSObject { public override init() { super.init() let t = LGSwiftTeacher() t.speek() } }
编译成功,
Swift
静态库中的LGSwiftTeacher
类,可以在Swift
下正常使用
在
LGSwiftA.framework
和LGSwiftB.framework
两个静态库中,都存在LGSwiftTeacher
,有时甚至会存在头文件相同的情况。所以在案例中,手动构建的目录结构,可以有效避免相同头文件的冲突。并且在使用的时候,导入的头文件是谁的,使用的LGSwiftTeacher
对应就是谁的链接静态库,只要没指定
-all_load
或-ObjC
参数,默认会使用-noall_load
参数。所以在同一个文件内,即使导入两个头文件,当链接一个文件找到代码后,就不会链接另一个,因此也不会冲突
OC映射到Swift方式
搭建
OCFramework
项目
OCFramework
是一个OC
动态库项目
打开
LGToSwift.h
文件,写入以下代码:#import <Foundation/Foundation.h> typedef NS_ENUM(NSUInteger, LGTeacherName) { LGTeacherNameHank, LGTeacherNameCat, }; typedef NSString * LGTeacherNameString; extern NSString *getTeacherName(void); extern NSString * const LGTeacherCat; extern LGTeacherNameString const LGTeacherHank; @interface LGToSwift : NSObject - (nullable NSString *)teacherNameForIndex:(NSUInteger)index; - (BOOL)changeTeacherName:(nullable NSDictionary<NSString *, id> *)options; @end
打开
LGToSwift.m
文件,写入以下代码:#import "LGToSwift.h" NSString *getTeacherName(void) { return nil; } NSString * const LGTeacherCat = @"Cat"; LGTeacherNameString const LGTeacherHank = @"Hank"; @implementation LGToSwift - (nullable NSString *)teacherNameForIndex:(NSUInteger)pageIndex { return nil; } - (BOOL)changeTeacherName:(nullable NSDictionary<NSString *, id> >*)options { return NO; } @end
搭建
SwiftProject
项目
SwiftProject
是一个App
项目
创建
MulitProject.xcworkspace
,加入OCFramework
动态库项目。SwiftProject
链接OCFramework
动态库
在
ViewController.swift
中,使用OCFramework
动态库的方法,出现以下问题:
- 无法对
LGTeacherNameString
类型的属性赋值枚举值teacherName
方法的命名,被改为teacherName(for:)
,但我们预期的是teacherName(forIndex:)
changeTeacherName
方法,我们希望它作为私有方法,并以双下划线字符__
开头
解决办法:
可以使用特定宏,改变映射规则
在
OCFramework
中,打开LGToSwift.h
文件,改为以下代码:#import <Foundation/Foundation.h> typedef NS_ENUM(NSUInteger, LGTeacherName) { LGTeacherNameHank, LGTeacherNameCat, }; typedef NSString * LGTeacherNameString NS_TYPED_EXTENSIBLE_ENUM; extern NSString *getTeacherName(void); extern NSString * const LGTeacherCat; extern LGTeacherNameString const LGTeacherHank; @interface LGToSwift : NSObject - (nullable NSString *)teacherNameForIndex:(NSUInteger)index NS_SWIFT_NAME(teacherName(forIndex:)); - (BOOL)changeTeacherName:(nullable NSDictionary<NSString *, id> *)options NS_REFINED_FOR_SWIFT; @end
NS_TYPED_EXTENSIBLE_ENUM
:属性指示编译器,使用struct(swift_wrapper(struct)属性)
,通过指定NS_TYPED_ENUM
宏,编译器被指示使用enum(swift_wrapper(enum)属性)
NS_SWIFT_NAME
:通过指定NS_SWIFT_NAME
宏,可以添加一些详细信息以使函数清晰可见NS_REFINED_FOR_SWIFT
:通过指定NS_REFINED_FOR_SWIFT
宏,Swift
的Clang Importer
将做一些额外的工作,将该方法导入为私有方法,并以双下划线字符__
开头在
SwiftProject
中,打开ViewController.swift
文件,写入以下代码:import UIKit import OCFramework class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let Hank: LGTeacherNameString = .hank let teacher: LGToSwift = LGToSwift() teacher.teacherName(forIndex: 1) } } extension LGToSwift { func change() -> Bool { return __changeTeacherName(nil) } }
问题解决,
OC
中的方法和属性,在Swift
中使用符合预期
但另一个问题又出现了:
通过指定宏的方式,需要修改原有代码。如果一个使用
OC
开发的SDK
需要适配Swift
,需要为每一个方法或属性指定宏,这将是工程浩大且费时费力的事情
解决办法:
使用
.apinotes
文件,代替宏的方式在
OCFramework
目录下,创建OCFramework.apinotes
文件
在
OCFramework
中,将OCFramework.apinotes
文件加入到项目
.apinotes
文件必须要放在SDK
的目录中,采用yaml
格式书写,类似JSON
格式打开
OCFramework.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'"
- 将
changeTeacherName:
方法,在Swift
中设置为不可用编译项目,显示自定义错误提示:
prefer 'deinit'
.apinotes
文件最终会被放入编译后的.framework
中
官方文档
更多
API
可查看 官方文档
总结
module
(模块):最小的代码单元,表示头文件与目标文件的关系
modulemap
:最小的代码单元,表示头文件与目标文件的映射定义一个
module
:
export
:导出当前代表的头文件使用的头文件export *
:匹配目录下所有的头文件module *
:目录下所有的头文件都当作一个子module
explicit *
:显式声明一个module
的名称
Swift
库使用OC
代码:
- 不能使用桥接文件
OC
的头文件放到modulemap
下- 使用私有
modulemap
更好的表达公共API
和私有API
Swift
静态库合并
- 必须保留
.swiftmodule
文件(Swift
的头文件)- 使用
libtool
命令,合并静态库本身- 用到的头文件、
Swift
头文件以及modulemap
文件,通过目录的形式放到一起OC
要用合并的静态库:clang: other c flags
:-fmodule-map-file <modulemap path>
Swift
要用合并的静态库:SwiftC :other swift flags
显式告诉SwiftC <modulemap dir>
OC
映射到Swift
方式
- 宏
- 使用
.apinotes
文件:<工程名称>.apinotes
网友评论