头文件 头文件搜索篇 #import 循环搜索,并将引入关系导入到头文件中 做了去重,能够避免头文件重复引用 小结: 文件A import B,C,那么就是将A和C的 .h 文件内容直接copy到A文件中。就 是干了查找,复制粘贴的功能。 缺点: 不能处理循环引用 过度搜索导致编译耗时 #include 主要是C语言导入头文件 问:导入问文件涉及嵌套引入 严重影响性能,该如何处理? 就算一个空文件也要引入完整UIKit ,是不小的消耗 PCH 文件 一种引入折中的办法,将大部分功能使用的头文件都放到PCH中,这样在编译时 就不需要重复寻找,直接使用。 缺点 不需要的文件都会被引入这些公共头文件,导致引用混乱。 维护时无法确实是否需要删除或者抽离,导致越发臃肿,无法维护。 模块化 Clang 提出了 Module 的概念 以解决上面遇到的问题 当你的项目到一定程度后,就应该抽离模块,组件化,模块 化,也就是Module 化。 写法: 原先的写法是 #import <iAd/ADBannerView.h.h> 现在是: @import iAd.ADBannerView; 不过,@import 方式由系统自动转换,只要设置就行 原理: 模块化代码通常使用 cocoapod 来管理,在实际编译之 时,编译器会创建一个全新的空间,用它来存放已经编译过的 Module 产物。这样可以从产物中去获取。 Umbrella Header 模块公开头文件 pod install 后,更新模块文件同时,会将public 头文件全部放到这里,不设置 public header 就会默认全部。 modulemap 此文件会关联 Umbrella Header文件“ framework module UIKit { umbrella header "UIKit.h" module * {export *} link framework "UIKit" } setting 中开启 Enable Module = YES Defines Module = YES 是指开发者编写的组件是否采用 Module 的形式 疑问? 对于未开启 Clang Module 特性的组件,Clang 是通过怎样的 机制查找到头文件的呢? 在查找系统头文件和非系统头文件的过程中,有什么区别么? 对于已开启 Clang Module 特性的组件,Clang 是如何决定编 译当下组件的 Module 呢? 另外构建的细节又是怎样的,以及如何查找这些 Module 的? 还有查找系统的 Module 和非系统的 Module 有什么区别么? 回答第一个问题: 早期就是简单通过Header Search Path 指定路径搜索 设置有三处 Header Search Path :通用路径 System Header Search Path:系统路径 User Header Search Path:用户文件路径 #import <A/A.h> #import "A/A.h" #import <A.h> #import "A.h" 通常双引号“xx” 指本地文件。<xx>指系统库,或第三方库等 Search Path 带来的问题 很难控制开发者严格分类,或者全部使用 通用路径,这导致搜索效果更差 随着发展,头文件索引机制还是受到了一些挑战 苹果又增加类 Header Maps 功能 Header Maps 简称 HMap Build Setting 中开启 Use Header Map 选项 编译后,生成后缀名为 .hmap 的文件,用来记录已经编译过的文件索引 有了HMap之后,就会优先搜索此文件,找不到再去Header Search Path 搜索 系统库搜索:比如会首先判断是否存在 Foundation 这个 Framework,接着, 我们会进入 Framework 的 Headers 文件夹里寻找对应的头文件,没有就结束了 Framework Search Path 啥 ? 说完前面Search Path 和 hmap 后,其实还有 库搜索。 Framework 里除了public 还有private header, 难道是私有header吗? 三种Header public header 外部可访问 公开头文件,不限制使用 private header 外部可访问 不建议使用 未完全公开,表示未开发完成,目前不具备完全公开条件。不建议使用,但是能 被使用,在modulemap 中设置private 路径,外部就可以通过module来访问 【官方解释】 只是打上private 标签而已,不是说私有 project header 外部不可访问,内部使用 当前项目可用,不对外公开 小结1 通过 hmap,framework search,Header search 基本上能满足常规项目了 但是对一些CocoaPods 集成场景下会有新的问题 CocoaPods 项目源码开发疑问? project依赖: 主工程依赖pods project pods project 包含 podA,podB,podC,等等 podA 依赖 podB 假设 podB中有class B, 非public,非private。 源码开发模式,开启HMap时,podC 居然可以访问 classB ,原因是Project Header 在pods中属于同一个project 范围,而被访问(不开启hmap不会) CocoaPods 库疑问 构建产物为 Framework Public : public_header_files 或者无任何指定时,都是公开 Private: private_header_files (private未描述时,取非公开) Project : 其它 建产物为 Static Library Project: 总是全部文件 Public : public_header_files Private: 总是全部文件 public_header_files 未设置时,public和private 都包含全部文件 Static Library 此库 生成的 hmap 里只会包含 #import "A.h" 的键值引用,使用#import <A/ A.h> 将缓存不到 美团开发 cocoapods 插件就是在pods不开启hmap下,自己 创建hmap文件,并修改配置 回答第二个问题 开启hmap 后搜索逻辑 首先会去 Framework 的 Headers 目录下寻找相应的头文件是否存在,然后就会 到 Modules 目录下查找 modulemap 文件 Clang 命令 - 哈希值命名对应的pcm文件,参数不同影响hash值,所以相同库 应该使用相同命令,这样就能可以命中同一个文件 VFS Clang 可以在现有的文件结构上虚拟出来一个 Framework 文件结构,进而让 Clang 遵守前面提到的构建准则,顺利完成 Module 的编译,同时 VFS 也会记 录文件的真实位置,以便在出现问题的时候,将文件的真实信息暴露给用户 Swift 第一问 - 如何寻找 Target 内部的 Swift 方法声明 A.swift 引用B.swift 文件: 没有.h 文件,通过获取 B.swift 的初始化构造器的类型,检查调用是否正确 不会检查构造器之外过多方法 每个 xx.swift 文件,编译后都会生成对应 xx.o 文件: Name.swift ---> Name.o 每个 xx.swift 文件 都会多次被其它文件接口文件反复解析 第二问 - 如何找到 Objective-C 组件里的方法声明 也就是swift文件总是需要访问OC文件的,比如UIKit 框架是 OC的,不过要分不同情况了 访问OC: 1. App 或者 Unit Test 类型的 Target 工程内 桥接:通过为 Target 创建 Briding Header 来导入 2. Swift 访问 外部的 Framework中 OC 文件 编译器会通过 modulemap 里的 Header 信息寻找方法声明 3. 访问 当前Framework 内部的OC 搜索 Umbrella Header 中暴露头文件 API 转换 swift 获取 oc 的API后,不是直接使用的,毕竟参数类型都不一样,需要内部转 换成swift 风格类型api使用 -(void)loadWothName:(NSString *)name; swift 读取方式: load("Bobo") 上面这种转换是hard code 硬编码 硬编码相应的代码 : https://github.com/apple/swift/blob/main/lib/Basic/PartsOfSpeech.def 当然,如果已经子啊OC api 设置了swift命名规则,那么优先使用 第三问 - Target 内的 Swift 代码是如何为 Objective-C 提供 接口的 OC访问Swift: OC 是 通过 import "<工程模块名>-Swift.h" 来访问swift的 1. App 或者 Unit Test 类型的 Target 工程内 swift访问级别为open, Public 和 internal 的 API都可以,默认都是internal 2. OC 访问 外部的 Framework中 Swift文件 Public 权限以上 类型的 API (open不用说了) 3. 访问 当前Framework 内部的swift Public 权限以上 类型的 API ,一旦API 被 @objc 关键字标注,那么被当前库访问同时,还会被其它库访问。 第四问 - Swift Target 如何生成供外部 Swift 使用的接口? .swiftmodule 引入 Swift 的 Module 后,编译器会反序列化一个后缀名为 .swiftmodule 的文 件,并通过这种文件里的内容来了解相关接口的信息 二进制 文件 Swiftinterface 随着 ABI 的逐渐稳定,原先swiftmodule 存在版本不兼容的问题,为解决这个问 题,Swiftinterface 诞生了 生成的.swiftinterface 包含public 接口,主要信息,声明,所有的隐式声明方 法,但是不会包含实现等等 这样在后续版本更新后依然能做到更好的兼容性 小结: Clang Module 是面向 C 语言家族的一种技术,通过 modulemap 文件来组织 .h 文件中的接口 信息,中间产物是二进制格式的 pcm 文件 Swift Module 是面向 Swift 语言的一种技术,通过 Swiftinterface 文件来组织 .swift 文件中的 接口信息,中间产物二进制格式的 Swiftmodule 文件 对比: .h/.m .swift .modulemap .swiftinterface .pcm .swiftmodule 为什么一定需要 modulemap 的 Search Path 呢? 基于前面了解到的内容,Swiftc 包含了 Clang 的大部分逻辑,在预编译方面, Swiftc 只包含了 Clang Module 的模式,而没有其他模式,所以 Objective-C 想要暴露自己的 API 就必须通过 modulemap 来完成 抛开 Framework 的限制,还有别的办法构建 Swift 产物么? VFS 技术 podA依赖podB时,不需要下载全部二进制文件: 只提供相应的 .h 文件和 .modulemap 文件就可以完成 Swift 二进制产物的构 建,而不再依赖 Framework 的实体。同时,对于 CI 系统来说,在构建产物 时,可以避免下载无用的二进制产物(.a文件),这从某种程度上会提升编译效 简单说,利用 VFS 机制构建,可以在构建 Swift 产物的过程中 避免下载无用的二进制产物,进一步提升编译的效率