How to best modularize an Xcode project to speed up building - ios

I work in a team on an iOS project that has grown to enormous size in terms of Swift code.
It takes about 10 minutes to build the project from the clean state and, most scrutinizing, 30 seconds to build and run the project after changing anything in the code, even if that code pertains to a single line in a private method in Swift file symbols from which are not used anywhere else.
We've tried lots of thing to improve build times, including techniques from this nice resource https://github.com/fastred/Optimizing-Swift-Build-Times
Nothing helped, you still need to wait the whole 30 seconds after changing every minor thing to see it in the app.
We use Xcode 10, the "New build system" with the compilation mode set to Incremental. If I build the project via Perform Action > Build With Timing Summary, the longest phase is "Swift code compilation" which is nothing new. We suspect that Xcode tries to follow conservative compilation decision making and rebuilds every Swift file that could potentially have any connection to the modified Swift code. And it seems that Xcode is wrong most of the time and does redundant work.
I kinda miss Objective-C days when the compiler would look at the all import/include statements and only rebuild explicitly declared dependencies, which meant blazing fast build times.
So I now think that maybe we could break our project into modules and ubiquitously use import in Swift to tell the compiler what Swift files depend on what other Swift files.
Is there a good and perhaps automated way to modularize a big project into many small components to speed up regular lets-try-how-it-works builds?

You can split your projects into Cocoa Touch Frameworks. -> https://www.raywenderlich.com/5109-creating-a-framework-for-ios
Add each framework to its own git repo. It may be private repo.
Create private cocoapods. -> https://guides.cocoapods.org/making/private-cocoapods.html
Add new dependencies to your project podfile. -> pod 'YourFramework', :git => 'FrameworkGitRepoPath'

I might be late to the party here, but we are using this tool: https://github.com/yonaskolb/XcodeGen/
Not only did it allow us to easily split the project into modules, it completely eliminated conflicts in the project file, because it doesn't need to be added to the repository any more.

Related

Xcode 11 recompiles too much

Xcode 11 is recompiling (nearly?) my whole project, even if I just change a local private variable, or change a value of a constant in local scope, sometimes even in local private function scope. I sometime can get 2 or 3 changes with quick builds as expected, but soon enough it decides to recompile everything again (which takes too long).
Any ideas what might be going on? Is Xcode not able to determine what's changed, why does it recompile so much other stuff (even other modules).
Any advice is highly appreciated, thanks!
We had the same problem and we fixed it. Twice.
Incremental build (same build machine):
before: ~10m
after: ~35s
HOW?
Let's start with our experience first. We had a massive Swift/Obj-C project and that was the main concern: build times were slow and you had to create a new project to implement a new feature (literally). Bonus points for never-working syntax highlighting.
Theory
To truly fix this you have to truly understand how build system works.
For example, let's try this code snippet:
import FacebookSDK
import RxSwift
import PinLayout
and imagine you use all of these imports in your file. And also this file depends on another file, which depends on another libraries, which in turn uses another libraries etc.
So to compile your file Xcode has to compile every library you mentioned and every file it depends on, so if you change one of the "core" files Xcode has to rebuild literally whole project.
Xcode build is multi-threaded, but it consists of many single-threaded trees.
So on the first step of every incremental build Xcode is deciding which files have to be re-compiled and builds an AST tree. If you change a file which is acting as "dependable" on other files, so every other file which acts as "dependent" has to be recompiled.
So the first advice is to lower coupling. Your project parts have to be independent of each other.
Obj-C/Swift bridge
Problem with those trees if you're using a Obj-C/Swift bridge, Xcode has to go through more phases than usual:
Perfect world:
Builds Obj-C code
Build Swift code
Obj-C/Swift bridge:
[REPEATABLE STEP] Build Swift code, which is needed to compile Obj-C code
[REPEATABLE STEP] Build Obj-C code, which is needed to compile Swift code
Repeat 1 & 2 until you have only non-dependable Swift & Obj-C code left
Build Obj-C code
Build Swift code
So if you change something from step 1 or 2, you're basically in a trouble.
The best solution is to minimize Obj-C/Swift Bridge (and remove it from your project).
If you don't have an Obj-C/Swift Bridge, that's awesome and you're good to go to the next step:
Swift Package Manager
Time to move to SwiftPM (or at least configure your Cocoapods better).
Thing is, most frameworks with default Cocoapods configuration drag along with themselves a lot of stuff you don't need.
To test this create an empty project with only one dependency like PinLayout, for example and try to write this code with Cocoapods (default configuration) and SwiftPM.
import PinLayout
final class TestViewController: UIViewController {
}
Spoiler: Cocoapods will compile this code, because Cocoapods will import EVERY IMPORT of PinLayout (including UIKit) and SwiftPM will not because SwiftPM imports frameworks atomically.
Dirty hack
Do you remember Xcode build is multi-threaded?
Well, you can abuse it, if you are able to split your project to many independent pieces and import all of them as independent frameworks to your project. It does lower the coupling and that was actually the first solution we used, but it wasn't in fact very effective, because we could only reduce incremental build time to ~4-5m which is NOTHING compared to the first method.
There's no golden bullet here, but plenty of things to check:
Make sure you're actually using the Debug configuration in your scheme
See below for how to ensure you're using incremental builds versus whole module per matt's advice. Also make sure your Optimization Level for Debug builds is none.
If you're using type-inference heavy frameworks like RxSwift, adding explicit type annotations can speed up build times.
If the project is very big, you could consider refactoring out logical groups of source files into frameworks, but that may be too drastic of a change than you'd prefer
It might help if you provided some more specifics about the project: are you statically linking any libraries? Is it a framework or app target? How big and what swift version are you using? Do you have any custom Build Phases like linters or code generation that could be skipped sometimes?

Whole Module Optimization - place all swift files from pods in main project?

I read this article about Whole Module Optimization (WMO). I am curious if I fully benefit from the optimizations if I place all the swift files from Cocoapods directly in my main project, since then, WMO sees all the swift files as a whole and can make optimizations like function inlining and generic specialization across my whole project, instead of per module (pod).
How does WMO works for multiple pods? Does it do WMO per module/pod? If so, I think a project can benefit even more from WMO if all the swift files are in the same project, since then WHO can optimize everything as a whole, instead of per module/pod. Or am I missing something?
So before going in production, remove all the pods, place the .swift files directly in the project and run WMO looks to me like I will benefit more from WMO than not doing it.
Yes.
Whether it's worth the trouble is another question, but yes to all of it.
(I've done it this way for years, and still do in some projects, but on newer projects I've given up and accepted that frameworks are life and just let CocoaPods be CocoaPods. It's just too much work to do it by hand. But that's opinion. The answer is "yes.")

Recommended way to migrate project source code from swift 2.2 XCode 7.3.1 to swift 3 XCode 8?

I have a project which is almost completed in swift 2.2, Now I want to update the code to swift 2.2 to swift 3.0.I am using the Cocoapods for thrid party library integration and need following suggestions:
Should I go ahead update the libraries used first by running pod update? If any lib has not updated to support swift 3 what should i do?
Should I go ahead to update code to swift 3 manually or follow the XCode8 suggestions?
If I directly go for XCode8 suggestions then XCode8 starts giving suggestions to update library source code also.I am searching for the good way to handle all this. Thanks.
There is not a "best" way — as every project is potentially different in how easily it can be converted to an updated version of the language it's written in.
It would be recommended to:
Save a backup copy of the project before attempting to convert/migrate anything.
Follow the Xcode recommendations for updating the code.
Update the dependencies such as Cocoa Pods.
Fix any unresolved issues that might not have worked during the conversion.
Have patience, as often the conversion is not a one-click process.
If some of your libraries haven't been updated to Swift 3.0 then that's no problem, as you can tell the compiler to compiler only that library with the old compiler.
My suggestion, (and indeed how I updated my own projects), is to run the automatic migration wizard. (Editor>Code Syntax>Upgrade to latest Swift syntax). Then you should run pod update making sure to remove any version specifiers in your podfile.
Once you have done both of these things you can open your project and be greeted with a lot of compiler errors, the bigger your project the more you will have; unfortunately this is somewhat unavoidable because the built in migration tool sucks.
Now it is going to be a case of trudging through and fixing these errors, most of them will probably be something really simple like dropping AnyObject for Any, but a few may trip you up.
Now as I mentioned, if a library hasn't yet been updated for Swift 3.0 you can open your Pods project settings and select the library in question. Under build settings you'll find a key called Use legacy Swift syntax. Set this to true and rebuild. This forces the compiler to use the old compiler for this pod only.
I hope this in depth explanation is of help.
I Found some strategy migrating to Swift 3
I migrated directly to Swift 3.0. I use CocoaPods, so I migrated my dependencies first. This means following the steps above for each open source library (or private pod).
So far, the only dependencies I have for this project are my own libraries, which made this quite easy since I control all of the code. However, if you have third-party dependencies, then I would recommend opening an issue on the project to discuss migration plans with the current maintainers. I expect most popular projects are doing something similar to what I described above. For example, AlamoFire has a swift2.3 and swift3.0 branch. If needed, you can fork and migrate third-party libraries yourself — then submit a pull request or use your fork until the maintainers offer a solution. However, you should definitely reach out to project maintainers before submitting a pull request for migration.
Until Xcode 8 is final, you’ll need to point your pods to these new branches:
pod 'MyLibrary', :git => 'https://github.com/username/MyLibrary.git', :branch => 'swift3.0'
This tells CocoaPods to fetch the latest on the swift3.0 branch, instead of the latest published version.
Once your dependencies and Podfile are updated, you can run pod update to bring in the Swift 3.0 versions of each library. Then you can migrate your main app. I suggest commiting all of this migration in a single commit — update all dependencies, migrate your app, then commit — to keep your history clean.
Bugs
Xcode’s migration tool is not perfect.It would sometimes fail to migrate test targets, or only partially migrate app and framework targets. When this happens, you can attempt to run the tool again, but it’s probably best to make changes manually. Here are some of the specific issues that I saw:
Some expressions inside of XCTAssert*() did not migrate.
Some expressions inside closures did not migrate.
Sometimes waitForExpectations(timeout:) did not migrate.
Migrating NSIndexPath to IndexPath when used in certain contexts often resulted in derpy things like (indexPath as! NSIndexPath).section.
Enums with associated NSDate values migrated to case myCase(Foundation.Date) instead of case myCase(Date).
Sometimes optional protocol methods did not migrate, which can produce hard-to-find bugs.
Ref Links :
http://www.jessesquires.com/migrating-to-swift-3/
https://swift.org/migration-guide/

How to handle large Swift Project?

After iPhone app that I'm writing in Swift become quite big (> 150 .swift files + various Objective-C libs), Xcode start behave pretty badly:
every second compilation I get various errors, e.g.:
Command failed due to signal: Segmentation fault: 11
compilation take enormous amount of time (> 2 min on MacBook Pro Retina)
and so on.
I just wonder if everyone has same problems and maybe someone found a way to reduce this nightmare?
What I have done so far — I split project into several dynamic frameworks that I link from main project, it helps to reduce compile time, but introduce some new problems.
I also use iRamDisk to keep DerivedData folder in RAM and periodically delete all files from it, it sometimes helps with SourceKit crashes.
Swift toolchain is still a bit gross, you'll need to use some temporary workarounds until Apple fixes it (see UPDATES below)
Here is a list of items that you can do to keep yourself from going crazy.
Slowness caused by immature Swift compiler
Change your development workflow using Injection for Xcode. Once you installed the plugin, you'll be able to inject code changes in your simulator\device without recompiling. You don't need to hardcode\modify anything in your project. We started using it recently at work and it made a huge impact on our side, even if it doesn't apply to every use case (for example you can't create new functions, you can only modify the existing ones).
Some particular code constructs that the compiler doesn't like and takes too much time to compile. The most common problem is with the Type Checker that slows down compile time exponentially based on how many type checks it needs to do (read more here for practical examples and here for a detailed explanation). In order to identify if you are suffering from this problem you can follow this blog post, you will gather information about the functions that creates slowness by using some compiler additional flags. Alternatively you can use this Xcode plugin to identify the source of the build slowness.
Use dynamic frameworks wisely, where it makes sense. A framework recompilation will be done only when you modify one of its Swift files (dynamic frameworks are only available for iOS >= 7).
Condense code in the same files. Lowering the number of Swift files speeds up the compile process sensibly. You can easily achieve it enabling "Whole module optimization" by adding a user-defined custom flag SWIFT_WHOLE_MODULE_OPTIMIZATION and set it to YES and at the same time set optimization level to none (to disable optimizations that would make it slow) OUTDATED You may consider to use this gist, it's a build script that collapses all your code in a "merge.swift" file.
You'll need to create a new target for it, but it is worth a
try.
Double check things listed here (there are a few some more misc reasons because the compilation is slow)
OUTDATED Try the approach described in this blog post, it involves creating a build script that generates a make file. It requires manual intervention on the build script (it contains the list of swift files).
OUTDATED Try this hacked up incremental compilation technique
UPDATE: Incremental builds introduced on Swift 1.2 (Xcode 6.3)
Apple finally introduced incremental builds with Swift 1.2 (shipped with Xcode 6.3). It's not still perfect, but it's a huge improvement.
From now on a class is recompiled only when it is changed (or when one of the class it depends on has been changed).
However the compiler still can’t understand if the changes to a class are to its interface or not. So any kind of change to a class causes a recompilation of that class and all of its dependencies.
UPDATE: Recompile dependent classes only when public interface changes introduced on Swift 2.1 (Xcode 7.1)
Starting from Swift 2.1 (Xcode 7.1), the dependent classes are recompiled only when you change the public interface of a class, and not at every change. This makes an huge difference in particular for big projects.
Project (mis)configuration (not related to Swift)
Be sure that "Build Active Architecture Only" is YES for debug.
Be sure that you didn't add pre\post compilation scripts that take too much time.
Apple has some advices for speeding up your Xcode build in Technical Note 2190. Have you thought about creating and precompiling an own framework for outsourcing unchanged Swift modules or some/all Objective-C code?
Remove all type inferences in Swift.
This SO topic has some nice ideas and this blog post suggest to
stop generating dSYM bundles and
avoid compiling with -O4 if using Clang.
Although lots of these improvements are related to Objective-C, I am quite sure, that some of them are still relevant for Swift.
The (re)compiling is a known issue that I am sure will be resolved soon. Some recommendations:
Use Objective C where possible - it compiles fast even if it is a part of a Swift project
Split code to frameworks
Specify types instead of leaving it up to the compiler to infer them
Again, there is a good chance that this will be fixed soon, so perhaps it is best not to make big investments in rewriting or reorganizing the code at this point in time.
you could try:
upgrading the amount of RAM in your computer
if you have multiple .swift files that do things on the same view controller, try condensing them into one .swift file per view controller
tweaking the settings under compile sources to see if you have any duplicates or if there are any scripts or settings you can add to make it compile faster...
you can also take a look at this post's answers for some hints as to what you can do to slow down compile time
I've discovered that one of the main causes of segmentation faults and slow compilation is hardcoding big arrays and dictionaries, especially when declaring them as global constants and trying to access values from them from within another .swift file. When I store all that data inside plists, these problems are gone.
In my experience avoid creating the large swift files, when I started a project in my new company, there was a 'UIViewController' with more than 2000 lines, little changes on this file taking much time to build, I made 4 extensions with <500 lines from that class, my speed improvement was incredible.

Remove unnecessary frameworks in Xcode project

I have been writing iOS applications and completed a project with a lot of frameworks. Now I am using it as a template to start a new project that requires less functionality and hence I should be able to reduce the frameworks required, and hopefully reduce build time and size of project.
Question:
Is there a quick way to check which frameworks are no longer required within the project?
I don't think there is a better way than removing the framework, building, and seeing if there are link errors. You might be able to write a bash script but it's probably more work than it's worth.
Sadly not. The quick way is to remove all the frameworks, look for build errors and add back in the necessary frameworks.
search in the project files(cmd+shift+F).i.e whether you are imported any files related to the frameworks.

Resources