前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >module & Swift库

module & Swift库

原创
作者头像
ruochen
发布2021-12-05 23:08:53
2K0
发布2021-12-05 23:08:53
举报

一个module是机器代码和数据的最小单位,可以独立于其他代码单位进行链接

通常,module是通过编译单个源文件生成的目标文件。例如:当前的test.m被编译成目标文件test.o时,当前的目标文件就代表了一个module

这里有一个问题,module在调用的时候会产生开销,当使用一个静态库的时:@import TestStaticFramework;

如果静态库中包含许多.o文件。这岂不是会导入很多module

当然不会。在静态链接的时候,也就是静态库链接到主项目或者动态库,最终生成可执行文件或者动态库时,静态链接器可以把多个module链接优化成一个,来减少本来多个module直接调用的问题

module原理

未开启module时,当B文件导入A.hC文件又导入了A.hB.h

* #includeA.h会跟随B文件和C文件编译多次。使用#include造成C文件重复包含A.h,所以当C文件编译时,A.h又会被编译多次,相当于编译了`NM次 *#importA.h依然会跟随B文件和C文件编译多次。但使用#import可以避免C文件重复包含A.h,此时C文件编译,A.h只编译一次,相当于编译了NM`次

开启module时,头文件会被预先编译成二进制文件,并且每个头文件只会被编译一次。此时无论有多少文件导入头文件,都不会被重复编译,只需要执行N次即可

Cat目录中,有A.hB.h两个头文件,还有一个use.c代码和一个module.modulemap文件。和Cat目录平级,创建prebuilt目录,用来存储编译后的module缓存

打开A.h文件,写入以下代码:

代码语言:javascript
复制
#ifdef ENABLE_A
void a() {}
#endif

打开B.h文件,写入以下代码:

代码语言:javascript
复制
#import "A.h"

打开use.c文件,写入以下代码:

代码语言:javascript
复制
#import "B.h"
void use() {
#ifdef ENABLE_A
 a();
#endif
}

use.c文件中,使用了B.h,同时B.h使用了A.h

打开module.modulemap文件,写入以下代码:

代码语言:javascript
复制
module A {
 header "A.h"
}

module B {
 header "B.h"
 export A
}

module.modulemap文件的作用,它是用来描述头文件module之间映射的关系 定义了名称为AB的两个modulemodule A中,定义了header A.h,表示module AA.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-filemodule map的路径。此参数缺失,默认找module.modulemap文件。如果文件不存在,执行会报错 -fmodules-cache-path:编译后的module缓存路径

打开prebuilt目录,两个.pcm文件,分别对应A.hB.h,它们就是预编译头文件后的产物

moduleXcode中是默认开启的

如果在Build Settings中,将Enable Modules设置为NO,导入头文件将不能使用@import方式

开启module后,项目中导入头文件,无论使用#include#import@import中何种方式,最终都会映射为@import方式

module解读

查看实际开发中使用的.modulemap文件,例如:AFNetworking

打开AFNetworking.framework中的module.modulemap文件framework module AFNetworking

代码语言:javascript
复制
 {
 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文件

代码语言:javascript
复制
#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

代码语言:javascript
复制
{
 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.hAsyncDisplayKit.h文件中,所有.h头文件重新导出 创建子module,使用*通配符,将AsyncDisplayKit.h中导入的头文件,按照头文件名称命名为子module名称。将子module中导入的头文件重新导出explicit:显示指明子module名称

官方文档

更多API可查看 官方文档

自定义module

搭建LGOCFramework项目

LGOCFramework是一个动态库项目,创建项目后,系统默认并不提供.modulemap文件

项目编译后,在LGOCFramework.framework中的Modules目录下,会自动生成module.modulemap文件

打开module.modulemap文件,里面存储了基本的头文件与module之间映射的关系

代码语言:javascript
复制
/* 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文件,写入以下代码:

代码语言:javascript
复制
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文件,写入以下代码:

代码语言:javascript
复制
#import <Foundation/Foundation.h>

@interface LGOCStudent : NSObject

- (void)speek;

@end

打开LGOCStudent.m文件,写入以下代码:

代码语言:javascript
复制
#import "LGOCStudent.h"

@implementation LGOCStudent

- (void)speek {
   NSLog(@"LGOCStudent--speek");
}

@end

打开LGSwiftTeacher.swift文件,写入以下代码:

代码语言:javascript
复制
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文件,写入以下代码:

代码语言:javascript
复制
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文件,会自动生成以下代码:

代码语言:javascript
复制
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类可以正常使用

代码语言:javascript
复制
#import "ViewController.h"
#import <LGSwiftFramework/LGOCStudent.h>

@implementation ViewController

- (void)viewDidLoad {
   [super viewDidLoad];
   LGOCStudent *student=[LGOCStudent new];
}

@end

另一种解决办法,通过.modulemap文件,暴露出LGOCStudent

打开LGSwiftFramework.modulemap文件,改为以下代码:

代码语言:javascript
复制
framework module LGSwiftFramework {
   umbrella "Headers"
   export *
}

module LGSwiftFramework.LGOCStudent {
   header "LGOCStudent.h"
   requires objc
}

再次编译项目,使用@import方式,此时可以找到LGOCStudent

导入LGSwiftFramework.LGOCStudent后,LGOCStudent类可以正常使用

代码语言:javascript
复制
#import "ViewController.h"
@import LGSwiftFramework.LGOCStudent;

@implementation ViewController

- (void)viewDidLoad {
   [super viewDidLoad];
   LGOCStudent *student=[LGOCStudent new];
}

@end

私有module映射

在某些情况下,是否使用特定头文件用于区分指定库的公共API私有API

例如:一个库可能包含分别提供公共API私有API的头文件LGOCStudent.hLGOCStudent_Private.h。此外,LGOCStudent_Private.h可能仅在某些版本的库中可用,而在其他版本库中不可用。使用统一的module.modulemap文件无法表达这一点

LGSwiftFramework项目

创建LGOCStudent_Private.h文件,写入以下代码:

代码语言:javascript
复制
#import <Foundation/Foundation.h>

@interface LGOCStudent_Private : NSObject

- (void)speek;

@end

创建LGOCStudent_Private.m文件,写入以下代码:

代码语言:javascript
复制
#import "LGOCStudent_Private.h"

@implementation LGOCStudent_Private

- (void)speek {
   NSLog(@"LGOCStudent_Private--speek");
}

@end

创建LGSwiftFramework.private.modulemap文件,写入以下代码:

代码语言:javascript
复制
framework module LGSwiftFramework_Private {
   module LGOCStudent {
       header "LGOCStudent_Private.h"
       export *
   }
}

私有.modulemap文件的名称,中间的.private一定要加,这个是命名规则 定义module名称为LGSwiftFramework_Private,模块是framework 定义私有module名称,后面一定要加Private后缀,并且首字母大写 定义module名称为LGOCStudent,映射LGOCStudent_Private.hLGOCStudent_Private.h中导入的头文件重新导出

BuildSetting中,修改Private Module Map File配置项:

LGApp项目

代码语言:javascript
复制
打开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没有头⽂件的概念,外界如何使⽤Swiftpublic修饰的类和函数?

Swift库中引⼊了⼀个全新的⽂件.swiftmodule

.swiftmodule包含序列化过的AST(抽象语法树,Abstract Syntax Tree),也包含SILSwift中间语⾔,Swift Intermediate Language

Swift静态库合并

搭建LGSwiftA项目

LGSwiftA是一个Swift静态库项目

打开LGSwiftTeacher.swift文件,写入以下代码:

代码语言:javascript
复制
import Foundation

@objc open class LGSwiftTeacher: NSObject {

   public func speek() {
       print("speek!")
   }

   @objc public func walk() {
       print("walk!")
   }
}

搭建LGSwiftB项目

LGSwiftB是一个Swift静态库项目

打开LGSwiftTeacher.swift文件,写入以下代码:

代码语言:javascript
复制
import Foundation

@objc open class LGSwiftTeacher: NSObject {

   public func speek() {
       print("speek!")
   }

   @objc public func walk() {
       print("walk!")
   }
}

创建MulitProject.xcworkspace,加入LGSwiftALGSwiftB两个静态库项目

创建Products目录,和MuiltProject.xcworkspace平级

LGSwiftALGSwiftB项目中,选择Build Phases,创建Run Script,写入以下代码:cp -Rv -- "${BUILT_PRODUCTS_DIR}/" "${SOURCE_ROOT}/../Products"

使用cp命令,将编译后的.framework文件拷贝到Products目录

编译LGSwiftALGSwiftB项目,打开Products目录,.framework文件已成功拷贝

使用libtool命令,合并LGSwiftALGSwiftB两个静态库libtool -static \ -o \ libLGSwiftC.a \ LGSwiftA.framework/LGSwiftA \ LGSwiftB.framework/LGSwiftB

由于LGSwiftALGSwiftB项目中,都存在了相同的LGSwiftTeacher.swift文件,使用libtool命令合并后提示警告libtool: warning same member name (LGSwiftTeacher.o) in output file usedfor 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.frameworkLGSwiftB.framework拷贝到Public目录下

打开LGSwiftA.frameworkLGSwiftB.framework文件,将里面的库文件、.plist文件、签名等信息全部删除,最终只保留HeadersModules两个目录

虽然生成.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上,写入以下代码:

代码语言:javascript
复制
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文件,改为以下代码:

代码语言:javascript
复制
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,写入以下代码:

代码语言:javascript
复制
#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文件,改为以下代码:

代码语言:javascript
复制
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文件,写入以下代码:

代码语言:javascript
复制
import Foundation
import LGSwiftA

@objc open class LGSwiftTest: NSObject {

   public override init() {
       super.init()

       let t = LGSwiftTeacher()
       t.speek()
   }
}

编译成功,Swift静态库中的LGSwiftTeacher类,可以在Swift下正常使用

LGSwiftA.frameworkLGSwiftB.framework两个静态库中,都存在LGSwiftTeacher,有时甚至会存在头文件相同的情况。所以在案例中,手动构建的目录结构,可以有效避免相同头文件的冲突。并且在使用的时候,导入的头文件是谁的,使用的LGSwiftTeacher对应就是谁的

链接静态库,只要没指定-all_load-ObjC参数,默认会使用-noall_load参数。所以在同一个文件内,即使导入两个头文件,当链接一个文件找到代码后,就不会链接另一个,因此也不会冲突

OC映射到Swift方式

搭建OCFramework项目

OCFramework是一个OC动态库项目

打开LGToSwift.h文件,写入以下代码:

代码语言:javascript
复制
#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文件,写入以下代码:

代码语言:javascript
复制
#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文件,改为以下代码:

代码语言:javascript
复制
#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)indexNS_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文件,写入以下代码:

代码语言:javascript
复制
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格式

代码语言:javascript
复制
打开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(模块):最小的代码单元,表示头文件与目标文件的关系undefined 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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • module原理
  • module解读
  • 自定义module
  • Swift库使用OC代码
  • Swift静态库
  • OC映射到Swift方式
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档