探究Xcode New Build System对于构建速度的提升

在Xcode9发布的时候,Apple在Build System上提供了新版本的构建系统(New Build System),具体可见WWDC2017,不过令人失望的是,该新特性的讲解很简短,短到只在一页PPT上露脸,在这短短的时间里,苹果讲述了该构建系统的优点:降低构建开销,尤其可以降低大型项目的构建开销。

当然对于该新特性的使用,苹果为开发者提供了足够的过度时间。在Xcode9中,该构建系统没有设置为默认的构建系统,而在Xcode10中,苹果将该系统设置为默认的构建系统,开发者可以通过Xcode->File->Project Settings/WorkSpace Settings->Build System在新旧构建系统间切换。

切换构建系统

幸运的是,苹果在WWDC2018中讲述了新构建系统在构建时的优化方式,我们接下来就探究一下New Build System是如何提高构建速度的。当然,该方式可能会引起一些问题,我们在最后进行讨论解决。

一、项目环境准备

在我们实际开发中,我们的项目可能会有依赖多个其他的工程或者说依赖多个第三方库。这些依赖包括两部分,分别为Target Dependencies以及Link Binary With Libraries两部分,这二者有一些区别:

  • Target Dependencies是指Target所嘘依赖的其他Target,被依赖的Target必须在本Target构建之前就构建完成。除此之外没有任何关联。
  • Link Binary With Libraries是指最终要Link到Product中的文件,同时在Link到Product中时,需要保证文件存在,这就要求在构建Target时该项目下的文件必须提前构建完成。

说白了,Target无论通过哪种依赖,都需要保证被依赖的内容在Target构建前就已经被构建成功。

接下来我们就构建一个项目环境,并设置好依赖关系,此处我们使用WWDC演示中提供的项目结构。

Target Dependencies

其中连线为依赖关系,箭头所指为被依赖target。

二、旧构建系统

对于所有程序来说,如果我们要构建其中一个Target,那么以下几点是可以确定的:

  1. 所需要构建的所有Target。
  2. 这些Target之间的依赖关系。
  3. 这些Target构建的顺序。

以本例中构建Tests Target来说:我们要想构建Tests,那么图中所有Target都需要进行构建,同时图中依赖线也能够很好的表示所有Target之间的依赖关系,而对于构建顺序可以表示为下图:

Build Order

其实对于上图来说,在构建过程中,会造成多处理器系统资源的浪费,从而表现为编译时间的浪费,解决这个问题的方式就是采用并行编译,这也是New Build System优化的核心思想。

三、New Build System优化

1. 内容拆分

首先我们再来细分一下Tests的依赖关系,Tests现在的依赖关系如下:

Tests Target Dependency

那么Target中的内容可以分为三类,即:

  1. Game的test
  2. Shaders的test
  3. Utilities的test

如图:

Tests Target Content

此时,Tests的构建就不必等到GameShadersUtilities三个Target都构建完成才进行。对于每一部分的构建可以等到对应Target构建完成之后就可以立刻开始,如图:

Tests Content Build

由此可见,通过内容拆分,我们可以并行的进行构建,从而降低构建时间。

2. 内容提取

现在我们再来看一下ShadersUtilities之间的依赖,由名称可以看出,Utilities会提供一些工具方法,而Shaders会使用到Utilities中的一些方法,同时显而易见的是,对于Utilities中的方法,Shaders并不会全部使用,这就为构建优化提供了思路:Shaders的构建可以在Utilities中与之有关的内容构建完成之后就可以进行,如图所示:

Shaders And Utilities

虽然Utilities存在对Physics的依赖,但是理想状态下,如果提取出的Code Gen不存在对Physics的依赖,那么Code Gen的编译就可以提前到与Physics一个时间点,如图:

Code Gen Build

通过内容提取,可以将某些内容的构建时间点提前,从而减少整体构建时间。

3. 遗留依赖清理

在我们的项目迭代开发中,我们可能会删除一些之前的无用代码,慢慢的,最新的代码可能不会依赖之前的某些框架,但是对于框架依赖的设置可能由于遗忘而遗留下来,我们可以通过清理这部分遗留无用依赖来加快构建速度。

在本例中,假设经过长时间的迭代,Utilities中的内容已经不存在对Physics框架的任何依赖,此时如果我们清理掉UtilitiesPhysics依赖的设置,那么Utilities的构建就不必等到Physics完成了,如图:

Utilities Build

及时清理遗留的无用依赖设置,可以提前某些模块的编译时间点,进而减少整体构建时间。

4. Xcode10新特性

Xcode10同时提供了一些新特性来更好的利用并行构建系统:

  1. 提前编译源码的时间点。
  2. 一旦所依赖的内容构建完成就可以开始构建,无需等待全部依赖构建。
  3. 优化Run Script phases的执行来减少编译工作。

四、 Run Script phases的优化

New Build System中,优化了Run Script phases的执行工作,总得来说,就是为Run Script phases引入了依赖的概念,进而将Run Script phases放入并行构建中,从而加快构建速度。那么Run Script phases的依赖关系如何确定呢?

1. Input Files/Output Files

New Build System中,将Input FilesOutput Files作为该Run Script phase的依赖关系,构建系统会根据这些文件来确定Run Script phases在构建过程中的执行时间点,具体原则如下:

  1. 符合如下情况会执行
  • 没有指定Input File
  • Input File内容改变
  • Output File丢失
  1. 执行时间点
  • 若没有指定Input File,执行时间点会在构建最开始
  • 若指定了Input File,则需要保证Input File构建完成

2.Input Files List/Output Files List

在Xcode10中,苹果为了避免开发者在执行脚本时可能指定过多的Input FilesOutput Files,新增了Input Files ListOutput Files List,在这两个参数中,可以指定一个后缀为.xcfilelist的文件,在该文件中列举所需依赖的Input FilesOutput Files,文件内容格式如下:

xcfilelist

3. New Build System可能引起的问题

开发者可能在项目中设置一些Script,在其中可能会做一些Build versionApp Icon等的设置,这些脚本在旧的串行构建系统中会在最后执行,最终完成所需内容的替换,达到所需目的。但是在新构建系统中,若不做特殊设置,该脚本会在并行构建的开始阶段就执行,从而无法保证最终的替换能够生效(可能会被其余构建过程替换),例如:

项目中设置了如下脚本:

svnv=`svnversion -c |sed 's/^.*://' |sed 's/[A-Z]*$//'`

/usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${svnv}" "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}"

将当前svn的版本号写入最终App的Info.plist文件中,在并行构建系统中,该脚本很大可能会在当前Target构建过程的Process Info.plist操作前完成,从而对应Bundle Version的值被覆盖重写,造成脚本执行结果失效。

解决方式就是为脚本设置好依赖关系,从而保证脚本执行在Target构建之后。我们知道,Process Info.plist过程会为.app文件生成Info.plst文件并进行初始化,我们可以将该文件设置为Run Script phasesInput Files,保证脚本的执行时间点在Info.plist更改之后,进而保证脚本的执行结果有效。

五、 参考文献

  1. https://developer.apple.com/videos/play/wwdc2018/408/
发布了71 篇原创文章 · 获赞 34 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/TuGeLe/article/details/84885211