iOS project template with fastlane lanes, Travis CI jobs and GitHub integrations of Codecov, HoundCI for SwiftLint and Danger.
https://to.messeb.com/ios-project-template
This repository contains a template for iOS projects with a framework-oriented architecture approach, preconfigured fastlane lanes, Travis CI jobs and Github integrations of Codecov, HoundCI for SwiftLint and Danger. It provides a starting point for new projects which can be immediately distributed via HockeyApp and Testflight.
This template has three goals:
- It can easily be configured.
- It will contain all the necessary files to build and distribute the app.
- The different parts work independently.
Contact
Follow me on Twitter: @_messeb
Contact me on LinkedIn: @messingfeld
Table of Contents
Introduction
To set up new iOS projects with a ci/cd pipeline is always a little mess. Everyone gives input, but usually, someone of the team members has to care about all the following steps:
- Setup a build server or build service.
- Manage the internal distribution of the app to the product owner and testers.
- Providing the signing certificates and provisioning profiles.
- Configure signing for different release stages.
- Creating ci/cd pipelines for pull request and distribution.
- Add code quality definitions for pull requests, like linting and test coverage rules.
The repository contains an example solutions for all of the points. For every step, it includes one solution.
Xcode Project
The iOS template consists of an Xcode workspace file (iOSProject.xcworkspace) with the project for the app (iOSProject.xcodeproj) and a framework project (Core.xcodeproj), named Core. The structure is:
Project structure
The app project is separated into three main folders: Scenes, Resources, and Configurations. It's only an alternative structure to the default Xcode project.
Scenes: Should be the folder, where the different scenes or modules of the app are placed. The concrete structure depends on the chosen app architecture.
Resources: Contains all assets for the app, like the launch screen or the image assets.
Configurations: Contains all the files which define the build artifacts of the app. It contains the Info.plist and a Builds folder with *.xcconfig files for the different build configurations.
The root folder of the project also includes the AppDelegate.swift and the main.swift files.
AppDelegate.swift: In the AppDelegate the main UIWindow instance of the app is created, and an empty UIViewController instance is assigned as the root view controller. Modern app architectures, like MVVM or VIPER, work better with manual creation of the entry point, than using the Main Interface possibility. Therefore the Main.storyboard file was deleted and the Main Interface reference from the project removed:
main.swift: In a default iOS project, the AppDelegate class is annotated with @UIApplicationMain (scroll down to UIApplicationMain). It replaces the main function of the project and the manual call of UIApplicationMain(::::). To reenable the possibility to call it manual, the annotation has to be removed, and you have to create a main.swift file. With a main.swift file, it's possible to customize the parameter for the UIApplicationDelegate. You can set an empty instance for unit tests to prevent side effects of parallel code execution in the test host.
Configurations (*.xcconfig files)
The whole project configurations are moved from the project file to *.xcconfig files in the iOSProject/Configurations/Builds folder.
The usage of *.xcconfig files in a project solves two problems:
- Separation of project configuration and file references in the project. Now, only changes of files and folders are made in the project file. Changes of configurations in the project file should always be rejected. This prevents mistaken changes during development.
- Traceability which changes are done in the project configuration during development. The history of changes in a decides *.xcconfig file can be more straightforward analyze, than in a complex project file with additional modifications.
You see in the project build settings that all the configurations are moved from the project to config files. Enable All and Levels:
The configurations are also split in different files:
Application.xcconfig: Contains all configurations which were in the Project section of the build settings. These values are set for all of the targets of the project, iOSProject and iOSProjectTests.
Debug.xcconfig, Staging.xcconfig, Release.xcconfig: Contains the different configuration values for the various app builds variants of the iOSProject target. The target has configured for three app builds, for varying stages and with different bundle identifiers. Same configuration values are extracted to Shared.xcconfig. For the test target exists the equivalent *Test.xcconfig files.
DebugSigning.xcconfig, StagingSigning.xcconfig, ReleaseSigning.xcconfig: Contains the configurations for creating the different build artifacts of the iOSProject target. Like the bundle identifiers or if you want Manual Code Signing.
Target configurations
The *.xcconfig files can assign to project configurations:
With the different configurations, Debug, Staging and Release, you can produce different app artifacts. The app artifacts can distinguish by bundle identifier, display name and signing, because if the different *.xcconfig files.
Debug: Can be used for development. It has an own bundle identifier, the code signing is set to Automatically, and the Team could be set to an (enterprise) developer team account, to which all the developers belong. So, Xcode manages the code signing, and every developer can test the app directly on a real device.
Staging: Can be used for In-House-Testing. With an own bundle identifier and signing information, it can be distributed via an external service, like HockeyApp. If you sign your staging app artifacts for an enterprise release, every member of your company or your client could test the app without submitting to Testflight.
Release: Should produce the app artifacts for the Testflight beta test and the App Store release. It also has it's own bundle identifier and signing configuration.
The whole management of the different app artifacts is
done inside the Xcode project, because this maintains the independence
from third-party configurations steps, like in fastlane. Switching between different build methods is straightforward. You can build and export the app artifacts via Xcode or use xcodebuild on the command line, or yet use fastlane. With the extracted signing configurations in the *.Signing.xcconfig files, it's also simple to modify the different app artifacts settings.
Project frameworks
The project workspace also contains a Core framework. It's a sample integration of a custom framework mainly for separate different code parts in different modules.
Dedicated frameworks for different logical components in the app have some advantages:
- The separated code in frameworks makes it easier to manage the whole code basis. If you have additional targets, like for a Today Extension, you only need to import the relevant modules in it and keep the target artifacts smaller. And you don't have to hassle with the Target Membership of the source code files. Keep in mind to enable the App Extension option Allow app extension API only in the project settings of the frameworks to prevent using unsupported features for extensions.
- Because only public members of classes in frameworks are accessible from outside, you usually care more about the public interface. That's a small part of creating cleaner code, but it helps you a lot if you can improve the implementation of features over time and don't have to worry that someone uses modules in an not intended way.
- The frameworks are separated projects of the project workspace and have an own project file. If you add files to the framework, these only effects the framework project file. That makes commits and merges much more comfortable, than handling the whole file references in one project file.
- Framework projects in one workspace make Swift code migration easier to handle, because you migrate your whole project in one step, and not each framework for its own.
- The usage of frameworks affects the build time positive during development, too. Frameworks will only rebuild if you made changes in it.
Project frameworks vs. Carthage / Cocoapods
An alternative to the framework projects in the workspace, you could use Carthage or Cocoapods to build submodules for the project and include them into it. But I decide against that solution, because:
- The development with frameworks in a workspace is much faster and direct than via a dependency management system. You can develop in the same Xcode window, and changes in the framework will directly affect the other parts of the code base. You don't have to run an additional step or change paths for development.
- The frameworks are dedicated to using these at first only in this one app. You don't need some extra release step or specific versioning. Changes in one framework are changes in the app. Of course, the framework could extract later in an own Carthage / Cocoapods project, if it grows to an own project - and then you will lose your git history.
Integration in project
To integrate a framework in your app project, just add it to the Embedded Binaries section of your project target:
Third-party dependencies
The project template uses Carthage as dependency manager. Just follow the instructions in the Adding frameworks to an application to use the dependencies in your app.
If you want to use the Carthage dependencies in one of the project frameworks, you have to add the frameworks also in the app target (Adding frameworks to an application). Also, you have to link the Carthage frameworks to your framework:
Also, you have to add the Carthage folder (relative path from framework: $(PROJECT_DIR)/../Carthage/Build/iOS) to the environment variables LD_RUNPATH_SEARCH_PATHS and FRAMEWORK_SEARCH_PATHS. Have a look at the Shared.xcconfig file of the framework.
Build & sign the app
To build and sign an app artifact the template uses fastlane.
Fastlane provides an extensive collection of Ruby scripts to support
the daily routines of iOS developer. The most used functionality is
probably the abstraction for the command line tool xcodebuild with the Ruby functions run_tests and build_ios_app.
Fastlane also delivers fast and regular updates for changes of the
abstracted functionality. Perhaps you don't even recognize that the
command line arguments of xcodebuild changed over time if
you kept fastlane up to date. The abstraction and the proper maintenance
are only two strengths of fastlane which makes it worth to use it.
Used fastlane features
The project template divides responsibilities for the build and distribution process to the Xcode project and the fastlane scripts.
The variant configuration of the different build artifacts is done via the Debug, Staging and Release configuration in the Xcode project. Also, the used signing settings are configured in the different *.xcconfig files. These configurations could also be done via fastlane using functions like update_app_identifier or the use the Appfile.
But if you do that configuration with fastlane, you always need it.
With the base configuration already defined in the project, you could
build your variants directly with Xcode - if you have installed the
signing certificate and the mobile provisioning profile.
To build the different app configuration on a build server fastlane is used. Fastlane is also responsible for the creation of the signing environment and the distribution via HockeyApp and Testflight. The alternative is to create scripts in other languages or do it manually. So if you want to switch from fastlane to another solution, you only have to care about these steps.
These are the aberrations in a project. You have to decide on which step you want to use which tool. There is IMHO no right or wrong way, only personal preferences.
Code signing with fastlane
Fastlane also offers excellent solutions for code signing with match or cert and sigh, but I choose another way to create a signing environment. Instead, that fastlane creates and organizes the certificate and provisioning profiles, I want to create them manually. It's often an use case that these files cannot be generated automatically or managed by fastlane, because different parties with different development setups in the company should work with them, like in-house developer and external IT project houses.
My solution, inspired by Travis CI for iOS from objc.io, is that the certificates and mobile provisioning profiles are saved encrypted in the git repository. And for each distribution configuration (Staging, Release) a pair of them are saved:
Staging: In signing/staging could be kept a certificate for enterprise distribution with the corresponding provisioning profile for ad-hoc or in-house distribution.
Release: signing/release should contain the certificate and mobile provisioning profile for the app store release.
To create a signing environment with pre-shipped certificates and mobile provisioning profiles, you have to care about the following steps.
- Decrypt the certificates and provisioning profiles.
- Create and configure a keychain for the certificate. From my experience, the created keychain should set as default keychain, added to the search list (then it's also displayed in the Keychain Access) and be unlocked.
- Import the certificate to the created keychain.
- Copy the mobile provisioning profile to
/Library/MobileDevice/Provisioning Profiles/
After a build, you should clean up your signing environment. Especially if it's shared build server. Do the following steps:
- Delete the created keychain.
- Delete the provisioning profiles, which were copied to
/Library/MobileDevice/Provisioning Profiles/. - Delete the unencrypted certificates and mobile provisioning profiles.
I created some ruby methods in fastlane/libs/signing.rb, which use built-in fastlane functions like create_keychain, unlock_keychain and import_file from KeychainImporter to create and delete the signing environment. In the fastlane lane you can directly use it with create_signing_configuration and clear_signing_configuration.
To encrypt and decrypt the certificates and mobile provisioning profile the template uses the OpenSSL::Cipher::AES256 symmetric algorithm. The implementation is in fastlane/libs/encryption.rb.
To create the appropriate signing environment, with the right certificate and mobile provisioning profile, the appropriate folder is referenced in the fastlane lane.
from https://github.com/messeb/ios-project-template







No comments:
Post a Comment