6

理解 Xcode 中的各种概念

 2 years ago
source link: http://chuquan.me/2021/12/07/understand-concepts-in-xcode/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

理解 Xcode 中的各种概念

发表于 2021-12-07

|

更新于 2021-12-16

| 分类于 iOS

Xcode 有非常多的概念,比如:workspace、project、target、product、scheme 等,这些概念之间有着千丝万缕的关系,当我们理解了这些概念及其关系之后,会对整个 Xcode 工程体系有一个整体的理解,对我们自身工程能力的提升也会有所帮助。本文将对这些概念及其关系进行梳理,从而为后续的学习提供基础。

下图所示是 Xcode 的核心概念之间的关系示意图,我们简单地进行介绍一下。

  • Workspace:Xcode 提供的一个工作空间,其可以包含多个 project。通过组合 project,我们可以实现非常庞大、复杂的工程。
  • Project:一个 Xcode 工程的核心,project 维护并管理源代码、资源文件、框架和库等。此外,project 还提供了默认的 build configurations,用于指导构建 product。其中,每个 build configuration 包含一组 build setting
  • Target:其包含构建特定 product 所需的 build configuration、build rule、build phase,并指定对应的构建产物 product。
  • Product:基于 project 的源文件,根据 build phase、build rule 以及 project 和 target 的 build configuration 所构建的产物。

下面,我们分别介绍一下这几个核心概念。

Workspace

Xcode Workspace 主要用于为 Xcode project 提供一个工作空间,通过组合 project 可以实现一个复杂的工程。

一个 workspace 可以包含任意数量的 project,以及任意其他要处理的文件Xcode 能够记录或推导这些 project 和 target 之间显式或隐式的依赖关系

  • 对于隐式依赖,比如,在同一个 workspace 中,project A 构建了一个库,该库又被 project B 链接,那么 Xcode 会在构建 project B 之前,自动构建 project A(即使 build setting 中并没有明确此依赖关系)。
  • 对于显式依赖,我们必须创建 project 进行引用。

Workspace 也是 Xcode 工作流的一种扩展。比如:文件的索引(indexing)是在 workspace 中进行的,因此代码补全、定义跳转以及其他内容感知的相关功能都可以在 workspace 下的所有 project 中使用。

默认情况下,workspace 中的所有 project 都在同一个目录下构建,即 workspace 构建目录。由于同一个 workspace 下的 project 的所有文件都在同一个构建目录下,所以该目录下的所有文件对于每一个 project 都是可见的。因此,如果有两个或多个 project 使用了相同的库,我们无需将它们复制到每个 project 中。如果某个 project 指定了构建目录,那么在构建该 project 时,该构建目录会被 project 所在的 workspace 的构建目录所覆盖。

同一个 workspace 下的每个 project 都是独立的。因此,为了不影响其他 project 或不受其他 project 影响,我们可以在不打开 workspace 的情况下直接打开 project,或者将 project 添加到另一个 workspace。由于 一个 project 可以属于多个 workspace,因此我们可以以任意组合 project,而无需重新配置任何 project 或 workspace。

Project

Xcode Project 是一个 Xcode 工程的核心部分,其本质上是一个文件、资源、构建信息所组成的仓库,能够构建一个或多个 product。一个 project 包含了所有用于构建 product 的元素,并且维护了这些元素之间的关系。Project 包含一个或多个 target,这些 target 各自定义了如何构建一个 product。一个 project 为其所包含的所有 target 定义了默认的 build configurations,其中的每个 target 又可以指定自己的 build configurations,target 定义的 build configurations 能够覆盖 project 定义的 build configurations。

Project 使用一个 .pbxproj 为后缀的文件进行描述,其主要包含了以下内容:

  • 对源文件的引用
    • 源代码,包含头文件和实现文件
    • 库和框架,内部或外部的
    • nil 文件
  • 用于在 Xcode 项目导航栏中组织源文件的组(Group)
  • Project 级别的 build configurations。我们会为一个 project 指定多个 build configuration,比如,我们会定义 Debug 和 Release 两种类型的 build configuration,并分别为它们定义 build settings。
  • 一个或多个 target,每个 target 均包含以下内容:
    • 对应 product 的引用
    • 构建 product 所需源文件的引用
    • 用于构建 product 的 build configurations
  • 用于调试或测试的可执行环境,其中每个可执行环境均包含以下内容:
    • 当通过 Xcode 运行或调试时,要启动哪个可执行文件
    • 当执行可执行文件时,要传递哪些命令行参数
    • 当程序运行时,要设置哪些环境变量

我们可以通过 scheme 来指定某一时刻哪个 target、哪个 build configuration 是有效的。

Target

Xcode Target 主要用于指导如何从 project 或 workspace 构建 product,其定义了构建系统的输入——源文件和处理源文件的配置,从而构建 product

Target 中构建 product 的 build configurations 主要包括:build settings、build configuration 的名称(如:Debug)以及对 project 级别的 build configuration 的引用。

对于 build settings,我们应该都很熟悉,在日常开发中我们一般都通过 Xcode 项目编辑器直接进行编辑。Target 默认继承 project 定义的 build settings,当然,target 也可以通过自定义 build settings 进行覆盖。

一个 target 及其创建的 product 可以与另一个 target 产生关联。如果 target A 需要 target B 的输出才能构建,即 target A 依赖了 target B。如果两个 target 在同一个 workspace 中,那么 Xcode 就能够找到其中的依赖关系,在这种场景下,Xcode 会按顺序构建 product。这种关系称为隐式依赖。

我们还可以在 build settings 中显式地指定 target 的依赖项,并且我们可以将 Xcode 认为具有隐式依赖关系的两个 target 指定为不相关。比如:我们可以在同一个 workspace 中同时构建一个库和一个链接了这个库的应用程序。但是,如果我们想要链接到某个版本的库,而 workspace 中构建的这个库并不是这个版本,那么我们可以在 build settings 中创建一个显式依赖,它将覆盖隐式依赖。

同一时刻只能有一个活跃的 target,由 scheme 指定。

Product

Xcode Product 是基于 project 或 workspace 提供的源文件,根据 project 和 target 的 build configuration 构建而成的产物。苹果预定义了几种 Product 类型,主要有:应用程序(application)、测试(test)、静态库(static library)、框架(framework)等。

Build Settings

Xcode Build Settings 是一组变量,包含有关 product 构建过程的各种信息。比如:build settings 中的信息可以指定 Xcode 传递哪些选项给编译器。

我们可以在 project 级别或 target 级别设置 build settings。每个 project 级别的 build setting 都会应用到 project 中的所有 target,除非 target 显式地覆盖了 build setting。

每个 target 组织构建一个 product 所需的所有源文件。Build Configuration 则指定了一组 build settings,从而以特定方式为 target 构建 product。比如:对于构建 Debug 和 Release 类型的 product,我们会分别定义两个 build configuration,每个 build configuration 各自包含一组 build settings。

Xcode 中的 build setting 有两个部分:

  • setting title:标识了 build setting,并且可以被其他 setting 所使用。
  • setting definition:一个常量或公式,Xcode 在构建时会用它来确定 build setting 的值。

Build setting 也可能有一个显示名称,用于在 Xcode 用户界面中显示 build setting。如下所示就是 Xcode 中的一些 build setting。

...
ALWAYS_SEARCH_USER_PATHS: 'NO'
CLANG_ANALYZER_NONNULL: 'YES'
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION: YES_AGGRESSIVE
CLANG_CXX_LANGUAGE_STANDARD: gnu++14
CLANG_CXX_LIBRARY: libc++
CLANG_ENABLE_MODULES: 'YES'
CLANG_ENABLE_OBJC_ARC: 'YES'
CLANG_ENABLE_OBJC_WEAK: 'YES'
GCC_WARN_UNUSED_FUNCTION: 'YES'
GCC_WARN_UNUSED_VARIABLE: 'YES'
IPHONEOS_DEPLOYMENT_TARGET: '10.0'
MTL_ENABLE_DEBUG_INFO: INCLUDE_SOURCE
MTL_FAST_MATH: 'YES'
ONLY_ACTIVE_ARCH: 'YES'
SDKROOT: iphoneos
SWIFT_ACTIVE_COMPILATION_CONDITIONS: DEBUG
SWIFT_OPTIMIZATION_LEVEL: "-Onone"
...

当我们使用模板创建一个新的 project 时,除了 Xcode 提供的默认的 build settings,我们还可以为 project 或 target 创建自定义的 build setting。我们还可以指定 条件式 build setting。条件式 build setting 的值取决于是否满足一个或多个先决条件。例如:我们可以根据 target 的架构决定是否使用某个 SDK。

Scheme

Xcode Scheme 定义一组操作,默认有:Build、Test、Launch、Profile、Analyze、Archive。每一种操作定义了一系列的指令,包括:target、build configuration、arguments、options 等等,这些参数、指令共同构成一个构建方案,从而用于构建一个或多个 target。

我们可以拥有任意数量的 scheme,但一次只能激活一个 scheme,对应在 Xcode 的右上角我们每次只能选中一个 scheme。

project.pbxproj

Xcode Project 使用 project.pbxproj 来描述一个 Xcode 工程所包含的所有内容,包括文件、配置、库等,其存储在 Xcode 工程文件 *.xcodeproj 中。

project.pbxproj 是一种旧风格的 Property List 文件(简称 plist)。Property List 与 JSON 最主要的区别在于数组和字典的表示:

  • 数组:使用小括号表示,数组元素使用逗号进行分隔
  • 字典:使用大括号表示,键和值之间使用等号链接
# plist 数组
("1", "2", "3")

# plist 字典
{
"key" = "value";
}

本质上,project.pbxproj 是一个对象关系图,所有的对象都与 Xcode 中的某个文件、配置、操作相对应,每一个对象都使用一个 96 bit(24 位的十六进制)的UUID 进行唯一标识,如下所示。

00004989A1D96C5B52A8E09B8A3BC4B5 /* DoraemonFPSPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = 3B3712CE0D0A78D8379015D4C5647631 /* DoraemonFPSPlugin.h */; settings = {ATTRIBUTES = (Project, ); }; };
00010094FB6FF27A828CF8A15DEAA184 /* TTCommonModelUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = 740D1037D25E920AA9913B575B6AF7C0 /* TTCommonModelUtils.h */; settings = {ATTRIBUTES = (Project, ); }; };
0007199E29D1EB8473295A64FB7C1D99 /* CLIColor.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B0381C866EF001605422EEE7598F3CB /* CLIColor.h */; settings = {ATTRIBUTES = (Project, ); }; };

project.pbxproj 整体构成了一个树状的对象关系图,其类图大体如下所示。上文中提到的概念大多数在类图中能够找到对应的类,比如:

  • Project 对应 PBXProject
  • Build Configuration 对应 XCBuildConfiguration
  • Target 对应 PBXTarget

Root Element

project.pbxproj Root Element 作为整个对象树的根节点。通过 rootObejct 为键引用一个 PBXProject 对象。objects 则定义了整个 project 中的所有相关文件。

PBXProject

PBXProject 定义了一个编译配置列表对象 buildConfigurationList,该对象中定义了一组 XCBuildConfiguration,其中每一个 XCBuildConfiguration 定义了一系列的 build settings。

关于文件的组织和维护,PBXProjectmainGroup 为键引用了一个 PBXGroup 对象。PBXGroup 对应 Xcode 中的 Group 的概念,类似于 Folder,但有所区别,主要用于 Xcode 中组织管理源文件。mainGroup 定义了文件组织结构的根节点,通过层层递进,可以索引到工程中的所有文件。

当然,PBXProject 还定义了一系列的 PBXTarget。这里的 PBXTarget 对应上述的 Xcode Target。

PBXTarget

类图中定义的 PBXTarget 是一个抽象类,其主要有以下几种具体类型:

  • PBXNativeTarget:一次构建一个 target。Xcode 项目中最常用的 target。
  • PBXLegacyTarget:通过命令行进行构建。如果一个 project 的依赖项需要通过 make 来构建,那么应该使用这种 target。
  • PBXAggregateTarget:同时构建多个 target,也可以用于执行不输出 product 的场景。

这些具体类型的 target 引用了一系列的 PBXBuildRulePBXBuildPhase,还定义了输出的 product 的相关信息。

当然,这些 target 还引用了一组 XCBuildConfiugrationXCBuildConfiguration 中通过 baseConfigurationReference 引用了 project 级别的 XCBuildConfiguration,从而进行继承或覆盖。

PBXTarget 还定义了一组 PBXTargetDependency,即上述提到的如果 target A 依赖了 target B,那么 target B 就是 target A 的一个依赖项。

PBXBuildPhase

PBXBuildPhase 也是一个抽象类,其主要包含以下几种具体类型:

  • PBXHeadersBuildPhase:对应 Headers Phase
  • PBXSourcesBuildPhase:对应 Compile Sources Phase
  • PBXFrameworksBuildPhase:对应 Link Binary With Libraries Phase
  • PBXCopyFilesBuildPhase:对应 Copy Files Phase
  • PBXShellScriptBuildPhase:对应 Run Script Phase
  • PBXResourcesBuildPhase:对应 Copy Bundle Resources Phase
  • PBXRezBuildPhase:对应 Build Carbon Resources Phase

project.pbxproj 中最基本的元素是文件引用,其定义了以下五种类型的文件引用。

  • PBXFileReference
  • PBXGroup
  • PBXReferenceProxy
  • PBXVariantGroup
  • XCVersionGroup

这里提到了 group 的概念,group 和 folder 是存在区别的,主要有以下两方面:

  • Group 只在工程一般是文件夹的形式,但是在本地的目录还是以散乱的形式放在一起,除非是从外部以 group 的形式引用进来,这种情况下的 group 同时是一个文件夹实体。
  • Folder 只能作为资源,folder 下的所有内容都会引入项目,不会被编译。也就是说,以 folder 形式引用进来的文件,不能被放在 compile sources 列表中。

contents.xcworkspacedata

Xcode Workspace 使用 contents.xcworkspacedata 来描述 workspace 中 project 的组成。如下所示,一个 workspace 包含两个 project,分别是:Demo.xcodeprojPods/Pods.xcodeproj

<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Demo.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

.xcscheme

Xcode Scheme 使用 .xcscheme 后缀的 XML 文件进行描述,其存储在 xcshareddata 目录下。事实上,这个文件与 project.pbxproj 文件紧密耦合,其采用 UUID 引用每个关联的 target。

.xcscheme 定义了一系列的操作,如下所示。每个操作中具体定义相关的 build configuration、arguments、options 等信息。

<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0500"
version = "1.7">
<BuildAction>
...
</BuildAction>
<TestAction>
...
</TestAction>
<LaunchAction>
...
</LaunchAction>
<ProfileAction>
...
</ProfileAction>
<AnalyzeAction>
...
</AnalyzeAction>
<ArchiveAction>
...
</ArchiveAction>
</Scheme>

BuildableReference

.xcscheme 每一个 action 的依赖被定义在 BuildableReference 中。如果有一个 action 中包含了 BuildableReference,那么就意味着 Build Action 被作为当前 action 的依赖,首先被执行。在 .xcscheme 中,同一个 BuildableReference 可能会被包含多次,从而引用同一个 target。这里使用 UUID 来引用一个 target,由 BlueprintIdentifier 指定。这里的 UUID 对应的 target 定义在 project.pbxproj 文件中。如下所示,是一个 BuildableReference 的示例。

<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "3AA4FE2703EA4DC3A89C1CCC"
BuildableName = "libPods.a"
BlueprintName = "Pods"
ReferencedContainer = "container:Pods/Pods.xcodeproj">
</BuildableReference>

本文简单梳理了一下 Workspace、Project、Target、Product、Scheme 等概念,并且介绍了其中一些概念在 Xcode 中对应的文件,如:project.pbxproj.xcschemecontents.workspacedata 等。

另外,重点介绍了一下 project.pbxproj 的构成,通过类图对上述这些概念进行了详细地梳理,包括概念抽象而成的对象,概念之间的关系等等。

后续,我们有时间再来介绍一下 CocoaPods 项目的一个依赖工具 Xcodeproj,从而进一步理解 Xcode 的相关概念。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK