This post is an update from my previous post Is Swift production ready?, with some advice to remedy the slow compile times (as of Xcode 6.1.1 and Xcode 6.3 beta) you may be suffering with the new Apple language.
Split the app into frameworks
Once an app grows, it becomes clearer how to group classes with great cohesion. On pre-Xcode 6 we used to move them to their own Xcode groups, plain dedicated directories, or if real compile benefits were wanted you created a static library.
With Xcode 6 you can create dynamic frameworks and get great advantages. One of them is reducing recompilation impact since any change only forces to recompile the container framework and its dependents.
Creating a directed acyclic graph
In order to separate the app in several frameworks, the first step is to make sure there are no cyclic dependencies between classes of different frameworks. Otherwise, it will bring some headaches as well as leaving Xcode without clues on which framework has to build first.
There are two strategies to break these cycles:
Use the Dependency Inversion Principle
I’ll show a grotesque and familiar example of a cyclic dependency and how to turn it into an acyclic graph. It’s the common tab bar app that features the ability to jump from one tab to another.
The trespassing happens when
AppDelegate directly. This is a common example of a low level class trying to access a top level class directly without any layer of indirection in between.
If we suddenly notice that
ChartViewController is a class on its own and want to move it to a new
Chart framework that will be shared among other areas of your project, this is the result:
As the graph shows, a cyclic dependency between the two frameworks appears. By using the delegate pattern we can invert one of the dependencies and break the cycle:
ChartViewController can be safely moved to the new framework.
If you want more theory behind this, check the Acyclic Dependencies Principle.
Delegate communication between two classes to a third one
Another option is to use the typical Observer pattern and create a third class that they both depend upon by moving this last to a new framework.
A variant of this solution to avoid creating a new framework is by using
NSNotification, but pay attention that NSNotifications can make the project really intricate if abused.
But Xcode 6.3 beta has Incremental builds!
Apple just released Xcode 6.3 beta and it supports incremental builds. This is very good news but it’s not the end of the story with the cares you have to take when architecting your app.
The drawback that Xcode 6.3 beta has is that it can’t recognize if the modified class changed its interface or not. This means that even if you change the implementation of a private method, the compiler assumes the interface of the class suffered a modification and triggers the recompilation of all the files that depend on it too. On the other hand, Objective-C has a clear separation between interface (header file) and implementation, making it much easier to spot when it has to compile just a single file or a group of them.
As an example, Healthy Baby has a
DeviceData class that six other files depend on. Changing a private detail of that class takes up to 14 seconds for you to see the result on the simulator. I tried hiding that class behind a factory but got the same results.
Try one or more of the options below. Although they are pretty ad-hoc and probably you won’t need them in a near future Xcode release, they’re pretty handy and could make your day much more smoother:
Live changes with Injection for Xcode lets you modify your source code and view the results instantly on the simulator/device. This is a great tool for fast iteration when you’re tweaking variables and such.
Build Active Architecture Only set to
YESwill prevent building versions that are not needed while iterating.
Merge files into a single big
.swiftfile could reduce compile time around 40%. The drawback is that the breakpoints in the original source won’t work. I wrote a small script that can be added as a pre-action to Xcode.
Use roopc method when doing changes that do not alter public interfaces of your classes.
Give Swift compiler some hints on the types so the inference engine don’t go crazy, check impathic post on this topic.