Tutorial: Build, Test, & Deploy an iOS App with CI/CD
Tags: ios, devops
Published: 2019-09-20 21:36:59

<!-- wp:paragraph -->
<p>When we develop iOS apps,&nbsp;we usually manage the app publication process using the Xcode Organizer. Then we sign, test, build, archive, submit, change versions, submit new builds again and again to the TestFlight or AppStore.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>If we generate our builds daily,&nbsp;this process is tedious and tiring. Sooner or later, you will ask yourself:</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2><strong>How can we automate this entire process?</strong></h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p><a href="https://semaphoreci.com/ios-continuous-integration">Continuous Integration and Continuous Delivery (CI/CD) for iOS</a>&nbsp;enable us to improve our build deploys. We’re able to release updates at any time in a sustainable way without the hurdle of doing it manually every time. We don’t need to run all our tests when we add new code or take a trial and error approach before pushing a commit to our repository.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>In this tutorial, we’ll learn how to automatically deploy our apps to the TestFlight using Semaphore as our CI/CD platform. When we push our code to our remote repository, Semaphore will test, build, deploy and generate screenshots of our app.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>To make things simple, we’ve divided this tutorial into two parts:</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li><a href="https://semaphoreci.com/continuous-integration"><strong>Continuous integration</strong></a>:&nbsp;build, test and archive the app.</li><li><a href="https://semaphoreci.com/cicd"><strong>Continuous deployment</strong></a>:&nbsp;submit the build to Testflight.</li></ul>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>We will start by:</p>
<!-- /wp:paragraph -->

<!-- wp:list {"ordered":true} -->
<ol><li>Setting up our repository in GitHub.</li><li>Signing up for Semaphore CI.</li><li>Linking our GitHub repository to Semaphore CI Project.</li><li>Configuring our first&nbsp;<a href="https://semaphoreci.com/blog/cicd-pipeline">CI/CD pipeline</a>.</li><li>Improving our CI/CD pipeline with Fastlane.</li></ol>
<!-- /wp:list -->

<!-- wp:heading -->
<h2><a href="https://github.com/renderedtext/tech-content/blob/master/ios-semaphore-intro.md#continuous-integration-with-semaphore"></a>Continuous integration with Semaphore</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>You will need an&nbsp;<a href="https://semaphoreci.com/product/ios">iOS</a>&nbsp;app to configure our Continuous Integration and Delivery pipeline. Then, you will need to configure a new repository.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>We will use:</p>
<!-- /wp:paragraph -->

<!-- wp:list {"ordered":true} -->
<ol><li><a href="https://git-scm.com/">Git</a>: for version control.</li><li>A&nbsp;<a href="https://github.com/">GitHub</a>&nbsp;account to manage our repository.</li><li><a href="https://developer.apple.com/xcode/">Xcode 11 with Swift 5.1</a>&nbsp;as our development tools.</li><li><a href="https://developer.apple.com/xcode/swiftui/">SwiftUI</a>&nbsp;to build our user interface.</li><li><a href="https://developer.apple.com/documentation/xctest">XCTest</a>&nbsp;to create and run unit tests and UI tests.</li></ol>
<!-- /wp:list -->

<!-- wp:quote -->
<blockquote class="wp-block-quote"><p><strong>Note:</strong>&nbsp;SwiftUI previews and inspectors are only available when running on macOS Catalina 10.15.</p></blockquote>
<!-- /wp:quote -->

<!-- wp:paragraph -->
<p> You can download the project and follow along with this tutorial, or create a new iOS&nbsp;<strong>Single View Application</strong>&nbsp;project from scratch. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p> <strong>Download</strong>:&nbsp;<a href="https://github.com/semaphoreci-demos/semaphore-demo-ios-swift-xcode">Semaphore Demo iOS Swift Xcode&nbsp;</a> </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Xcode will set up Git automatically for you since it is checked by default. Otherwise, you can set up manually by running&nbsp;<code>git init</code>&nbsp;in the root project directory in the terminal.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>You can also check your new git repository by writing&nbsp;<code>git status</code>&nbsp;in the terminal.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="language-bash">$ git status

On branch master
nothing to commit, working tree clean</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Build and run the app on the simulator. If you have unit tests and UI tests in the project, run your test cases first in the Test navigator. If everything looks right, push the code to the GitHub repository. Nearly every new iOS project runs without problems the first time. If you run into a problem, you can always grab the&nbsp;<a href="https://github.com/semaphoreci-demos/semaphore-demo-ios-swift-xcode">Semaphore demo app from Github</a>&nbsp;that we are using to following through this tutorial.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><strong>The Semaphore pipeline is configured to:</strong></p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li>Run all unit and UI tests.</li><li>Build the app and generate an&nbsp;<code>ipa</code>&nbsp;archive.</li><li>Generate automated App Store screenshots.</li><li>Upload the archived&nbsp;<code>ipa</code>&nbsp;and screenshots.</li></ul>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>At the end of this tutorial you will achieve a result like in the below image:</p>
<!-- /wp:paragraph -->

<!-- wp:image {"id":12255} -->
<figure class="wp-block-image"><img src="https://wpblog.semaphoreci.com/wp-content/uploads/2020/01/68747470733a2f2f746563686e6963616c2d636f6e74656e742e73332d73612d656173742d312e616d617a6f6e6177732e636f6d2f73656d6170686f72652d636963642d737563636573732d626c6f636b732e706e67.png" alt="semaphore CI pipeline for iOS" class="wp-image-12255"/></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p></p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3>Setting up Semaphore for an iOS project</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p><strong>Go to</strong>&nbsp;<a href="https://semaphoreci.com/login">https://semaphoreci.com/login</a>&nbsp;and&nbsp;log in&nbsp;with your GitHub account.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Click on&nbsp;<strong>+ Projects</strong>&nbsp;to add the project.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"linkDestination":"custom"} -->
<figure class="wp-block-image"><a href="https://camo.githubusercontent.com/0d7c5aaa507385c9e6ab40aca6c071ede1387afd/68747470733a2f2f616d6172696c646f6c756361732e73332d73612d656173742d312e616d617a6f6e6177732e636f6d2f53656d6170686f726543492f696f732d63692d63642f436170747572612b64652b54656c612b323031392d30392d31372b61254343253830732b30382e34382e32372e706e67" target="_blank" rel="noreferrer noopener"><img src="https://camo.githubusercontent.com/0d7c5aaa507385c9e6ab40aca6c071ede1387afd/68747470733a2f2f616d6172696c646f6c756361732e73332d73612d656173742d312e616d617a6f6e6177732e636f6d2f53656d6170686f726543492f696f732d63692d63642f436170747572612b64652b54656c612b323031392d30392d31372b61254343253830732b30382e34382e32372e706e67" alt="screenshot of creating a new project in semaphore for iOS"/></a></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>Semaphore will list GitHub repositories. If you haven't pushed the code yet, you will need to push it so you can see the repository listed.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Follow the GitHub instructions after you created a new repository, which is the most important part:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code class="language-bash">$ git remote add origin git@github.com:amarildolucas/ios-semaphore-intro.git
$ git add -A
$ git commit -m "First commit"
$ git push -u origin master</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p><strong>This will push your code to GitHub.</strong>&nbsp;Since this is likely part of your day-to-day workflow, I won't go into too many details here.  Remember, if you don't want to start a new project from scratch, you can just fork the&nbsp;<a href="https://github.com/semaphoreci-demos/semaphore-demo-ios-swift-xcode">Semaphore iOS demo app</a>&nbsp;as recommended above. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Now, in Semaphore, connect the new repository. Refresh the list to see the repository, if needed.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"linkDestination":"custom"} -->
<figure class="wp-block-image"><a href="https://camo.githubusercontent.com/585887c911d351905be02aa6c39382c1fe1bdada/68747470733a2f2f616d6172696c646f6c756361732e73332d73612d656173742d312e616d617a6f6e6177732e636f6d2f53656d6170686f726543492f696f732d63692d63642f436170747572612b64652b54656c612b323031392d30392d31372b61254343253830732b30382e35302e32352e706e67" target="_blank" rel="noreferrer noopener"><img src="https://camo.githubusercontent.com/585887c911d351905be02aa6c39382c1fe1bdada/68747470733a2f2f616d6172696c646f6c756361732e73332d73612d656173742d312e616d617a6f6e6177732e636f6d2f53656d6170686f726543492f696f732d63692d63642f436170747572612b64652b54656c612b323031392d30392d31372b61254343253830732b30382e35302e32352e706e67" alt="screenshot of adding a repository from GitHub in Semaphore"/></a></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>After you connected the repository to Semaphore,&nbsp;<strong>select your project's main language</strong>. Choose "Swift" to continue with an iOS setup.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"id":12256} -->
<figure class="wp-block-image"><img src="https://wpblog.semaphoreci.com/wp-content/uploads/2020/01/68747470733a2f2f746563686e6963616c2d636f6e74656e742e73332d73612d656173742d312e616d617a6f6e6177732e636f6d2f73656d6170686f72652d6c616e67756167652d776f726b666c6f772e706e67.png" alt="screenshot of choosing a starter workflow for Swift in Semaphore" class="wp-image-12256"/></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>You will see that Semaphore created a new file to be committed to our repository.  If you chose the&nbsp;<a href="https://github.com/semaphoreci-demos/semaphore-demo-ios-swift-xcode">Semaphore demo app</a>&nbsp;to follow through this tutorial, find the configured pipeline inside the root directory&nbsp;<code>.semaphore/semaphore.yml.</code>. </p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="yaml" class="language-yaml"># Use the latest stable version of Semaphore 2.0 YML syntax:
version: v1.0

# Name your pipeline. If you choose to connect multiple pipelines with
# promotions, the pipeline name will help you differentiate between
# them. For example, you might have a build phase and a delivery phase.
# For more information on promotions, see:
# https://docs.semaphoreci.com/article/67-deploying-with-promotions
name: Tallest Towers

# The agent defines the environment in which your CI runs. It is a combination
# of a machine type and an operating system image. For a project built with
# Xcode you must use one of the Apple machine types, coupled with a macOS image
# running either Xcode 10 or Xcode 11.
# See https://docs.semaphoreci.com/article/20-machine-types
# https://docs.semaphoreci.com/article/161-macos-mojave-xcode-10-image and
# https://docs.semaphoreci.com/article/162-macos-mojave-xcode-11-image
agent:
  machine:
    type: a1-standard-4
    os_image: macos-mojave-xcode11

# Blocks are the heart of a pipeline and are executed sequentially. Each block
# has a task that defines one or more parallel jobs. Jobs define commands that
# should be executed by the pipeline.
# See https://docs.semaphoreci.com/article/62-concepts
blocks:
  - name: Run tests
    task:
      # Set environment variables that your project requires.
      # See https://docs.semaphoreci.com/article/66-environment-variables-and-secrets
      env_vars:
        - name: LANG
          value: en_US.UTF-8
      prologue:
        commands:
          # Download source code from GitHub.
          - checkout

          # Restore dependencies from the cache. This command will not fail in
          # case of a cache miss. In case of a cache hit, bundle  install will
          # complete in about a second.
          # See https://docs.semaphoreci.com/article/68-caching-dependencies
          - cache restore
          - bundle install --path vendor/bundle
          - cache store
      jobs:
        - name: Test
          commands:
            # Select an Xcode version.
            # See https://docs.semaphoreci.com/article/161-macos-mojave-xcode-10-image and
            # https://docs.semaphoreci.com/article/162-macos-mojave-xcode-11-image
            - bundle exec xcversion select 11.2.1

            # Run tests of iOS and Mac app on a simulator or connected device.
            # See https://docs.fastlane.tools/actions/scan/
            - bundle exec fastlane test

  - name: Build app
    task:
      env_vars:
        - name: LANG
          value: en_US.UTF-8
      secrets:
        # Make the SSH key for the certificate repository and the MATCH_PASSWORD
        # environment variable available.
        # See https://docs.semaphoreci.com/article/109-using-private-dependencies
        - name: match-secrets
      prologue:
        commands:
          # Add the key for the match certificate repository to ssh
          # See https://docs.semaphoreci.com/article/109-using-private-dependencies
          - chmod 0600 ~/.ssh/*
          # Add the key to the ssh agent:
          - ssh-add ~/.ssh/*
          # Continue with checkout as normal
          - checkout
          - cache restore
          - bundle install --path vendor/bundle
          - cache store
      jobs:
        - name: Build
          commands:
            - bundle exec xcversion select 11.2.1
            - bundle exec fastlane build

            # Upload the IPA file as a job artifact.
            # See https://docs.semaphoreci.com/article/155-artifacts
            - artifact push job build/TallestTowers.ipa
  - name: Take screenshots
    task:
      env_vars:
        - name: LANG
          value: en_US.UTF-8
      prologue:
        commands:
          - checkout
          - cache restore
          - bundle install --path vendor/bundle
          - cache store
      jobs:
        - name: Screenshots
          commands:
            - bundle exec xcversion select 11.2.1
            - bundle exec fastlane screenshots

            # Upload the screenshots directory as a project artifact.
            # See https://docs.semaphoreci.com/article/155-artifacts
            - artifact push job screenshots</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Every Semaphore pipeline starts with the <strong>version</strong>,&nbsp;<strong>name</strong>&nbsp;and <strong>agent</strong>. An agent is a virtual machine that powers the pipeline. Let's understand the above file, which defines our pipeline workflow.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":4} -->
<h4>Version</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The first line&nbsp;<code>version: v1.0</code>&nbsp;uses the latest stable version of Semaphore 2.0 YML syntax. </p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="yaml" class="language-yaml">version: v1.0
name: Tallest Towers</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>By default, they are named&nbsp;with the language-specific template that you chose. This is our pipeline&nbsp;<strong>name</strong>. </p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":4} -->
<h4>Agent</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Next, we have an&nbsp;<code>agent</code>. </p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="yaml" class="language-yaml">agent:
  machine:
    type: a1-standard-4
    os_image: macos-mojave-xcode11</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>An agent defines the environment in which your code will run. There are multiple available machine types and operating system images. This means that on the Semaphore side, your code will be built in a hosted&nbsp;<strong>macos-mojave</strong>&nbsp;<a href="https://docs.semaphoreci.com/article/20-machine-types#apple-machine-type">machine</a>.  This is a customized image with Xcode 11 preinstalled. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>In our case,&nbsp;<code>a1-standard-4</code>&nbsp;is our machine name with 4 virtual CPUs, 8 GB of memory and 50 GB of disk. </p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":4} -->
<h4>Blocks</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Blocks define actions for the pipeline. They are executed sequentially and are probably the most important part of this file. Each block has a task that defines one or more jobs, and these jobs define the commands to execute. Once all jobs in a block are complete, the next block begins. Essentially, blocks define your Continuous Integration and Continuous Delivery pipeline flow. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p> In this file we have 3 blocks named&nbsp;**&nbsp;Run tests**,&nbsp;<strong>Build app</strong>&nbsp;and **&nbsp;Take screenshots**. All defining a task with some jobs. These jobs define some commands to run, for example&nbsp;<code>bundle exec xcversion select 11.2.1&nbsp;</code>. Below is a more descriptive explanation for the job named&nbsp;<em>Test</em>&nbsp;inside&nbsp;<em>Run tests</em>&nbsp;block: </p>
<!-- /wp:paragraph -->

<!-- wp:list {"ordered":true} -->
<ol><li><code>bundle exec xcversion select 11.2.1&nbsp;</code>&nbsp;selects an Xcode version.</li><li><code>bundle exec fastlane test</code>&nbsp;run tests of iOS and Mac app on a simulator or connected device. </li></ol>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p><strong>The first block run all our unit and UI tests.</strong></p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="yaml" class="language-yaml">- name: Run tests
  task:
    env_vars:
      - name: LANG
        value: en_US.UTF-8
    prologue:
      commands:
        - checkout
        - cache restore
        - bundle install --path vendor/bundle
        - cache store
    jobs:
      - name: Test
        commands:
          - bundle exec xcversion select 11.2.1
          - bundle exec fastlane test</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>The&nbsp;<code>checkout</code>&nbsp;command download source code from GitHub. The next commands, restore dependencies from the cache and install it. Finally, we select the Xcode version of the project and run the tests using&nbsp;<a href="https://fastlane.tools/">Fastlane</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><strong>The next block build the app and generates an&nbsp;<code>ipa</code>&nbsp;archive.</strong></p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="yaml" class="language-yaml">- name: Build app
  task:
    env_vars:
      - name: LANG
        value: en_US.UTF-8
    secrets:
      - name: match-secrets
    prologue:
      commands:
        - chmod 0600 ~/.ssh/*
        - ssh-add ~/.ssh/*
        - checkout
        - cache restore
        - bundle install --path vendor/bundle
        - cache store
    jobs:
      - name: Build
        commands:
          - bundle exec xcversion select 11.2.1
          - bundle exec fastlane build
          - artifact push job build/TallestTowers.ipa</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>It is very similar to the previous block. The commands inside this block configure our secrets, sign our app and upload it to&nbsp;<a href="https://developer.apple.com/testflight/">TestFlight</a>&nbsp;using Fastlane. We will go more in-depth about the other commands and Fastlane later.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><strong>The last block capture and upload our app screenshots.</strong></p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="yaml" class="language-yaml">- name: Take screenshots
  task:
    env_vars:
      - name: LANG
        value: en_US.UTF-8
    prologue:
      commands:
        - checkout
        - cache restore
        - bundle install --path vendor/bundle
        - cache store
    jobs:
      - name: Screenshots
        commands:
          - bundle exec xcversion select 11.2.1
          - bundle exec fastlane screenshots
          - artifact push job screenshots</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Notice that we repeat some commands like checkout, cache, etc, to get the necessary files from our repositories and dependencies. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>To commit your file, tap the button&nbsp;<strong>Run this workflow</strong>. Also, pay attention to the fact that you can customize the default configuration before running by clicking on the&nbsp;<em>Customize it first</em>&nbsp;link.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Is everything green&nbsp;<strong>(Passed)</strong>? You did it!&nbsp;🎉</p>
<!-- /wp:paragraph -->

<!-- wp:image {"id":12257} -->
<figure class="wp-block-image"><img src="https://wpblog.semaphoreci.com/wp-content/uploads/2020/01/68747470733a2f2f746563686e6963616c2d636f6e74656e742e73332d73612d656173742d312e616d617a6f6e6177732e636f6d2f73656d6170686f72652d636963642d737563636573732d626c6f636b732e706e67-1.png" alt="screenshot of successful commit in semaphore" class="wp-image-12257"/></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>We have a working setup for a real workflow that makes it easy for iOS developers entering the Continuous Delivery stage. After running the code, everything shows a “Passed” message in green. Go further and see the output of every step by tapping in your commit message or job name&nbsp;in the Semaphore dashboard to understand everything that happened in the backend of the console while running the pipeline steps.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>We can even have more blocks if we want. For example, a block to create changelog files of build versions or to send notifications on Slack, etc. This all depends on you and your pipeline strategy. So, now that you understand the file and how the integration process works, let’s understand our deployment process in depth.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Before doing it, let’s make a quick overview of Continuous Integration. In this process, we commit our changes to the main code branch that triggers an automated code to build and run. Every&nbsp;<code>git push origin branch</code>&nbsp;will trigger the automated process defined in the&nbsp;<code>semaphore.yml</code>&nbsp;configuration file. This way we focus on our work rather than manually build and test code every time we do changes.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Continuous Integration helps us archive a build that can be deployed and the automated tests check every tested code to be safe to deploy.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2><a href="https://github.com/renderedtext/tech-content/blob/master/ios-semaphore-intro.md#continuous-deployment-with-semaphore"></a>Continuous Deployment with Semaphore</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Our code changes continuously while working on our apps. In practice, we deploy several versions of our apps, sometimes even on the same day. Rather, to fix bugs, add new features, refactoring some code, etc. It’s an endless cycle. Continuous Delivery is exactly this process. If we configure our project to trigger our defined pipeline automatically and fully automate the entire process of push code from our repository to production, the process is called Continuous Deployment.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>We want to define our pipeline with steps to do Continuous Deployment, so we can focus only on our code while working in small iterations and always keep the code in a deployable state.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>If you forked the&nbsp;<a href="https://github.com/semaphoreci-demos/semaphore-demo-ios-swift-xcode">Semaphore demo app</a>&nbsp;you got all of this out of the box.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Make sure that you:</p>
<!-- /wp:paragraph -->

<!-- wp:list {"ordered":true} -->
<ol><li>Configured Fastlane to automate deployments and releases (Continuous Deployment).</li><li>Added a block to build and run our unit and UI tests.</li><li>Added a block with steps to build and deploy our app to TestFlight.</li></ol>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>Fastlane makes our Continuous Deployment life a bit easier. You may know it as is vastly used for deployment of apps to the App Store.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>If you don’t have Fastlane installed, you will need to:</p>
<!-- /wp:paragraph -->

<!-- wp:list {"ordered":true} -->
<ol><li>Install Fastlane.</li><li>Follow the instructions of configuring Fastlane files.</li><li>Test if everything is working right.</li></ol>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>If you want a quick setup help, you can see the files that we use in the configuration of the Semaphore Demo iOS Swift app&nbsp;<a href="https://github.com/renderedtext/tech-content/blob/master/ios-semaphore-intro.md#">here</a>. These files are added after we install and run Fastlane for the first time. To understand more about the process of Fastlane installation and configuration you should read this&nbsp;<a href="https://github.com/renderedtext/tech-content/blob/master/ios-semaphore-intro.md#">introductory article</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Run&nbsp;<code>fastlane init</code>&nbsp;in the terminal after installation and follow the instructions.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>As we will automate our deployment of build distributions to TestFlight, you will need to have these steps finished:</p>
<!-- /wp:paragraph -->

<!-- wp:list {"ordered":true} -->
<ol><li>Your new app configured on the Apple Dev Center.</li><li>Your new app available in App Store Connect.</li><li>A successfully generated Fastlane configuration.</li></ol>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>Also, as an iOS Developer, I think that is your day to day for every new app. So, we will not enter in details here.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>This is everything we need to set up and deploy our first build with Continuous Deployment. In case you’re blocked in some steps, check the code of&nbsp;<a href="https://github.com/semaphoreci-demos/semaphore-demo-ios-swift-xcode">Semaphore Demo for iOS</a>&nbsp;again to guide you, it is very specific and self-explanatory.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><strong>Note:</strong>&nbsp;Ensure that Xcode “automatically manage signing” it’s unchecked in Xcode when you work with Fastlane. And that your Signing (Release) contains the match AppStore provisioning profile selected if you had a Match file.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3><a href="https://github.com/renderedtext/tech-content/blob/master/ios-semaphore-intro.md#configure-your-keys-and-secrets"></a>Configure your keys and secrets</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Now that we have configured Fastlane, we must also provide a way for Semaphore CI to access the Git certificates repository and the Apple Developer portal.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Because we will access a private repository, we need to create a deploy key and add it to Semaphore CI secrets. This typically happens over&nbsp;<a href="https://en.wikipedia.org/wiki/Secure_Shell">SSH</a>. If you are not familiar with SSH,&nbsp;<a href="https://docs.semaphoreci.com/article/109-using-private-dependencies">this article</a>&nbsp;walks you through the process to create, add and manage SSH keys to use in your pipeline.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Also, I recommend this&nbsp;<a href="https://help.github.com/en/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent">GitHub article</a>&nbsp;on generating a new SSH key.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="bash" class="language-bash">$ cd ~/.ssh
$ ssh-keygen -t rsa -f id_rsa_semaphoreci</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>After generating the key, you will need to connect the SSH key to the project or user. Deploy your key to GitHub to grant access to your private repository.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Also, let’s store the deploy key as a secret file in the Semaphore CI environment.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Open the&nbsp;<strong>Semaphore Dashboard</strong>&nbsp;for your Organization. On the left sidebar find the&nbsp;<strong>Configuration</strong>&nbsp;section and click&nbsp;<strong>Secrets</strong>.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"align":"center","linkDestination":"custom"} -->
<div class="wp-block-image"><figure class="aligncenter"><a href="https://camo.githubusercontent.com/6b219848ba565f420fc207150f6efff907367d3c/68747470733a2f2f746563686e6963616c2d636f6e74656e742e73332d73612d656173742d312e616d617a6f6e6177732e636f6d2f73656d2d736964656261722d736563726574732e706e67" target="_blank" rel="noreferrer noopener"><img src="https://camo.githubusercontent.com/6b219848ba565f420fc207150f6efff907367d3c/68747470733a2f2f746563686e6963616c2d636f6e74656e742e73332d73612d656173742d312e616d617a6f6e6177732e636f6d2f73656d2d736964656261722d736563726574732e706e67" alt="Configuration section of the Semaphore CI dashboard"/></a></figure></div>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>To store the deploy key as a secret file in the Semaphore environment, click&nbsp;<strong>Create a new Secret</strong>&nbsp;button.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"linkDestination":"custom"} -->
<figure class="wp-block-image"><a href="https://camo.githubusercontent.com/22d699ed144a11e2d490176495c62aa0f72ba845/68747470733a2f2f746563686e6963616c2d636f6e74656e742e73332d73612d656173742d312e616d617a6f6e6177732e636f6d2f73656d2d6372656174652d6e65772d7365637265742e706e67" target="_blank" rel="noreferrer noopener"><img src="https://camo.githubusercontent.com/22d699ed144a11e2d490176495c62aa0f72ba845/68747470733a2f2f746563686e6963616c2d636f6e74656e742e73332d73612d656173742d312e616d617a6f6e6177732e636f6d2f73656d2d6372656174652d6e65772d7365637265742e706e67" alt="create new secret in Semaphore Ci 2.0"/></a></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>Name the secret as&nbsp;<code>match-secrets</code>&nbsp;than enter your environment variables name and value to match Semaphore and Fastlane configuration.</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li><strong>MATCH_GIT_URL</strong>: your SSH Git URL.</li><li><strong>MATCH_PASSWORD</strong>: password for decryption of the match certificates.</li><li><strong>FASTLANE_USER</strong>: App Store developer’s Apple ID.</li><li><strong>FASTLANE_PASSWORD</strong>: App Store developer’s password.</li></ul>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>Enter a destination file path&nbsp;<code>/Users/semaphore/.ssh/match-secrets</code>&nbsp;to upload the SSH key that you previously generated&nbsp;<code>id_rsa_semaphoreci</code>&nbsp;on your computer. Click&nbsp;<strong>Save Changes</strong>.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"linkDestination":"custom"} -->
<figure class="wp-block-image"><a href="https://camo.githubusercontent.com/fec1a41511a7be2e227e695acee6a2d68f016e27/68747470733a2f2f746563686e6963616c2d636f6e74656e742e73332d73612d656173742d312e616d617a6f6e6177732e636f6d2f73656d2d736563726574732d636f6e6669672e706e67" target="_blank" rel="noreferrer noopener"><img src="https://camo.githubusercontent.com/fec1a41511a7be2e227e695acee6a2d68f016e27/68747470733a2f2f746563686e6963616c2d636f6e74656e742e73332d73612d656173742d312e616d617a6f6e6177732e636f6d2f73656d2d736563726574732d636f6e6669672e706e67" alt="Entering the destination file in the iOS CI/CD tutorial"/></a></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>This created the file&nbsp;<code>~/.ssh/match-secrets</code>&nbsp;with necessary environment variables and made it accessible in Semaphore CI jobs.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Now that you created your secret key, go to your&nbsp;<strong>Semaphore project</strong>&nbsp;page, click&nbsp;<strong>Edit workflow button</strong>, select block to which you want to connect secret, in your case is the&nbsp;<code>Build app</code>&nbsp;block. On the right sidebar, scroll to&nbsp;<strong>Secrets</strong>&nbsp;section, click the arrow button and check the secret that you previously created&nbsp;<code>match-secrets</code>. Click&nbsp;<strong>Run the workflow</strong>&nbsp;button to add the secret.</p>
<!-- /wp:paragraph -->

<!-- wp:image {"linkDestination":"custom"} -->
<figure class="wp-block-image"><a href="https://camo.githubusercontent.com/6d116aec60ab9abd35f3c30bb736cf193cdb3417/68747470733a2f2f746563686e6963616c2d636f6e74656e742e73332d73612d656173742d312e616d617a6f6e6177732e636f6d2f73656d2d776f726b666c6f772e706e67" target="_blank" rel="noreferrer noopener"><img src="https://camo.githubusercontent.com/6d116aec60ab9abd35f3c30bb736cf193cdb3417/68747470733a2f2f746563686e6963616c2d636f6e74656e742e73332d73612d656173742d312e616d617a6f6e6177732e636f6d2f73656d2d776f726b666c6f772e706e67" alt="Running the workflow in Semaphore CI dashboard"/></a></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>After running this command, you will find your secrets inside the Semaphore Dashboard Secrets.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Our&nbsp;<em>Build app</em>&nbsp;block will look like below:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="yaml" class="language-yaml">- name: Build app
  task:
    env_vars:
      - name: LANG
        value: en_US.UTF-8
    secrets:
      - name: match-secrets
    prologue:
      commands:
        - chmod 0600 ~/.ssh/*
        - ssh-add ~/.ssh/match-secrets</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Let’s explain some commands that we repeated in our pipeline blocks:</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li><strong>cache restore</strong>: restores an archive that matches any given key. In case of a cache hit, retrieves the archive and make it available at its original path in the job environment.</li><li><strong>bundle install</strong>: installs all Fastlane dependencies.</li><li><strong>cache store</strong>: archives a file or directory specified by the path and associates it with a given key.</li><li><strong><a href="https://en.wikipedia.org/wiki/Chmod">chmod</a></strong>&nbsp;0600&nbsp;<code>~/.ssh/*</code>:&nbsp;reads and writes in files if it is the owner. It is all about permissions! You should be familiar with it in UNIX-like systems.</li><li><strong>ssh-add</strong>&nbsp;<code>~/.ssh/match-secrets</code>:&nbsp;gives the path of the key file as an argument to ssh-add to add the generated keys.</li><li><strong>secrets:</strong>&nbsp;allows Semaphore to download certificates from a private repository configured with Fastlane. We needed to create a deploy key and add it to Semaphore secrets.</li></ul>
<!-- /wp:list -->

<!-- wp:heading {"level":3} -->
<h3><a href="https://github.com/renderedtext/tech-content/blob/master/ios-semaphore-intro.md#deploy"></a>Deploy</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>After you do all your changes, commit it to get the CI/CD workflow started:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code lang="bash" class="language-bash">$ git add -A
$ git commit -m "Updates pipeline"
$ git push</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Check your Semaphore dashboard and&nbsp;<strong>Voilá</strong>!</p>
<!-- /wp:paragraph -->

<!-- wp:image {"id":12262} -->
<figure class="wp-block-image"><img src="https://wpblog.semaphoreci.com/wp-content/uploads/2020/01/68747470733a2f2f746563686e6963616c2d636f6e74656e742e73332d73612d656173742d312e616d617a6f6e6177732e636f6d2f73656d6170686f72652d636963642d737563636573732d626c6f636b732e706e67-2.png" alt="Screenshot of the final successful test in Semaphore iOS tutorial" class="wp-image-12262"/></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>Now, you need to continuously push code to your branches, and App Store Connect processes the build. It becomes available in TestFlight, and you will be able to select it for your new iOS app version.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Let’s do a quick recap of what we did in this tutorial. Our pipeline:</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li>Run all unit and UI tests.</li><li>Build the app and generate an&nbsp;<code>ipa</code>&nbsp;archive.</li><li>Generate automated App Store screenshots.</li><li>Upload the archived&nbsp;<code>ipa</code>&nbsp;and screenshots.</li></ul>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>Open the finished project and explore the code on your own.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><strong>Download</strong>:&nbsp;<a href="https://github.com/semaphoreci-demos/semaphore-demo-ios-swift-xcode">Semaphore Demo iOS Swift Xcode&nbsp;</a></p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2>Next steps</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>There is much more that you can do with automated deployments. For example, you can add more steps to your pipeline configuration, such as getting your CHANGELOG file, pushing messages to Slack, automating build number incrementation, dSYM uploads to Crashlytics, and more.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>After you take some time to configure CI/CD for your iOS applications, you can focus on writing code instead of manually delivering your app every time.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Have questions about this tutorial? Want to show off your results? Reach out to us on Twitter&nbsp;<a href="https://twitter.com/semaphoreci">@semaphoreci</a>.</p>
<!-- /wp:paragraph -->