美文网首页
Module & Swift库 (6)

Module & Swift库 (6)

作者: 为了自由的白菜 | 来源:发表于2021-05-18 09:57 被阅读0次

    Module & Swift库 (6)

    Module

    Module定义

    Module(模块)-最小的代码单元
    一个Module是机器代码和数据的最小单位,可以独立于其他代码单位进行链接。
    通常,Module是通过编译单个源文件生成的目标文件。例如,当前的test.m被编译成目标文件test.o时,当前的目标文件就代表了一个Module。

    但是,有一个问题,Module在调用的时候会产生开销,比如我们在使用一个静态库的时候.

    include 与 import的区别

    1. #import导入头文件的好处 -> 头文件会编译生成一份二进制文件, 只用编译一份.
    2. #include导入头文件, 不同的文件会重新编译导致编译多份.

    module脚本

    1. 打开module原理工程
    2. 运行脚本module
      # -fmodules:允许使用module语言来表示头文件
      # -fmodule-map-file:module map的路径。如不指明默认module.modulemap
      # -fmodules-cache-path:编译后的module缓存路径
      clang  -fmodules -fmodule-map-file=Cat.modulemap -fmodules-cache-path=../prebuilt -c use.c -o use.o
      
    3. 文章参考
    4. 终端运行clang -fmodules -fmodule-map-file=module.modulemap -fmodules-cache-path=../prebuilt -c use.c -o use.o -> .o文件
    5. 去缓存目录里面找.pcm文件 -> 就是头文件编译之后的产物
    6. .modulemap -> 描述头文件跟module之间的映射关系
      // 用来描述头文件与module之间映射的关系
      // A -> A.h
      module A {
         header "A.h"
      }
      
      // B -> B.h
      module B {
         header "B.h"
         //  导入 -> 重新导出(把使用的其他头文件一并暴露出来) 通常使用通配符 export * 导出所有的
         export A
      }
      

    看下AFNetworking的modulemap

    module.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 * }
        
        //explicit: 可以指定子module的名称
        //explicit module ASControlNode_Subclasses {
            //header "ASControlNode+Subclasses.h"
            //export *
        //}
    }
    ```
    

    注意:

    1. -fmodules是Xcode默认开启的
    2. 当开启的时候 #include/#import/@import -> 转化成 @import

    Module实操

    1. App 与 Framework放在一起
      1. File -> Save As Workspace -> 取名 -> 关闭工程重新从workspace打开
      2. 工程左下角 + -> 库文件
      3. 编译APP的时候,同时编译库文件
        1. 将库文件的编译产物.framework -> 拖拽到 -> APP的Targets/General/Frameworks
        2. 如果没有编译,也没关系,手动编译一下库文件
    2. 手写modulemap
      1. 配置Targets -> Build Settings -> module -> Module Map File
      2. 编译 -> 默认生成module.modulemap
    3. APP使用
      1. 记得暴露库的头文件 -> 库 -> Build Phases -> Headers -> 查看头文件是否暴露

    Swift库使用Module与OC混编

    1. swift -> 调用OC时, 需要一个桥接文件, 但是在swift中是没有桥接文件一说的.
    2. 解决方法,定义modulemap文件LGSwiftFramework来暴露 -> 记得配置Build Settings -> module map file -> 编译,可以正常使用OC文件 -> 问题:此时外面的使用Swift库的也能使用了
    3. 只有自己使用的情况LGSwiftFramework.private -> 注意命名中间一定要有private,这是是规则 -> 配置 private module map file ->注意:此时外界也是可以访问到的,private只是有助于区分私有的
      //_Private固定写法规则,私有的modulemap文件
      framework module LGSwiftFramework_Private {
          module LGOCStudent {
          header "LGOCStudent.h"
          export *
          }
      }
      
    4. 还有一种方法, 这里只讲理论 -> OC语言特性 协议 -> Swift -> 协议(暴露出去) -> OC

    Swift库

    定义

    在Xcode9之后, Swift开始支持静态库.
    Swift没有头文件的概念, 那么我们外界要使用Swift中用public修饰的类和函数怎么办?
    Swift库中引入了一个全新的文件.swiftmodule.
    .swiftmodule包含序列化过的AST(抽象语法书, Abstract Syntax Tree), 也包含SIL(Swift中间语言, Swift Intermediate Language).

    1. 两个Swift库

      1. 两个库都有同样的swift命名文件
      2. 分别编译(有脚本) -> 静态库
        1. 脚本的作用 -> 将编译后的库拷贝到一个文件夹(具体参考该工程设置)
    2. libtool -static LGSwiftA LGSwiftB -o libLGSwiftC.a

      1. 注意将两个库拷贝出来
      2. 确定命令 -> 有警告 -> 两个库有相同的文件命名
        1. 注意: 用libtool会报冲突, 但是不会替换
        2. ar -t libLGSwiftC.a -> 列出合并库的.o文件列表
      3. 然后就有问题了,两个静态库的Headers文件以及Modules文件怎么处理
        1. OC可以不要Modules文件, 只把Header文件放在一起就可以
        2. 但是Swift会多生成swiftModule文件,是个合并难题
        3. 将两个库多余的数据删除,只保留Headers和Module,跟libLGSwiftC.a合成一个文件
        4. image.png
    3. 参考cocoapods的处理

      1. 会把所有库的头文件放在pods -> Headers 目录下
    4. 将合并的头文件拖到项目中的Frameworks

      1. 新建xcconfig -> 告诉项目头文件所在地方
      2. xcconfig文件配置如下
        HEADER_SEARCH_PATHS = $(inherited) "${SRCROOT}/LGSwiftC/Public/         LGSwiftA.framework/Headers" "${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework/Headers"
        // OTHER_CFLAGS:传递给用来编译C或者OC的编译器,当前就是clang
        // OTHER_CFLAGS -> 相当于Build Setings里的Other C flag
        OTHER_CFLAGS="-fmodule-map-file=${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework/module.modulemap" "-fmodule-map-file=${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework/module.modulemap"
        
        // SWIFT_INCLUDE_PATHS: 传递给SwiftC编译器,告诉他去下面的路径中查找module.file
        // SWIFT_INCLUDE_PATHS相当于Build Setting -> 搜import path
        SWIFT_INCLUDE_PATHS="${SRCROOT}/LGSwiftC/Public/LGSwiftB.framework"  "${SRCROOT}/LGSwiftC/Public/LGSwiftA.framework"
        
      3. 以上编译没有问题, 但是swift运行库LGSwiftA文件会报错
        1. 因为上面的OTHER_CFLAGS是编译C或者OC的
        2. 请看上面SWIFT_INCLUDE_PATHS的配置
          1. 注意将Modules文件夹里的文件配置到跟上一级Headers相同的目录下,并删除Modules

    Swift库文件合并总结

    1. 合并库文件 -> libLGSwiftC.a
    2. 处理头文件
      1. module文件已经处理了, 让编译器找到module文件就可以了
      2. OC配置 -> OTHER_CFLAGS
      3. Swift配置 -> SWIFT_INCLUDE_PATHS

    Swift配置.apinotes

    当我们Swift调用OC方法的时候, 调用的命名有可能有变化.

    1. 那么不想Swift进行优化怎么处理

      1. 宏 -> NS_SWIFT_NAME -> 规定名称
      typedef NS_ENUM(NSUInteger, LGTeacherName) {
      LGTeacherNameHank,
      LGTeacherNameCat,
      };
      
      //属性指示编译器使用struct(swift_wrapper(struct)属性),而与NS_TYPED_ENUM,编译器被指示使用enum(swift_wrapper(enum)属性)
      typedef NSString * LGTeacherNameString NS_TYPED_EXTENSIBLE_ENUM;
      
      extern NSString *getTeacherName(void);
      extern NSString * const LGTeacherCat;
      extern LGTeacherNameString const LGTeacherHank;
      
      // 宏来配置 -》SDK -〉 OC -》 修改原有的代码 -〉发布新版本
      // 弊端 -> 工作量大
      @interface LGToSwift : NSObject
      
      - (instancetype)initWithName:(NSString *)name;
      
      - (id)objectForKeyedSubscript:(id)key; // subscript getter
      - (void)setObject:(id)obj forKeyedSubscript:(id)key;
      // 通过指定NS_SWIFT_NAME宏,我们可以添加一些详细信息以使函数清晰可见,从而使其变得如下所示:
      // 规范
      - (nullable NSString *)teacherNameForIndex:(NSUInteger)index NS_SWIFT_NAME(teacherName(forIndex:));
      
      // 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;
      
    2. 通过宏的方法不可取, 工作量大 -> 那么怎么办 -> .apinotes

    3. 命名为SDK名称.apinotes -> 已经要将该文件放在SDK的根目录里面

    4. image.png
    5. 该文件格式为yaml格式 -> 方法映射 OC到Swift或者反之

    #yaml
    ---
    # Name: -> SDK的名称
    Name: OCFramework
    # Classes -> 你要修改的类名
    Classes:
    - Name: LGToSwift
      SwiftName: ToSwift
      # 方法名的修改
      Methods:
      - Selector: "changeTeacherName:"
        Parameters:
        - Position: 0
          Nullability: O
        MethodKind: Instance
        SwiftPrivate: true
        #Availability: nonswift -> 意思是Swift中不能用,可以不写,写了就是加限制的意思
        Availability: nonswift
        AvailabilityMsg: "这个不能用"
      - Selector: "initWithName:"
        MethodKind: Instance
        DesignatedInit: true
        
    # 更准确的参考官方文档
    # https://clang.llvm.org/docs/APINotes.html
    

    总结

    1. module -> 头文件->目标文件的关系
    2. modulemap ->头文件 -> 目标文件的映射
    3. module:定义一个module
      export:导出当前代表的头文件使用的头文件
      export * :匹配目录下所有的头文件
      module * :目录下所有的头文件都当作一个子module
      explicit : 显式声明一个module的名称
    4. Swift库使用OC代码:不能使用桥接文件
      1. oc的头文件放到modulemap下
      2. oc的头文件放到私有的modulemap下
      3. 协议的方式 投机取巧
    5. Swift静态库的合并
      难点:.swiftmodule 文件(Swift的头文件)
      1. libtool 合并静态库本身
      2. 用到的头文件和Swift头文件和modulemap文件通过目录的形式放到一起
      3. OC要用合并的静态库:clang: other c flags :-fmodule-map-file <modulemap path>
      4. Swift要用合并的静态库 : SwiftC :other swift flags 显式告诉SwiftC <modulemap dir>
        1. 为什么OC根Swift要分开传递, 因为两个语音的编译器不一样
        2. OC -> Clang
        3. Swift -> Swift
    6. OC映射到Swift方式
      1. <工程名称>.apinotes

    **官方文档: **
    APINotes.html
    Modules.html

    相关文章

      网友评论

          本文标题:Module & Swift库 (6)

          本文链接:https://www.haomeiwen.com/subject/bmsejltx.html