2014 年 12 月 25 日
关注 @mrackwitzTL;DR:CocoaPods 0.36 将带来期待已久的对框架和 Swift 的支持。它尚未发布,尚未被认为是稳定的,但现在可以通过 [sudo] gem install cocoapods --pre
为每个人提供一个测试版。Pod 作者尤其希望尝试此版本,以确保他们的 Pod 将与即将发布的版本一起使用。这是因为如果用户项目中的单个依赖项需要成为框架,那么您的 Pod 也将成为框架。
CocoaPods 集成的框架有什么特别之处?
使用 CocoaPods,框架的设置方式与通过 Xcode 完成的方式非常相似。这是为了使整个集成可检查、可理解,并使我们能够释放现有整个工具链的力量。
许多工具仅在存在某些构建变量的 Xcode 环境中才能很好地协同工作。Cocoa Touch 框架使用 Clang 模块,还需要将它们导入并链接到您的 Swift 应用程序。因此,模块映射包含在构建的框架包中。
动态框架与静态库
那么这两种产品类型有什么区别?
动态框架是包,这基本上意味着它们是具有文件后缀 .framework
的目录,Finder 主要将它们视为常规文件。如果您点击框架,您将看到一个常见的目录结构
除了二进制文件之外,它们还捆绑了额外数据,在这种情况下,二进制文件可以动态链接,并为每个架构保存不同的切片。到目前为止,它与静态库相同。但是,框架保存以下附加数据
- 公共头文件 - 这些头文件针对应用程序目标进行了剥离,因为它们仅对将框架作为代码进行编译分发很重要。公共头文件还包括为公共 Swift 符号生成的标头,例如
Alamofire-Swift.h
。 - 整个内容的代码签名 - 在将框架嵌入应用程序目标时,必须重新计算此代码签名,因为头文件在之前已剥离。
- 其资源 - 使用的资源,例如用于 UI 组件的图像。
- 托管动态框架和库 - 这可能是 Apple 提供的所谓 Umbrella Frameworks 的情况。CocoaPods 中不会出现这种情况。
- Clang 模块映射和Swift 模块 - 这些主要是内部工具链工件,它们携带有关 API/标头可见性和模块链接能力的声明。
- Info.plist - 这指定作者、版本和版权信息。
关于捆绑资源的一个警告是,到目前为止,我们必须将所有资源嵌入到应用程序包中。这些资源由 [NSBundle mainBundle]
以编程方式引用。
Pod 作者能够使用 mainBundle
引用来包含 Pod 带入应用程序包的资源。但是对于框架,您必须确保通过获取对框架包的引用来更具体地引用它们,例如
# in Objective-C
[NSBundle bundleForClass:<#ClassFromPodspec#>]
# or in swift
NSBundle(forClass: <#ClassFromPodspec#>)
这将同时适用于框架和静态库。在极少数情况下,您希望直接或间接地引用主包,例如通过使用 [UIImage imageNamed:]
。
改进后的资源处理的优点是,当资源具有相同的名称时,它们不会发生冲突,因为它们是由框架包命名的。此外,我们不必自己将构建规则应用于资源,例如资产目录和情节提要需要编译。这应该会减少使用包含许多资源的 Pod 的项目的构建时间。
模块名称
Clang 模块的名称仅限于 C99ext 标识符。这意味着它们只能包含字母数字字符和下划线,并且不能以数字开头。通过查看官方规范存储库,我们发现了一些不符合这些要求的流行 Pod。
以前,作为 Pod 作者,您可以使用 header_dir
来自定义用户目标中标头的名称前缀。例如,如果您的 Pod 名称为 123BánànâKit
,您可以将其设置为 BananaKit
,它可以通过 import <BananaKit/BananaKit.h>
获得,而不是 #import <123BánànâKit/BananaKit.h>
。
我们仍然支持这种用法,但也引入了一个新的属性 module_name
,您可以在 Podspec 中声明该属性。此新属性的优点在于它将被正确地进行 lint 和验证,否则我们将从 header_dir
选项开始。如果任一属性不存在,那么我们将使用规范名称来匹配 Clang 模块名称要求。
简而言之,请查看以下 Swift 代码段,它简洁地表达了我们确定模块名称的方式。
//let c99ext_identifier: String -> String?
func module_name(spec: Specification) -> String {
return spec.module_name
?? c99ext_identifier(spec.header_dir)
?? c99ext_identifier(spec.name)!
}
模块映射
模块映射是头文件声明,它形成 Clang 模块的公共(或私有)接口。幸运的是,这些头文件已被设计为可以保持在后台,并且开发人员可以利用已知和现有的结构,而无需学习 DSL。默认模块映射基本上始终相同
framework module BananaKit {
umbrella header "BananaKit.h"
export *
module * { export * }
}
这仅明确引用了一个文件:保护伞头文件。
您可以在保护伞头文件中导出框架的公共 API,以及所有传递导入的头文件。Clang 将负责制作可由 Objective-C 和 Swift 导入的模块导出。
在这种情况下,传递导入是什么意思?
传递关系是一个数学概念
每当元素a与元素b相关,而b又与元素c相关时,则a也与c相关。
这里我们有头文件的二元关系,它导入另一个头文件。传递闭包意味着所有头文件都由您从某个文件导入的头文件导入,因此也间接导入到该文件。对于由这些头文件集合导入的所有头文件,情况也是如此。当然,您从应用程序目标中了解此属性,每当您导入导入其他头文件的头文件时,其中定义的类和符号也将在您的应用程序代码中可用。对于保护伞头文件中的导入语句,也必须应用相同的情况,并通过 Clang 模块影响模块可见性。
什么是保护伞头文件?
对于我们的示例,它可能如下所示
#import <Foundation/Foundation.h>
@import Monkey;
#import "BKBananaFruit.h"
#import "BKBananaPalmTree.h"
#import "BKBananaPalmTreeLeaf.h"
FOUNDATION_EXPORT double BananaKitVersionNumber;
FOUNDATION_EXPORT const unsigned char BananaKitVersionString[];
最初的目的是索引目录的所有公共标头,以便为导入/包含提供简写,以访问库的完整 API。随着时间的推移,它们开始涵盖越来越多的目的
- 使用(Cocoa Touch)框架:它们允许通过动态生成的 C 代码快速访问其 Info.plist 中定义的版本控制值。因此,它们必须定义一个接口才能使它们可访问。这些是 Xcode 模板中以
FOUNDATION_EXPORT
为前缀的常量声明。 - 使用Clang 模块:它们用于定义模块的公共接口。
- 使用Swift:它们是框架模块的桥接标头,这实质上意味着您在框架内从 Swift 中调用的所有 Objective-C 代码都必须是其公共 API 的一部分。
现有 Podspec 的当前情况
Xcode 中从未有过可声明的伞形标头。因此,Pod 作者从未指定过一个。
尽管始终有一个已知的模式,即有一个公共标头,它传递性地导入所有其他公共标头。情况并非总是如此。
出于此原因,CocoaPods 承担责任并生成一个自定义伞形标头(例如 Pods-iOS Example-AFNetworking-umbrella.h
)。这是通过自定义模块映射注入的,这样我们就不会遇到名称歧义。否则,默认模块映射会假设它与框架同名,而这可能已被采用。
我们生成的标头导入所有声明的公共标头。这也为版本控制常量定义了 FOUNDATION_EXPORT
,其名称由 CocoaPods 用于框架集成。此外,这避免了在某些特殊情况下的问题:例如,AFNetworking 有一个子规范,它为 UIKit 提供了类别,它有自己的批量导入标头 AFNetworking+UIKit.h
,它没有被 AFNetworking.h
标头导入以实现 OSX 兼容性。
要在 Swift 中使用此子规范而不使用生成的伞形头文件,你需要创建一个桥接头文件并使用导入,例如 #import <AFNetworking/AFNetworking+UIKit.h>
。使用生成的伞形头文件,如果你在 Podfile 中包含了子规范,你只需要 import AFNetworking
。如果你的 pod 无法开箱即用,你可以使用 pod lib lint --use-frameworks <YourPod.podspec>
来检查问题所在。我们尝试在不同的流行 pod 中使用它,有时会遇到由错误配置的公共头文件导致的问题。
关于公共头文件
Podspec 中的公共头文件通过 s.public_header_files = ["Core/*.h", "Tree/**.h"]
声明。
如果你不包含此规范,那么你的所有头文件都将是公共的。在大多数情况下,不建议这样做。
通常,你应该确保你的头文件是自包含的,并且只公开 Pod 用户使用的实现部分。这有几个优点
- 它允许你重构私有实现部分,而无需发布重大更新,这使得版本迁移更容易,并且允许你专注于进一步改进你的 pod,而不是向用户解释 API 如何更改。
- 它阻碍了错误使用,因为你需要修改头文件访问权限才能使用或操作类或属性,而这些类或属性不打算在外部使用。
常见的头文件陷阱
如果你有一个像这样的头文件
/// BKBananaFruit.h
#import "BKBananaTree.h"
#import "monkey.h"
@interface BKBananaFruit
@property (nonatomic, weak) BKBananaTree *tree;
- (void)peel:(Monkey *)monkey;
@end
如果你收到如下错误,不要被它迷惑。
你可以在框架中包含头文件,但不能包含带引号的头文件,这些头文件不在框架的公共头文件的范围内。因此,在这种情况下,你有两个选择:通过从公共头文件声明中排除头文件 BKBananaFruit.h
来使其变为私有,或使用系统导入来导入猴子。
-#import "monkey.h"
+#import <monkey/monkey.h>
Xcode 怪异之处
我们在开发期间遇到过几次此错误。
<unknown>:0: error: could not build Objective-C module 'BananaKit'
如果你在 Xcode 中开发框架,并且更改头文件可见性以修复前面描述的构建问题,并尝试通过执行清除操作(在 Xcode 中为 ⌘+ ⇧+K)来确保构建状态干净,则可能会出现上述错误。在这种情况下,从文件系统中手动删除产品构建目录(即 DerivedData
)可能会有所帮助。
可用性
CocoaPods 仅支持 OS X 10.9 及更高版本上的 Swift,以及 iOS 8 及更高版本。
原因如下
- 如 Apple 多次声明,Swift 受 OS X 10.9/iOS 7 及更高版本支持。
- 不支持使用 Swift 构建静态归档。无
- 所有版本的 OS X 均支持动态框架。
iOS 8 之前的版本不支持动态框架
ld: 警告:嵌入式 dylib/框架仅在 iOS 8 或更高版本上运行。
由此我们可以得出结论,不可能在早于 OS X 10.9 和 iOS 8 的任何平台上支持 Swift。
要在支持 iOS 7 的应用上使用 Swift 库,必须手动将文件复制到应用项目中。
更新
要安装 CocoaPods 的最新 Beta 版,可以运行
$ [sudo] gem install cocoapods --prerelease
在 1.0 版本之前,我们强烈建议你保持 CocoaPods 为最新版本。有关面向用户的更多更改,可以查看我们的即将发布的官方版本博客文章。
有关所有详细信息,请查看PR!