iOS Development: 20 Best Practices
Let’s get right into it.
- Most of us tend to jump directly into coding the core logic of the app. We get it, that is where the fun lies. Following processes is tedious. Nevertheless, when you devote good time upfront to set up your project correctly, you’ll see for yourself how much time and energy you save in the future. Start by setting up the coding style guides for your project. There is no single right coding style. Setup your own style before starting with your project but keep in mind that the entire team will have to follow those guidelines. The guidelines will help make your code more uniform and therefore, easier to read.
- It’s prudent to make an early decision on the minimum iOS version you need to support in your project. Knowing which system APIs you can count on, and which OS versions you need to develop and test against, allows you to estimate your workload better, enabling you to ascertain early on, what’s possible and what’s not.
- Naming principles is often an overlooked aspect of writing high-quality, clear code. Follow Apple’s basic conventions while giving names to classes, methods, functions, constants, and other code elements. Avoid ambiguity and abbreviations, keep the names clear and brief and practice proper grammar. Additionally, the naming principles should be consistent across the whole project.
- Decide on your app architecture before writing code. Good architecture will ensure your application is more testable, easier to understand and down the line, reduce its maintenance cost. You can either go with the traditional MVC architecture or fancier architectures like MVVM or VIPER. Don’t be afraid of architectures and design patterns. Learn them and employ them, if you find yourself fighting against the architecture, you're doing something wrong. You might have to refactor some of your code. There are enough, well-explained articles about MVC, MVP, MVVM, VIPER and many more. Do your homework.
- Another aspect of building a maintainable and clear code is keeping its source files in order. Set some common folder structure, and keep it consistent throughout the project. Depending on your architecture, it’s a good idea to set up some folder structure to keep all those hundreds of source files from ending up in the same directory. This seems like a minor thing, but this will undoubtedly make your app more structured and easier to understand.
- Choose to add an external dependency to your project. Sure, for now, the one neat library solves your issue, but with the next OS version just around the corner, that breaks everything, you could get stuck in maintenance limbo soon. Else, a feature only possible with external libraries could quickly become part of the official APIs. Switching out the implementation will require minimal effort and pays off fast, in a well-designed codebase. Always consider solving any problem using Apple's extensive frameworks first.
- Instead of keeping all state around in singletons, pass any required objects in as parameters, unless the state truly is global. Meaning, use dependency injection. Also, don't bloat your view controllers with logic which could safely reside in another place.
- Don't manually integrate external dependencies into your project. It'll take up far too much of your time. A great way to manage dependencies in your application is by using CocoaPods. Its approach is similar to Ruby Gems. However, CocoaPods, because it's centralized, will eat up far too much space. However, it is a standard package manager for iOS projects. Another dependency manager, Carthage provides the simplest method of adding frameworks to your Cocoa application. Carthage is flexible and unintrusive, but the CocoaPods’ approach is easier to work with.
- Right from the beginning, keep all user strings in localization files. This is good not just for translations but also for finding user-facing text fast. You'll have with you a powerful tool that knows how to make plurals for "one," some," "few" and "many" items when needed, as soon as you've wrapped your head around the crazy syntax.
- Keep your models immutable. Then use them to translate the remote API's types and semantics to your app. Github's Mantle is a good choice for Objective-C projects. In Swift, to ensure immutability, use structs instead of classes and to do the JSON-to-model mapping, use a parsing library such as Argo or SwiftyJSON.
- Keep the scope of your constants as small as possible. For instance, it should live in that class, when you only need it inside a class. The constants should be kept in one place, that needs to be truly app-wide. Actual constants are type-safe, cannot be undefined or redefined later on in the code, have a more explicit scope and are available in the debugger.
- Schemes tell Xcode what should happen when you hit the Run, Test, Profile, Analyze or Archive action. They basically map each of these actions to a target and a build configuration. Setup Proper Schemes for your app. To test your localizations or to set diagnostic flags for debugging, you can pass launch arguments like the language the app must run in.
- Automation is a good thing. It'll help you to maintain code quality. Setup a Continuous Integration and Delivery Process. This will help you squash out bugs early in the development cycle. In Continuous Integration, as the name suggests developers integrate code several times a day into a shared repository. It allows teams to detect problems early, as an automated build verifies each check-in. Tools like Jenkins, Xcode Server, and Travis CI can help you with this. In continuous delivery, teams produce software in short cycles to make sure the software can be reliably released at any time. It aims at building, testing, and releasing software faster and more frequently. Fastlane is an excellent tool for this. It is super easy to set up and offers some powerful features that can automate your entire build and distribution process.
- Production builds should never log API tokens, passwords and the like, as this can easily lead to them being leaked to the public. Before releasing your app, take extra care to set up proper log levels. Logging the basic control flow, on the other hand, can help you pinpoint issues that your users are experiencing.
- Until Apple comes to appreciate the real power of asset catalogs and gives us an asset compiler, we have with us the R.swift tool. Fire it up and familiarize yourself with it. It'll make your code better. Asset catalogs can hold both universal and device-specific assets and will automatically serve the correct ones for a given name. They are the best way to manage all of your project's visual assets. Teaching your designer how to add and commit things there can save much time that would otherwise be spent copying things from emails or other channels to the codebase. It also allows designers to try out their changes and iterate if necessary instantly.
- You must use different environments for staging, testing and other activities related to your service. Each could have its own base URL, log level, bundle identifier, provisioning profile and so on. A basic debug/release distinction won't suffice even though Apple suggests that you use the debug build configuration for development, and create your App Store packages using the release build configuration. This is far too simple for real-world applications. On the "Info" tab of your project settings in Xcode, you can add more build configurations.
- If your app has to store sensitive data like passwords, personal user details or authentication tokens, you have to keep these in a location where they can't be accessed from outside the app. Never use Core Data or plist files on disk for this, as they aren't encrypted. The iOS Keychain will help you here in most such cases. If you're uncomfortable working with the C APIs directly, use a wrapper library.
- Ensure your app sends crash logs onto a server someplace so that you can access them. Using PLCrashReporter and your own backend, you can implement this manually, but it’s better to make use of an existing service like Fabric, Crittercism, HockeyApp, Splunk MINTexpress or Instabug. Once you have this setup, ensure that you save the Xcode archive of every build you release. The archive contains the built app binary, and the debug symbols which you will require to symbolicate crash reports from that version of the application.
- Including some analytics framework in your app is strongly recommended, as it allows you to gain insights on how people actually use it. Is button Y too hard to find? Does feature X add value? To answer these, you can send events, timings and other measurable information to a service that aggregates and visualizes them, like the Google Tag Manager. It's more versatile than Google Analytics as you can modify the data logic without having to update the app through a web service with Google Tag Manager.
- It isn't exactly a straightforward process to deploy software on iOS devices. When you need to run software on an actual device (and not a simulator), you will have to sign your build using an Apple-issued certificate. Every certificate is linked to a public/private key pair. The private half of it will reside in your Mac's Keychain. There are two types of certificates. The development certificate is generated upon request, and every developer on a team will have one. To deploy development builds to devices, this certificate is necessary. On the other hand, there can be several distribution certificates. However, it’s best to have just one per organization and then, through an internal channel, share its associated key. This certificate is necessary to ship to your company’s internal enterprise app store or the App Store. Now the provisioning profile is the missing link between certificates and devices. Perhaps the most confusing part of the system, this provisioning profile will point to the devices for which an application is signed correctly. When you visit the developer portal, you’ll notice you can create two types (again named Development and Distribution). Setup Proper Provisioning Profiles and Certificates. This is a painful, but essential step necessary for distributing and testing an iOS app.
It can be intimidating to get on board with iOS. Neither Objective-C nor Swift is widely used anywhere else, for almost everything, the platform has its own names, and it's a bumpy road for your code to actually make it onto a physical device. This blog is meant to serve as a reminder of all the best practices that will serve your application well, whether you're new to iOS or just curious about doing things "the right way." If you have good reasons for doing things differently than mentioned, we’d love to hear about them.