module & Swift库

2021-12-05 23:08:53 浏览数 (1)

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

0 人点赞