iOS project best practices and tools
With an open source Xcode project template
When working on greenfield iOS projects I often had to start a new project from scratch. While doing so, me and my team were always spending a lot of time on basic project setup like integrating tools, setting up the project structure, writing base classes, integrating external libraries etc.
I decided that the time spent on project startup could be saved and the process could be mostly automated. I wrote down all the usual best practices and tools we used and prepared a project template that me and my team could use when starting new projects. This template should save project setup time and also provide a common foundation that each team member will be accustomed to so that you don’t have to think and explore the project structure and foundations. They will always be the same.
Each and every tool or best practice included in the template deserve an article on its own but I wanted to try and sum up each point and give a brief explanation why I included them.
I don’t think this one needs an introduction. This is the library for managing external dependencies for iOS projects. It’s been around for a long time and is robust and battle tested in thousands (if not millions) of projects. There are alternative dependency managers out there like Carthage but I decided to go with Cocoapods because it has the widest range of open source project that it supports. Using Cocoapods is very easy and it comes with a search index that lets you easily discover packages that you may need.
The template project comes with a simple Podfile that includes Swiftlint and R.swift. The template also includes a Gemfile for managing the Cocoapods version used to resolve the dependencies. This is an often overlooked improvement that prevents issues arising when developers on your team install dependencies using different versions of Cocoapods itself. The Gemfile enforces using the same Cocoapods version across the whole team.
Swiftlint is a very useful tool for enforcing certain rules and coding style for every programmer on the team. You can think of it as an automated code review system that warns the programmer about dangerous things like force unwraps, force casts, force tries etc. but also enforces a common coding style by making sure all programmers follow the same “code style” related rules like indentation or spacing rules. This has tremendous benefits of not only saving code review time by doing those basic checks, but also it makes all files in the project look familiar which increases their readability and as a result their understanding by all the devs. You can find a list of all the rules here. In the template, Swiftlint is installed via Cocoapods and included in the Build Phases step so it lints and warns the developer at every project build.
R.swift is a tool for getting strongly typed, autocomplete resources like images, fonts segues and localizations. It does this by scanning your project and generating swift classes needed to get the resources. The biggest selling point of this library is that while using resources it makes your code:
- Fully typed — less casting and guessing what a method will return
- Compile time checked — no more incorrect strings that make your app crash at runtime
- Autocompleted — never have to guess that image/nib/storyboard name again
Consider the following code using the official string API:
let icon = UIImage(named: “custom-icon”)
If you misspell the image name you will get a nil here. If any member of your team changes the name of the image resource this code will return nil or will crash if you force unwrap the image. When using R.swift this becomes:
let icon = R.image.customIcon()
Now you can be sure that the icon really exists (the compiler will warn you if it does not thanks to compile time checks) and you are sury you will not make a typo in the icon name since you will be using autocomplete.
R.swift is installed via Cocoapods and integrated in the template as a Build Phase and will generate the Swift wrapper classes on each build. That means if you add a file/image/localization/font/color/nib etc. it will be available to you using R.swift once you compile the project.
Separate AppDelegate for tests
An often overlooked good practice is to have a separate TestAppDelegate class when running tests. Why is it a good idea? Well, usually the AppDelegate class does a lot of work at app startup. It could set up a window, build the basic UI structure of the app, register for notifications, set up a database and even sometimes make API calls to some backend service. Unit tests should not have any side effects. Do you really wan’t to make random api calls and setup all of the UI structure of your app just to run some unit tests?
The TestAppDelegate is also a great place to have code that you only want run once during your test suite execution. It could contain code that generates mocks, stubs network requests etc.
The template contains a main.swift file which is the main entry point for the app. This file has methods that check what is the environment the app is currently running and if it’s the test environment, invokes the TestAppDelegate.
Compiler performance profiling flags
Swift is a great language, easier to use and much safer than Objective-C (IMO). But when it was first introduced it had one big downside — compile times. Back in the Swift 2 days, I was working on a project that had roughtly 40k lines of Swift code (a medium sized project). The code was very heavy with generics and type inference and it took nearly 5 minutes to compile a clean build. When you made even a small change, the project would recompile and took around 2 minutes to see the change. That was one of the worst developer experiences I ever had and I nearly stopped using Swift because of it.
The only solution back then, was to try and profile the compile times of the project and try to change your code in such a way, that would make the compiler faster. To help with this, Apple introduces some unofficial compiler flags which would warn you when compiling a method body or resolving the type of an expression took too long. I added those flags to the template project so you will be warned from the start about long compile times for you app.
Nowadays the build times have been dramatically improved and you very rarely need to tweak your code just to improve build times. But still it’s better to know up front then to try to fix the problem when the project gets too big.
Another good practice (or may I say necessity) is to have separate configurations and environment variables for the development, staging and production environments. Almost every app nowadays have to connect to some kind of backend service and usually, those services are deployed on multiple environments. The development environment is used for daily deploys and for devs to test their code. The staging environment is used for stable releases for testers and clients to test. We all know what the production environment is for.
One way to support multiple environments in an iOS project is to add project level configurations.
Once you defined the configurations, you can create a Configuration.plist file containing the variables for each environment.
When running the project, you can specify which configuration should be used. You can do this in the build scheme.
You then need to add one additional property in the project Info.plist file. The value of this property will be dynamically resolved at runtime to the name of the current configuration.
This is all preconfigured for you in the template.
The only thing left is to write a class that can retrieve those variables at runtime depending on the configuration selected in the build scheme. The template contains a ConfigurationManager class that can retrieve the variables for the current environment. You can check the implementation of that class on Github to see how it works.
Every project should have a basic Readme that at least has instructions how to install dependencies and run the project. It should also contain a descriptions of the project architecture and modules. Unfortunately developers don’t like to write documentation (a readme is part of that) and I have seen project that were being developed for months and they did have even a basic Readme. To take away the burden of writing this basic readme, the template contains a standard readme that covers installation and project structure. When you set up a new project using the template, you will have the Readme included automatically.
Nowadays, most projects use GIT as their version control system. When using GIT, you usually wan’t to ignore some files or folders in the project like the build folder or the derived data folder. To save you the trouble of searching for a gitignore file that suits your iOS project, the template includes a standard gitignore provided by Github contributors.
Base classes for handling deeplinks and notifications
Almost every app nowadays has to handle deeplinks and notifications. To do this, a developer has to write some amount of boilerplate code in the AppDelegate class. The template has that covered and also provides base classes that make working with deeplinks and notifications easier.
To sum up, the template tries to include best practices and integrates useful third party tools. This should save you and our team hours spent on new project setup and also provide a common and strong foundation for the rest of the project. May it serve you well!
PS: If you have any issues or feature request for the template just leave me an issue on Github. I will try to resolve it in my free time.