Xcode Build Times with Custom SF Symbols
In WWDC 2019: 206, Introducing SF Symbols Apple announced a great new resource for iOS developers:
SF Symbols introduces a comprehensive library of vector-based symbols that you can incorporate into your app to simplify the layout of user interface elements through automatic alignment with surrounding text, and support for multiple weights and sizes.
Since then, many more symbols and various color render modes have been added. Apple also made it possible for designers and developers to create their own SF Symbols for including in their apps.
Apple’s SF Symbols Mac app is the official browser for the symbols. It was recently updated to v3.2 (build 67) and now includes the images that were added in iOS 15.2, watchOS 8.3 and macOS 12.1. I have previously written about the SF Symbol changes in iOS 15.2. That article has links to other articles detailing changes in earlier iOS versions.
My own Adaptivity app for iOS and Mac includes a very comprehensive view to browse SF Symbols. It can view any of the different SF Symbol data sets that your device supports and has smart collections to highlight added and renamed symbols. It also has support for two custom SF Symbol data sets:
- 75 social media icons from Icons8: https://icons8.com/l/sf
- 286 icons from Niels Boey based on the open source Feather icons: https://nielsx.gumroad.com/l/sf-feathers
The Icons8 symbols only support monochrome rendering. They take advantage of the v3 symbol format introduced alongside iOS 15 to include just three weights and a single scale. The other weights and scales can be derived from these using interpolation.
In my testing, the v3 symbols can be included in an Xcode asset catalog and used prior to iOS 15, but some weights and scales do not render correctly. Importing the symbols in to the SF Symbols Mac app and exporting them in the v2 format fixes that issue but introduces another: the images are not tinted if they are shown in hierarchical or palette render modes on iOS 15. Since they are monochrome images, there’s no need to try and render them with those modes!
The SF Feathers symbols support monochrome and the new hierarchical/palette rendering mode added in iOS 15. They too use the v3 symbol format but include images for all the weights and scales.
Xcode Build Times
When I added the SF Feathers symbols to Adaptivity, I noticed a considerable increase in build times, especially when building for Mac Catalyst. This article summarises my investigations as to why, and has some suggestions for improving build times if you include a lot of custom symbols in your own app.
In order to eliminate as many other factors as possible, I created a simple test app and removed unnecessary things like storyboards, main view controller and scene delegate. I tweaked the Info.plist and build settings to remove references to them. I did, however, keep the asset catalog that Xcode created with an empty icon set and empty
AccentColor. Removing those caused build issues and it seemed easier and less likely to cause problems to just keep them. They did not make a noticeable difference to asset catalog build times. I also enabled support for Mac Catalyst and selected
Optimize Interface for Mac (although this didn’t seem to make any difference to the results). [EDIT: following a comment by Steve Troughton-Smith on Twitter, I also added an AppKit target to test building for a native Mac app.]
Finally, I added separate asset catalogs, each of which included varying numbers of symbols from the SF Feathers data set (16–256 in powers of two).
For each test run I would exclude all my custom asset catalogs from the target and do a build. I would then add just one asset catalog of the appropriate size for the test and do another build. This was to try and ensure a minimal incremental build and to try and accurately measure the time it took to build the asset catalog without other parallel build steps competing for CPU resources. I tested each asset catalog size five times and averaged the results, and tested iOS, Mac Catalyst and AppKit builds. I also measured the size of the
Assets.car file that was created by the build.
All my tests were made on a 2018 Mac Mini, 3.2GHz 6-Core Intel Core i7 running macOS 12.1 and Xcode 13.2.1. I tried to minimise the impact of other processes by closing as many apps as possible, stopping cloud backups, Dropbox and (when I noticed!) skipping any Time Machine backups that would start during my testing. My timings won’t be perfect, but are good enough to show general trends.
June 2022 Update: I re-ran the tests on the same Mac mini, now running macOS 12.4, and with Xcode 14 beta 2 and have included my results below.
iOS Target Build Times
By default, when Xcode builds for an iOS target it only includes assets that it needs for that target. This, for example, will result in 3x assets not being included when building for devices that don’t have a 3x display. The
Build Active Resources Only build setting controls this.
When archiving for uploading to App Store Connect, this build setting seemed to be ignored. Creating different versions of the asset catalog is presumably one of the mysterious things that happens when uploads are “Processing”.
The screenshot below shows the
Compile asset catalogs step took 7.9s in one particular test. I have expanded the command line and highlighted the parameters that were included due to the
Build Active Resources Only setting: a 5th generation 12.9" iPad Pro simulator running iOS 15.2:
When building for iOS targets, Xcode would use a process called
AssetCatalogSimulatorAgent. This would run at 100% CPU until the asset catalog had been built. Interestingly/annoyingly, aborting the build in Xcode would not kill this process and it would need to run to completion.
Test Results (Xcode 13.2.1)
As you might expect, the time taken to build the asset catalog was proportional to the number of assets. Somewhat unexpectedly, the
Build Active Resources Only build setting had a significant difference on build times. For an approximately 23% reduction in
Assets.car file size with the setting enabled, it took approximately ten times as long to build!
Test Results (Xcode 14 beta 2)
Using Xcode 14.0 beta 2, build times with the
Build Active Resources Only build setting enabled were faster (approximately 70% of the Xcode 13.2.1 build times). With the setting disabled build times were still considerably quicker than with it enabled, but about 5% slower than with Xcode 3.2.1. The
Assets.car file sizes were approximately 1% larger than with Xcode 13.2.1.
Mac Catalyst Target Build Times
Build Active Resources Only build setting didn’t seem to make any difference when building for Mac Catalyst. No extra flags were passed on the command line when the setting was enabled. I was only able to test with one Mac and one version of macOS.
Test Results (Xcode 13.2.1)
Unlike building for iOS targets, building for Mac Catalyst used a process called
ibtoold. It too would run at 100% CPU and not be killed when aborting a build. I have no idea why a separate tool is used, nor why the build times were both considerably worse than for iOS targets, and seemingly O(n²) instead of scaling linearly with the number of symbols 😱.
Test Results (Xcode 14 beta 2)
Using Xcode 14 beta 2, Mac Catalyst builds were about 93% of the Xcode 3.2.1 build times. They remained considerably worse than iOS and are still seemingly O(n²) instead of scaling linearly with the number of symbols. The
Assets.car file sizes were about 0.01% larger than with Xcode 13.2.1.
As with a Mac Catalyst build, the
Build Active Resources Only build setting didn’t make any difference to build times or the command line used for compiling the asset catalog.
Test Results (Xcode 13.2.1)
Again, similarly to Mac Catalyst builds,
ibtoold was used for compiling the asset catalog and would consume 100% CPU. However, unlike Mac Catalyst, the build times scaled linearly with the number of assets. The build times were very similar to the iOS target when
Build Active Resources Only was disabled.
Comparing the command line arguments passed to
actool when compiling for Mac Catalyst or AppKit the interesting (non-path-related) parameters are:
It looks like
--ui-framework-family uikit parameter used for Mac Catalyst targets is the key difference. This is not documented in the man page.
Test Results (Xcode 14.0 beta 2)
Using Xcode 14 beta 2, AppKit build times were about 90% of the Xcode 3.2.1 build times (except for the 256 image case — but maybe I recorded bad data or other tasks were running in the background). The
Assets.car file sizes were about 96% of the Xcode 13.2.1 sizes.
Something very, very strange is happening with Mac Catalyst builds!
I can believe that building for Mac Catalyst could require some different processing and produce different-sized
Assets.car files compared to building for iOS. But I cannot begin to understand why it is so much slower and significantly more so as the number of symbols increases. With just 16 custom symbols, a Mac Catalyst build takes approximately four times as long as building for iOS with
Build Active Resources Only disabled. For 256 symbols, it took over thirty times as long! Enabling that build setting worsened the iOS builds by a factor of about ten but at least iOS builds have the decency to scale linearly. AppKit builds behaved very similarly to iOS builds with
Build Active Resources Only disabled: fast and linear.
I didn’t have enough time (or will) to test other factors such as minimum deployment versions. I did notice that targetting an iOS 14 device when
Build Active Resources Only was enabled took a little over half as long as building for an iOS 15 target (i.e. only approximately six times as long as disabling the setting). I’m guessing this is because versions of the symbols suitable for hierarchical/palette rendering are not required on iOS 14.
I have raised Feedback FB9828086 with Apple.
June 2022 Update: Xcode 14.0 beta 2 build times were improved on all platforms, most significantly on iOS when
Build Active Resources Only was enabled. The Mac Catalyst builds are still considerably slower than iOS and continue to look like O(n²) performance.
If your app has even a small number of custom SF Symbols you might want to consider one or more of the following suggestions to improve build times:
- avoid switching build targets, especially between iOS and Mac Catalyst, in order to prevent Xcode re-compiling the asset catalog
Build Active Resources Only
How I improved Adaptivity Build Times
Re-compiling the asset catalog in Adaptivity (with all 286 SF Feathers symbols, 75 Icons8 symbols and a few other assets) for Mac Catalyst would take over 15 minutes with
ibtoold eating 100% CPU all that time! During development I often switch build targets and so this was completely unacceptable. Since showing the Icons8 and SF Feathers symbols is not something I need to test often, I have implemented a more drastic approach to keep my build times sane:
- in my
mainbranch I have excluded the asset catalogs containing the custom symbols. I see no images when browsing those data sets
- my app delegate has conditional code to cause a compiler error in non-debug builds. This prevents me from ever accidentally releasing a build without the custom symbols
- these changes are inherited by any other branches I make during feature development. Build times are now sensible again
- if I ever want to test the custom symbols, I can temporarily include them, go make a cup of tea and hope I never accidentally commit their inclusion
- for releases, I have a separate
releasebranch which was taken from
mainjust after I excluded the assets and added my compile-time protection code. The first commit in this branch re-enabled the assets and removed that code. All future releases to the App Store will need to be made by merging to this branch so that the custom symbols are included
Adaptivity has many other features in addition to browsing SF Symbols. It is primarily a tool to visualize the different window sizes, layout margins, readable content guides, bar heights and Dynamic Type sizes that a modern, adaptive, iOS app uses when running on different devices and iPad multitasking sizes. There are also views for browsing System Colors, System Fonts and System Materials, and a view for exploring iPadOS Pointer Interactions. In iOS/iPadOS 15 you can also configure
UISheetPresentationController options for modally-presented view controllers.
The app is a universal purchase and includes the Mac Catalyst version. On macOS 11 and later, this is “optimised for Mac” with native controls and does not scale content. If you are an iOS developer or designer, I’m sure you will find Adaptivity very useful. Testimonials, more screenshots and information on all the features is available on my web site.
Other Articles That You Might Like
I have written articles on How iPad Apps Adapt to the New 8.3" iPad Mini, How iOS Apps Adapt to the various iPhone 12 Screen Sizes, Bringing Adaptivity to Mac Catalyst, How to Switch Your iOS App and Scene Delegates for Improved Testing and the View Controller Presentation Changes in iOS 13.
The search algorithm used in Adaptivity’s System Colors and System Images views is described in A Simple, Smart Search Algorithm for iOS in Swift. I have also written about Working with Multiple Versions of Xcode and how to Hide Sensitive Information in the iOS App Switcher Snapshot Image.
If you found any of these articles helpful then please take a look at my apps in the iOS App Store to see if there’s anything that you’d like to download (especially the paid ones 😀).
If you work with a lot of Xcode projects you might like my Mac Menu Bar utility XcLauncher. It’s like having browser bookmarks for your favorite Xcode projects, workspaces, playgrounds, and Swift packages. There is more information on my website about XcLauncher’s features.