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> 指令并在传递之前将该头文件(以及它包含的任何其他头文件以及它们包含的头文件等)中的所有文本复制到您的源文件中它交给编译器。如您所料,为项目中的每个文件重新编译数千行系统头声明并不是那么高效。

所以,几年前我们得到了预编译的标头——您可以将常见的 #imports 放在一个地方,这些部分的编译步骤将完成一次,结果编译器后端可以为每个部分重用文件中的项目。但这也有它的问题:让您的 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 所做的只是告诉编译器你需要什么符号(以及链接器在生成二进制可执行文件时在哪里找到它们)。

因此,将相同的imports 放在每个文件的顶部的唯一成本就是打字。这是一个必要的成本——在 Swift 中,源文件是一个语义单元,决定其中的内容具有真正的意义。例如,如果你 import Foundation 启用与 Cocoa 集合和值类型的桥接,许多 Swift 标准库类型的行为会发生变化——如果你的应用程序的一部分想要严格使用 Swift 集合和值类型,你可能不想导入 Foundation(或 Cocoa 或 UIKit 或其他包含它的东西)。


更新:此外,您在 Swift 文件中选择的 import 具有实际意义。

例如,编译器如何优化泛型和静态/动态分派取决于给定文件中可见的声明,因此如果您import 超出您的需要,您可能会生成较慢的代码。所以一般来说,最好只import你需要的东西。

显式导入还有助于提高清晰度和可读性。如果imports 是项目范围内的,那么当您将代码从一个项目复制粘贴到另一个项目中时,您会在新位置看到很多错误......而且不太清楚@987654338 是什么@s 你需要解决它们。


“但我讨厌一直在每个文件的顶部放置相同的几个imports,”你说。让我们考虑一下。

你真的需要几个吗?大多数模块传递import 它们的依赖项。如果您已经在使用 importing 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文件

Prefix Header 在另一个项目中导入

iOS添加Prefix Header

不懂 gcc --with-local-prefix, --with-native-system-header-dir=

Adding a prefix header to an iOS project

自动生成的 Swift-Header 在 Xcode 8 中损坏