大多数浏览器和
Developer App 均支持流媒体播放。
-
在 Xcode 中更快地构建
在 Xcode 10 中更快地构建您的 app。了解如何构造项目和调整代码,从而充分利用所有处理器内核。无论是在小幅修改代码后查看具体效果,还是要完整地构建 app 以备发布,这些技巧都能够节省构建可运行 app 所需的时间。
资源
-
下载
大家好 早上好 我很高兴 你们今天早上能来 我叫 David Owens 我是 Xcode 团队的一名工程师 今天 我将和 Swift 团队的 工程师 我的同事 Jordan Rose 一起和你们讲讲 如何在 Xcode 中 构建的更快 现在基于你的项目 它们的配置 它们的复杂性 有很多机会可以让你对其 进行改进 在某些情况下 可以显著地提高你的构建 运行情况
今天我们讨论的是 在 Xcode 中构建更快 我们会从两个不同的角度 来考虑 第一个就是 提升你的整体构建效率 第二个则是 减少你在构建中需要 做的工作 尤其是你的 增量式构建
现在 我将为你们介绍一些 项目级对象 包括如何并行你的 构建过程 如何宣告并配置你的 运行的脚本阶段 我还会给你们介绍一些 Xcode 10 中的关于测量你的 构建时间的新性能 现在 Jordan 将给我们介绍 一些我们可以 为我们的项目带来的 源级的提升 包括使用 Swift 来 理解我们的依赖性 在 Swift 中处理复杂的表达式 以及如何将 Objective-C 限制到 Swift 界面
现在让我们来讨论一下 并行你的构建 Xcode 通过使用目标 来配置你的项目 目标指定了你想要构建的 产品或者输出 有一些例子是 iOS App 框架和单位测试 还有另一个信息 那就是 目标之间的依赖性 Xcode 为定义我们的依赖性 提供了两种方法 有一种很清楚的方法 就是通过 目标依赖性阶段 还有一种隐含的方法 就是通过函数库阶段的 连接的二进制 一会我们会用一点时间 深入地了解这些 现在 我想用 一个样本项目来 开始我们的讨论 我们将看到 这个项目的依赖性图表
现在 一个依赖性图表 只是一个包含所有目标的名单 在这个例子中 我们将要构建五个目标
在这些目标中间 有依赖性信息 基于这两个信息 Xcode 可以得到我们的 构建次序 现在 让我们看看 它在时间表中是 什么样的 正如我们所见 每个目标都按顺序构建得 很有秩序 它们都需要等到之前的 目标完成构建 就其本身而言 构建 时间线并没有什么错 但是这代表了对潜在的 硬件资源应用的浪费 尤其是在你有一个 如 iMac Pro 的 多核或微核机器 这对于作为开发者的你来说 就是一种时间的浪费 因此我们要采用 另一种方法
看起来大概是这样 现在 有几件事 或者很少的几件事 我想要提醒大家 首先 我们在构建项目中 所做的工作量 其实并没有变化
但是用来构建的时间 的确减少了 在这种情况下 时间其实大幅度减少了 我们可以通过 更好地利用可用的硬件 来减少构建的时间 如果并行 或者说并行是这么好的一件事 为什么我们不创建一个 和这个很像的构建图表 我们只需要在我们的构建时间表中 从头开始构建
在最好的情况下 你们将遇到确定性的 构建错误 这是因为依赖信息 其实是你们 项目配置的重要部分 当它像这样被设置时 你们将试着在你们构建 依赖性之前 去构建你们的游戏目标 这并不是一个好的状态
所以我们如何做到这样 我们如何从漫长的 系列化的构建时间线 转换成并行的构建时间 首先 你需要确保 Xcode 被设置并配置成 允许我们的目标 被平行构建 我们通过 Scheme Editor (方案编辑器) 完成这个指令 你可以打开 Scheme Chooser(方案选择器) 选择 Edit Scheme (编辑方案) 然后来到 Scheme Editor 明确来说 你需要 看一下 Build Action (构建行为)
在这里是 Build Options (构建选项) 这里有两个已经列出来的 第一个是 Parallelize Build (并行构建选项) 第二个是 Find Implicit Dependencies (寻找隐含依赖性) 你可能想检查 Parallelize Build 这会允许 Xcode 通过 你的目标来使用 依赖性信息 这样就可以让其尝试着并行 构建你的目标 让我们来看看 如何在 Xcode 内 配置你的依赖性
这是通过 Build Phase Editor (构建阶段 编辑器)实现的 你可以通过你的 Project Navigator (项目导航器) 选择你的项目 然后来到 Build Phase Editor 在这个例子中我们 来看下 Game Target (游戏目标)或者 Game Project (游戏项目) 接下来点击 Build Phases (构建阶段)
我们来看一下 Game Target 这是针对我们的 Game Target 的 Build Phase Editor 我们将看看它的依赖性 是如何配置的 首先我希望 你们能注意一下 Link Binary with Libraries phase (带函数库阶段的连接二进制) 现在是你定义 所有你想要连接到你目标的 项目的构建过程阶段
这种情况下我有两个项目 分别是物理和实用性 现在这些是在我们的项目中 和我们的工作环境中的目标 Xcode 可以在这些目标中 创建一个隐含的依赖性
如果你在使用如 自动连接或 其他的 LD 构建标志构建设置 这些都不会 暗中地在你这里生效 你要么在这个构建中 或者在目标依赖性构建过程中 生成一个明确的依赖性 我们可以看到在这里 还有一个名为 Shaders (渲染)的 另一个项目 Shaders 并不在连接时候 使用 而是在 我们现有的目标中 被另一个构建过程使用 很重要的一件事就是 我们让 Xcode 知道 这是一个依赖性而且我们需要 等 Shaders 完成编辑 同时 在我们构建现有的目标前 就进行构建 现在 这个目标实际上 存在于不同的项目 如果你想要 对该项目做个参考 你可以拖拽如 你目前正在处理的项目的孩子一样 拖拽该项目 我想再介绍一下 我们项目其余的依赖性
我们的 Shaders 目标 对于我们的实用性目标 有一个依赖性
我们的实用性 目标 在我们的物理目标上 有一个依赖性
最后 我们的测试(Tests) 对我们的 Shaders 和实用性目标 有一个依赖性 现在我们对我们项目的 配置有了 一定的了解 我们来看看 将这个序列化的构建过程 变成并行的 必要步骤 首先我们要看一看 我们的测试依赖性
现在 我将依赖性 分成了三种 不同类型 我想要谈论的依赖性 第一个依赖性 我称之为 “什么都做的” 依赖性 有一点很清楚的就是 这个测试 测试了很多成分 它测试了 Game 还测试了 Shaders 同时还测试了 Utilities
在这个例子中 最好将我们的测试分解 这样它就能测试 每个单独的成分 我们来试试这样操作会怎样 我们将介绍 构建过程中的并行性
我们在三个依赖性中都被构建了的 测试目标 现在可以只构建 在 Game 测试中寻找的部分 我们的 Shaders 测试和 Utilities 测试可以被移去 与我们其他的目标 并行构建 当它们各自的成分 Shaders 和 Utilities 完成时即可进行构建
下一个我想要看的 依赖性类型就是 我称之为“爱管闲事的邻居”的 依赖性 这是需要存在的一种 依赖性 这个要考虑另一个目标 但是它只需要那个目标的一点 不过它将获得 在那个目标中的一切 如果我们看一下我们的游戏 它对 Physics Shaders 和 Utilities 有一个依赖性 这其实没问题
值得怀疑的是 在 Shaders 目标和 Utilities 目标 之间的依赖性 现在 我们的 Shaders目标 产生了一个元函数库 它本质上来说是一群 将在我们的显示卡上 运行的 GPU 代码
我们的 Utilities 目标 生成的则是一个正常的框架 也就是 CPU 代码 所以已经有一些 00:09:26.906 --> 00:09:28.946 A:middle 疑似依赖性了
当我们深入挖掘时 我们会发现 Utilities 目标 实际上有一个会生成 被两个目标使用的信息 的构建过程 这样很好 只不过 Shaders 不再需要从 Utilities 目标中 获得任何其他事物 所以最好将其转入其目标 我们会看到 这个小小的增量改变 其实对我们整个的 构建时间线有着 巨大的影响
所以刚刚移入的 新的绿色框是我们新的代码目标 现在我们可以缩小我们的实用性目标 因为我们将其 移入到 Code Gen (自动生产安全程式码) 因为 Code Gen 没有其他 依赖性 它可以移动到 我们构建过程的前端 它还可以与我们的 Physics 目标 也就是底端的红框 一起并行构建
最后 因为 Shaders 不再 依赖于 Utilities 它不需要等待 Utilities 和 Physics 目标的构建 与此相反 一旦 Code Gen 目标 完成后它就能被构建 最后我想要和大家介绍的 依赖性我称之为 “被遗忘的” 在我们产品或 代码的进化或生命周期中 我们需要移动代码 并删除事情 我们得到的 仿佛是无作用程序代码 我们的依赖性 也会发生这样的事情 有时候我们只是忘记 清理它们
在这些情况中 移除依赖性 是比较安全的
最后一个变化将通过 允许 Utilities 目标 在 Code Gen 目标之后立马搭建 而不需要等待 Physics 目标完成 来加强或构建图 早前在 Xcode 中 当你构建对其他目标 有依赖性的目标时 你必须要等待依赖的目标 完成它的整体 构建过程 在 Xcode 10 中我们有 一个新性能 可以让我们为你的构建 引入一些并行 一旦构建过程 与适合我们编辑的依赖性 完成了 我们就 可以开始 编辑你的目标了
像连接之类的事情 可以并行完成
现在如果你运行了脚本阶段 这就是你的目标 在可以利用这些 新的并行性之前 为了有秩序的 完成而需要 等待的众多构建过程之一 让我们来讨论一下 运行脚本过程 运行脚本过程允许你 根据你的需求来 自定义你的构建过程 这个弹性也给 作为开发者的你 带来了一些责任 所以我们想要带大家 看一下配置过程 同时确保你已经 设定好了你的脚本过程 同时已经配置好 这样你的构建才能运行
这个是你的脚本过程编辑器 它也可以在你的构建过程 编辑器中被发现 现在我想要 让你们注意一下第一个 脚本主体 你可以把你整体的 脚本内容放在这里 或者你可以像我刚刚那样做 并参考在你项目中的 另一个脚本
现在 在你运行的脚本过程的 整体中 有一整套 对你有效的 构建设置 我现在在利用这些中的 一个 即源组
在这种情况下你的 产品工作时就不需要 提供绝对路径 或尝试做一些相关的 路径侵入
第二个部分是你输入文件 这对于你的 运行脚本过程非常重要 这也是 Xcode 构建系统 用来决定 你的运行脚本是否能 成功运行的 关键信息 这应该包含你运行脚本过程的 所有文件 脚本内容其实应该在这个过程中 被阅读或者被观看
现在 你们中的一些人可能 在你们的运行脚本过程中 有很多输入 这个任务可能会有些令人气馁 在 Xcode 10 中我们有能力为你 或者说我们为你提供了 能在一个外部文件中 保留这个名单的能力 我们称之为 文件名单
现在 一个文件名单是一个 把所有输入都列在其中的 简单的文本文件
在你的运行脚本过程中 你可以使用 所有有效的 相同的构建设置
有一件重要的事需要注意 这些文件不能 在你的构建过程中 被修改或生成 当你的构建过程开始时 它们就可以读取了 所有的信息都被使用了 接下来我要向你介绍一下 你的输出文件 你的输出文件是 你的构建过程中使用的 另一个关键信息 Xcode 会使用 这个信息来决定 你的运行脚本过程 是否需要运行 当然了 我们对输出文件 有支撑 还有对你的输出 文件的名单 所以当你的 运行脚本过程运行时 我想要为你概括
如果你没有宣告任何 输入文件 那 Xcode 构建系统 会需要在每个单一的 构建中都进入到你的运行脚本过程 这就是为什么 宣告你的输入是很重要的一个 关键原因
如果你的输入文件变化了 包括你文件名单的内容 或者其他文件名单指向的 任何输入 Xcode 都会知道它将 为你返回你的 脚本过程 最后 如果你的任何输出 文件丢失了 Xcode 构建系统会运行 你的运行脚本过程 来为你提供 生成丢失的输出文件的机会
Xcode 10 还有一个新性能 就是我们为运行脚本过程 提供了文档
这包含了更多 我刚刚解释过的细节 也告诉了你 那些对你有效的 新增的构建设置 包括你如何在 你的脚本内容中 使用这些文件名单
当你设定你的运行脚本过程 并宣告所有这些 新的依赖性 包括你在目标中 修改依赖性时 你都可能遇到依赖性周期 依赖性周期是一个 有着回路的 相互依赖的图表
在 Xcode 10 中 我们有检测这些周期的更好诊断 同时会告诉你错误 包括扩大 获得所有输入的框的能力 这些输入 都是 Xcode 构建系统了解的参与了周期 创建的
其实 周期因为一些原因 并不是很好 首先 它们代表了在你项目中的 一个配置问题 其次 他们可能是 你项目中虚假重建的来源 或者在你的构建过程中 获得过时的信息
我们还在依赖性周期中 更新了一些话题 包括一些我们找到的 你最常遇到的依赖性 周期以及 你修复它们的 方法 我今天要和你说的 最后一件事 就是测量你的构建时间 在 Xcode 10 中 我们针对此有两个新功能
第一个就是我们介绍的 内嵌的任务时间 它可以告诉你 每个任务运行的时间 现在 我想要指出一些 关于你的构建日志中的东西 在顶端有一个过滤栏 准确来说 是 All (所有) Recent (最近的)过滤器
当你选择 ”All" 时 它将会展示 所有帮助你 最终产品整体输出的 任务 这些往往是 你不想看的 你想要看的 尤其是当你在 增量构建中 试图诊断的是 Recent 标签 这将会向你展示 所有在之前的构建运行中的 构建路径
Xcode 10 里另一个 新特征是一个计时总结 你可以通过打开 Product (产品) 菜单 选择 Perform with Action(与动作运行) 再选择 Build with Timing Summary(采用 计时总结构建)
在你这样操作后 你会在构建日志最后 得到一个新的日志部分 如果我们集中关注它的话 你会看到它给你 一个你最后构建的操作内 所有任务的时间总和 这也是查看 Recent 过滤器的 另一个重要原因 有一点我需要 特别指出的是 Phase Script Execution(过程脚本执行) 你可以看到在我们刚刚操作的 最后一个构建中 我们有一个 shell 脚本 这只是众多脚本中的一个 它指明一个任务 花费 5 秒钟 如果你在你的 每一个增量构建中 都看到了它们 这是在你的运行脚本过程中 错误配置的指示 可能有一些事情是 你为了减少整体构建时间 而想要说明的
通过从命令行输入 Show Build Timing Summary(展示构建计时总结)标识 你也可以获取 构建计时总结
下面我想要让 上台来给大家 讲一下大家自己的项目也可以 做的资源层面的提升
谢谢 David 好的 只要一个小小的改变 你们会有 提升你们 Xcode 项目的 多种方法 在我们来到 资源级和文件级 主题时 我想要再说一个 Xcode 10 中的新性能 这是一个 你们当中有些人 在你们的项目有很多 Swift 文件时 为了让它们构建得更快而采用的 临时解决方法
你们已经听说了 这是用在调配置 中的 Whole Module(整体模块)设置 在之前版本的 Xcode 中 对于一些项目来说 开启整体模块编辑模式 即便是调试构建 也会比在默认增量模式 中的构建速度更快 这确实提升了构建速度 因为它可以分享 Swift 的编译器 可以用增量模式不可行的方法 在文件中分享工作 但这也意味着 你每次都要放弃 你的增量构建 并重建整个目标 Swift 文件的价值 所以在 Xcode 10 中 我们提升了增量构建 让它们也能拥有那些通过文件 分享的工作 所以你应该不再需要 使用整体模块模式 来获得良好的构建时间
如果你在项目中完成了这个 那你应该回到你的 构建设定编辑器 在编辑模式构架设置中 选择编译配置 然后点击删除 这将回到 Xcode 的 增量构建的 默认设置 关于这个我不会再多说 因为你们已经了解了 这方面的知识 我们在周二时在 “Swift 中有什么新性能”演讲里提到了这个 如果你想要了解更多 在明天的 “Xcode 构建过程背后的情景”中 我们将提到这个 和其他可以让你的 构建更有深度的话题 今天我们有很多 需要讲到的话题 David 已经介绍了一半的内容 我将会介绍剩下的 三个解决 列表顶端复杂表达式的问题 这是因为它很好的同时 举证了我们两个部分的 一个关键点 当一个构建花费很长时间时 总会有一个 你可以提供给 Xcode 的 用来提升状态的关键信息
所以我们要 在复杂的 Swift 表达式语境中 首先看看它 这是我最近的一个项目 中的一些编码
这个结构的一个问题就是 我在各个地方都使用它 拥有一个结构真的很好 拥有一个有性能的结构 也很好 拥有一个有推论的类型的 性能的结构也很好 但是我们在这里 推论的表达式可能 有一些复杂 这不是像 啊 我的 PPT 上已经展示出来了 好吧 我在这里泄露了答案 如果这是一些像 0.0 的东西 那么 double 类型的推论 在这里是没有必要的 因为我们已经从系统框架中 拥有包含 简化函数和幂函数 的大型、复杂的表达式 你可能不会猜到 double 是这个性能中的 推论类型 00:22:11.066 --> 00:22:12.126 A:middle 通过在这里提供这个信息
编译器使用这个结构 在每个文件中 需要做的工作 都被你节省了 你还节省了 你的同事 为了弄清楚这个 big number 到底是什么 而花费的时间 所以你通过用 这个额外信息来帮助您 优化构建时间 就是一个很好的
我们看看涉及闭包的 其他例子
这次我将会试着宏定义一个 会返回所有 非可选参数值的和的函数 如果这三个参数是零 它就会返回零
我将会试着使用一个 Swift 的性能 如果你在主体内 有一个拥有单一表达式的闭包 那么编译器会 使用该表达式来决定 闭包的类型 有时候这真的很方便 其他时候它则会导致像这样的代码
这样非常丑 我不认为我会用这个 00:23:14.846 --> 00:23:16.046 A:middle 来回顾过去的代码
我们还有一些内嵌的工艺运算符 和一些针对 nil 的 明确的比较方法 还有一些相关的其他运算 我不认为它会运行的很好 并且还有另一个问题 因为这个表达式太大了 拥有着很多独立的块 Swift 编译器会报错说 它不能在一个合理数量的 时间内完成编译 这个构建时间
简直慢到头了 甚至连编译器都放弃了
这也真的让我对 这个代码加深了理解 我的首选是 如之前的例子一样做相同的事情 同时提供附加的类型 通过一个闭包 你可以在 In Key 词 之前就完成这些
不过这对于特定的问题来说 可能不是最好的 解决办法 所以让我们回到之前有的东西
回想起我之前说的 我想要试着在这里写一个 表达式这样它就可以用来 决定闭包的类型
但是在这种情况下 这其实不太必要 我们已经从 Reduce 的调用中了解到 这个闭包是什么样的 Reduce 对一个整型可选择的 数组调用 这个结果类型需要 匹配这个函数的返回类型 我们已经知道 Reduce 的回调 只能对可选择 的整型参数操作 这意味着我们不需要 在闭包中放上一个表达式 我们可以把它分解成 各自的部分 成为更可读的指令
这里有一个我之前的 代码的直译
我现在还可以让它 更加的灵活 这就更具有可读性了 也可以更容易维护 它在一个快速的 合理的时间内编译 我要在这个环节展示的最后一个例子 并不像之前两个 一样 应用的 那么广泛 我要说说 Any Object 这个类型
Any Object 是一个描述 所有类别实例的便利的类型 不是一个结构体 或一个枚举 而是一个类 但是我们不知道是哪一个
不过它还有一个 从 Objective-C's ID 类型 传下来的附加的功能 这也是为什么 这个方法被称作语法 如果你试图调用一个方法 或在 Any Object 类型的数值上 读取其性质 Swift 会允许你这样做 前提是这个方法在你的 项目中是可见的 并被展示给了 Objective-C 运行时间
不过 这样是有代价的 因为编译器不知道 你要调用哪个方法 它需要检索 你整个项目所有可能的应用 和你所有引入的框架 因为编译器会认为 任何一个都有可能 是你要调用的那个
必须这样做的原因是 如果它们中没一个匹配 它就需要报错
所以与此相反 我们可以 更好更多更全面的 描述我们的意图 我们可以宏定义一个协议 它可以在相同的文件 或不同的文件中完成 重要的是一旦我们 将委托的性质 改成去使用我们协议 而不是 Any Object 那编译器就知道 在调用的是哪个方法
现在你有机会 检查所有实施类型 是否在正确 实施方法
我们已经谈论了 当编译器决定 重新编译一个文件时 我们可以通过一些技术来减少 编译器的工作量 但是如果完全不重新编译 这些文件会怎么样呢 是什么让编译器决定 一个文件是否需要 重新编译呢 这就需要我们了解 Swift 的依赖性模型
Swift 的依赖性模型 是基于文件的 这有一点棘手因为 在 Swift 中没有头文件 我们看到的都是 在我们的目标中被 默认定义的 在这个例子中 我在文件的左侧声明了 一个结构体 名字是 Point 如果我这时候从右侧引入一个文件 编译器就会知道我是对 第一个声明引入的 这也同样适用于 右侧文件的 X 和 Y 属性 这个以文件为基础的依赖性 意味着如果我改变左边的文件 那么两个文件 都需要被重新编译
这很重要因为 我们其实在尝试调用这个 初始化程序 我们想要确保我们 对其正确调用 编译器很智能 它能知道你什么时候在 一个函数主体中做了修改 在这个例子中是将 assertion 做得 更加合适 所以就只有那个文件 需要被重新编译 其他文件不需要改变 它们是如何使用第一个文件 的 API 的 不过 它确实需要稳妥一些 如果我为这个文件 增加了一个单独的类型 人们可以知道 这个新的 名为 path segment 的结构体 不会影响右侧的文件 但是编译器还是需要稳妥一些 它会重新编译 这两个文件 我们来看看这如何 应用到前面 David 用到的 游戏例子中
现在我们有 App 目标 和 Utilities 框架 我要在每个目标中 展示一些 Swift 文件
如果我在 App 目标中 改变了一个文件 我们就已经知道 该文件需要被重新编译了 当然了 任何依赖于 这个文件的文件 都需要被重新编译 但是在 utilities 目标中的 任何文件 都不需要被重新编译 因为这是另一个分离的目标 它有明确的依赖性 在两个文件中 并没有不清晰的 可见关系 与此相似的是 如果我在框架目标中做一些改变 我将会需要 重新编译这个文件以及 在 utilities 框架中 所有依赖于此的其他文件 但是 这些依赖性更加保守 所以 Xcode 也会 重新编译在 Game 目标中的 一切 除非修改 被完全限制在函数体中 为了综合这些规则 编译器需要保守点 即使人们知道说 这个修改不会影响 其他文件 这不代表 编译器就可以知道 不过 编译器可以知道 该如何处理函数体的变化 它知道其不会影响 文件的界面 因此 也不会要求其他 文件被重新编译
这个通过文件的依赖性基础 在模块内发生 在这里 Swift 的声明 对彼此是隐式可见的 当你在通过你的 引入文件或者你的 头文件来处理 跨模块依赖性时 这些都是 整个目标的依赖性 这些就是关于 Swift 依赖性 和 Swift 目标的好的信息 但我知道你们中很多人 都是混合 Objective-C 目标和 Swift 目标 所以最后一个部分将 集中在 混合源的 App 中如何减少 Swift 和 Objective-C 代码的接口
为了实现这一点 我们将 讨论一下混合源 App 的一些内容
这个图表将有些复杂 所以请耐心点 如果你在观看视频 你可能需要暂停并重新开始 放轻松
首先看到的是 描述你的 Objective-C 接口的头文件 这就是你 App 中用 Objective-C 写的部分 你可能想要把这部分给 Swift 或者你只是想对 你的 App 中的其他 Objective-C 部分 声明头文件
我们还有桥接头文件 这就是收集 所有你想要展示给你的 App 的 Swift 部分的信息 的头文件 这是 Xcode 中 控制头文件使用的构建设置 一旦它设置完成 Swift 编译器就知道 该把这些 Objective-C 接口 给你的 Swift 代码
Swift 编译器会接着 产生一个生成的头文件 它会反向地做相同的事情 它描述了你的 Swift 代码中 哪些部分会被展示给 Objective-C
这个可以在你的 Objective-C 应用 文件中被使用 它们可能也会使用一些 第一步中的头文件 当然了 你可能拥有 不依赖于任何 Swift 代码的 Objective-C 代码 这可能不是我们今天在这里 要讨论的内容 所以 让我们从左到右 再缕一次
我们拥有 Objective-C 头文件
为了将某些 信息接入 Swift 的桥接头文件
你们的 Swift 应用文件
将信息送回 Objective-C 的 生成的头文件 最后 就是你的 Objective-C 应用文件 在像这样的图表中 所有这些箭头都代表 依赖性 不是目标层面的依赖性 而是目标内的 文件间的依赖性 所以 我们想要做的是 关注生成的头文件和 桥接头文件 因为如果我们可以 缩小这些头文件的内容 那我们就会知道事情变化的机会 其实更少 因此 需要重新构建的也更少 让我们来看一下 对于生成的头文件来说 你最有力的工具是 私有关键词 在这个例子中 我有一个在 Swift 定义的 视图控制器 它有一个 IBOutlet 属性 和一个 IBAction 方法 在默认情况下 这些会 展示在你生成的头文件中 因为它们是展示给 Objective-C 的方法和属性 它们并没有被声明成私有的
但大部分时间 你不需要将这些 展示给你项目中的任何其他文件 00:33:25.376 --> 00:33:26.276 A:middle 它们只是为了与
Interface Builder(接口构建器)交互的
所以 在这种情况下 我可以将其标为私有 并看着属性和方法 从生成的头文件中消失
另外一个例子就是 在为了用于 Objective-C 运行时间特征 如 #selector 而处理展示给 Objective-C 的方法时 在这个例子中 我使用基础的 Notification Center API 在通知发送时 它使用 selector 作为回调
再一次 这里唯一的要求 就是将方法 展示给 Objective-C 在我的项目 其他文件中 不论是 Swift 还是 Objective-C 它其实不太被使用 所以我可以把它标记为私有 这再一次 缩小了我生成的头文件的大小 在这种情况中 往往还有其他选择 那就是将其转换为 基于分块的 API 在很多例子中 这甚至可以清理你的代码 因为你可从那些 注册事件通知 的函数 暗中地获取状态 而不用一直带着它 就像带着个环境目标 最后一个 减少你生成的头文件大小 的方法其实是非常古老的 你可以更新到 Swift 4
你应该已经听说了 今年你就需要这么做 Xcode 10 将会是 Swift 3 模式被支持的 最后一个版本了
所以 这会是你之后 需要做的事情 编辑 转换 至现行的 Swift 语法
然而 当你在更新的时候 你可能会选择 为一个特定的构建设置 保持 Swift 3 的 兼容性模式 这就是 Swift 3 @objc inference 当你更新到 Swift 4 时 这是一个你可以选择的选项 它可以保持 Swift 3 中 在 NS 对象的任何子类中 自动向 Objective-C 展示内置方法和性能 的规则 如果你在 Swift 3 中编写程序 你可能会依赖这个性能 但是还有很多种情况 你们可能根本不需要 依赖于此 在运行时间 编译时间都不需要
一旦你合适的 将你的 Objective-C 依赖性明确地 标注为 @objc 或者 IBOutlet 或者 IBAction 你就可以选择这些 构建设置并点击 删除来回到 默认模式 在该模式下 OB-C 属性只会从 遵循协议要求的方法 和性能或者 那些重写自 Objective-C 的 方法推导出来 我们谈论了很多关于 生成的头文件和对你的 Swift 代码 可以做的事情 但是你也有 Objective-C 代码 Objective-C 代码也同样会 造成重新构建
一个桥接头文件一般看起来 是这样的 它拥有很多在项目中 你想要展示给 Swift 的其他头文件
我们可以放大显示 这些头文件中的一个 MyViewController 头文件 可以看到 它视图控制器的 一个非常正常的声明 它自己本身还包含了 其他头文件
这意味着如果 这些头文件中的任意一个变化了 你目标中的 Swift 代码 需要重新编译 因为它可能依赖于 某些已经变化的东西
这并不是最理想的 在这个例子中我们可以注意 我们引入 MyNetwork Manager 头文件的 唯一理由就是 声明这个属性 这个视图控制器上的 网络管理属性
很有可能 该属性从来没在 Swift 中被使用过 在这种情况下 我们就不需要在这里声明它 你能做的就是使用分类 Objective-C 的等价 拓展包 来将这个接口分解 我将在这里定义 一个新的文件 MyViewController Internal 并使用特别的 无名的语法 来让我们可以 声明附加的属性并 依旧可以使用 我的主要区块 Add Implementation 中的 属性整合功能 现在我可以将引入代码 和属性移入到的这个类别 看 引入到 Swift 的头文件 变得更小了 而且被改变的可能更小 同时也不会造成 不必要的重新构建 还有一点要注意的是
我刚刚定义的文件 很有可能在我的 Objective-C 代码中的 所有代码都不需要 访问这个属性
在这种情况下 我们不需要一个单独的文件 我可以直接将这个分类放进 我的 .m
这样做不会报错 一切都会运行得很好 正如我之前说的 属性综合还是会为 网络管理属性工作 所以我们看了些什么 我们使用私有和基于分块的 API 关闭构建设置来 缩减生成头文件的内容
我们从声明的 Objective-C 头文件中分离 单独的内容 它们缩减了桥接头文件的内容 更少的内容意味着 在每个构建中更少的工作量 同时也意味着出现变化的 几率更小了 也意味着重新构建的几率更小 这真的是一箭双雕
让我们整理一下今天的内容
David 和我讲了 很多不同的话题 包括你可以从 Xcode 中获得 更多信息的方法 以及你可以为 Xcode 提供更多的信息 来提升你构建的速度 这包括提升你 构建的效率 和减少你重新构建 所需要的工作
所以我们就快速地总结一下 如果你想要再次观看 可以去我们的视频页面 你今天下午和 和明天下午也可以在 我们的实验室找到我们 非常感谢 祝你们享受接下来的会议 [ 掌声 ]
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。