脚本合并FrameworkSwift iOS 开发每天分享优质文章

14、iOS强化 --- Module与Swift库(OC方法转

2021-03-22  本文已影响0人  Jax_YD

Module(模块)

@import TestStaticFramework;

这个静态库中可能包含了许多的.o文件。岂不是要导入很多的Module
并不需要,在静态链接的时候,也就是静态库链接到主项目或者动态库,最后生成可执行文件或者动态库时,静态链接器可以把多个Module链接优化成一个,来减少原本多个Module直接调用的问题。

/// 声明一个module A,它映射的头文件是 A.h
module A {
  header "A.h"
}

/// 声明一个module B,它映射的头文件是 B.h
module B {
  header "B.h"  
  /// 导出 A,这里假设 "B.h" 映入了 "A.h";
  /// 那么 导出 的意思就是将"B.h"引入的其他的"头文件" 也暴露出来。 
  export A
}

---
通常我们看到的`module`里面是 {export *} ,如上面的`AFNetworking.modulemap`
"*" 是通配符,意思是所有引入的`头文件`,全部 导出。

我们来读一下AFNetworking.modulemap

/// framework module 名称 AFNetworking
framework module AFNetworking {
  /// umbrella <目录> 伞柄  <目录>/.h
  /// AFNetworking-umbrella.h 伞柄 
  /// AFNetworking-umbrella.h/.h 伞骨(里面引入的所有的其他头文件)
  umbrella header "AFNetworking-umbrella.h"

  /// 重新导出
  export *
  /// module: 子module*
  module * { export * }
}
image.png
如果要显示指明子module的名称,要加上explicit关键字:
/// 假设在 `AFNetworking.modulemap` 中显示指明 `子module` 
framework module AFNetworking {
  /// umbrella <目录> 伞柄  <目录>/.h
  /// AFNetworking-umbrella.h 伞柄 
  /// AFNetworking-umbrella.h/.h 伞骨(里面引入的所有的其他头文件)
  umbrella header "AFNetworking-umbrella.h"

  /// 重新导出
  export *
  /// module: 子module*
  module * { export * }

  /// 假设 `子module` 叫 `SubAFN`
  explicit module SubAFN {
    header "SubAFN.h"
    export *
  }
}

此时Module的好处就提现出来了。它会预先把A.h编译成二进制文件,那么后面不管有多少个文件使用到A.h,只有一个A.h的二进制文件(除非A.h被改动)。
下面我们来演示一下:
1、module.modulemap:

module A {
  header "A.h"
}

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

2、终端指令:

# -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

3、B.h 引用 A.h, use.c 引用 B.h:

image.png

可以看到生成了两个二级制文件,这就是我们头文件预先编译的产物:

image.png

Swift库

framework module YSSwiftFramework_Private {
    module YSOCStudent {
        header "YSOCStudent.h"
        export *
    }
}
@import YSSwiftFramework_Private.YSOCStudent;

这样做虽然做不到完全的隐藏,但是可以达到提醒使用者的作用,告诉用户这是一个私有库,不要随便使用。

这里还有另外一种方法可以达到上面的要求:swiftOC遵守同一个协议,通过协议来调用OC的代码,从而做到隐藏OC代码的效果。


Swift Module

我们要怎么用Swift Module呢?下面我们通过swift静态库来看一下。


Swift 静态库的合并

libtool -static SwiftA SwiftB -o libSwiftC.a

此时会有一个警告,提示我们合并的两个库文件中有相同的文件。

image.png
这就是我们使用libtool的好处,我们之前讲过可以使用ar来合并静态库,但是使用ar的情况下,先解压再合并,可能发生文件的替换。
我们来查看下当前静态库里面有哪些.o文件:
image.png
可以看到,两个库里面的同名.o文件都在,并没有产生替换。
HEADER_SEARCH_PATHS = $(inherited) "SwiftC/SwiftA.framework/Headers" "SwiftC/SwiftB.framework/Headers"

// OTHER_CFLAGS: 传递给 用来编译C或者OC的编译器,当前就是clang
OTHER_CFLAGS="-fmodule-map-file=${SRCROOT}/SwiftC/SwiftA.framework/module.modulemap" "-fmodule-map-file=${SRCROOT}/SwiftC/SwiftB.framework/module.modulemap"

// SWIFT_INCLUDE_PATHS: 传递给SwiftC编译器,告诉它去下面的路径查找module.file
SWIFT_INCLUDE_PATHS="${SRCROOT}/SwiftC/SwiftA.framework" "${SRCROOT}/SwiftC/SwiftB.framework"

这里强调一下: 配置OTHER_CFLAGS是为了给OC代码用;配置SWIFT_INCLUDE_PATHS是为了给Swift代码用。

image.png

这也与两门语言的特性有关系,OC是运行时语言,我们已经告诉编译器,头文件的地址,所以只要运行时能够找到对应的符号就不会报错。但是Swift就不一样,在编译的时候检测到可能存在的隐患就会报错。

另外,不管是静态库还是动态库的合并,大家尽量用不同的文件夹隔开要合并的库的库文件,这样可以预防Header里面有相同的文件(也就是我们上面的埋点)。


Swift配置

在我们日常的开发过程中,Swift去使用OC的一些方法的时候,Swift会进行一些优化。
比如:

/// OC 定义
- (void)ysOCFuncationWithValue:(NSString *)value WithKey:(NSString *)key;
/// Swift 使用
let obj = YSObject.init()
obj.ysOCFuncation(withValue: "123", withKey: "key")

/**************此时我们如果想要规范一下Swift中的函数名可以这样*****************/
- (void)ysOCFuncationWithValue:(NSString *)value WithKey:(NSString *)key
    NS_SWIFT_NAME(ysAction(key:value:));
/// Swift 使用
let obj = YSObject.init()
obj.ysAction(key: "key", value: "123")

如果想要定义私有方法:

// NS_REFINED_FOR_SWIFT从现在开始,Swift的Clang Importer将做一些额外的工作并将该方法导入为私有方法,并以双下划线字符开头__,例如:
//- (BOOL)changeTeacherName:(nullable NSDictionary<NSString *, id> *)options;
- (BOOL)changeTeacherName:(nullable NSDictionary<NSString *, id> *)options
    NS_REFINED_FOR_SWIFT;
Name: OCFramework // SDK 名称
Classes:
- Name: LGToSwift // 类名
  SwiftName: ToSwift // 在Swift中的名称
  Methods: // 方法
  - Selector: "changeTeacherName:" // 方法名
    Parameters:
    - Position: 0
      Nullability: O
    MethodKind: Instance
    SwiftPrivate: true
    Availability: nonswift // 设置在Swift中不能用
    AvailabilityMsg: "这个不能用" // 提示
  - Selector: "initWithName:"
    MethodKind: Instance
    DesignatedInit: true
image.png
这个时候我们就可以结合脚本批量修改。
另外还有很多的用法,可以参考:API Notes
上一篇下一篇

猜你喜欢

热点阅读