Swift 中是不是有 Prefix Header(或具有此功能的东西)?
Posted
技术标签:
【中文标题】Swift 中是不是有 Prefix Header(或具有此功能的东西)?【英文标题】:Is there a Prefix Header (or something with this functionality) in Swift?Swift 中是否有 Prefix Header(或具有此功能的东西)? 【发布时间】:2015-02-24 11:24:55 【问题描述】:有没有办法在 Swift 中获得前缀标头的功能?我不想在使用它们的每个文件中导入外部库。
【问题讨论】:
【参考方案1】:没有。但您不需要它——import UIKit
无需花费超过您输入 12 个字符的时间。 (或者使用已经有它们的 Xcode 新文件模板。)
这就是 TLDR。有关整个故事,请继续阅读...
在 (Obj)C 中,使 API 可用于源代码文件的旧方法是文本包含。预处理器会看到您的 #import <Foundation/Foundation.h>
指令并在传递之前将该头文件(以及它包含的任何其他头文件以及它们包含的头文件等)中的所有文本复制到您的源文件中它交给编译器。如您所料,为项目中的每个文件重新编译数千行系统头声明并不是那么高效。
所以,几年前我们得到了预编译的标头——您可以将常见的 #import
s 放在一个地方,这些部分的编译步骤将完成一次,结果编译器后端可以为每个部分重用文件中的项目。但这也有它的问题:让您的 PCH 满意存在维护负担,并且它不允许您限制每个源文件中使用的命名空间(即,如果您希望项目中的一个 .m
文件只看到它需要使用的符号,而不是项目中其他地方使用的所有其他东西)。
最重要的是,文本包含有一个潜在的脆弱性问题。如果您在 #define
行上方的 #import
行上方定义了某些内容,并且定义更改了导入标头中使用的符号,则这些标头将出现编译错误(或以更微妙的方式失败,例如定义错误的 API)。有一些惯例可以防止这种情况发生,但惯例并没有强制执行——你总是一个错字/新团队成员/糟糕的合并,远离一切分崩离析。
无论如何,文本包含并不是那么好,即使有预编译的标头,所以 Apple 在 Xcode 5 中引入了 Modules。 (实际上,不仅仅是 Apple。They're in the LLVM/Clang compiler suite, so they're open source.)模块基于语义导入,而不是文本包含——也就是说,模块在抽象级别告诉编译器它为您的源提供了哪些 API 符号代码,而不是粘贴到这些符号定义的文本中——因此它们并不脆弱,并且它们在后端单独预编译,因此可以快速构建您的项目。
模块现在是 ObjC 项目的默认设置。 (请注意,如果您创建一个新的 ObjC 项目,它不再包含预编译头文件。您可以关闭模块,因此如果您有一个旧项目,您可能仍在使用文本包含和预编译头文件。)您可以找到更多关于 Session 404 from WWDC 2013 中的 ObjC 模块。
为什么所有这些都与 ObjC 相关?我们说的是 Swift,对吧?好吧,Swift 基于很多相同的基础架构。
Swift 从一开始就使用模块,所以它总是基于语义导入。这意味着没有构建时性能损失,也没有脆弱性。 Swift import
所做的只是告诉编译器你需要什么符号(以及链接器在生成二进制可执行文件时在哪里找到它们)。
因此,将相同的import
s 放在每个文件的顶部的唯一成本就是打字。这是一个必要的成本——在 Swift 中,源文件是一个语义单元,决定其中的内容具有真正的意义。例如,如果你 import Foundation
启用与 Cocoa 集合和值类型的桥接,许多 Swift 标准库类型的行为会发生变化——如果你的应用程序的一部分想要严格使用 Swift 集合和值类型,你可能不想导入 Foundation(或 Cocoa 或 UIKit 或其他包含它的东西)。
更新:此外,您在 Swift 文件中选择的 import
具有实际意义。
例如,编译器如何优化泛型和静态/动态分派取决于给定文件中可见的声明,因此如果您import
超出您的需要,您可能会生成较慢的代码。所以一般来说,最好只import
你需要的东西。
显式导入还有助于提高清晰度和可读性。如果import
s 是项目范围内的,那么当您将代码从一个项目复制粘贴到另一个项目中时,您会在新位置看到很多错误......而且不太清楚@987654338 是什么@s 你需要解决它们。
“但我讨厌一直在每个文件的顶部放置相同的几个import
s,”你说。让我们考虑一下。
你真的需要几个吗?大多数模块传递import
它们的依赖项。如果您已经在使用 import
ing Cocoa (OS X) 或 UIKit (ios/tvOS/watchOS),则不需要 import Foundation
。例如,如果您正在编写 SpriteKit 或 SceneKit 游戏,您会自动免费获得 UIKit/Cocoa(适用于任何平台)和 Foundation。
您真的需要在每个文件中都使用相同的吗?当然,你在一个 UIKit 项目中,所以你几乎在任何地方都在使用 UIKit。但这只是一个import
,顶部有十二个字符。也许您的项目也在使用联系人或照片或 CoreBluetooth 或 HealthKit ......但它可能不需要使用您定义的每种类型中的所有这些。 (如果是这样,您的代码可能会受到关注点分离不佳的影响。)
您真的一直在管理import
语句吗?我不了解您的项目,但在我参与过的大多数大型项目中,我会说至少 90% 的开发活动涉及编辑现有源文件,而不是创建新源文件……在开始工作之后一个项目或主要功能,我们很少(重新)定义一组源文件或其依赖项。还有一些快捷方式可以帮助(除其他外)设置导入,例如Xcode file templates。
【讨论】:
+1 以获得出色的答案!但是在我看来,每次在每个源文件中导入我需要的所有东西仍然不是“正确的”。它有一些代码重复的味道,尽管它可能不完全是这样。如果我需要在我的 all 源文件中导入一些新东西怎么办?我需要打开所有 485 个并添加新的导入行吗?这不可能是最终的解决方案...... 如果您有一个项目,其中 485 个源文件确实每个都具有 完全相同 组依赖项,则可能存在代码异味,但语言中没有... over导入的成本也不高,但每个文件也有充分的理由只导入它使用的内容。 我同意你的论点,但在某些情况下,在所有源文件中导入一些新东西并不是代码异味。例如,将日志记录功能添加到项目中需要将其导入每个需要的源文件中。不幸的是,没有一种机制可以在整个项目中使用日志记录功能。 @Vince 您可以将该日志框架包装在您自己的内部函数中,这些函数随处可用。无论如何,这是包装此类功能的好习惯。 @deadbeef 我不知道有关该主题的参考文档,但在过去几年中,有关 Swift 和 Xcode 的多个WWDC presentations 中都出现了这个主题。【参考方案2】:创建一个Objective-C桥接头文件:
[新建文件→iOS→源代码→头文件]:
Bridging-Header.h
转到这个新标头并导入您的外部库:
@import Module1Name;
@import Module2Name;
...
进入Build Settings,设置Objective-C Bridging Header的路径:
[目标→构建设置→Swift 编译器 - 代码生成→Objective-C 桥接头]:
$(SRCROOT)/.../Bridging-Header.h
然后您可以在每个文件中使用外部库,而无需 import
代码。
参考资料:
Third Party Swift Frameworks Importing Objective-C into Swift【讨论】:
【参考方案3】:There 是 -enable-bridging-pch
功能。但似乎在 Xcode 9 中不起作用 :(
我决定写这个答案只是为了完全涵盖这个主题。
【讨论】:
这是为了预编译定义了你的 Objective-C 代码的公共 API 的头文件,这些 API 在你的混合语言项目的 Swift 代码中是可见的。 Swift 编译器每次编译 Swift 文件时都必须解析桥接头。因此,预编译加快了速度。另见swift.org/blog/bridging-pch【参考方案4】:您可以创建将导入您的依赖项并仅导入它的模块。
例如拨打Core
。它将只包含一个带有导入的 swift 文件。
每个导入都应以 @_exported
关键字开头。
例如:
@_exported import UIKit;
@_exported import Combine;
【讨论】:
以上是关于Swift 中是不是有 Prefix Header(或具有此功能的东西)?的主要内容,如果未能解决你的问题,请参考以下文章
Xcode6为什么干掉pch(Precompile Prefix Header)&如何添加pch文件
不懂 gcc --with-local-prefix, --with-native-system-header-dir=