iOS - Umbrella Header在framework中的应用

在相对较大的互联网App研发中, framework静态库被广泛应用, 那么在研发framework的时候也产生好多的问题? 其实一个常见的问题的就是umbreall header文件的使用。

大家是否有遇到过一个常见的错误使用umbrella header的场景: framework的文件明明被主工程引用了, 但是在编译的时候依旧抛出Lexical or Preprocessor Issue - Umbrella header for module 'xxx' does not include header 'xxx.h'的警告。

那么问题来了, 什么是umbrella header? 什么又是umbrella framework呢?

参考官方文档《Introduction to Framework Programming Guide》学习说明, 可以了解到Framework区分Standard Framework和Umbrella Framework。但是, 我找了半天也没有找到官方文档有对Umberlla framework给出明确的定义(大家找到了记得告诉我)。在官方文档《Anatomy of Framework Bundles》章节中, 我找到三段比较合理说明Umbrella Framework的话:

Umbrella frameworks add minor refinements to the standard framework structure, such as the ability to encompass other frameworks


The structure of an umbrella framework is similar to that of a standard framework, and applications do not distinguish between umbrella frameworks and standard frameworks when linking to them. However, two factors distinguish umbrella frameworks from other frameworks. The first is the manner in which they include header files. The second is the fact that they encapsulate subframeworks.


Physically, umbrella frameworks have a similar structure to standard frameworks. One significant difference is the addition of a Frameworks directory to contain the subframeworks that make up the umbrella framework.

字面上的意思应该是在标准的Framework做了一些改良的工作, 使其可以嵌套包含Framework。
在物理结构上, Umbrella Framework只在包含头文件的方式以及是否包含子Framework和普通的Framework存在区别。

那么引用头文件的地方又有什么区别呢? 还是参考官方文档引用:

For most frameworks, you can include header files other than the master header file. You can include any specific header file you want as long as it is available in the framework’s Headers directory. However, if you are including an umbrella framework, you must include the master header file. Umbrella frameworks do not allow you to include the headers of their constituent subframeworks directly. See Restrictions on Subframework Linking for more information.

简单翻译一下: 普通的framework可以通过引用对应的heaedr文件而不是Master Header File去引用需要使用的类, 只需要对应的header头文件在Headers文件夹下暴露, 并没有强制要求引用Master Header File。Umbrella Framework要求必须要引用Master Header File, 并且头文件中不能直接引用子Framework的东西。

上述描述已经说了Umbrella Framework一定要引用Master Header File, 而Umbrella Framework的Master Header File就是Umbrella header文件。

大家是否存在一个疑问, 官方说明中只有强制规定一定要引用Umbrella Header文件, 但是却没有说能不能单独引用Umbrella Framework的其他头文件呢? 我们可以自己试验一下:

  1. 在Umbrella Framework新建一个testObject类, 分别产生了testObject.htestObject.m文件。
  2. 打开Framework配置文件, 在Build PhasesHeaders里的Public目录下, 将testObject.h文件添加进去。
  3. Build Framework看是否报错。
  4. 在主工程中调用初始化testObject对象, 看编译是否报错。

执行结果:

  • 步骤3: 没有编译报错, 但是报出了Lexical or Preprocessor Issue - Umbrella header for module 'STDemoUI' does not include header 'testObject.h'的警告。
  • 步骤4: 执行正常。

那么我们来总结一下:

  1. Standard Framework不能包含Sub Framework; Umbrella Framework可以包含子Framework;
  2. Standard Framework可以直接引用需要使用的头, 也可以通过引用Master Header file来引用需要使用的类; Umbrella Framework需要通过引用Master Header File(Umbrella Header)来引用需要使用的类;

规范的写法

Umbrella Framework默认会创建一个同名.h文件最为Umbrella Header文件。规范的写法当然是遵从默认的模式, 将所有需要暴露的头文件都写在Umbrella Header文件中。

例如: STDemoUI.framework工程包含了STClassOneSTClassTwoSTClassThree三个类。STDemoUI会生成一个默认的伞头文件(直译Umbrella Header, 不专业)STDemoUI.h。假设该framework的三个类均需要在外部调用使用, 则STDemoUI.h需要将三个类的引用均写入伞头文件中。

// STDemoUI.h
// ...

#import 
#import 
#import 

在需要调用的主工程中, 仅仅只要将Umbrella Header引用即可调用所有在Umbrella Header中包含的类了。

// 在主工程需要应用的类中包含Umbrella Header
#import 

如何重命名umbrella header

如果大家都遵从默认的Umbrella Framework的写法, 在同名头文件中写需要暴露的引用头文件, 那么就不需要考虑怎么重命名Umbrella header了。

很多时候, 理想和现实是有差距的, 程序员写代码多数是在二次接手进行开发的。假设公司的前辈已经将Framework的同名文件用作了一个逻辑类, 给同名文件创建了.m文件, 并已经书写了逻辑并应用了各个工程里面去了。那么显然迁移头文件功能代码是不可能的, 因为很多依赖该Framework的业务部门都需要针对库进行代码优化。

在这种不能将同名文件作为Umbrella header的情况下, 我们又不想通过Public强制暴露头文件的情况下(不写在Umbrella Header中会有警告)。我们就需要对Umbrella Header进行指定了。

指定Umbrella Header入口在哪里呢?

万事开头难, 我们想要指定Umbrella Header, 但是在哪里指定呢? 通过文章搜索, 都没有找到一个比较合理的方案, 那就只能自己摸索了。

  1. 在工程全局搜索umbrella关键字 - Failed
  2. 在Build Settings里搜索umbrella关键字 - Failed
  3. 在打包好的STDemoUI.framework中搜索umbrella关键字 - Bingo

既然搜索到了关键字就好办了, 双击点开STDemo.framework, 我们可以看到如下图所示五个文件(夹)。

iOS - Umbrella Header在framework中的应用_第1张图片
Framework内部结构

初略看名称可以推测出每个文件以及文件夹所承担的作用:

  • _CodeSignature: 保存签名相关文件
  • Headers: framework暴露的所有头文件
  • Info.plist: 描述了该framework所包含的项目配置信息
  • STDemo: 编译后的核心库文件
  • Modules: 模块相关文件夹, 目测只包含了module.modulemap文件

我们在module.modulemap文件中找到了umbrella关键字。文件内容如下:

framework module STDemoUI {
  umbrella header "STDemoUI.h"

  export *
  module * { export * }
}

原来Framework的umbrella header是在这个位置被指定的, 但是这个已经是编译好的工程, 我们总不能每次编译好了再进到包里面修改下。既然我们已经找到umbrella header是在module中去指定, 那么我们就用module作为关键字再去Build Settings里重新搜索下呗~

这回我们在Kernel ModulePackaging中均找到了Module关键字, 在Packaging标签中, 有一项Module Map File属性, 看名字应该是用来指定modulemap文件的, 这不解决了么?

指定Modulemap文件

找到了解决方案, 那么接下来就要动手去解决问题了

  1. 创建一个新的.h文件; ex: STHeader.h

    • 将所有需要暴露的头文件均写入STHeader.h
  2. 创建一个新的modulemap文件; ex: stdemoalt.modulemap

  3. 在新的modulemap中指定umbrella header

    framework module STDemoUI {
        umbrella header "STHeader.h"
        
        export *
        module * { export * }
    }
    
  4. 在framework的Build Settings中的Module Map File指定新建的modulemap文件

好了, 准备工作完毕, CMD+B, 编译完了, 打开framework包中的Module文件夹, 看是否包含了新指定的modulemap。

抛出两个疑问:

  1. Module是什么?
  2. 如果Defines Module指定为NO, 那会发生什么事情呢?

题外话

这里我要引用官网Guidelins for Creating Frameworks的一个子标题Don't Create Umbrella Frameworks。原文如下:

While it is possible to create umbrella frameworks using Xcode, doing so is unnecessary for most developers and is not recommended. Apple uses umbrella frameworks to mask some of the interdependencies between libraries in the operating system. In nearly all cases, you should be able to include your code in a single, standard framework bundle. Alternatively, if your code was sufficiently modular, you could create multiple frameworks, but in that case, the dependencies between modules would be minimal or nonexistent and should not warrant the creation of an umbrella for them

在大多数情况下, 苹果是不建议手动创建Umbrella Framework。

总结

本文简单的梳理了官方文章关于Umbrella Framework和Umbrella Header的介绍说明, 产生警告的原因是没有引用umbrella header或者暴露头没有写在umbrella header中。在umbrella header被已使用的前提下, 本文提供了一种通过重命名Umbrella Header文件的方式来消除警告的解决方案。

虽然引用警告可以被消除, 但是建议大家还是采用规范的做法: 尽量不要在同名头文件中写业务逻辑代码, 用同名文件作为Umbrella库的Master Header File。

水平有限, 有错误请大家及时指出哈~
转载请注明出处哦~

参考文献:

  1. https://developer.apple.com/library/ios/documentation/MacOSX/Conceptual/BPFrameworks/Frameworks.html
  2. http://stackoverflow.com/questions/32153131/renaming-umbrella-header/32153383#32153383

你可能感兴趣的:(iOS - Umbrella Header在framework中的应用)