Jekyll2021-01-21T17:04:12+00:00https://felginep.github.io/feed.xmlPierre FelginesA blog dedicated to iOS development.Pierre FelginesXcode configuration for multiple environments2021-01-21T00:00:00+00:002021-01-21T00:00:00+00:00https://felginep.github.io/2021-01-21/xcode-configuration-multiple-environments<p>I recently stumbled upon yet another article that explains different techniques to configure an Xcode project for multiple environments. Most of the articles out there propose the same solutions and although these are valid techniques, I think Xcode is not giving us the right tools and that there is room for improvement.</p>
<p>Let’s see what the proposed solutions are, the issues associated with them and how we can do better.</p>
<h2 id="the-not-so-recommended-setup">The (not so) recommended setup</h2>
<p>The root of the problem is to be able to handle multiple development environments for an Xcode project. For each environment we want to define dynamic environment variables, such as server url, logging, feature flagging, etc. And each environment should be compiled in two modes: <code class="language-plaintext highlighter-rouge">Debug</code> (for local development, or to run tests) and <code class="language-plaintext highlighter-rouge">Release</code> (to distribute on the stores).</p>
<p>We can sum up the requirements we want to achieve with the following matrix:</p>
<table>
<thead>
<tr>
<th>Build \ Environment</th>
<th>Development</th>
<th>Production</th>
<th>…</th>
</tr>
</thead>
<tbody>
<tr>
<td>Debug</td>
<td>Development Debug</td>
<td>Production Debug</td>
<td>… Debug</td>
</tr>
<tr>
<td>Release</td>
<td>Development Release</td>
<td>Production Release</td>
<td>… Release</td>
</tr>
</tbody>
</table>
<p>Many solutions out there propose to get rid of the default <code class="language-plaintext highlighter-rouge">Debug</code> and <code class="language-plaintext highlighter-rouge">Release</code> build configurations and to instead create two build configurations for each environment.</p>
<p>For instance if we have a Development and a Production environment, we will create four configurations (all the possibilities from the matrix above):</p>
<ul>
<li>Development Debug</li>
<li>Development Release</li>
<li>Production Debug</li>
<li>Production Release</li>
</ul>
<p>Once we have these build configurations, we need to store the environment variables for each environment. The majority of resources I found propose one of the following:</p>
<ul>
<li>to use <code class="language-plaintext highlighter-rouge">.xcconfig</code> files for each build configuration (with the issue of <a href="https://stackoverflow.com/q/21317844">escaping characters</a>)</li>
<li>to store values directly in Xcode build settings for each configuration (with User Defined settings)</li>
</ul>
<h2 id="the-drawbacks">The drawbacks</h2>
<p>There are a few drawbacks with the previous approach.</p>
<p>The main issue is that we correlate compilation options defined in the project Build Settings and development environments. These are two separate things that should not be treated the same way. The issue here is mostly due to Xcode that only provides Build Configurations to store the build settings and Schemes to choose the configuration used during the build.</p>
<p>In practice, for each new environment we want to add, we have to create a <code class="language-plaintext highlighter-rouge">Debug</code> and a <code class="language-plaintext highlighter-rouge">Release</code> configuration. That’s not a very scalable solution when we have a lot of development environments.</p>
<p>What’s more, the second issue is that we will find ourselves duplicating a lot of build settings between all these configurations. For instance, both DevelopmentDebug and ProductionDebug will share the same build settings associated with compilation. There is no reason these settings should diverge between Development and Production.</p>
<h2 id="another-approach">Another approach</h2>
<p>We saw earlier that it’s a bad practice to mix compilation build settings and development environment.</p>
<p>I propose to stick with only two configurations, <code class="language-plaintext highlighter-rouge">Debug</code> and <code class="language-plaintext highlighter-rouge">Release</code>, that come for free when creating a new Xcode project. All compilation options will be set in the build settings.</p>
<p>To handle the environment variables that change for each environment, we will store them in yaml files on disk.</p>
<p>For instance, here is the content of the <code class="language-plaintext highlighter-rouge">env.development.yml</code>:</p>
<figure class="highlight"><pre><code class="language-yml" data-lang="yml"><span class="na">pbxproj</span><span class="pi">:</span>
<span class="na">PRODUCT_BUNDLE_IDENTIFIER</span><span class="pi">:</span> <span class="s2">"</span><span class="s">com.felginep.DemoBlog.dev"</span>
<span class="na">env</span><span class="pi">:</span>
<span class="na">baseUrl</span><span class="pi">:</span> <span class="s2">"</span><span class="s">dev.demoblog.api.com/"</span> <span class="c1"># the end point url</span>
<span class="na">logLevel</span><span class="pi">:</span> <span class="s2">"</span><span class="s">debug"</span> <span class="c1"># the log level used by the logger in the app</span>
<span class="c1"># ... any other variable ...</span></code></pre></figure>
<p>We can notice two things here:</p>
<ul>
<li>the values under the <code class="language-plaintext highlighter-rouge">pbxproj</code> key represent the values that will be integrated to the build settings of Xcode and that are related to the environment and not to the compilation</li>
<li>the values under the <code class="language-plaintext highlighter-rouge">env</code> key represent the environment variables that will be used at runtime</li>
</ul>
<p>Note: the main advantage to use yaml to store our environment variables is that we can add comments alongside the values.</p>
<p>The next step is to create a <a href="https://github.com/felginep/DemoBlog/blob/main/prepare.rb">script</a> that will read the yaml files and integrate their values in the project. Any scripting language will do, but I personally prefer Ruby because it’s easy to handle yaml and to integrate with fastlane.</p>
<p>So let’s say we have a script that we can call like this:</p>
<figure class="highlight"><pre><code class="language-sh" data-lang="sh">./prepare.rb development</code></pre></figure>
<p>The script will read the <code class="language-plaintext highlighter-rouge">env.development.yml</code> and do two things:</p>
<ul>
<li>integrate the environment build settings values (under the <code class="language-plaintext highlighter-rouge">pbxproj</code> key) into Xcode</li>
<li>generate a Swift file that represents our environment variables (under the <code class="language-plaintext highlighter-rouge">env</code> key)</li>
</ul>
<h3 id="updating-the-pbxproj">Updating the pbxproj</h3>
<p>To integrate the environment build settings into our project, the script will copy the values from the yaml to an xcconfig file named <code class="language-plaintext highlighter-rouge">Environment.xcconfig</code>. This file is auto generated so we can add it to the <code class="language-plaintext highlighter-rouge">.gitignore</code>.</p>
<p>In our example, the file <code class="language-plaintext highlighter-rouge">Environment.xcconfig</code> that is generated would look like this:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">// Environment.xcconfig
PRODUCT_BUNDLE_IDENTIFIER = com.felginep.DemoBlog.dev</code></pre></figure>
<p>Once this <code class="language-plaintext highlighter-rouge">Environment.xcconfig</code> is created, we need to tell Xcode to take it into account. For this, we create two more xcconfig files, <code class="language-plaintext highlighter-rouge">Debug.xcconfig</code> and <code class="language-plaintext highlighter-rouge">Release.xcconfig</code>, one for each configuration.</p>
<p>Their content is the following:</p>
<figure class="highlight"><pre><code class="language-text" data-lang="text">// DemoAppDebug.xcconfig
#include "Environment.xcconfig"
#include "../../Pods/Target Support Files/Pods-DemoBlog/Pods-DemoBlog.debug.xcconfig"
// Here add any build settings you want for Debug configuration</code></pre></figure>
<figure class="highlight"><pre><code class="language-text" data-lang="text">// DemoAppRelease.xcconfig
#include "Environment.xcconfig"
#include "../../Pods/Target Support Files/Pods-DemoBlog/Pods-DemoBlog.release.xcconfig"
// Here add any build settings you want for Release configuration</code></pre></figure>
<p>Note: we include the cocoapods generated xcconfigs as they are not automatically loaded by cocoapods anymore. If we forget to include them, we will see a warning during <code class="language-plaintext highlighter-rouge">pod install</code>.</p>
<p>The last step is to specify in Xcode that we want to use these new xcconfig files for each of our build configurations.</p>
<!-- _includes/image.html -->
<div class="image-wrapper">
<img src="https://felginep.github.io/assets/xcode-configuration/xcode-configuration.png" alt="Integration of xcconfig files" />
<p class="image-caption">Integration of xcconfig files</p>
</div>
<p>Finally, we can verify that the xcconfig has been correctly loaded because the bundle identifier has been updated for the environment:</p>
<!-- _includes/image.html -->
<div class="image-wrapper">
<img src="https://felginep.github.io/assets/xcode-configuration/product-bundle-identifier.png" alt="Bundle identifier for development" />
<p class="image-caption">Bundle identifier for development</p>
</div>
<p>To recap what we just did here:</p>
<ul>
<li>we copy all the values from the <code class="language-plaintext highlighter-rouge">pbxproj</code> key of our yaml into a <code class="language-plaintext highlighter-rouge">Environment.xcconfig</code> file (which is in the <code class="language-plaintext highlighter-rouge">.gitignore</code>)</li>
<li>this <code class="language-plaintext highlighter-rouge">Environment.xcconfig</code> is then loaded for each configuration in the files <code class="language-plaintext highlighter-rouge">Debug.xcconfig</code> and <code class="language-plaintext highlighter-rouge">Release.xcconfig</code></li>
<li>the files <code class="language-plaintext highlighter-rouge">Debug.xcconfig</code> and <code class="language-plaintext highlighter-rouge">Release.xcconfig</code> are associated in Xcode for each configuration <code class="language-plaintext highlighter-rouge">Debug</code> and <code class="language-plaintext highlighter-rouge">Release</code></li>
</ul>
<p>If we run the script for another environment, only the values contained in <code class="language-plaintext highlighter-rouge">Environment.xcconfig</code> will change, and they will be automatically loaded by Xcode.</p>
<h3 id="environment-variables">Environment variables</h3>
<p>The final step of our solution is to handle the environment variables that will be used at runtime. The idea here is to define an <code class="language-plaintext highlighter-rouge">Environment</code> struct that will hold the values from our yaml:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// Environment.swift</span>
<span class="kd">struct</span> <span class="kt">Environment</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">baseUrl</span><span class="p">:</span> <span class="kt">String</span>
<span class="k">let</span> <span class="nv">logLevel</span><span class="p">:</span> <span class="kt">String</span>
<span class="p">}</span></code></pre></figure>
<p>As we did with the <code class="language-plaintext highlighter-rouge">Environment.xcconfig</code>, the script will generate a file <code class="language-plaintext highlighter-rouge">Environment+Current.swift</code> that contains all the values from the <code class="language-plaintext highlighter-rouge">env</code> key of the yaml. This file will also be added in the <code class="language-plaintext highlighter-rouge">.gitignore</code> because it will change each time we change the environment.</p>
<p>In our example, for the development environment, the script will create the following file:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// Environment+Current.swift</span>
<span class="kd">extension</span> <span class="kt">Environment</span> <span class="p">{</span>
<span class="kd">static</span> <span class="k">let</span> <span class="nv">current</span> <span class="o">=</span> <span class="kt">Environment</span><span class="p">(</span>
<span class="nv">baseUrl</span><span class="p">:</span> <span class="s">"dev.demoblog.api.com/"</span><span class="p">,</span>
<span class="nv">logLevel</span><span class="p">:</span> <span class="s">"debug"</span>
<span class="p">)</span>
<span class="p">}</span></code></pre></figure>
<p>In the code, we now can use the <code class="language-plaintext highlighter-rouge">Enviroment.current</code> value wherever we want. We can also create our own instance of <code class="language-plaintext highlighter-rouge">Environment</code> for test purposes, or for different sections of the app.</p>
<p>The real power here is that the environment is a first class citizen in our app and values are verified at <strong>compile time</strong>. If we forget to add a key in a yaml file, or if the key has the wrong type, we will get an error during compilation.</p>
<h2 id="in-practice">In practice</h2>
<p>So how does it work in practice?</p>
<p>Anytime we want to change the <strong>development environment</strong>, we run the following <strong>script</strong> command: <code class="language-plaintext highlighter-rouge">./prepare.rb [development|production]</code>. This will generate two files <code class="language-plaintext highlighter-rouge">Environment.xcconfig</code> and <code class="language-plaintext highlighter-rouge">Environment+Current.swift</code> that are in the <code class="language-plaintext highlighter-rouge">.gitignore</code>. These files contain the values defined in the yaml. <code class="language-plaintext highlighter-rouge">Environment.xcconfig</code> will contain all the build settings that can change with the environment, whereas <code class="language-plaintext highlighter-rouge">Environment+Current.swift</code> will contain all the dynamic environment variables and will be verified at compile time.</p>
<p>When we want to change the <strong>build configuration</strong> (meaning the compilation options), we change the <strong>scheme</strong> to choose between <code class="language-plaintext highlighter-rouge">Debug</code> and <code class="language-plaintext highlighter-rouge">Release</code>. We now have a lot more flexibility to choose which environment to build in which configuration. What’s more, when we add a new environment in our project, all we need to do is to create a new yaml file with the correct values, run the script with this new environment, and that’s it. There is no need to create new build configurations anymore, nor to think about which compilation options will be associated.</p>
<p>You can find the demo project I used in this article <a href="https://github.com/felginep/DemoBlog">here</a>. Note that the <a href="https://github.com/felginep/DemoBlog/blob/main/prepare.rb">script</a> is just for demonstration purposes and feel free to improve it for your specific needs. For instance, you may use it to load Firebase plist instead of relying of the build phases.</p>Pierre FelginesI recently stumbled upon yet another article that explains different techniques to configure an Xcode project for multiple environments. Most of the articles out there propose the same solutions and although these are valid techniques, I think Xcode is not giving us the right tools and that there is room for improvement.Linting licenses2020-09-09T00:00:00+00:002020-09-09T00:00:00+00:00https://felginep.github.io/2020-09-09/linting-licenses<p>Dependencies are at the core of programming. In iOS, we often use <a href="https://cocoapods.org/">Cocoapods</a> to manage dependencies in our projects. But if we are not careful, we can add a dependency that is not free to use, and expose our company to legal issues later. That’s why it’s really important to verify the LICENSE files of each dependency before adding it.</p>
<p>But we all know that humans make mistakes, so in this article, we’ll cover how to automate the process of verifying each dependency.</p>
<p><em>Note</em>: If you want to know more about the different kinds of licenses, go check <a href="https://choosealicense.com/">https://choosealicense.com/</a>.</p>
<h2 id="listing-the-licenses">Listing the licenses</h2>
<p>First of all, if you want to quickly see the state of the licenses in your project, I created a gem called <a href="https://github.com/faberNovel/ad_licenselint/">ADLicenselint</a> that lists the licenses for each pod in your Podfile.</p>
<p>To use it, install the gem and run <code class="language-plaintext highlighter-rouge">ad_licenselint</code> in the directory that contains the Podfile.</p>
<p><em>Note</em>: by default the gem prints the dependencies that have licenses not free to use, but you can print the whole list with the <code class="language-plaintext highlighter-rouge">--all</code> option:</p>
<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="o">></span> <span class="nb">cd</span> /path/to/Podfile
<span class="o">></span> ad_licenselint <span class="nt">--all</span>
+-------------------+----------------------------+----------------------------------------------------+
| Pod | License | Source |
+-------------------+----------------------------+----------------------------------------------------+
| Alamofire | MIT | https://github.com/Alamofire/Alamofire |
| Firebase | Apache | https://github.com/firebase/firebase-ios-sdk |
| ObjectivePGP | BSD <span class="k">for </span>non-commercial use | https://github.com/krzyzanowskim/ObjectivePGP |
| SwiftGen | MIT | https://github.com/SwiftGen/SwiftGen |
| SwiftLint | MIT | https://github.com/realm/SwiftLint |
+-------------------+----------------------------+----------------------------------------------------+</code></pre></figure>
<p>You can check all the other options on the <a href="https://github.com/faberNovel/ad_licenselint/">github page</a> directly.</p>
<p>Under the hood, the gem uses the plists generated by Cocoapods during the <code class="language-plaintext highlighter-rouge">pod install</code>. The plists are located here: <code class="language-plaintext highlighter-rouge">Pods/Target\ Support\ Files/Pods-MyApp/Pods-MyApp-acknowledgements.plist</code>.</p>
<p>Here is an example of one entry in the plist for the pod <code class="language-plaintext highlighter-rouge">Alamofire</code>:</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"><span class="nt"><dict></span>
<span class="nt"><key></span>FooterText<span class="nt"></key></span>
<span class="nt"><string></span>Copyright (c) 2014-2020 Alamofire Software Foundation (http://alamofire.org/) ...<span class="nt"></string></span>
<span class="nt"><key></span>License<span class="nt"></key></span>
<span class="nt"><string></span>MIT<span class="nt"></string></span>
<span class="nt"><key></span>Title<span class="nt"></key></span>
<span class="nt"><string></span>Alamofire<span class="nt"></string></span>
<span class="nt"><key></span>Type<span class="nt"></key></span>
<span class="nt"><string></span>PSGroupSpecifier<span class="nt"></string></span>
<span class="nt"></dict></span></code></pre></figure>
<p>The gem simply parses this file and extracts the informations for each pod. By default, the licenses automatically validated are <code class="language-plaintext highlighter-rouge">MIT</code>, <code class="language-plaintext highlighter-rouge">Apache</code>, <code class="language-plaintext highlighter-rouge">Apache 2.0</code> and <code class="language-plaintext highlighter-rouge">BSD</code>. Every license that is not included in this list will be considered unsafe.</p>
<h2 id="using-it-with-fastlane">Using it with fastlane</h2>
<p>Along with the gem, you can find a <a href="https://github.com/faberNovel/ad_licenselint/tree/master/fastlane-plugin-ad_licenselint">fastlane plugin</a> that can be used in your Fastfile.</p>
<p>First add the plugin to your Pluginfile:</p>
<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c"># fastlane/Pluginfile</span>
gem <span class="s1">'fastlane-plugin-ad_licenselint'</span></code></pre></figure>
<p>Then call the <code class="language-plaintext highlighter-rouge">ad_licenselint</code> action with your parameters:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># fastlane/Fastfile</span>
<span class="n">lane</span> <span class="ss">:lint_pods</span> <span class="k">do</span>
<span class="n">ad_licenselint</span><span class="p">(</span>
<span class="ss">format: </span><span class="s2">"md"</span><span class="p">,</span> <span class="c1"># markdown format</span>
<span class="ss">path: </span><span class="s2">"."</span> <span class="c1"># the Podfile is in the current directory</span>
<span class="ss">all: </span><span class="kp">true</span><span class="p">,</span> <span class="c1"># display dependencies without warnings</span>
<span class="ss">only: </span><span class="p">[</span><span class="s2">"Alamofire"</span><span class="p">]</span> <span class="c1"># only lint Alamofire</span>
<span class="p">)</span>
<span class="c1"># the formatted string is in the lane_context</span>
<span class="n">summary</span> <span class="o">=</span> <span class="n">lane_context</span><span class="p">[</span><span class="ss">:AD_LICENSE_LINT_SUMMARY</span><span class="p">]</span>
<span class="c1"># the hash of the result is also in the lane context</span>
<span class="n">report</span> <span class="o">=</span> <span class="n">lane_context</span><span class="p">[</span><span class="ss">:AD_LICENSE_LINT_REPORT</span><span class="p">]</span>
<span class="o">...</span>
<span class="k">end</span></code></pre></figure>
<p>The <code class="language-plaintext highlighter-rouge">summary</code> is a string and can be printed as is, whereas the <code class="language-plaintext highlighter-rouge">report</code> hash has the following structure:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="p">{</span>
<span class="ss">entries: </span><span class="p">[</span>
<span class="p">{</span>
<span class="ss">pod_name: </span><span class="s2">"Alamofire"</span><span class="p">,</span>
<span class="ss">license_content: </span><span class="s2">"Copyright (c) 2014-2020 Alamofire Software Foundation (http://alamofire.org/) ..."</span><span class="p">,</span>
<span class="ss">license_name: </span><span class="s2">"MIT"</span><span class="p">,</span>
<span class="ss">source_url: </span><span class="s2">"https://github.com/Alamofire/Alamofire"</span>
<span class="p">},</span>
<span class="o">...</span>
<span class="p">]</span>
<span class="p">}</span></code></pre></figure>
<h2 id="automation">Automation</h2>
<p>Once we have access to the license linter in our lanes, we can imagine some nice use cases.</p>
<h3 id="pull-request-comment">Pull request comment</h3>
<p>Let’s say you want to post an automatic message to your pull request if you detect that a pod with a commercial license has been used.</p>
<p>For instance, let’s add <code class="language-plaintext highlighter-rouge">ObjectivePGP</code> (with a commercial license) and update <code class="language-plaintext highlighter-rouge">ADUtils</code> (with a MIT license) in our Podfile.</p>
<figure class="highlight"><pre><code class="language-diff" data-lang="diff"><span class="gh">diff --git a/Podfile b/Podfile
index ef25df6..d295727 100644
</span><span class="gd">--- a/Podfile
</span><span class="gi">+++ b/Podfile
</span><span class="p">@@ -16,7 +16,7 @@</span> target 'MyApp' do
pod 'Alamofire', '~> 5.0'
<span class="gd">- pod 'ADUtils', '~> 10'
</span><span class="gi">+ pod 'ADUtils', '~> 11'
</span> pod 'Firebase/Analytics', '~> 6.20'
<span class="p">@@ -24,6 +24,7 @@</span> target 'MyApp' do
pod 'OverlayContainer', '~> 3.3'
<span class="gi">+ pod 'ObjectivePGP', '~> 0.15'
</span> end</code></pre></figure>
<p>Once the pull request is pushed to Github, here is an example of an automatically posted message:</p>
<!-- _includes/image.html -->
<div class="image-wrapper">
<img src="https://felginep.github.io/assets/license-github-action.png" alt="Example of automatic comment" />
<p class="image-caption">Example of automatic comment</p>
</div>
<p>With this warning, the developer can check if he really wants to use this dependency or not.</p>
<p>The code to automatically post this message in a pull request can be found <a href="https://gist.github.com/felginep/a04c0b772d532c200e81ef9c862ae424#file-add_license_message-rb" target="_blank">here</a>. In this case we use the formatted report summary available in <code class="language-plaintext highlighter-rouge">lane_context[:AD_LICENSE_LINT_SUMMARY]</code> after we called <code class="language-plaintext highlighter-rouge">ad_licenselint</code>.</p>
<p><em>Note</em>: what is interesting here is to use the gem <code class="language-plaintext highlighter-rouge">cocoapods-core</code> to get the list of updated pods for a branch. This library is useful if you want to manipulate the Podfile and Podfile.lock backing models.</p>
<p>The following snippet shows an example of what you can do with the library:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">podfile</span> <span class="o">=</span> <span class="no">Pod</span><span class="o">::</span><span class="no">Podfile</span><span class="p">.</span><span class="nf">from_file</span><span class="p">(</span><span class="s2">"/path/to/Podfile"</span><span class="p">)</span>
<span class="c1"># if you want the list of pods contained in the podfile</span>
<span class="n">all_pod_names</span> <span class="o">=</span> <span class="n">podfile</span><span class="p">.</span><span class="nf">dependencies</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="o">&</span><span class="ss">:name</span><span class="p">)</span>
<span class="c1"># if you want to compare the podfile</span>
<span class="c1"># to the Podfile.lock from a previous revision</span>
<span class="n">lockfile</span> <span class="o">=</span> <span class="no">Pod</span><span class="o">::</span><span class="no">Lockfile</span><span class="p">.</span><span class="nf">from_file</span><span class="p">(</span><span class="no">Pathname</span><span class="p">(</span><span class="s2">"/path/to/Podfile.lock"</span><span class="p">))</span>
<span class="n">changes</span> <span class="o">=</span> <span class="n">lockfile</span><span class="p">.</span><span class="nf">detect_changes_with_podfile</span><span class="p">(</span><span class="n">podfile</span><span class="p">)</span>
<span class="n">updated_pods</span> <span class="o">=</span> <span class="n">changes</span><span class="p">[</span><span class="ss">:added</span><span class="p">]</span> <span class="o">+</span> <span class="n">changes</span><span class="p">[</span><span class="ss">:changed</span><span class="p">]</span></code></pre></figure>
<h3 id="pull-request-review">Pull request review</h3>
<p>You could also imagine posting a review directly to the Podfile in the pull request.</p>
<p>For instance if we add the same pod <code class="language-plaintext highlighter-rouge">ObjectivePGP</code>, we can create this automatic review:</p>
<!-- _includes/image.html -->
<div class="image-wrapper">
<img src="https://felginep.github.io/assets/license-github-action-review.png" alt="Example of automatic review" />
<p class="image-caption">Example of automatic review</p>
</div>
<p>The code to automatically post this review in a pull request can be found <a href="https://gist.github.com/felginep/a04c0b772d532c200e81ef9c862ae424#file-add_license_review-rb" target="_blank">here</a>. This example uses the report summary hash available in <code class="language-plaintext highlighter-rouge">lane_context[:AD_LICENSE_LINT_REPORT]</code> after we called <code class="language-plaintext highlighter-rouge">ad_licenselint</code>.</p>
<p><em>Note</em>: to interact with the Github api, we use the <code class="language-plaintext highlighter-rouge">octokit</code> gem that is really simple to use:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># just create a client and access all the APIs</span>
<span class="n">client</span> <span class="o">=</span> <span class="no">Octokit</span><span class="o">::</span><span class="no">Client</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">:access_token</span> <span class="o">=></span> <span class="s2">"YOUR TOKEN"</span><span class="p">)</span>
<span class="c1"># get all files from a pull request</span>
<span class="n">files</span> <span class="o">=</span> <span class="n">client</span><span class="p">.</span><span class="nf">pull_request_files</span><span class="p">(</span><span class="n">repo_name</span><span class="p">,</span> <span class="n">pull_request_number</span><span class="p">)</span>
<span class="c1"># post a comment to a pull request</span>
<span class="n">client</span><span class="p">.</span><span class="nf">add_comment</span><span class="p">(</span><span class="n">repo_name</span><span class="p">,</span> <span class="n">pull_request_number</span><span class="p">,</span> <span class="s2">"My comment"</span><span class="p">)</span>
<span class="c1"># post a review to a pull request for the Podfile diff</span>
<span class="c1"># the method does not exist by default, so we can use `post` directly</span>
<span class="n">client</span><span class="p">.</span><span class="nf">post</span><span class="p">(</span>
<span class="s2">"</span><span class="si">#{</span><span class="no">Octokit</span><span class="o">::</span><span class="no">Repository</span><span class="p">.</span><span class="nf">path</span> <span class="n">repo_name</span><span class="si">}</span><span class="s2">/pulls/</span><span class="si">#{</span><span class="n">pull_request_number</span><span class="si">}</span><span class="s2">/comments"</span><span class="p">,</span>
<span class="p">{</span>
<span class="ss">commit_id: </span><span class="n">commit_sha</span><span class="p">,</span>
<span class="ss">path: </span><span class="s1">'Podfile'</span><span class="p">,</span>
<span class="ss">body: </span><span class="s2">"My review"</span><span class="p">,</span>
<span class="ss">line: </span><span class="mi">12</span><span class="p">,</span>
<span class="ss">side: </span><span class="s1">'RIGHT'</span>
<span class="p">}</span>
<span class="p">)</span></code></pre></figure>
<h2 id="conclusion">Conclusion</h2>
<p>The earlier you know a dependency is not compatible with your project, the earlier you can reject it and search for another alternative.
The best option is always to go to the Github page of the pod and check the LICENSE file, but <a href="https://github.com/faberNovel/ad_licenselint/">ADLicenselint</a> has been developed to act as an extra safeguard.</p>
<p>The fastlane plugin along with the gem should give you all the tools you need to use it in your workflow if you want to automate the linting process.</p>
<p><em>Note: This article was also published on Fabernovel <a href="https://www.fabernovel.com/en/engineering/lintint-licenses-ios">blog</a></em>.</p>Pierre FelginesDependencies are at the core of programming. In iOS, we often use Cocoapods to manage dependencies in our projects. But if we are not careful, we can add a dependency that is not free to use, and expose our company to legal issues later. That’s why it’s really important to verify the LICENSE files of each dependency before adding it.Make impossible states impossible2020-08-07T00:00:00+00:002020-08-07T00:00:00+00:00https://felginep.github.io/2020-08-07/make-impossible-states-impossible<p>Swift is a strongly typed language and in this article I want to focus on how to strengthen some part of our code using Swift types.</p>
<h2 id="optionals">Optionals</h2>
<p>Optionals are a powerful tool, but they quickly become a pain in one particular case: when the value of the object is set in a distant future and most of the logic relies on this value.</p>
<p>For example, let’s imagine we are working on an application that fetches some articles. For an article we can share it or open it in a webview.</p>
<p>The code might look like this:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">ArticleViewController</span><span class="p">:</span> <span class="kt">UIViewController</span> <span class="p">{</span>
<span class="kd">private</span> <span class="k">var</span> <span class="nv">article</span><span class="p">:</span> <span class="kt">Article</span><span class="p">?</span>
<span class="k">override</span> <span class="kd">func</span> <span class="nf">viewDidLoad</span><span class="p">()</span> <span class="p">{</span>
<span class="k">super</span><span class="o">.</span><span class="nf">viewDidLoad</span><span class="p">()</span>
<span class="n">fetcher</span><span class="o">.</span><span class="nf">fetchArticle</span><span class="p">()</span> <span class="p">{</span> <span class="p">[</span><span class="k">weak</span> <span class="k">self</span><span class="p">]</span> <span class="n">result</span> <span class="k">in</span>
<span class="k">switch</span> <span class="n">result</span> <span class="p">{</span>
<span class="k">case</span> <span class="kd">let</span> <span class="o">.</span><span class="nf">success</span><span class="p">(</span><span class="n">article</span><span class="p">):</span>
<span class="k">self</span><span class="o">.</span><span class="n">article</span> <span class="o">=</span> <span class="n">article</span>
<span class="c1">// display article somehow</span>
<span class="k">case</span> <span class="kd">let</span> <span class="o">.</span><span class="nf">error</span><span class="p">(</span><span class="n">error</span><span class="p">):</span>
<span class="k">break</span> <span class="c1">// Handle error</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// ...</span>
<span class="kd">func</span> <span class="nf">shareButtonTapped</span><span class="p">()</span> <span class="p">{</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">article</span> <span class="o">=</span> <span class="n">article</span> <span class="k">else</span> <span class="p">{</span>
<span class="nf">preconditionFailure</span><span class="p">(</span><span class="s">"Article should exists"</span><span class="p">)</span>
<span class="p">}</span>
<span class="c1">// Share article</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">openInWebView</span><span class="p">()</span> <span class="p">{</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">article</span> <span class="o">=</span> <span class="n">article</span> <span class="k">else</span> <span class="p">{</span>
<span class="nf">preconditionFailure</span><span class="p">(</span><span class="s">"Article should exists"</span><span class="p">)</span>
<span class="p">}</span>
<span class="c1">// Open article</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>The <code class="language-plaintext highlighter-rouge">article</code> instance variable has to be declared as <code class="language-plaintext highlighter-rouge">Optional<Article></code> for two reasons:</p>
<ul>
<li>the article will be available later in the lifecycle of the view controller, when the fetcher gets its result</li>
<li>the fetcher may return an error instead of an article</li>
</ul>
<p>What is annoying is that the <em>happy path</em> is when the article is available and stored in the <code class="language-plaintext highlighter-rouge">article</code> property. Most of the code will handle this case (the <em>unhappy path</em> is often a smaller part of the code).</p>
<p>The issue in the snippet above is the repeated <code class="language-plaintext highlighter-rouge">guard</code> statements for each action that wants to access the article.</p>
<p>Logically these actions can <em>only</em> exist when the article is not nil.</p>
<p>One solution in this case is to extract all the code that depends on the article into its own class. This new class represents the <em>happy path</em> and must be initialized with a non optional article.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">ArticleActionHandler</span> <span class="p">{</span>
<span class="kd">private</span> <span class="k">let</span> <span class="nv">article</span><span class="p">:</span> <span class="kt">Article</span>
<span class="nf">init</span><span class="p">(</span><span class="nv">article</span><span class="p">:</span> <span class="kt">Article</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">article</span> <span class="o">=</span> <span class="n">article</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">share</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// Share article</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">openInWebView</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// Open article</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>This class is really simple and can be tested easily. Contrary to view controllers, all the dependencies the class need are passed in the <code class="language-plaintext highlighter-rouge">init</code> function.</p>
<p>The caller site becomes simpler too:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">ArticleViewController</span><span class="p">:</span> <span class="kt">UIViewController</span> <span class="p">{</span>
<span class="kd">private</span> <span class="k">var</span> <span class="nv">actionHandler</span><span class="p">:</span> <span class="kt">ArticleActionHandler</span><span class="p">?</span>
<span class="k">override</span> <span class="kd">func</span> <span class="nf">viewDidLoad</span><span class="p">()</span> <span class="p">{</span>
<span class="k">super</span><span class="o">.</span><span class="nf">viewDidLoad</span><span class="p">()</span>
<span class="n">fetcher</span><span class="o">.</span><span class="nf">fetchArticle</span><span class="p">()</span> <span class="p">{</span> <span class="p">[</span><span class="k">weak</span> <span class="k">self</span><span class="p">]</span> <span class="n">result</span> <span class="k">in</span>
<span class="k">switch</span> <span class="n">result</span> <span class="p">{</span>
<span class="k">case</span> <span class="kd">let</span> <span class="o">.</span><span class="nf">success</span><span class="p">(</span><span class="n">article</span><span class="p">):</span>
<span class="k">self</span><span class="p">?</span><span class="o">.</span><span class="n">actionHandler</span> <span class="o">=</span> <span class="kt">ArticleActionHandler</span><span class="p">(</span><span class="nv">article</span><span class="p">:</span> <span class="n">article</span><span class="p">)</span>
<span class="c1">// display article somehow</span>
<span class="k">case</span> <span class="kd">let</span> <span class="o">.</span><span class="nf">error</span><span class="p">(</span><span class="n">error</span><span class="p">):</span>
<span class="k">break</span> <span class="c1">// Handle error</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// ...</span>
<span class="kd">func</span> <span class="nf">shareButtonTapped</span><span class="p">()</span> <span class="p">{</span>
<span class="n">actionHandler</span><span class="p">?</span><span class="o">.</span><span class="nf">share</span><span class="p">()</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">openInWebView</span><span class="p">()</span> <span class="p">{</span>
<span class="n">actionHandler</span><span class="p">?</span><span class="o">.</span><span class="nf">openInWebView</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>Note that this technique is not reserved for view controllers, it can be applied everywhere. When you see that <code class="language-plaintext highlighter-rouge">preconditionFailure</code> start to be scattered all over the place, there is a need for refactoring.</p>
<h2 id="nonempty">NonEmpty</h2>
<p>Let’s say we are in an app that has some sort of <code class="language-plaintext highlighter-rouge">Project</code> entity. A project contains some <em>products</em>, and a project cannot live without products (it has at least one product).</p>
<p>We can represent this relationship with the following structures:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">Product</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">name</span><span class="p">:</span> <span class="kt">String</span> <span class="o">=</span> <span class="s">""</span>
<span class="kd">static</span> <span class="k">let</span> <span class="nv">empty</span> <span class="o">=</span> <span class="kt">Product</span><span class="p">()</span>
<span class="p">}</span>
<span class="kd">class</span> <span class="kt">Project</span> <span class="p">{</span>
<span class="kd">private(set)</span> <span class="k">var</span> <span class="nv">products</span><span class="p">:</span> <span class="p">[</span><span class="kt">Product</span><span class="p">]</span>
<span class="nf">init</span><span class="p">(</span><span class="nv">products</span><span class="p">:</span> <span class="p">[</span><span class="kt">Product</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="o">.</span><span class="n">empty</span><span class="p">])</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">products</span> <span class="o">=</span> <span class="n">products</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">add</span><span class="p">(</span><span class="n">_</span> <span class="nv">product</span><span class="p">:</span> <span class="kt">Product</span><span class="p">)</span> <span class="p">{</span>
<span class="n">products</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="n">product</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>In the app there is a class responsible to edit a product. For example we can change the product name in the editor.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">ProductEditor</span> <span class="p">{</span>
<span class="kd">private</span> <span class="k">let</span> <span class="nv">project</span><span class="p">:</span> <span class="kt">Project</span>
<span class="kd">private</span> <span class="k">var</span> <span class="nv">currentProduct</span><span class="p">:</span> <span class="kt">Product</span><span class="p">?</span>
<span class="nf">init</span><span class="p">(</span><span class="nv">project</span><span class="p">:</span> <span class="kt">Project</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">project</span> <span class="o">=</span> <span class="n">project</span>
<span class="k">self</span><span class="o">.</span><span class="n">currentProduct</span> <span class="o">=</span> <span class="n">project</span><span class="o">.</span><span class="n">products</span><span class="o">.</span><span class="n">first</span>
<span class="p">}</span>
<span class="c1">// ...</span>
<span class="kd">func</span> <span class="nf">updateProductName</span><span class="p">()</span> <span class="p">{</span> <span class="o">...</span> <span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>The issue here is that our editor has an optional <code class="language-plaintext highlighter-rouge">currentProduct</code>. Based on the specifications of the <code class="language-plaintext highlighter-rouge">Project</code>class, a product should always exist in a project. <code class="language-plaintext highlighter-rouge">currentProduct</code> <em>can’t</em> be nil. But there is no way to know it at compile time, that’s why <code class="language-plaintext highlighter-rouge">currentProduct</code> is optional.</p>
<p>A possible solution would be to declare <code class="language-plaintext highlighter-rouge">var currentProduct: Product</code> and raise an error if the project does not contains any product:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">ProductEditor</span> <span class="p">{</span>
<span class="kd">private</span> <span class="k">let</span> <span class="nv">project</span><span class="p">:</span> <span class="kt">Project</span>
<span class="kd">private</span> <span class="k">var</span> <span class="nv">currentProduct</span><span class="p">:</span> <span class="kt">Product</span>
<span class="nf">init</span><span class="p">(</span><span class="nv">project</span><span class="p">:</span> <span class="kt">Project</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">project</span> <span class="o">=</span> <span class="n">project</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">product</span> <span class="o">=</span> <span class="n">project</span><span class="o">.</span><span class="n">products</span><span class="o">.</span><span class="n">first</span> <span class="k">else</span> <span class="p">{</span>
<span class="nf">preconditionFailure</span><span class="p">(</span><span class="s">"A project should contains at least one product"</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">self</span><span class="o">.</span><span class="n">currentProduct</span> <span class="o">=</span> <span class="n">product</span>
<span class="p">}</span>
<span class="c1">// ...</span>
<span class="p">}</span></code></pre></figure>
<p>But that does not feel right. The editor should not <em>know</em> that a project has at least one product, it is not its responsability.</p>
<p>One solution here is to create a type that represents the fact that a project must have at least one product.</p>
<p>We can create a <code class="language-plaintext highlighter-rouge">NonEmpty</code> type that does just this. The folks at Pointfree have covered the <a href="https://www.pointfree.co/episodes/ep20-nonempty">topic</a> and even released a <a href="https://github.com/pointfreeco/swift-nonempty">library</a> for this use case.</p>
<p>The core implementation of <code class="language-plaintext highlighter-rouge">NonEmpty</code> is really simple: a non empty list is composed of a first element (the <code class="language-plaintext highlighter-rouge">head</code>) and a possible empty list (the <code class="language-plaintext highlighter-rouge">tail</code>).</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">NonEmpty</span><span class="o"><</span><span class="kt">C</span><span class="p">:</span> <span class="kt">Collection</span><span class="o">></span><span class="p">:</span> <span class="kt">Collection</span> <span class="p">{</span>
<span class="kd">typealias</span> <span class="kt">Element</span> <span class="o">=</span> <span class="kt">C</span><span class="o">.</span><span class="kt">Element</span>
<span class="kd">private</span> <span class="k">var</span> <span class="nv">head</span><span class="p">:</span> <span class="kt">Element</span>
<span class="kd">private</span> <span class="k">var</span> <span class="nv">tail</span><span class="p">:</span> <span class="kt">C</span>
<span class="nf">init</span><span class="p">(</span><span class="n">_</span> <span class="nv">head</span><span class="p">:</span> <span class="kt">Element</span><span class="p">,</span> <span class="n">_</span> <span class="nv">tail</span><span class="p">:</span> <span class="kt">C</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">head</span> <span class="o">=</span> <span class="n">head</span>
<span class="k">self</span><span class="o">.</span><span class="n">tail</span> <span class="o">=</span> <span class="n">tail</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>What’s beautiful about it, is that we cannot instantiate an empty collection. The <code class="language-plaintext highlighter-rouge">head</code> is always a non optional element.</p>
<p>With this structure, we can refactor our code to be more expressive and safer:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">Project</span> <span class="p">{</span>
<span class="kd">private(set)</span> <span class="k">var</span> <span class="nv">products</span><span class="p">:</span> <span class="kt">NonEmpty</span><span class="o"><</span><span class="p">[</span><span class="kt">Product</span><span class="p">]</span><span class="o">></span>
<span class="nf">init</span><span class="p">(</span><span class="nv">products</span><span class="p">:</span> <span class="kt">NonEmpty</span><span class="o"><</span><span class="p">[</span><span class="kt">Product</span><span class="p">]</span><span class="o">></span> <span class="o">=</span> <span class="kt">NonEmpty</span><span class="o"><</span><span class="p">[</span><span class="kt">Product</span><span class="p">]</span><span class="o">></span><span class="p">(</span><span class="o">.</span><span class="n">empty</span><span class="p">,</span> <span class="p">[]))</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">products</span> <span class="o">=</span> <span class="n">products</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">add</span><span class="p">(</span><span class="n">_</span> <span class="nv">product</span><span class="p">:</span> <span class="kt">Product</span><span class="p">)</span> <span class="p">{</span>
<span class="n">products</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="n">product</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>When someone reads the <code class="language-plaintext highlighter-rouge">Project</code> public API, he can instantly understand that the products will not be empty. It is written in the type, no more need for a comment.</p>
<p>The <code class="language-plaintext highlighter-rouge">NonEmpty</code> type can define a <code class="language-plaintext highlighter-rouge">safeFirst</code> accessor to access the head as a non optional element:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">extension</span> <span class="kt">NonEmpty</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">safeFirst</span><span class="p">:</span> <span class="kt">Element</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">self</span><span class="o">.</span><span class="n">head</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">ProductEditor</span> <span class="p">{</span>
<span class="kd">private</span> <span class="k">let</span> <span class="nv">project</span><span class="p">:</span> <span class="kt">Project</span>
<span class="kd">private</span> <span class="k">var</span> <span class="nv">currentProduct</span><span class="p">:</span> <span class="kt">Product</span>
<span class="nf">init</span><span class="p">(</span><span class="nv">project</span><span class="p">:</span> <span class="kt">Project</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">project</span> <span class="o">=</span> <span class="n">project</span>
<span class="k">self</span><span class="o">.</span><span class="n">currentProduct</span> <span class="o">=</span> <span class="n">project</span><span class="o">.</span><span class="n">products</span><span class="o">.</span><span class="n">safeFirst</span>
<span class="p">}</span>
<span class="c1">// ...</span>
<span class="p">}</span></code></pre></figure>
<p>Note that this time the <code class="language-plaintext highlighter-rouge">currentProduct</code> instance is still not an optional but we removed the <code class="language-plaintext highlighter-rouge">preconditionFailure</code> too. The editor does not care about the relationships between a project and the list of products, it is now constrained by the available API.</p>
<p>Creating your own types is really powerful and is often another way to remove <code class="language-plaintext highlighter-rouge">preconditionFailure</code> in your code. But sometimes it is not always possible to define such types.</p>
<h2 id="enums">Enums</h2>
<p>Let’s face it, code is rarely bad from the start. The issue is that it grows incrementally and is often changed by multiple developers.</p>
<p>That’s why we may find ourselves having difficulty keeping track of the current state in an application.</p>
<p>For instance imagine a simplified situation like this:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">ArticleViewController</span><span class="p">:</span> <span class="kt">UIViewController</span> <span class="p">{</span>
<span class="kd">private</span> <span class="k">var</span> <span class="nv">isLoading</span><span class="p">:</span> <span class="kt">Bool</span> <span class="o">=</span> <span class="kc">true</span>
<span class="kd">private</span> <span class="k">var</span> <span class="nv">result</span><span class="p">:</span> <span class="kt">Result</span><span class="o"><</span><span class="kt">Article</span><span class="p">,</span> <span class="kt">Error</span><span class="o">></span><span class="p">?</span>
<span class="c1">// ...</span>
<span class="p">}</span></code></pre></figure>
<p>If we think about it, when you load an article, the <code class="language-plaintext highlighter-rouge">result</code> variable should not exist. And once <code class="language-plaintext highlighter-rouge">result</code> is set, the <code class="language-plaintext highlighter-rouge">isLoading</code> should always be false.</p>
<p>For now, 6 states are possible:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">isLoading == true</code> and <code class="language-plaintext highlighter-rouge">result == nil</code></li>
<li><code class="language-plaintext highlighter-rouge">isLoading == true</code> and <code class="language-plaintext highlighter-rouge">result == .success</code></li>
<li><code class="language-plaintext highlighter-rouge">isLoading == true</code> and <code class="language-plaintext highlighter-rouge">result == .failure</code></li>
<li><code class="language-plaintext highlighter-rouge">isLoading == false</code> and <code class="language-plaintext highlighter-rouge">result == nil</code></li>
<li><code class="language-plaintext highlighter-rouge">isLoading == false</code> and <code class="language-plaintext highlighter-rouge">result == .success</code></li>
<li><code class="language-plaintext highlighter-rouge">isLoading == false</code> and <code class="language-plaintext highlighter-rouge">result == .failure</code></li>
</ul>
<p>Adding a boolean state in the instance variables should be an alert to think if it’s time for refactoring. In this case we should consider lowering the possible states with an enum:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">enum</span> <span class="kt">ContentState</span><span class="o"><</span><span class="kt">T</span><span class="o">></span> <span class="p">{</span>
<span class="k">case</span> <span class="n">loading</span>
<span class="k">case</span> <span class="nf">loaded</span><span class="p">(</span><span class="kt">Result</span><span class="o"><</span><span class="kt">T</span><span class="p">,</span> <span class="kt">Error</span><span class="o">></span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>
<p>The view controller state becomes simpler:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">ArticleViewController</span><span class="p">:</span> <span class="kt">UIViewController</span> <span class="p">{</span>
<span class="kd">private</span> <span class="k">var</span> <span class="nv">state</span><span class="p">:</span> <span class="kt">ContentState</span><span class="o"><</span><span class="kt">Article</span><span class="o">></span> <span class="o">=</span> <span class="o">.</span><span class="n">loading</span>
<span class="c1">// ...</span>
<span class="p">}</span></code></pre></figure>
<p>With this change, only 3 states are possible:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">loading</code></li>
<li><code class="language-plaintext highlighter-rouge">loaded</code> with <code class="language-plaintext highlighter-rouge">success</code></li>
<li><code class="language-plaintext highlighter-rouge">loaded</code> with <code class="language-plaintext highlighter-rouge">failure</code></li>
</ul>
<p>We removed 3 states from the possible outputs. It is now impossible to be loading while a result exists and vice versa.</p>
<h2 id="pushing-responsibility-away">Pushing responsibility away</h2>
<p>When designing an API you should also consider what types you expect. Let’s say you have an application that takes the initials of an user, and creates some fancy images with the initials (for instance a placeholder profile picture for the user). For simplicity, let’s assume the initials are always composed of two letters.</p>
<p>The first idea that comes in mind is to represent initials with a string like this:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">InitialsViewController</span><span class="p">:</span> <span class="kt">UIViewController</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">initials</span><span class="p">:</span> <span class="kt">String</span><span class="p">?</span>
<span class="kd">func</span> <span class="nf">initialsTextFieldChanged</span><span class="p">(</span><span class="n">_</span> <span class="nv">text</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">text</span><span class="o">.</span><span class="n">count</span> <span class="o">==</span> <span class="mi">2</span> <span class="p">{</span>
<span class="n">initials</span> <span class="o">=</span> <span class="n">text</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">initials</span> <span class="o">=</span> <span class="kc">nil</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>For now this seems fine, we only create initials if there are two characters in the string.</p>
<p>Later in the application we want to generate a profile picture out of the initials.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">generateProfilePicture</span><span class="p">(</span><span class="nv">initials</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="o">-></span> <span class="kt">UIImage</span> <span class="p">{</span>
<span class="k">guard</span> <span class="n">initials</span><span class="o">.</span><span class="n">count</span> <span class="o">==</span> <span class="mi">2</span><span class="p">,</span>
<span class="k">let</span> <span class="nv">firstLetter</span> <span class="o">=</span> <span class="n">initials</span><span class="o">.</span><span class="n">first</span><span class="p">,</span>
<span class="k">let</span> <span class="nv">secondLetter</span> <span class="o">=</span> <span class="n">initials</span><span class="o">.</span><span class="n">last</span> <span class="k">else</span> <span class="p">{</span>
<span class="nf">preconditionFailure</span><span class="p">(</span><span class="s">"Initials must have two letters"</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nf">image</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="n">firstLetter</span><span class="p">)</span><span class="o">.</span><span class="nf">compose</span><span class="p">(</span><span class="nv">with</span><span class="p">:</span> <span class="nf">image</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="n">secondLetter</span><span class="p">))</span>
<span class="p">}</span></code></pre></figure>
<p>Note the <code class="language-plaintext highlighter-rouge">preconditionFailure</code>. In this case initials are supposed to be already valid, meaning having only two characters. But the input type <code class="language-plaintext highlighter-rouge">String</code> is too wide and allows the caller of the function to pass invalid data. That’s why we need to re-validate the content before using it.</p>
<p>To avoid this duplication, let’s consider creating a type for our initials and pushing the responsibility of creating and validating initials outside of the <code class="language-plaintext highlighter-rouge">generateProfilePicture</code> function.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">Initials</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">firstLetter</span><span class="p">:</span> <span class="kt">Character</span>
<span class="k">let</span> <span class="nv">secondLetter</span><span class="p">:</span> <span class="kt">Character</span>
<span class="p">}</span></code></pre></figure>
<p>The <code class="language-plaintext highlighter-rouge">generateProfilePicture</code> is now very simple and do not care about the generation of initials:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">generateProfilePicture</span><span class="p">(</span><span class="nv">initials</span><span class="p">:</span> <span class="kt">Initials</span><span class="p">)</span> <span class="o">-></span> <span class="kt">UIImage</span> <span class="p">{</span>
<span class="k">return</span> <span class="nf">image</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="n">initials</span><span class="o">.</span><span class="n">firstLetter</span><span class="p">)</span>
<span class="o">.</span><span class="nf">compose</span><span class="p">(</span><span class="nv">with</span><span class="p">:</span> <span class="nf">image</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="n">initials</span><span class="o">.</span><span class="n">secondLetter</span><span class="p">))</span>
<span class="p">}</span></code></pre></figure>
<p>There is no need to validate the initials because the type itself does not allow any bad inputs.</p>
<p>All the responsibility of creating initials is now at a single place:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">extension</span> <span class="kt">Initials</span> <span class="p">{</span>
<span class="nf">init</span><span class="p">?(</span><span class="n">_</span> <span class="nv">content</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
<span class="k">guard</span> <span class="n">content</span><span class="o">.</span><span class="n">count</span> <span class="o">==</span> <span class="mi">2</span><span class="p">,</span>
<span class="k">let</span> <span class="nv">firstLetter</span> <span class="o">=</span> <span class="n">content</span><span class="o">.</span><span class="n">first</span><span class="p">,</span>
<span class="k">let</span> <span class="nv">secondLetter</span> <span class="o">=</span> <span class="n">content</span><span class="o">.</span><span class="n">last</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">nil</span>
<span class="p">}</span>
<span class="k">self</span><span class="o">.</span><span class="n">firstLetter</span> <span class="o">=</span> <span class="n">firstLetter</span>
<span class="k">self</span><span class="o">.</span><span class="n">secondLetter</span> <span class="o">=</span> <span class="n">secondLetter</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">class</span> <span class="kt">InitialsViewController</span><span class="p">:</span> <span class="kt">UIViewController</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">initials</span><span class="p">:</span> <span class="kt">Initials</span><span class="p">?</span>
<span class="kd">func</span> <span class="nf">initialsTextFieldChanged</span><span class="p">(</span><span class="n">_</span> <span class="nv">text</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
<span class="n">initials</span> <span class="o">=</span> <span class="kt">Initials</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<h2 id="conclusion">Conclusion</h2>
<p>Many times, we use an assertion (like <code class="language-plaintext highlighter-rouge">preconditionFailure</code> or <code class="language-plaintext highlighter-rouge">assert</code>) to stop the program execution if a path should not happen. But assertions are not the best tool because they are very fragile. Indeed they raise a runtime error instead of a compilation error. It’s very easy to duplicate code and to introduce bugs.</p>
<p>To avoid misusing assertions, we saw in this article different solutions:</p>
<ul>
<li>create a type that match your requirements and enforce the constraints (<code class="language-plaintext highlighter-rouge">NonEmpty</code>, <code class="language-plaintext highlighter-rouge">Initials</code>)</li>
<li>create a enum to limit the number of states</li>
<li>extract happy path code into its own class</li>
</ul>Pierre FelginesSwift is a strongly typed language and in this article I want to focus on how to strengthen some part of our code using Swift types.Visit Monitoring2020-06-09T00:00:00+00:002020-06-09T00:00:00+00:00https://felginep.github.io/2020-06-09/visit-monitoring<p>As Covid-19 started to spread, we tried to find a way to help as developers, with the tools at our disposal. One idea was to use out of the box <a href="https://developer.apple.com/documentation/corelocation/getting_the_user_s_location/using_the_visits_location_service">Visit Monitoring</a> service proposed by Apple to match locations with infected people. The idea at the time was the same as the <a href="https://developer.apple.com/documentation/exposurenotification">ExposureNotification</a> framework, but using location service instead of bluetooth.</p>
<p>That’s a good opportunity to focus on visit monitoring here and see if it’s a good candidate for this use case.</p>
<h2 id="the-service">The service</h2>
<h3 id="setup">Setup</h3>
<p>Visit monitoring requires the <code class="language-plaintext highlighter-rouge">Always</code> authorization for location. The service is not available when <code class="language-plaintext highlighter-rouge">When In Use</code> is requested. You can find all the location services and their needed authorizations <a href="https://developer.apple.com/documentation/corelocation/choosing_the_location_services_authorization_to_request#3376460">here</a>.</p>
<p>Visit monitoring is meant to be used in background. Like the other background services, it will continue to run even when the app is suspended, and will <a href="https://developer.apple.com/documentation/corelocation/getting_the_user_s_location/handling_location_events_in_the_background#2865362">launch</a> the app in the background to deliver events.
Make sure to enable <code class="language-plaintext highlighter-rouge">Location Updates</code> in the application background modes capabilities and to set <code class="language-plaintext highlighter-rouge">allowsBackgroundLocationUpdates</code> to <code class="language-plaintext highlighter-rouge">true</code> for the location manager.</p>
<h3 id="start-monitoring-visits">Start monitoring visits</h3>
<p>The use is really straightforward, as for other location services.</p>
<p>First create your <code class="language-plaintext highlighter-rouge">CLLocationManager</code> and call the method <code class="language-plaintext highlighter-rouge">startMonitoringVisits()</code>. If the service is authorized and running, you will receive callbacks on the delegate method <code class="language-plaintext highlighter-rouge">locationManager(_:didVisit:)</code>.</p>
<p>Here is a skeleton of implementation of such a feature.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">import</span> <span class="kt">CoreLocation</span>
<span class="kd">class</span> <span class="kt">LocationService</span><span class="p">:</span> <span class="kt">NSObject</span><span class="p">,</span> <span class="kt">CLLocationManagerDelegate</span> <span class="p">{</span>
<span class="kd">private</span> <span class="k">let</span> <span class="nv">locationManager</span><span class="p">:</span> <span class="kt">CLLocationManager</span>
<span class="kd">private</span> <span class="k">var</span> <span class="nv">pendingAuthorizationCompletion</span><span class="p">:</span> <span class="p">((</span><span class="kt">Bool</span><span class="p">)</span> <span class="o">-></span> <span class="kt">Void</span><span class="p">)?</span>
<span class="nf">init</span><span class="p">(</span><span class="nv">locationManager</span><span class="p">:</span> <span class="kt">CLLocationManager</span> <span class="o">=</span> <span class="kt">CLLocationManager</span><span class="p">())</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">locationManager</span> <span class="o">=</span> <span class="n">locationManager</span>
<span class="k">super</span><span class="o">.</span><span class="nf">init</span><span class="p">()</span>
<span class="n">locationManager</span><span class="o">.</span><span class="n">allowsBackgroundLocationUpdates</span> <span class="o">=</span> <span class="kc">true</span>
<span class="n">locationManager</span><span class="o">.</span><span class="n">delegate</span> <span class="o">=</span> <span class="k">self</span>
<span class="p">}</span>
<span class="c1">// MARK: - LocationService</span>
<span class="kd">func</span> <span class="nf">start</span><span class="p">()</span> <span class="p">{</span>
<span class="k">switch</span> <span class="kt">CLLocationManager</span><span class="o">.</span><span class="nf">authorizationStatus</span><span class="p">()</span> <span class="p">{</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">notDetermined</span><span class="p">:</span>
<span class="n">locationManager</span><span class="o">.</span><span class="nf">requestAlwaysAuthorization</span><span class="p">()</span>
<span class="k">case</span> <span class="o">.</span><span class="n">authorizedAlways</span><span class="p">,</span>
<span class="o">.</span><span class="n">authorizedWhenInUse</span><span class="p">,</span>
<span class="o">.</span><span class="n">restricted</span><span class="p">,</span>
<span class="o">.</span><span class="nv">denied</span><span class="p">:</span>
<span class="c1">// Handle unauthorized</span>
<span class="kd">@unknown</span> <span class="k">default</span><span class="p">:</span>
<span class="k">break</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// MARK: - CLLocationManagerDelegate</span>
<span class="kd">func</span> <span class="nf">locationManager</span><span class="p">(</span><span class="n">_</span> <span class="nv">manager</span><span class="p">:</span> <span class="kt">CLLocationManager</span><span class="p">,</span>
<span class="n">didChangeAuthorization</span> <span class="nv">status</span><span class="p">:</span> <span class="kt">CLAuthorizationStatus</span><span class="p">)</span> <span class="p">{</span>
<span class="k">switch</span> <span class="n">status</span> <span class="p">{</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">authorizedAlways</span><span class="p">:</span>
<span class="n">locationManager</span><span class="o">.</span><span class="nf">startMonitoringVisits</span><span class="p">()</span>
<span class="k">case</span> <span class="o">.</span><span class="n">notDetermined</span><span class="p">,</span>
<span class="o">.</span><span class="n">authorizedWhenInUse</span><span class="p">,</span>
<span class="o">.</span><span class="n">restricted</span><span class="p">,</span>
<span class="o">.</span><span class="nv">denied</span><span class="p">:</span>
<span class="c1">// Handle unauthorized</span>
<span class="kd">@unknown</span> <span class="k">default</span><span class="p">:</span>
<span class="k">break</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">locationManager</span><span class="p">(</span><span class="n">_</span> <span class="nv">manager</span><span class="p">:</span> <span class="kt">CLLocationManager</span><span class="p">,</span>
<span class="n">didVisit</span> <span class="nv">visit</span><span class="p">:</span> <span class="kt">CLVisit</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Handle visit</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<h3 id="debuging">Debuging</h3>
<p>The service runs in background and you have no idea when the system will choose to trigger a visit update. That’s why it’s difficult to debug it.</p>
<p>Something you can do to help debug the application is to trigger a local notification on the device every time the device logs a new visit. That way, when using your device in your daily life, you’ll be able to know precisely when and where the visits occur.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">locationManager</span><span class="p">(</span><span class="n">_</span> <span class="nv">manager</span><span class="p">:</span> <span class="kt">CLLocationManager</span><span class="p">,</span> <span class="n">didVisit</span> <span class="nv">visit</span><span class="p">:</span> <span class="kt">CLVisit</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">postDebugMessage</span><span class="p">(</span><span class="nv">for</span><span class="p">:</span> <span class="n">visit</span><span class="p">)</span>
<span class="c1">// ...</span>
<span class="p">}</span>
<span class="kd">private</span> <span class="kd">func</span> <span class="nf">postDebugMessage</span><span class="p">(</span><span class="k">for</span> <span class="nv">visit</span><span class="p">:</span> <span class="kt">CLVisit</span><span class="p">)</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">content</span> <span class="o">=</span> <span class="kt">UNMutableNotificationContent</span><span class="p">()</span>
<span class="n">content</span><span class="o">.</span><span class="n">title</span> <span class="o">=</span> <span class="s">"New place registered"</span>
<span class="n">content</span><span class="o">.</span><span class="n">body</span> <span class="o">=</span> <span class="s">"</span><span class="se">\(</span><span class="n">visit</span><span class="o">.</span><span class="n">coordinate</span><span class="se">)</span><span class="s">"</span>
<span class="n">content</span><span class="o">.</span><span class="n">sound</span> <span class="o">=</span> <span class="o">.</span><span class="k">default</span>
<span class="k">let</span> <span class="nv">trigger</span> <span class="o">=</span> <span class="kt">UNTimeIntervalNotificationTrigger</span><span class="p">(</span>
<span class="nv">timeInterval</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nv">repeats</span><span class="p">:</span> <span class="kc">false</span>
<span class="p">)</span>
<span class="k">let</span> <span class="nv">request</span> <span class="o">=</span> <span class="kt">UNNotificationRequest</span><span class="p">(</span>
<span class="nv">identifier</span><span class="p">:</span> <span class="s">""</span><span class="p">,</span>
<span class="nv">content</span><span class="p">:</span> <span class="n">content</span><span class="p">,</span>
<span class="nv">trigger</span><span class="p">:</span> <span class="n">trigger</span>
<span class="p">)</span>
<span class="n">center</span><span class="o">.</span><span class="nf">add</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="nv">withCompletionHandler</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>
<h2 id="pros-and-cons">Pros and cons</h2>
<h3 id="pros">Pros</h3>
<ul>
<li>
<p>The major feature of visit monitoring is to be able to receive user locations with a very minimal impact on battery life. Apple even describes this service as <em>the most power-efficient way of gathering location data</em>. The goal here is not to be very accurate about the user trace, but to be able to retrieve context from a user location.</p>
</li>
<li>
<p>Apple uses sophisticated algorithms to monitor for places that the user might consider a noteworthy part of their day. For instance the property <code class="language-plaintext highlighter-rouge">pausesLocationUpdatesAutomatically</code> automatically pauses the service if the user is unlikely to move. That means the framework learns from your habits and trigger events accordingly.</p>
</li>
</ul>
<h3 id="cons">Cons</h3>
<ul>
<li>Some visits returned by the system are incomplete and the documentation is misleading. The documentation states that <em>Visit objects contain as much information about the visit as possible but may not always include both the arrival and departure times</em>.</li>
</ul>
<p>In Swift, though, the visit class contains non optional <code class="language-plaintext highlighter-rouge">arrivalDate</code> and <code class="language-plaintext highlighter-rouge">departureDate</code>, as we could have expected. These two properties rather contain <em>invalid</em> data (like <code class="language-plaintext highlighter-rouge">distantPast</code> or <code class="language-plaintext highlighter-rouge">distantFuture</code>):</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">@available</span><span class="p">(</span><span class="n">iOS</span> <span class="mf">8.0</span><span class="p">,</span> <span class="o">*</span><span class="p">)</span>
<span class="kd">class</span> <span class="kt">CLVisit</span> <span class="p">:</span> <span class="kt">NSObject</span><span class="p">,</span> <span class="kt">NSSecureCoding</span><span class="p">,</span> <span class="kt">NSCopying</span> <span class="p">{</span>
<span class="c1">// This may be equal to [NSDate distantPast]</span>
<span class="c1">// if the true arrival date isn't available.</span>
<span class="k">var</span> <span class="nv">arrivalDate</span><span class="p">:</span> <span class="kt">Date</span> <span class="p">{</span> <span class="k">get</span> <span class="p">}</span>
<span class="c1">// This is equal to [NSDate distantFuture]</span>
<span class="c1">// if the device hasn't yet left.</span>
<span class="k">var</span> <span class="nv">departureDate</span><span class="p">:</span> <span class="kt">Date</span> <span class="p">{</span> <span class="k">get</span> <span class="p">}</span>
<span class="k">var</span> <span class="nv">coordinate</span><span class="p">:</span> <span class="kt">CLLocationCoordinate2D</span> <span class="p">{</span> <span class="k">get</span> <span class="p">}</span>
<span class="k">var</span> <span class="nv">horizontalAccuracy</span><span class="p">:</span> <span class="kt">CLLocationAccuracy</span> <span class="p">{</span> <span class="k">get</span> <span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>That means you can’t trust the visit dates and have to discard invalid visits if you rely on both arrival and departure dates.</p>
<ul>
<li>
<p>What’s more, the precision of the visit, described in the property <code class="language-plaintext highlighter-rouge">horizontalAccuracy</code> in meters, is not very precise. The center of the visit (described as the <code class="language-plaintext highlighter-rouge">coordinate</code> property) may not be the exact location where you were during the visit. That means you should not rely on the <code class="language-plaintext highlighter-rouge">coordinate</code> property to track the precise location of the user.</p>
</li>
<li>
<p>Another issue about precision is that you can’t know the elevation of the user. That means you may have a single event when the user visits a building, although he may have been in very different levels for different purposes.</p>
</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>Visit monitoring is a powerful service that should be considered as an alternative to Significant-change monitoring in some cases. Its minimal impact on battery life makes it perfect to use in situations where you just need to get context from a noteworthy place: restaurant, gym, home, work, etc.</p>
<p>In our use case we wanted to match locations of multiple users during the same period of time. But as we have seen previously, arrival and departure dates are not always present and the location is not precise. So visit monitoring is not well suited for this situation.</p>
<p><em>Note: This article was also published on Fabernovel <a href="https://www.fabernovel.com/en/engineering/visit-monitoring-ios">blog</a></em>.</p>Pierre FelginesAs Covid-19 started to spread, we tried to find a way to help as developers, with the tools at our disposal. One idea was to use out of the box Visit Monitoring service proposed by Apple to match locations with infected people. The idea at the time was the same as the ExposureNotification framework, but using location service instead of bluetooth.Using intents to leave the app2020-03-19T00:00:00+00:002020-03-19T00:00:00+00:00https://felginep.github.io/2020-03-19/using-intents-to-leave-the-app<p>In an iOS application, we often have to redirect the user outside of the application for a variety of reasons: calling a number, sharing content, writing an email, etc.</p>
<p>UIKit comes with a number of built in ways to perform these actions but there is not an unified method to do it. And sometimes you even want to use third party schemes instead of native ones (using Google Maps instead of Maps for instance).</p>
<p>Let’s see with a few examples how we could hide this logic into more abstract types and simplify the calling site.</p>
<h2 id="mail">Mail</h2>
<p>Let’s say we want to display the user a mail compose sheet. Thanks to the documentation, the right class to use is <code class="language-plaintext highlighter-rouge">MFMailComposeViewController</code>.</p>
<blockquote>
<p>Use this view controller to display a standard email interface inside your app.</p>
</blockquote>
<p>The detail here is that <code class="language-plaintext highlighter-rouge">MFMailComposeViewController</code> is a class of the <code class="language-plaintext highlighter-rouge">MessageUI</code> framework. And this detail bothers me because I try to keep the imported frameworks to the minimum. I don’t want to import <code class="language-plaintext highlighter-rouge">MessageUI</code> in my view controller class, and rather hide implementation details.</p>
<p>A solution in this case is to use the <code class="language-plaintext highlighter-rouge">MFMailComposeViewController</code> in a specific object that will not be visible to the external world.</p>
<p>For this, let’s borrow the <em>intent</em> naming of Android developers and create a <code class="language-plaintext highlighter-rouge">MailIntent</code> that will be used by the sender to inform its intent to display a mail interface, but without knowing how.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">protocol</span> <span class="kt">MailIntent</span> <span class="p">{</span>
<span class="kd">func</span> <span class="nf">mail</span><span class="p">(</span><span class="n">to</span> <span class="nv">recipient</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>
<p>The caller can use it like so:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">ContactViewController</span> <span class="p">{</span>
<span class="kd">private</span> <span class="k">let</span> <span class="nv">mailIntent</span><span class="p">:</span> <span class="kt">MailIntent</span>
<span class="o">...</span>
<span class="kd">func</span> <span class="nf">requestEmail</span><span class="p">(</span><span class="n">to</span> <span class="nv">recipient</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
<span class="n">mailIntent</span><span class="o">.</span><span class="nf">mail</span><span class="p">(</span><span class="nv">to</span><span class="p">:</span> <span class="n">recipient</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>The implementation of <code class="language-plaintext highlighter-rouge">MailIntent</code> can import <code class="language-plaintext highlighter-rouge">MessageUI</code> and use <code class="language-plaintext highlighter-rouge">MFMailComposeViewController</code>. All the <code class="language-plaintext highlighter-rouge">MessageUI</code> code is constrained into this class. That also means we can use the intent in different places in the application, the display of the mail interface will always be the same and we won’t have to repeat ourselves.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">import</span> <span class="kt">MessageUI</span>
<span class="kd">class</span> <span class="kt">NativeMailIntent</span><span class="p">:</span> <span class="kt">NSObject</span><span class="p">,</span>
<span class="kt">MailIntent</span><span class="p">,</span>
<span class="kt">MFMailComposeViewControllerDelegate</span> <span class="p">{</span>
<span class="kd">private</span> <span class="k">let</span> <span class="nv">viewController</span><span class="p">:</span> <span class="kt">UIViewController</span>
<span class="nf">init</span><span class="p">(</span><span class="nv">viewController</span><span class="p">:</span> <span class="kt">UIViewController</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">viewController</span> <span class="o">=</span> <span class="n">viewController</span>
<span class="p">}</span>
<span class="c1">// MARK: - MailIntent</span>
<span class="kd">func</span> <span class="nf">mail</span><span class="p">(</span><span class="n">to</span> <span class="nv">recipient</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
<span class="k">guard</span> <span class="kt">MFMailComposeViewController</span><span class="o">.</span><span class="nf">canSendMail</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">let</span> <span class="nv">mailComposeViewController</span> <span class="o">=</span> <span class="kt">MFMailComposeViewController</span><span class="p">()</span>
<span class="n">mailComposeViewController</span><span class="o">.</span><span class="n">mailComposeDelegate</span> <span class="o">=</span> <span class="k">self</span>
<span class="n">mailComposeViewController</span><span class="o">.</span><span class="nf">setToRecipients</span><span class="p">([</span><span class="n">recipient</span><span class="p">])</span>
<span class="n">viewController</span><span class="o">.</span><span class="nf">present</span><span class="p">(</span><span class="n">mailComposeViewController</span><span class="p">,</span> <span class="nv">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
<span class="p">}</span>
<span class="c1">// MARK: - MFMailComposeViewControllerDelegate</span>
<span class="kd">func</span> <span class="nf">mailComposeController</span><span class="p">(</span><span class="n">_</span> <span class="nv">controller</span><span class="p">:</span> <span class="kt">MFMailComposeViewController</span><span class="p">,</span>
<span class="n">didFinishWith</span> <span class="nv">result</span><span class="p">:</span> <span class="kt">MFMailComposeResult</span><span class="p">,</span>
<span class="nv">error</span><span class="p">:</span> <span class="kt">Error</span><span class="p">?)</span> <span class="p">{</span>
<span class="n">viewController</span><span class="o">.</span><span class="nf">dismiss</span><span class="p">(</span><span class="nv">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>That also means that if we want to change the way we send an email, it’s simple. For example if we choose to redirect the user to the Mail app instead of opening a compose sheet, we could just rewrite the <code class="language-plaintext highlighter-rouge">mail(to:)</code> function like so:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">NativeMailIntent</span><span class="p">:</span> <span class="kt">MailIntent</span> <span class="p">{</span>
<span class="kd">private</span> <span class="k">let</span> <span class="nv">application</span><span class="p">:</span> <span class="kt">UIApplication</span>
<span class="nf">init</span><span class="p">(</span><span class="nv">application</span><span class="p">:</span> <span class="kt">UIApplication</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">application</span> <span class="o">=</span> <span class="n">application</span>
<span class="p">}</span>
<span class="c1">// MARK: - MailIntent</span>
<span class="kd">func</span> <span class="nf">mail</span><span class="p">(</span><span class="n">to</span> <span class="nv">recipient</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">mailURLString</span> <span class="o">=</span> <span class="s">"mailto:</span><span class="se">\(</span><span class="n">mailTo</span><span class="o">.</span><span class="nf">removeWhitespaces</span><span class="p">()</span><span class="se">)</span><span class="s">"</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">url</span> <span class="o">=</span> <span class="kt">URL</span><span class="p">(</span><span class="nv">string</span><span class="p">:</span> <span class="n">mailURLString</span><span class="p">),</span>
<span class="n">application</span><span class="o">.</span><span class="nf">canOpenURL</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="n">application</span><span class="o">.</span><span class="nf">open</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="nv">options</span><span class="p">:</span> <span class="p">[:])</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>What’s more, we all know that <code class="language-plaintext highlighter-rouge">MFMailComposeViewController</code> is not working on simulator. That means we can’t be sure our actions that display the compose sheet are well implemented when we test on simulator (either manually or with UI tests). In this case we can create a dummy implementation of <code class="language-plaintext highlighter-rouge">MailIntent</code> that will just display an alert controller with the recipient as the message.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">DebugMailIntent</span><span class="p">:</span> <span class="kt">MailIntent</span> <span class="p">{</span>
<span class="kd">private</span> <span class="k">let</span> <span class="nv">viewController</span><span class="p">:</span> <span class="kt">UIViewController</span>
<span class="nf">init</span><span class="p">(</span><span class="nv">viewController</span><span class="p">:</span> <span class="kt">UIViewController</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">viewController</span> <span class="o">=</span> <span class="n">viewController</span>
<span class="p">}</span>
<span class="c1">// MARK: - MailIntent</span>
<span class="kd">func</span> <span class="nf">mail</span><span class="p">(</span><span class="n">to</span> <span class="nv">recipient</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">alert</span> <span class="o">=</span> <span class="kt">UIAlertController</span><span class="p">(</span>
<span class="nv">title</span><span class="p">:</span> <span class="s">"DebugMailIntent"</span><span class="p">,</span>
<span class="nv">message</span><span class="p">:</span> <span class="s">"Recipient </span><span class="se">\(</span><span class="n">recipient</span><span class="se">)</span><span class="s">"</span><span class="p">,</span>
<span class="nv">preferredStyle</span><span class="p">:</span> <span class="o">.</span><span class="n">alert</span>
<span class="p">)</span>
<span class="n">alert</span><span class="o">.</span><span class="nf">addAction</span><span class="p">(</span><span class="kt">UIAlertAction</span><span class="p">(</span><span class="nv">title</span><span class="p">:</span> <span class="s">"Ok"</span><span class="p">,</span> <span class="nv">style</span><span class="p">:</span> <span class="o">.</span><span class="n">cancel</span><span class="p">))</span>
<span class="n">viewController</span><span class="o">.</span><span class="nf">present</span><span class="p">(</span><span class="n">alert</span><span class="p">,</span> <span class="nv">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<h2 id="map">Map</h2>
<p>We can apply the same technique for opening map items into Maps application and hide the import of the <code class="language-plaintext highlighter-rouge">MapKit</code> framework.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">MapItem</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">title</span><span class="p">:</span> <span class="kt">String</span>
<span class="k">let</span> <span class="nv">coordinate</span><span class="p">:</span> <span class="kt">CLLocationCoordinate2D</span>
<span class="p">}</span>
<span class="kd">protocol</span> <span class="kt">MapIntent</span> <span class="p">{</span>
<span class="kd">func</span> <span class="nf">open</span><span class="p">(</span><span class="nv">item</span><span class="p">:</span> <span class="kt">MapItem</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>
<p>The implementation will be:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">import</span> <span class="kt">MapKit</span>
<span class="kd">class</span> <span class="kt">NativeMapIntent</span><span class="p">:</span> <span class="kt">MapIntent</span> <span class="p">{</span>
<span class="c1">// MARK: - MapIntent</span>
<span class="kd">func</span> <span class="nf">open</span><span class="p">(</span><span class="nv">item</span><span class="p">:</span> <span class="kt">MapItem</span><span class="p">)</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">placemark</span> <span class="o">=</span> <span class="kt">MKPlacemark</span><span class="p">(</span>
<span class="nv">coordinate</span><span class="p">:</span> <span class="n">item</span><span class="o">.</span><span class="n">coordinate</span><span class="p">,</span>
<span class="nv">addressDictionary</span><span class="p">:</span> <span class="kc">nil</span>
<span class="p">)</span>
<span class="k">let</span> <span class="nv">mapItem</span> <span class="o">=</span> <span class="kt">MKMapItem</span><span class="p">(</span><span class="nv">placemark</span><span class="p">:</span> <span class="n">placemark</span><span class="p">)</span>
<span class="n">mapItem</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">item</span><span class="o">.</span><span class="n">title</span>
<span class="n">mapItem</span><span class="o">.</span><span class="nf">openInMaps</span><span class="p">(</span><span class="nv">launchOptions</span><span class="p">:</span> <span class="p">[:])</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>What’s interesting in this case is that we can create another implementation for Google Maps, a third party application.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">GoogleMapIntent</span><span class="p">:</span> <span class="kt">MapIntent</span> <span class="p">{</span>
<span class="kd">private</span> <span class="k">let</span> <span class="nv">application</span><span class="p">:</span> <span class="kt">UIApplication</span>
<span class="nf">init</span><span class="p">(</span><span class="nv">application</span><span class="p">:</span> <span class="kt">UIApplication</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">application</span> <span class="o">=</span> <span class="n">application</span>
<span class="p">}</span>
<span class="c1">// MARK: - MapIntent</span>
<span class="kd">func</span> <span class="nf">open</span><span class="p">(</span><span class="nv">item</span><span class="p">:</span> <span class="kt">MapItem</span><span class="p">)</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">urlString</span> <span class="o">=</span> <span class="s">"comgooglemaps://?daddr=</span><span class="se">\(</span><span class="n">item</span><span class="o">.</span><span class="n">coordinate</span><span class="o">.</span><span class="n">latitude</span><span class="se">)</span><span class="s">,</span><span class="se">\(</span><span class="n">item</span><span class="o">.</span><span class="n">coordinate</span><span class="o">.</span><span class="n">longitude</span><span class="se">)</span><span class="s">"</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">url</span> <span class="o">=</span> <span class="kt">URL</span><span class="p">(</span><span class="nv">string</span><span class="p">:</span> <span class="n">urlString</span><span class="p">),</span>
<span class="n">application</span><span class="o">.</span><span class="nf">canOpenURL</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="n">application</span><span class="o">.</span><span class="nf">open</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="nv">options</span><span class="p">:</span> <span class="p">[:])</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<h2 id="phone-call">Phone Call</h2>
<p>Even if there is no framework to hide in this case, the naming of the intent makes things very convenient to use.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">protocol</span> <span class="kt">PhoneIntent</span> <span class="p">{</span>
<span class="kd">func</span> <span class="nf">call</span><span class="p">(</span><span class="nv">phone</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>
<p>The implementation will be:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">NativePhoneIntent</span><span class="p">:</span> <span class="kt">PhoneIntent</span> <span class="p">{</span>
<span class="kd">private</span> <span class="k">let</span> <span class="nv">application</span><span class="p">:</span> <span class="kt">UIApplication</span>
<span class="nf">init</span><span class="p">(</span><span class="nv">application</span><span class="p">:</span> <span class="kt">UIApplication</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">application</span> <span class="o">=</span> <span class="n">application</span>
<span class="p">}</span>
<span class="c1">// MARK: - PhoneIntent</span>
<span class="kd">func</span> <span class="nf">call</span><span class="p">(</span><span class="nv">phone</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
<span class="k">guard</span> <span class="o">!</span><span class="n">phone</span><span class="o">.</span><span class="n">isEmpty</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
<span class="k">let</span> <span class="nv">phoneURLString</span> <span class="o">=</span> <span class="s">"tel://</span><span class="se">\(</span><span class="n">phone</span><span class="o">.</span><span class="nf">ad_removingWhitespaces</span><span class="p">()</span><span class="se">)</span><span class="s">"</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">url</span> <span class="o">=</span> <span class="kt">URL</span><span class="p">(</span><span class="nv">string</span><span class="p">:</span> <span class="n">phoneURLString</span><span class="p">),</span>
<span class="n">application</span><span class="o">.</span><span class="nf">canOpenURL</span><span class="p">(</span><span class="n">url</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="n">application</span><span class="o">.</span><span class="nf">open</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="nv">options</span><span class="p">:</span> <span class="p">[:])</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>In this case we directly call the provided number, but we could imagine passing a view controller to the native intent, and display an alert before calling.</p>
<h2 id="conclusion">Conclusion</h2>
<p>This simple technique allows two things:</p>
<ul>
<li>hide the implementation details of the intent (import of frameworks) and keep things simple in the caller site</li>
<li>easily reuse the same intent in different places</li>
</ul>Pierre FelginesIn an iOS application, we often have to redirect the user outside of the application for a variety of reasons: calling a number, sharing content, writing an email, etc.UIAlertController with Function Builders2020-03-10T00:00:00+00:002020-03-10T00:00:00+00:00https://felginep.github.io/2020-03-10/uialertcontroller-function-builders<p>I always found the <code class="language-plaintext highlighter-rouge">UIAlertController</code> API too verbose. You first have to create an instance of <code class="language-plaintext highlighter-rouge">UIAlertController</code>, then create multiple instances of <code class="language-plaintext highlighter-rouge">UIAlertAction</code> and finally add the actions to the controller.</p>
<p>In this post, I will show you how we can leverage the new Swift 5.1 feature of <em>Function Builders</em> to create a simplified and highly readable API.</p>
<h2 id="our-goal">Our goal</h2>
<p>Let’s take this sample code:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">alertController</span> <span class="o">=</span> <span class="kt">UIAlertController</span><span class="p">(</span>
<span class="nv">title</span><span class="p">:</span> <span class="s">"Deletion"</span><span class="p">,</span>
<span class="nv">message</span><span class="p">:</span> <span class="s">"Are you sure?"</span><span class="p">,</span>
<span class="nv">preferredStyle</span><span class="p">:</span> <span class="o">.</span><span class="n">alert</span>
<span class="p">)</span>
<span class="k">let</span> <span class="nv">deleteAction</span> <span class="o">=</span> <span class="kt">UIAlertAction</span><span class="p">(</span><span class="nv">title</span><span class="p">:</span> <span class="s">"Delete"</span><span class="p">,</span> <span class="nv">style</span><span class="p">:</span> <span class="o">.</span><span class="n">destructive</span><span class="p">)</span> <span class="p">{</span> <span class="n">_</span> <span class="k">in</span>
<span class="c1">// Perform deletion</span>
<span class="p">}</span>
<span class="k">let</span> <span class="nv">cancelAction</span> <span class="o">=</span> <span class="kt">UIAlertAction</span><span class="p">(</span><span class="nv">title</span><span class="p">:</span> <span class="s">"Cancel"</span><span class="p">,</span> <span class="nv">style</span><span class="p">:</span> <span class="o">.</span><span class="n">cancel</span><span class="p">)</span>
<span class="n">alertController</span><span class="o">.</span><span class="nf">addAction</span><span class="p">(</span><span class="n">deleteAction</span><span class="p">)</span>
<span class="n">alertController</span><span class="o">.</span><span class="nf">addAction</span><span class="p">(</span><span class="n">cancelAction</span><span class="p">)</span>
<span class="nf">present</span><span class="p">(</span><span class="n">alertController</span><span class="p">,</span> <span class="nv">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span></code></pre></figure>
<!-- _includes/image.html -->
<div class="image-wrapper">
<img src="https://felginep.github.io/assets/alert-controller-deletion.png" alt="Image 1. Result" />
<p class="image-caption">Image 1. Result</p>
</div>
<p>As I said earlier, there is a lot to write and we can do better. We could improve the API with regular Swift <a href="https://gist.github.com/felginep/6ae269764dd9e8925320e62b91e838bd">patterns</a>, but the goal here is to use function builders to create this SwiftUI like sample code:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">alertController</span> <span class="o">=</span> <span class="kt">Alert</span><span class="p">(</span><span class="nv">title</span><span class="p">:</span> <span class="s">"Deletion"</span><span class="p">,</span> <span class="nv">message</span><span class="p">:</span> <span class="s">"Are you sure?"</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">Action</span><span class="o">.</span><span class="nf">destructive</span><span class="p">(</span><span class="s">"Delete"</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Perform deletion</span>
<span class="p">}</span>
<span class="kt">Action</span><span class="o">.</span><span class="nf">cancel</span><span class="p">(</span><span class="s">"Cancel"</span><span class="p">)</span>
<span class="p">}</span>
<span class="nf">present</span><span class="p">(</span><span class="n">alertController</span><span class="p">,</span> <span class="nv">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span></code></pre></figure>
<h2 id="function-builders">Function Builders</h2>
<p>This new feature introduced in Swift 5.1 is not fully implemented yet. Instead of the public <code class="language-plaintext highlighter-rouge">@functionBuilder</code> annotation, you have to use the private <code class="language-plaintext highlighter-rouge">@_functionBuilder</code> one. Though, you can find the details of the proposal <a href="https://github.com/apple/swift-evolution/blob/9992cf3c11c2d5e0ea20bee98657d93902d5b174/proposals/XXXX-function-builders.md#function-building-methods">here</a>.</p>
<p>To better understand the purpose of this feature, this extract highlights the use cases of function builders:</p>
<blockquote>
<p>This proposal does not aim to enable all kinds of embedded DSL in Swift. It is focused on one specific class of problem that can be significantly improved with the use of a DSL: creating lists and trees of (typically) heterogenous data from regular patterns. This is a common need across a variety of different domains, including generating structured data (e.g. XML or JSON), UI view hierarchies (notably including Apple’s new SwiftUI framework, which is obviously a strong motivator for the authors), and similar use cases.</p>
</blockquote>
<p>The proposal focuses on a DSL to represent HTML <a href="https://github.com/apple/swift-evolution/blob/9992cf3c11c2d5e0ea20bee98657d93902d5b174/proposals/XXXX-function-builders.md#declaring-list-and-tree-structures">tree</a> but it is also heavily used in SwiftUI to create view hierarchies with the <code class="language-plaintext highlighter-rouge">@ViewBuilder</code> <a href="https://developer.apple.com/documentation/swiftui/viewbuilder">attribute</a>.</p>
<p>The <a href="https://github.com/apple/swift-evolution/blob/9992cf3c11c2d5e0ea20bee98657d93902d5b174/proposals/XXXX-function-builders.md#function-building-methods">documentation</a> is not official, but after some digging, here are the methods we can implement:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">buildBlock(_ components: Component...) -> Component</code> <em>required</em></li>
<li><code class="language-plaintext highlighter-rouge">buildIf(_ component: Component?) -> Component</code> <em>optional</em>, <a href="https://forums.swift.org/t/function-builders/25167/53">previously named</a> <code class="language-plaintext highlighter-rouge">buildOptional</code>.</li>
<li><code class="language-plaintext highlighter-rouge">buildEither(first: Component) -> Component</code> and <code class="language-plaintext highlighter-rouge">buildEither(second: Component) -> Component</code>, <em>optionals</em></li>
</ul>
<p>The <code class="language-plaintext highlighter-rouge">buildExpression</code>, <code class="language-plaintext highlighter-rouge">buildDo</code> and <code class="language-plaintext highlighter-rouge">buildFunction</code> have no effect for the moment.</p>
<h2 id="in-practice">In practice</h2>
<p>What we want to build in our case is a list of <em>alert actions</em>. An <code class="language-plaintext highlighter-rouge">Action</code> is composed of a title, a style (<code class="language-plaintext highlighter-rouge">default</code>, <code class="language-plaintext highlighter-rouge">destructive</code> or <code class="language-plaintext highlighter-rouge">cancel</code>) and a function, triggered when the user taps the alert button on screen.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">Action</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">title</span><span class="p">:</span> <span class="kt">String</span>
<span class="k">let</span> <span class="nv">style</span><span class="p">:</span> <span class="kt">UIAlertAction</span><span class="o">.</span><span class="kt">Style</span>
<span class="k">let</span> <span class="nv">action</span><span class="p">:</span> <span class="p">()</span> <span class="o">-></span> <span class="kt">Void</span>
<span class="p">}</span></code></pre></figure>
<p>Once we have an array of <code class="language-plaintext highlighter-rouge">Action</code> we can build an alert controller with this factory method:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">makeAlertController</span><span class="p">(</span><span class="nv">title</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span>
<span class="nv">message</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span>
<span class="nv">style</span><span class="p">:</span> <span class="kt">UIAlertController</span><span class="o">.</span><span class="kt">Style</span><span class="p">,</span>
<span class="nv">actions</span><span class="p">:</span> <span class="p">[</span><span class="kt">Action</span><span class="p">])</span> <span class="o">-></span> <span class="kt">UIAlertController</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">controller</span> <span class="o">=</span> <span class="kt">UIAlertController</span><span class="p">(</span>
<span class="nv">title</span><span class="p">:</span> <span class="n">title</span><span class="p">,</span>
<span class="nv">message</span><span class="p">:</span> <span class="n">message</span><span class="p">,</span>
<span class="nv">preferredStyle</span><span class="p">:</span> <span class="n">style</span>
<span class="p">)</span>
<span class="k">for</span> <span class="n">action</span> <span class="k">in</span> <span class="n">actions</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">uiAction</span> <span class="o">=</span> <span class="kt">UIAlertAction</span><span class="p">(</span><span class="nv">title</span><span class="p">:</span> <span class="n">action</span><span class="o">.</span><span class="n">title</span><span class="p">,</span> <span class="nv">style</span><span class="p">:</span> <span class="n">action</span><span class="o">.</span><span class="n">style</span><span class="p">)</span> <span class="p">{</span> <span class="n">_</span> <span class="k">in</span>
<span class="n">action</span><span class="o">.</span><span class="nf">action</span><span class="p">()</span>
<span class="p">}</span>
<span class="n">controller</span><span class="o">.</span><span class="nf">addAction</span><span class="p">(</span><span class="n">uiAction</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">controller</span>
<span class="p">}</span></code></pre></figure>
<p>So far so good, but we didn’t bring anything new yet.</p>
<p>Let’s see how to retrieve this array of actions and create our function builder. We saw earlier that the only required method is <code class="language-plaintext highlighter-rouge">buildBlock</code>, so we will start here. <code class="language-plaintext highlighter-rouge">buildBlock</code> combines a list of components to a single one.</p>
<p><em>Note</em>: If we think about views and SwiftUI, that makes sense. From multiple subviews (the components for the <code class="language-plaintext highlighter-rouge">@ViewBuilder</code>), <code class="language-plaintext highlighter-rouge">buildBlock</code> will create a superview that contains all the subviews.</p>
<p>In our case, the <code class="language-plaintext highlighter-rouge">Component</code> type is an array of actions, so the <code class="language-plaintext highlighter-rouge">buildBlock</code> method can be written as follows:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">@_functionBuilder</span>
<span class="kd">struct</span> <span class="kt">ActionBuilder</span> <span class="p">{</span>
<span class="kd">typealias</span> <span class="kt">Component</span> <span class="o">=</span> <span class="p">[</span><span class="kt">Action</span><span class="p">]</span>
<span class="kd">static</span> <span class="kd">func</span> <span class="nf">buildBlock</span><span class="p">(</span><span class="n">_</span> <span class="nv">children</span><span class="p">:</span> <span class="kt">Component</span><span class="o">...</span><span class="p">)</span> <span class="o">-></span> <span class="kt">Component</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">children</span><span class="o">.</span><span class="n">flatMap</span> <span class="p">{</span> <span class="nv">$0</span> <span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>Now that we have our <code class="language-plaintext highlighter-rouge">ActionBuilder</code>, let’s use it to retrieve an array of actions and pass it to the <code class="language-plaintext highlighter-rouge">makeAlertController</code> function.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="kt">Alert</span><span class="p">(</span><span class="nv">title</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span>
<span class="nv">message</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span>
<span class="kd">@ActionBuilder</span> <span class="n">_</span> <span class="nv">makeActions</span><span class="p">:</span> <span class="p">()</span> <span class="o">-></span> <span class="p">[</span><span class="kt">Action</span><span class="p">])</span> <span class="o">-></span> <span class="kt">UIAlertController</span> <span class="p">{</span>
<span class="nf">makeAlertController</span><span class="p">(</span>
<span class="nv">title</span><span class="p">:</span> <span class="n">title</span><span class="p">,</span>
<span class="nv">message</span><span class="p">:</span> <span class="n">message</span><span class="p">,</span>
<span class="nv">style</span><span class="p">:</span> <span class="o">.</span><span class="n">alert</span><span class="p">,</span>
<span class="nv">actions</span><span class="p">:</span> <span class="nf">makeActions</span><span class="p">()</span>
<span class="p">)</span>
<span class="p">}</span></code></pre></figure>
<p>Note the use of the <code class="language-plaintext highlighter-rouge">@ActionBuilder</code> attribute, that will allow us to use our new DSL in the <code class="language-plaintext highlighter-rouge">makeActions</code> closure.</p>
<p>That way we can build the following:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">alertController</span> <span class="o">=</span> <span class="kt">Alert</span><span class="p">(</span><span class="nv">title</span><span class="p">:</span> <span class="s">"Deletion"</span><span class="p">,</span> <span class="nv">message</span><span class="p">:</span> <span class="s">"Are you sure?"</span><span class="p">)</span> <span class="p">{</span>
<span class="p">[</span><span class="kt">Action</span><span class="p">(</span><span class="nv">title</span><span class="p">:</span> <span class="s">"Delete"</span><span class="p">,</span> <span class="nv">style</span><span class="p">:</span> <span class="o">.</span><span class="n">destructive</span><span class="p">,</span> <span class="nv">action</span><span class="p">:</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">})]</span>
<span class="p">[</span><span class="kt">Action</span><span class="p">(</span><span class="nv">title</span><span class="p">:</span> <span class="s">"Cancel"</span><span class="p">,</span> <span class="nv">style</span><span class="p">:</span> <span class="o">.</span><span class="n">cancel</span><span class="p">,</span> <span class="nv">action</span><span class="p">:</span> <span class="p">{})]</span>
<span class="p">}</span></code></pre></figure>
<p>But… how is this supposed to be better ?!</p>
<p>I guess that’s not what you expected…</p>
<p>It’s really weird to see a list of arrays of actions. Why not a list of actions? That would make more sense to write.</p>
<p>The explanation here, as we saw earlier, is that our <code class="language-plaintext highlighter-rouge">Component</code> is the type <code class="language-plaintext highlighter-rouge">[Action]</code>. And <code class="language-plaintext highlighter-rouge">buildBlock</code> takes a list of components <code class="language-plaintext highlighter-rouge">Component...</code>, meaning <code class="language-plaintext highlighter-rouge">[[Action]]</code>. That’s why we have to write it like this.</p>
<p>An evolution, in <em>future</em> implementations of function builders, would be to use the <code class="language-plaintext highlighter-rouge">buildExpression</code> method (not yet available):</p>
<blockquote>
<p><code class="language-plaintext highlighter-rouge">buildExpression(_ expression: Expression) -> Component</code> is used to lift the results of expression-statements into the <code class="language-plaintext highlighter-rouge">Component</code> internal currency type. It is only necessary if the DSL wants to either (1) distinguish <code class="language-plaintext highlighter-rouge">Expression</code> types from <code class="language-plaintext highlighter-rouge">Component</code> types or (2) provide contextual type information for statement-expressions.</p>
</blockquote>
<p>If <code class="language-plaintext highlighter-rouge">buildExpression</code> was working, we could write:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">@_functionBuilder</span>
<span class="kd">struct</span> <span class="kt">ActionBuilder</span> <span class="p">{</span>
<span class="kd">typealias</span> <span class="kt">Expression</span> <span class="o">=</span> <span class="kt">Action</span>
<span class="kd">typealias</span> <span class="kt">Component</span> <span class="o">=</span> <span class="p">[</span><span class="kt">Action</span><span class="p">]</span>
<span class="kd">static</span> <span class="kd">func</span> <span class="nf">buildExpression</span><span class="p">(</span><span class="n">_</span> <span class="nv">expression</span><span class="p">:</span> <span class="kt">Expression</span><span class="p">)</span> <span class="o">-></span> <span class="kt">Component</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">[</span><span class="n">expression</span><span class="p">]</span>
<span class="p">}</span>
<span class="kd">static</span> <span class="kd">func</span> <span class="nf">buildBlock</span><span class="p">(</span><span class="n">_</span> <span class="nv">children</span><span class="p">:</span> <span class="kt">Component</span><span class="o">...</span><span class="p">)</span> <span class="o">-></span> <span class="kt">Component</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">children</span><span class="o">.</span><span class="n">flatMap</span> <span class="p">{</span> <span class="nv">$0</span> <span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">alertController</span> <span class="o">=</span> <span class="kt">Alert</span><span class="p">(</span><span class="nv">title</span><span class="p">:</span> <span class="s">"Deletion"</span><span class="p">,</span> <span class="nv">message</span><span class="p">:</span> <span class="s">"Are you sure?"</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">Action</span><span class="p">(</span><span class="nv">title</span><span class="p">:</span> <span class="s">"Delete"</span><span class="p">,</span> <span class="nv">style</span><span class="p">:</span> <span class="o">.</span><span class="n">destructive</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span>
<span class="kt">Action</span><span class="p">(</span><span class="nv">title</span><span class="p">:</span> <span class="s">"Cancel"</span><span class="p">,</span> <span class="nv">style</span><span class="p">:</span> <span class="o">.</span><span class="n">cancel</span><span class="p">)</span> <span class="p">{}</span>
<span class="p">}</span></code></pre></figure>
<p>But for now, we are stuck with our list of <code class="language-plaintext highlighter-rouge">[Action]</code>. That’s not really an issue though, because we can extract this weird behavior into factories:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">extension</span> <span class="kt">Action</span> <span class="p">{</span>
<span class="kd">static</span> <span class="kd">func</span> <span class="p">`</span><span class="nv">default</span><span class="p">`(</span><span class="n">_</span> <span class="nv">title</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span> <span class="nv">action</span><span class="p">:</span> <span class="kd">@escaping</span> <span class="p">()</span> <span class="o">-></span> <span class="kt">Void</span><span class="p">)</span> <span class="o">-></span> <span class="p">[</span><span class="kt">Action</span><span class="p">]</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">[</span><span class="kt">Action</span><span class="p">(</span><span class="nv">title</span><span class="p">:</span> <span class="n">title</span><span class="p">,</span> <span class="nv">style</span><span class="p">:</span> <span class="o">.</span><span class="k">default</span><span class="p">,</span> <span class="nv">action</span><span class="p">:</span> <span class="n">action</span><span class="p">)]</span>
<span class="p">}</span>
<span class="kd">static</span> <span class="kd">func</span> <span class="nf">destructive</span><span class="p">(</span><span class="n">_</span> <span class="nv">title</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span> <span class="nv">action</span><span class="p">:</span> <span class="kd">@escaping</span> <span class="p">()</span> <span class="o">-></span> <span class="kt">Void</span><span class="p">)</span> <span class="o">-></span> <span class="p">[</span><span class="kt">Action</span><span class="p">]</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">[</span><span class="kt">Action</span><span class="p">(</span><span class="nv">title</span><span class="p">:</span> <span class="n">title</span><span class="p">,</span> <span class="nv">style</span><span class="p">:</span> <span class="o">.</span><span class="n">destructive</span><span class="p">,</span> <span class="nv">action</span><span class="p">:</span> <span class="n">action</span><span class="p">)]</span>
<span class="p">}</span>
<span class="kd">static</span> <span class="kd">func</span> <span class="nf">cancel</span><span class="p">(</span><span class="n">_</span> <span class="nv">title</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span> <span class="nv">action</span><span class="p">:</span> <span class="kd">@escaping</span> <span class="p">()</span> <span class="o">-></span> <span class="kt">Void</span> <span class="o">=</span> <span class="p">{})</span> <span class="o">-></span> <span class="p">[</span><span class="kt">Action</span><span class="p">]</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">[</span><span class="kt">Action</span><span class="p">(</span><span class="nv">title</span><span class="p">:</span> <span class="n">title</span><span class="p">,</span> <span class="nv">style</span><span class="p">:</span> <span class="o">.</span><span class="n">cancel</span><span class="p">,</span> <span class="nv">action</span><span class="p">:</span> <span class="n">action</span><span class="p">)]</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>And our code now matches our initial goal !</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">alertController</span> <span class="o">=</span> <span class="kt">Alert</span><span class="p">(</span><span class="nv">title</span><span class="p">:</span> <span class="s">"Deletion"</span><span class="p">,</span> <span class="nv">message</span><span class="p">:</span> <span class="s">"Are you sure?"</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">Action</span><span class="o">.</span><span class="nf">destructive</span><span class="p">(</span><span class="s">"Delete"</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Perform deletion</span>
<span class="p">}</span>
<span class="kt">Action</span><span class="o">.</span><span class="nf">cancel</span><span class="p">(</span><span class="s">"Cancel"</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>
<h2 id="conditions">Conditions</h2>
<p>We can now add multiple actions to the same alert. But that’s not very dynamic yet…</p>
<p>What if we want to add actions conditionally ?
What if we want to add multiple actions at the same time ?</p>
<p>Let’s try to add a condition.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">alertController</span> <span class="o">=</span> <span class="kt">Alert</span><span class="p">(</span><span class="nv">title</span><span class="p">:</span> <span class="s">"Deletion"</span><span class="p">,</span> <span class="nv">message</span><span class="p">:</span> <span class="s">"Are you sure?"</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">Action</span><span class="o">.</span><span class="nf">destructive</span><span class="p">(</span><span class="s">"Delete"</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span>
<span class="k">if</span> <span class="n">canEdit</span> <span class="p">{</span>
<span class="kt">Action</span><span class="o">.</span><span class="nf">default</span><span class="p">(</span><span class="s">"Edit"</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span>
<span class="p">}</span>
<span class="kt">Action</span><span class="o">.</span><span class="nf">cancel</span><span class="p">(</span><span class="s">"Cancel"</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>
<p>We hit a compiler error:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="n">closure</span> <span class="n">containing</span> <span class="n">control</span> <span class="n">flow</span> <span class="n">statement</span> <span class="n">cannot</span> <span class="n">be</span> <span class="n">used</span> <span class="n">with</span> <span class="n">function</span> <span class="n">builder</span></code></pre></figure>
<p>That’s because we only have implemented the <code class="language-plaintext highlighter-rouge">buildBlock</code> method and we need to add <code class="language-plaintext highlighter-rouge">buildIf</code>. The implementation is simple, either we have a list of actions or else we return an empty list.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">@_functionBuilder</span>
<span class="kd">struct</span> <span class="kt">ActionBuilder</span> <span class="p">{</span>
<span class="kd">typealias</span> <span class="kt">Component</span> <span class="o">=</span> <span class="p">[</span><span class="kt">Action</span><span class="p">]</span>
<span class="kd">static</span> <span class="kd">func</span> <span class="nf">buildBlock</span><span class="p">(</span><span class="n">_</span> <span class="nv">children</span><span class="p">:</span> <span class="kt">Component</span><span class="o">...</span><span class="p">)</span> <span class="o">-></span> <span class="kt">Component</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">children</span><span class="o">.</span><span class="n">flatMap</span> <span class="p">{</span> <span class="nv">$0</span> <span class="p">}</span>
<span class="p">}</span>
<span class="kd">static</span> <span class="kd">func</span> <span class="nf">buildIf</span><span class="p">(</span><span class="n">_</span> <span class="nv">component</span><span class="p">:</span> <span class="kt">Component</span><span class="p">?)</span> <span class="o">-></span> <span class="kt">Component</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">component</span> <span class="p">??</span> <span class="p">[]</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>With the <code class="language-plaintext highlighter-rouge">buildIf</code> implemented, everything compiles and runs, the <code class="language-plaintext highlighter-rouge">if</code> statement is taken into account.</p>
<p>But that’s not enough yet, because if we try to have a <code class="language-plaintext highlighter-rouge">else</code> condition in our code, we hit the same compiler error again…</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">alertController</span> <span class="o">=</span> <span class="kt">Alert</span><span class="p">(</span><span class="nv">title</span><span class="p">:</span> <span class="s">"Deletion"</span><span class="p">,</span> <span class="nv">message</span><span class="p">:</span> <span class="s">"Are you sure?"</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">Action</span><span class="o">.</span><span class="nf">destructive</span><span class="p">(</span><span class="s">"Delete"</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span>
<span class="k">if</span> <span class="n">canEdit</span> <span class="p">{</span>
<span class="kt">Action</span><span class="o">.</span><span class="nf">default</span><span class="p">(</span><span class="s">"Edit"</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="kt">Action</span><span class="o">.</span><span class="nf">default</span><span class="p">(</span><span class="s">"Share"</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span>
<span class="p">}</span>
<span class="kt">Action</span><span class="o">.</span><span class="nf">cancel</span><span class="p">(</span><span class="s">"Cancel"</span><span class="p">)</span>
<span class="p">}</span>
<span class="c1">// error: closure containing control flow statement cannot be used with function builder</span></code></pre></figure>
<p>To overcome this, we need to add two more functions: <code class="language-plaintext highlighter-rouge">buildEither(first:)</code> and <code class="language-plaintext highlighter-rouge">buildEither(second:)</code> used when there is a decision tree with optional sub blocks.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">@_functionBuilder</span>
<span class="kd">struct</span> <span class="kt">ActionBuilder</span> <span class="p">{</span>
<span class="kd">typealias</span> <span class="kt">Component</span> <span class="o">=</span> <span class="p">[</span><span class="kt">Action</span><span class="p">]</span>
<span class="kd">static</span> <span class="kd">func</span> <span class="nf">buildBlock</span><span class="p">(</span><span class="n">_</span> <span class="nv">children</span><span class="p">:</span> <span class="kt">Component</span><span class="o">...</span><span class="p">)</span> <span class="o">-></span> <span class="kt">Component</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">children</span><span class="o">.</span><span class="n">flatMap</span> <span class="p">{</span> <span class="nv">$0</span> <span class="p">}</span>
<span class="p">}</span>
<span class="kd">static</span> <span class="kd">func</span> <span class="nf">buildIf</span><span class="p">(</span><span class="n">_</span> <span class="nv">component</span><span class="p">:</span> <span class="kt">Component</span><span class="p">?)</span> <span class="o">-></span> <span class="kt">Component</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">component</span> <span class="p">??</span> <span class="p">[]</span>
<span class="p">}</span>
<span class="kd">static</span> <span class="kd">func</span> <span class="nf">buildEither</span><span class="p">(</span><span class="n">first</span> <span class="nv">component</span><span class="p">:</span> <span class="kt">Component</span><span class="p">)</span> <span class="o">-></span> <span class="kt">Component</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">component</span>
<span class="p">}</span>
<span class="kd">static</span> <span class="kd">func</span> <span class="nf">buildEither</span><span class="p">(</span><span class="n">second</span> <span class="nv">component</span><span class="p">:</span> <span class="kt">Component</span><span class="p">)</span> <span class="o">-></span> <span class="kt">Component</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">component</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>And that makes our code compiling again with <code class="language-plaintext highlighter-rouge">if</code> / <code class="language-plaintext highlighter-rouge">else</code> conditions.</p>
<h2 id="loops">Loops</h2>
<p>At the moment we have no way to loop an array of strings and create actions out of them.</p>
<p>If we try, we always hit the same compiler error.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">alertController</span> <span class="o">=</span> <span class="kt">Alert</span><span class="p">(</span><span class="nv">title</span><span class="p">:</span> <span class="s">"Title"</span><span class="p">,</span> <span class="nv">message</span><span class="p">:</span> <span class="s">"Message"</span><span class="p">)</span> <span class="p">{</span>
<span class="k">for</span> <span class="n">string</span> <span class="k">in</span> <span class="p">[</span><span class="s">"Action1"</span><span class="p">,</span> <span class="s">"Action2"</span><span class="p">]</span> <span class="p">{</span>
<span class="kt">Action</span><span class="o">.</span><span class="nf">default</span><span class="p">(</span><span class="n">string</span><span class="p">)</span> <span class="p">{</span> <span class="nf">print</span><span class="p">(</span><span class="n">string</span><span class="p">)</span> <span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// error: closure containing control flow statement cannot be used with function builder</span></code></pre></figure>
<p>One way to solve this problem is to create an helper function that generates a list of actions for each element of a sequence, and then aggregates all those lists of actions into a single one.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="kt">ForIn</span><span class="o"><</span><span class="kt">S</span><span class="p">:</span> <span class="kt">Sequence</span><span class="o">></span><span class="p">(</span>
<span class="n">_</span> <span class="nv">sequence</span><span class="p">:</span> <span class="kt">S</span><span class="p">,</span>
<span class="kd">@ActionBuilder</span> <span class="nv">makeActions</span><span class="p">:</span> <span class="p">(</span><span class="kt">S</span><span class="o">.</span><span class="kt">Element</span><span class="p">)</span> <span class="o">-></span> <span class="p">[</span><span class="kt">Action</span><span class="p">]</span>
<span class="p">)</span> <span class="o">-></span> <span class="p">[</span><span class="kt">Action</span><span class="p">]</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">sequence</span>
<span class="o">.</span><span class="nf">map</span><span class="p">(</span><span class="n">makeActions</span><span class="p">)</span> <span class="c1">// of type [[Action]]</span>
<span class="o">.</span><span class="n">flatMap</span> <span class="p">{</span> <span class="nv">$0</span> <span class="p">}</span> <span class="c1">// of type [Action]</span>
<span class="p">}</span></code></pre></figure>
<p>And finally, we can use it like so:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">alertController</span> <span class="o">=</span> <span class="kt">Alert</span><span class="p">(</span><span class="nv">title</span><span class="p">:</span> <span class="s">"Title"</span><span class="p">,</span> <span class="nv">message</span><span class="p">:</span> <span class="s">"Message"</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">ForIn</span><span class="p">([</span><span class="s">"Action1"</span><span class="p">,</span> <span class="s">"Action2"</span><span class="p">])</span> <span class="p">{</span> <span class="n">string</span> <span class="k">in</span>
<span class="kt">Action</span><span class="o">.</span><span class="nf">default</span><span class="p">(</span><span class="n">string</span><span class="p">)</span> <span class="p">{</span> <span class="nf">print</span><span class="p">(</span><span class="n">string</span><span class="p">)</span> <span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<h2 id="conclusion">Conclusion</h2>
<p>We saw how we could improve the <code class="language-plaintext highlighter-rouge">UIAlertController</code> API with very little code. The difficulty with function builders is to find <a href="https://forums.swift.org/t/function-builders/25167/357">documentation</a> and to understand the cryptic error messages. The feature is really limited at the moment and maybe it’s for the best, to avoid overly complicated DSLs that would not be understandable. But be patient, swift folks are <a href="https://forums.swift.org/t/function-builders-implementation-progress/32981">working on it</a>.</p>
<p>You can find a gist with all the code <a href="https://gist.github.com/felginep/289985cf8bb92699004099f75a621ea0" target="_blank">here</a>.</p>
<p>If you are interested in the subject, here is a list of resources you can use to create your own function builders:</p>
<ul>
<li><a href="https://github.com/apple/swift-evolution/blob/9992cf3c11c2d5e0ea20bee98657d93902d5b174/proposals/XXXX-function-builders.md">Initial proposal</a></li>
<li><a href="https://forums.swift.org/t/function-builders/25167">Function Builder thread</a></li>
<li><a href="https://github.com/carson-katri/awesome-function-builders">Awesome function builders</a></li>
<li><a href="https://blog.vihan.org/swift-function-builders/">Introduction to function builders</a></li>
</ul>
<p>Note: This article was also published on Fabernovel <a href="https://www.fabernovel.com/en/engineering/uialertcontroller-with-function-builders">blog</a>.</p>Pierre FelginesI always found the UIAlertController API too verbose. You first have to create an instance of UIAlertController, then create multiple instances of UIAlertAction and finally add the actions to the controller.Github Actions2020-02-24T00:00:00+00:002020-02-24T00:00:00+00:00https://felginep.github.io/2020-02-24/github-actions<p>I recently wrote a public pod to share some common classes in my team. Since <a href="https://github.com/features/actions" target="_blank">Github Actions</a> came out I never had the chance to test them. So that was the perfect time to give it a shot.</p>
<p>I use them to validate that my library is compiling, that all the tests pass, that the codebase respects our coding style and that the podspec is valid.</p>
<p>Let’s see what are the simple steps you need to do.</p>
<h2 id="creating-the-pod">Creating the pod</h2>
<p>To create a new pod you just have to run <code class="language-plaintext highlighter-rouge">pod lib create MyAwesomePod</code>. This will bootstrap a new project, with directories and placeholder classes.</p>
<p>Once it’s done, the first thing to do is to create a <code class="language-plaintext highlighter-rouge">Gemfile</code> in the <code class="language-plaintext highlighter-rouge">Example</code> folder to lock the versions of your ruby dependencies.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">source</span> <span class="s1">'https://rubygems.org'</span>
<span class="n">gem</span> <span class="s1">'fastlane'</span><span class="p">,</span> <span class="s1">'<3.0'</span>
<span class="n">gem</span> <span class="s1">'cocoapods'</span><span class="p">,</span> <span class="s1">'1.8.4'</span>
<span class="n">gem</span> <span class="s1">'CFPropertyList'</span><span class="p">,</span> <span class="s1">'3.0.0'</span></code></pre></figure>
<p>You also want to create a <code class="language-plaintext highlighter-rouge">Fastfile</code> in the directory <code class="language-plaintext highlighter-rouge">Example/fastlane</code> and add a lane to compile and run the unit tests.</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">desc</span> <span class="s2">"Run all unit tests"</span>
<span class="n">lane</span> <span class="ss">:tests</span> <span class="k">do</span>
<span class="nb">scan</span><span class="p">(</span>
<span class="ss">workspace: </span><span class="s2">"MyAwesomePod.xcworkspace"</span><span class="p">,</span>
<span class="ss">configuration: </span><span class="s2">"Debug"</span><span class="p">,</span>
<span class="ss">scheme: </span><span class="s2">"MyAwesomePod-Example"</span><span class="p">,</span>
<span class="ss">clean: </span><span class="kp">true</span><span class="p">,</span>
<span class="ss">devices: </span><span class="p">[</span><span class="s2">"iPhone 8"</span><span class="p">]</span>
<span class="p">)</span>
<span class="k">end</span></code></pre></figure>
<p>Now, you can run your unit tests with a simple command: <code class="language-plaintext highlighter-rouge">bundle exec fastlane tests</code>.</p>
<h2 id="linting-source-files">Linting source files</h2>
<p>If you plan to allow other people to maintain your codebase, you may want to enforce some coding style rules. This can be easily done with <a href="https://github.com/realm/SwiftLint" target="_blank">Swiftlint</a>.</p>
<p>First add the dependency to the <code class="language-plaintext highlighter-rouge">Example/Podfile</code> like so:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">use_frameworks!</span>
<span class="n">target</span> <span class="s1">'MyAwesomePod_Example'</span> <span class="k">do</span>
<span class="n">pod</span> <span class="s1">'MyAwesomePod'</span><span class="p">,</span> <span class="ss">:path</span> <span class="o">=></span> <span class="s1">'../'</span>
<span class="n">pod</span> <span class="s1">'SwiftLint'</span>
<span class="n">target</span> <span class="s1">'MyAwesomePod_Tests'</span> <span class="k">do</span>
<span class="n">inherit!</span> <span class="ss">:search_paths</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>Then create a <code class="language-plaintext highlighter-rouge">.swiftlint.yml</code> file in the <code class="language-plaintext highlighter-rouge">Example</code> folder to list all the rules you want to apply. The source files directories are</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">MyAwesomePod</code> folder for the pod source files</li>
<li><code class="language-plaintext highlighter-rouge">Example/MyAwesomePod</code> folder for the example source files</li>
</ul>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="na">disabled_rules</span><span class="pi">:</span>
<span class="s">...</span>
<span class="na">opt_in_rules</span><span class="pi">:</span>
<span class="s">...</span>
<span class="na">excluded</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">Carthage</span>
<span class="pi">-</span> <span class="s">Pods</span>
<span class="na">included</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">../MyAwesomePod</span>
<span class="pi">-</span> <span class="s">MyAwesomePod</span></code></pre></figure>
<p>Finally, add a fastlane action to your <code class="language-plaintext highlighter-rouge">Fastfile</code> to run <code class="language-plaintext highlighter-rouge">swiftlint</code>:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">desc</span> <span class="s2">"Linting"</span>
<span class="n">lane</span> <span class="ss">:lint</span> <span class="k">do</span>
<span class="n">swiftlint</span><span class="p">(</span>
<span class="ss">mode: :lint</span><span class="p">,</span>
<span class="ss">config_file: </span><span class="s2">".swiftlint.yml"</span><span class="p">,</span>
<span class="ss">strict: </span><span class="kp">true</span><span class="p">,</span>
<span class="ss">executable: </span><span class="s2">"Pods/SwiftLint/swiftlint"</span>
<span class="p">)</span>
<span class="k">end</span></code></pre></figure>
<h2 id="github-actions">Github Actions</h2>
<p>Now that we laid the groundwork, we can add the Github workflow.</p>
<p>Create a <code class="language-plaintext highlighter-rouge">.github/workflows/main.yml</code> file with this content:</p>
<figure class="highlight"><pre><code class="language-yaml" data-lang="yaml"><span class="na">name</span><span class="pi">:</span> <span class="s">CI</span>
<span class="na">on</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">push</span><span class="pi">]</span>
<span class="na">jobs</span><span class="pi">:</span>
<span class="na">build</span><span class="pi">:</span>
<span class="na">runs-on</span><span class="pi">:</span> <span class="s">macOS-latest</span>
<span class="na">steps</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v2</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Bundle install</span>
<span class="na">working-directory</span><span class="pi">:</span> <span class="s">./Example</span>
<span class="na">run</span><span class="pi">:</span> <span class="s">bundle install</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Pod install</span>
<span class="na">working-directory</span><span class="pi">:</span> <span class="s">./Example</span>
<span class="na">run</span><span class="pi">:</span> <span class="s">bundle exec pod install</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Build and test</span>
<span class="na">working-directory</span><span class="pi">:</span> <span class="s">./Example</span>
<span class="na">run</span><span class="pi">:</span> <span class="s">bundle exec fastlane tests</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Swiftlint</span>
<span class="na">working-directory</span><span class="pi">:</span> <span class="s">./Example</span>
<span class="na">run</span><span class="pi">:</span> <span class="s">bundle exec fastlane lint</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Pod lib lint</span>
<span class="na">run</span><span class="pi">:</span> <span class="s">pod lib lint</span></code></pre></figure>
<p>Each time you push on the repository, the workflow will:</p>
<ul>
<li>install ruby dependencies</li>
<li>install pod dependencies</li>
<li>compile the project and run the tests</li>
<li>check the coding style</li>
<li>validate the pod with <code class="language-plaintext highlighter-rouge">pod lib lint</code></li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>We saw how easy it was to set up Github Actions with fastlane. With this simple workflow you can be confident that all those checks will run every time you or other contributors push to the repository.</p>
<p>If you are interested in all the actions available, you can find them <a href="https://github.com/marketplace?type=actions">here</a>.</p>Pierre FelginesI recently wrote a public pod to share some common classes in my team. Since Github Actions came out I never had the chance to test them. So that was the perfect time to give it a shot.Property based testing2019-03-20T00:00:00+00:002019-03-20T00:00:00+00:00https://felginep.github.io/2019-03-20/property-based-testing<p>You will always hear that writing tests is a good thing. But are you 100% sure about the pertinence of your test suite? What if you test only edges cases and miss something?
Property based testing let you generate your tests instead of writing them.
Let’s see what this is, how to use it, and how we can leverage it to write robust UI tests.</p>
<h2 id="basic-test">Basic test</h2>
<p>Let’s say for the sake of the demonstration that we want to implement a sort algorithm in our application.</p>
<p>Our implementation looks like this:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">extension</span> <span class="kt">Array</span> <span class="k">where</span> <span class="kt">Element</span><span class="p">:</span> <span class="kt">Comparable</span> <span class="p">{</span>
<span class="kd">func</span> <span class="nf">quickSort</span><span class="p">()</span> <span class="o">-></span> <span class="p">[</span><span class="kt">Element</span><span class="p">]</span> <span class="p">{</span>
<span class="k">return</span> <span class="nf">quickSort</span><span class="p">(</span><span class="k">self</span><span class="p">)</span>
<span class="p">}</span>
<span class="kd">private</span> <span class="kd">func</span> <span class="nf">quickSort</span><span class="p">(</span><span class="n">_</span> <span class="nv">array</span><span class="p">:</span> <span class="p">[</span><span class="kt">Element</span><span class="p">])</span> <span class="o">-></span> <span class="p">[</span><span class="kt">Element</span><span class="p">]</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">array</span><span class="o">.</span><span class="n">count</span> <span class="o"><</span> <span class="mi">3</span> <span class="p">{</span> <span class="k">return</span> <span class="n">array</span> <span class="p">}</span>
<span class="k">var</span> <span class="nv">data</span> <span class="o">=</span> <span class="n">array</span>
<span class="k">let</span> <span class="nv">pivot</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="nf">remove</span><span class="p">(</span><span class="nv">at</span><span class="p">:</span> <span class="mi">0</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">left</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">filter</span> <span class="p">{</span> <span class="nv">$0</span> <span class="o"><</span> <span class="n">pivot</span> <span class="p">}</span>
<span class="k">let</span> <span class="nv">right</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">filter</span> <span class="p">{</span> <span class="nv">$0</span> <span class="o">>=</span> <span class="n">pivot</span> <span class="p">}</span>
<span class="k">return</span> <span class="nf">quickSort</span><span class="p">(</span><span class="n">left</span><span class="p">)</span> <span class="o">+</span> <span class="p">[</span><span class="n">pivot</span><span class="p">]</span> <span class="o">+</span> <span class="nf">quickSort</span><span class="p">(</span><span class="n">right</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>If we want to test our code, we could write some example based unit tests like so:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kt">XCTAssertEqual</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span><span class="o">.</span><span class="nf">quickSort</span><span class="p">(),</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">])</span>
<span class="kt">XCTAssertEqual</span><span class="p">([</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="nf">quickSort</span><span class="p">(),</span> <span class="p">[</span><span class="mi">1</span><span class="p">])</span>
<span class="kt">XCTAssertEqual</span><span class="p">([]</span><span class="o">.</span><span class="nf">quickSort</span><span class="p">(),</span> <span class="p">[])</span></code></pre></figure>
<p>Attentive readers may have spotted that our code is buggy, but our tests pass anyway. How could we test it better to find the bug?</p>
<h2 id="property-based-testing">Property based testing</h2>
<p>We wrote some tests, that’s a very good thing, but our test suite is really minimal. In a perfect world, we would like to test the <code class="language-plaintext highlighter-rouge">quickSort()</code> function for every array of integers.</p>
<p>What we call a <em>property based test</em> is a test that assert a certain property holds for every input we use (meaning every array of integers in our case).</p>
<p>Conceptually, here is a property based test:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// repeat 100 times</span>
<span class="c1">// generate random input</span>
<span class="k">for</span> <span class="nf">all</span> <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="o">...</span><span class="p">)</span>
<span class="c1">// keep generating inputs while this is false</span>
<span class="n">such</span> <span class="n">that</span> <span class="nf">precondition</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="o">...</span><span class="p">)</span> <span class="n">holds</span>
<span class="c1">// test the property</span>
<span class="nf">property</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="o">...</span><span class="p">)</span> <span class="k">is</span> <span class="kc">true</span></code></pre></figure>
<p>In our example, the property is rather simple: if we use <code class="language-plaintext highlighter-rouge">quickSort()</code> on an array, the array should be sorted (meaning every element is lower or equal than the next element).</p>
<p>In pseudo code, here is our property based test.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="n">forAll</span> <span class="n">array</span> <span class="n">of</span> <span class="nv">integers</span><span class="p">:</span>
<span class="n">array</span><span class="o">.</span><span class="nf">quickSort</span><span class="p">()</span><span class="o">.</span><span class="nf">isSorted</span><span class="p">()</span>
<span class="c1">// the `isSorted` function compares pair of elements</span></code></pre></figure>
<p>In practice, even if this is not possible to test <em>every</em> array of integer in the world, we can write tests that run this property for <em>a lot</em> of arrays. And this will be more powerful that the first example based tests we wrote earlier.</p>
<p>The most known library to do property based testing is called <a href="http://hackage.haskell.org/package/QuickCheck">QuickCheck</a> in Haskell. In Swift, we can use <a href="https://github.com/typelift/SwiftCheck">SwiftCheck</a> instead.</p>
<p>Here is the protocol at the core of the library:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">public</span> <span class="kd">protocol</span> <span class="kt">Arbitrary</span> <span class="p">{</span>
<span class="c1">// a random generator of Self</span>
<span class="kd">static</span> <span class="k">var</span> <span class="nv">arbitrary</span><span class="p">:</span> <span class="kt">Gen</span><span class="o"><</span><span class="k">Self</span><span class="o">></span> <span class="p">{</span> <span class="k">get</span> <span class="p">}</span>
<span class="c1">// a function that returns smaller instances of Self than the current one</span>
<span class="kd">static</span> <span class="kd">func</span> <span class="nf">shrink</span><span class="p">(</span><span class="nv">_</span> <span class="p">:</span> <span class="k">Self</span><span class="p">)</span> <span class="o">-></span> <span class="p">[</span><span class="k">Self</span><span class="p">]</span>
<span class="p">}</span></code></pre></figure>
<p>In essence, this protocol is composed of two components:</p>
<ul>
<li>a random value generators (primitive types already conform to <code class="language-plaintext highlighter-rouge">Arbitrary</code>)</li>
<li>a way to shrink a value from an instance</li>
</ul>
<p>I encourage you to go read the tutorial <a href="https://github.com/typelift/SwiftCheck/tree/master/Tutorial.playground">playground</a> if you want to well understand the concepts.</p>
<p>The random value generator is useful to generate a lot of random inputs, but what do we call <em>shrinking</em>?</p>
<p>When SwiftCheck finds a test that fails for a random input, it will not simply return the input, but will iterate on some <em>shrunk</em> values of this input to find the minimal input that make the test fail. By definition, shrunk values have a size less or equal than the initial value.</p>
<p>For instance, if we ask the shrunk values for the <code class="language-plaintext highlighter-rouge">"test"</code> string, we will get this result:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="nf">print</span><span class="p">(</span><span class="kt">String</span><span class="o">.</span><span class="nf">shrink</span><span class="p">(</span><span class="s">"test"</span><span class="p">))</span>
<span class="p">[</span><span class="s">"t"</span><span class="p">,</span> <span class="s">"st"</span><span class="p">,</span> <span class="s">"te"</span><span class="p">,</span> <span class="s">"est"</span><span class="p">,</span> <span class="s">"tst"</span><span class="p">,</span> <span class="s">"tet"</span><span class="p">,</span> <span class="s">"tes"</span><span class="p">,</span> <span class="s">" est"</span><span class="p">,</span> <span class="s">"2est"</span><span class="p">,</span> <span class="s">"best"</span><span class="p">,</span>
<span class="s">"cest"</span><span class="p">,</span> <span class="s">"Cest"</span><span class="p">,</span> <span class="s">"aest"</span><span class="p">,</span> <span class="s">"1est"</span><span class="p">,</span> <span class="s">"</span><span class="se">\n</span><span class="s">est"</span><span class="p">,</span> <span class="s">"Aest"</span><span class="p">,</span> <span class="s">"Best"</span><span class="p">,</span> <span class="s">"3est"</span><span class="p">,</span>
<span class="s">"t st"</span><span class="p">,</span> <span class="s">"t2st"</span><span class="p">,</span> <span class="s">"tbst"</span><span class="p">,</span> <span class="s">"tcst"</span><span class="p">,</span> <span class="s">"tCst"</span><span class="p">,</span> <span class="s">"tast"</span><span class="p">,</span> <span class="s">"t1st"</span><span class="p">,</span> <span class="s">"t</span><span class="se">\n</span><span class="s">st"</span><span class="p">,</span>
<span class="s">"tBst"</span><span class="p">,</span> <span class="s">"tAst"</span><span class="p">,</span> <span class="s">"t3st"</span><span class="p">,</span> <span class="s">"te2t"</span><span class="p">,</span> <span class="s">"tebt"</span><span class="p">,</span> <span class="s">"tect"</span><span class="p">,</span> <span class="s">"teCt"</span><span class="p">,</span> <span class="s">"teat"</span><span class="p">,</span>
<span class="s">"te1t"</span><span class="p">,</span> <span class="s">"te</span><span class="se">\n</span><span class="s">t"</span><span class="p">,</span> <span class="s">"teBt"</span><span class="p">,</span> <span class="s">"teAt"</span><span class="p">,</span> <span class="s">"te3t"</span><span class="p">,</span> <span class="s">"te t"</span><span class="p">,</span> <span class="s">"tes "</span><span class="p">,</span> <span class="s">"tes2"</span><span class="p">,</span>
<span class="s">"tesb"</span><span class="p">,</span> <span class="s">"tesc"</span><span class="p">,</span> <span class="s">"tesC"</span><span class="p">,</span> <span class="s">"tesa"</span><span class="p">,</span> <span class="s">"tes1"</span><span class="p">,</span> <span class="s">"tes</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="s">"tesA"</span><span class="p">,</span> <span class="s">"tesB"</span><span class="p">,</span> <span class="s">"tes3"</span><span class="p">]</span></code></pre></figure>
<h2 id="back-to-the-example">Back to the example</h2>
<p>Now that we understand how to implement a property based test, let’s do it for the <code class="language-plaintext highlighter-rouge">quickSort()</code> function we defined earlier.</p>
<p>Following the SwiftCheck syntax, here is the test:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">import</span> <span class="kt">SwiftCheck</span>
<span class="nf">property</span><span class="p">(</span><span class="s">"Sort integers"</span><span class="p">)</span> <span class="o"><-</span> <span class="n">forAll</span> <span class="p">{</span> <span class="p">(</span><span class="nv">integers</span><span class="p">:</span> <span class="p">[</span><span class="kt">Int</span><span class="p">])</span> <span class="k">in</span>
<span class="k">return</span> <span class="n">integers</span><span class="o">.</span><span class="nf">quickSort</span><span class="p">()</span><span class="o">.</span><span class="n">isSorted</span>
<span class="p">}</span></code></pre></figure>
<p>When we run this code, we get an error right away !</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="p">[]</span>
<span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">]</span>
<span class="p">[]</span>
<span class="p">[</span><span class="o">-</span><span class="mi">3</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">]</span>
<span class="p">[]</span>
<span class="p">[</span><span class="o">-</span><span class="mi">5</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">4</span><span class="p">]</span>
<span class="o">***</span> <span class="kt">Failed</span><span class="o">!</span> <span class="p">[</span><span class="mi">4</span><span class="p">]</span>
<span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">4</span><span class="p">]</span>
<span class="p">[</span><span class="o">-</span><span class="mi">5</span><span class="p">,</span> <span class="mi">1</span><span class="p">]</span>
<span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">4</span><span class="p">]</span>
<span class="p">[</span><span class="o">-</span><span class="mi">5</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">4</span><span class="p">]</span>
<span class="p">[</span><span class="o">-</span><span class="mi">5</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">4</span><span class="p">]</span>
<span class="p">[</span><span class="o">-</span><span class="mi">5</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">5</span><span class="p">]</span>
<span class="c1">// ... Test ~600 more values to find minimal error</span>
<span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">]</span>
<span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">]</span>
<span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">]</span>
<span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">]</span>
<span class="p">[]</span>
<span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">]</span>
<span class="kt">Proposition</span><span class="p">:</span> <span class="kt">Test</span>
<span class="kt">Falsifiable</span> <span class="p">(</span><span class="n">after</span> <span class="mi">7</span> <span class="n">tests</span> <span class="n">and</span> <span class="mi">11</span> <span class="n">shrinks</span><span class="p">):</span>
<span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">]</span>
<span class="o">***</span> <span class="kt">Passed</span> <span class="mi">6</span> <span class="n">tests</span>
<span class="o">.</span></code></pre></figure>
<p>Our tests were not that strong because we already have found a failing input… In our case this is the array <code class="language-plaintext highlighter-rouge">[1, 0]</code>.</p>
<p>Note that the test did fail first with the input <code class="language-plaintext highlighter-rouge">[-5, 1, 5, 4]</code>. Then the library did shrink the value <code class="language-plaintext highlighter-rouge">[-5, 1, 5, 4]</code> to find the minimal input that makes our property fail. It generated more than 600 different values (7 tests and 11 shrinks) to find that <code class="language-plaintext highlighter-rouge">[1, 0]</code> is the minimal input that makes the test fail.</p>
<p>We can easily spot the bug with the failing input.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">private</span> <span class="kd">func</span> <span class="nf">quickSort</span><span class="p">(</span><span class="n">_</span> <span class="nv">array</span><span class="p">:</span> <span class="p">[</span><span class="kt">Element</span><span class="p">])</span> <span class="o">-></span> <span class="p">[</span><span class="kt">Element</span><span class="p">]</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">array</span><span class="o">.</span><span class="n">count</span> <span class="o"><</span> <span class="mi">2</span> <span class="p">{</span> <span class="k">return</span> <span class="n">array</span> <span class="p">}</span> <span class="c1">// replace 3 with 2</span>
<span class="o">...</span>
<span class="p">}</span></code></pre></figure>
<p>This time the result is correct, all the 100 tests pass.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="o">***</span> <span class="kt">Passed</span> <span class="mi">100</span> <span class="n">tests</span>
<span class="o">.</span></code></pre></figure>
<h2 id="when-to-use-property-based-testing">When to use property based testing?</h2>
<p>The advantages of property based testing are:</p>
<ul>
<li>they are more general and can replace many example based tests</li>
<li>they test all the edge cases (null, negative numbers, empty arrays, uncommon strings)</li>
<li>they are reproducible (once a test fail, we know for which input, and we can rerun the test)</li>
<li>they shrink the input in case of a failure</li>
</ul>
<p>But that does not mean you have to remove all your example based tests and replace them with property based tests. In practice it can be really difficult to write property based tests, and example based tests are important because they are simple. Meaning that someone else can quickly understand their utility.</p>
<p>There are some special cases where property based testing shines. For instance, when you have two symmetrical functions (for instance an <code class="language-plaintext highlighter-rouge">Encoder</code> and a <code class="language-plaintext highlighter-rouge">Decoder</code>): for any input, the result of passing the input in the first function, then in the reverse function should be equal to the initial input.
This idea is well covered in the <a href="https://www.youtube.com/watch?v=ME9aYZ9qGHQ">talk</a> of Jack Flintermann, creator of the <a href="https://github.com/jflinter/Dwifft">Dwiff</a> library that explains that property based testing helped him catch some weird bugs during refactoring.</p>
<p>For example, let’s say we want have a function that sub divide an array into chunks of a size <code class="language-plaintext highlighter-rouge">n</code>:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">extension</span> <span class="kt">Array</span> <span class="p">{</span>
<span class="kd">func</span> <span class="nf">chunked</span><span class="p">(</span><span class="n">into</span> <span class="nv">size</span><span class="p">:</span> <span class="kt">Int</span><span class="p">)</span> <span class="o">-></span> <span class="p">[[</span><span class="kt">Element</span><span class="p">]]</span> <span class="p">{</span>
<span class="k">guard</span> <span class="n">size</span> <span class="o">></span> <span class="mi">0</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">[]</span> <span class="p">}</span>
<span class="k">return</span> <span class="nf">stride</span><span class="p">(</span><span class="nv">from</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="nv">to</span><span class="p">:</span> <span class="n">count</span><span class="p">,</span> <span class="nv">by</span><span class="p">:</span> <span class="n">size</span><span class="p">)</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span>
<span class="kt">Array</span><span class="p">(</span><span class="k">self</span><span class="p">[</span><span class="nv">$0</span> <span class="o">..<</span> <span class="kt">Swift</span><span class="o">.</span><span class="nf">min</span><span class="p">(</span><span class="nv">$0</span> <span class="o">+</span> <span class="n">size</span><span class="p">,</span> <span class="n">count</span><span class="p">)])</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>The reverse function is easy to find, we just need to reassemble all the arrays to get the input back.</p>
<p>The test associated with this function could be:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// A generator for strictly positive integers</span>
<span class="k">let</span> <span class="nv">sizeGen</span> <span class="o">=</span> <span class="kt">Int</span><span class="o">.</span><span class="n">arbitrary</span><span class="o">.</span><span class="n">suchThat</span> <span class="p">{</span> <span class="nv">$0</span> <span class="o">></span> <span class="mi">0</span> <span class="p">}</span>
<span class="c1">// The default generator for array of integers</span>
<span class="k">let</span> <span class="nv">arrayGen</span> <span class="o">=</span> <span class="kt">Array</span><span class="o"><</span><span class="kt">Int</span><span class="o">>.</span><span class="n">arbitrary</span>
<span class="nf">property</span><span class="p">(</span><span class="s">"Test Chunk"</span><span class="p">)</span> <span class="o"><-</span> <span class="nf">forAll</span><span class="p">(</span><span class="n">arrayGen</span><span class="p">,</span> <span class="n">sizeGen</span><span class="p">)</span> <span class="p">{</span> <span class="p">(</span><span class="nv">integers</span><span class="p">:</span> <span class="p">[</span><span class="kt">Int</span><span class="p">],</span> <span class="nv">size</span><span class="p">:</span> <span class="kt">Int</span><span class="p">)</span> <span class="k">in</span>
<span class="k">return</span> <span class="n">integers</span><span class="o">.</span><span class="nf">chunked</span><span class="p">(</span><span class="nv">into</span><span class="p">:</span> <span class="n">size</span><span class="p">)</span><span class="o">.</span><span class="nf">reduce</span><span class="p">([],</span> <span class="o">+</span><span class="p">)</span> <span class="o">==</span> <span class="n">integers</span>
<span class="p">}</span></code></pre></figure>
<h2 id="what-about-ui-tests">What about UI tests?</h2>
<p>As we mainly write code that is related to UI, how could we leverage property based testing to test our layouts? For instance, it’s very common to forget activating an <code class="language-plaintext highlighter-rouge">NSLayoutConstraint</code>, or to provide the wrong <code class="language-plaintext highlighter-rouge">constant</code> value, and this results in layout issues. And sometimes these issues do not appear during the development phase, but later, in production with real data.</p>
<p>To use property based testing, the idea is similar to our previous example. We need to generate random inputs and find a property that holds for our layout for every input.</p>
<p>The property is easy to find: a view is just a bunch of subviews, and we want to assert that our layout is correct when there are no views overlap, no autolayout errors and no clipped subviews.</p>
<p>Now, how to generate our inputs?</p>
<p>I always create a struct called a <code class="language-plaintext highlighter-rouge">ViewModel</code> to configure my views. This is just a dumb data structure that holds all the properties that will be displayed in the view (booleans to hide / show subviews, strings to display in labels, etc.).</p>
<p>So all I need is to generate random view models, pass them to my view, and then assert that the layout is correct.</p>
<p>Here is an example of how to generate a random view model:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// A view model that could configure a profile page</span>
<span class="kd">struct</span> <span class="kt">ViewModel</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">userName</span><span class="p">:</span> <span class="kt">String</span>
<span class="k">let</span> <span class="nv">messagesCount</span><span class="p">:</span> <span class="kt">Int</span>
<span class="k">let</span> <span class="nv">displayFullProfile</span><span class="p">:</span> <span class="kt">Bool</span>
<span class="p">}</span>
<span class="kd">extension</span> <span class="kt">ViewModel</span><span class="p">:</span> <span class="kt">Arbitrary</span> <span class="p">{</span>
<span class="c1">// This is a ViewModel random generator</span>
<span class="kd">static</span> <span class="k">var</span> <span class="nv">arbitrary</span><span class="p">:</span> <span class="kt">Gen</span><span class="o"><</span><span class="kt">ViewModel</span><span class="o">></span> <span class="p">{</span>
<span class="c1">// We create a lower string random generator</span>
<span class="k">let</span> <span class="nv">lowerString</span> <span class="o">=</span> <span class="kt">Gen</span><span class="o"><</span><span class="kt">Character</span><span class="o">></span>
<span class="c1">// use a random value between "a" and "z"</span>
<span class="o">.</span><span class="nf">fromElements</span><span class="p">(</span><span class="nv">in</span><span class="p">:</span> <span class="s">"a"</span><span class="o">...</span><span class="s">"z"</span><span class="p">)</span>
<span class="c1">// create an array of random length of these characters</span>
<span class="o">.</span><span class="n">proliferateNonEmpty</span>
<span class="c1">// create a String from this array of characters</span>
<span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="kt">String</span><span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">$0</span><span class="p">)</span> <span class="p">}</span><span class="o">.</span>
<span class="k">return</span> <span class="kt">Gen</span><span class="o"><</span><span class="kt">ViewModel</span><span class="o">>.</span><span class="n">compose</span> <span class="p">{</span> <span class="n">c</span> <span class="k">in</span>
<span class="kt">ViewModel</span><span class="p">(</span>
<span class="c1">// use our lower string random generator</span>
<span class="nv">userName</span><span class="p">:</span> <span class="n">c</span><span class="o">.</span><span class="nf">generate</span><span class="p">(</span><span class="nv">using</span><span class="p">:</span> <span class="n">lowerString</span><span class="p">),</span>
<span class="c1">// use a positive integer random generator</span>
<span class="nv">messagesCount</span><span class="p">:</span> <span class="n">c</span><span class="o">.</span><span class="nf">generate</span><span class="p">(</span><span class="nv">using</span><span class="p">:</span> <span class="kt">Int</span><span class="o">.</span><span class="n">arbitrary</span><span class="o">.</span><span class="n">suchThat</span> <span class="p">{</span> <span class="nv">$0</span> <span class="o">></span> <span class="mi">0</span> <span class="p">}),</span>
<span class="c1">// use the default boolean random generator</span>
<span class="nv">displayFullProfile</span><span class="p">:</span> <span class="n">c</span><span class="o">.</span><span class="nf">generate</span><span class="p">()</span>
<span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>We now have a random <code class="language-plaintext highlighter-rouge">ViewModel</code> generator.</p>
<p>If you want to print a random instance of a <code class="language-plaintext highlighter-rouge">ViewModel</code>, you can do:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="nf">print</span><span class="p">(</span><span class="kt">ViewModel</span><span class="o">.</span><span class="n">arbitrary</span><span class="o">.</span><span class="n">generate</span><span class="p">)</span>
<span class="c1">// Example of result (will change every time)</span>
<span class="kt">ViewModel</span><span class="p">(</span><span class="nv">userName</span><span class="p">:</span> <span class="s">"ybqe"</span><span class="p">,</span> <span class="nv">messagesCount</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span> <span class="nv">displayFullProfile</span><span class="p">:</span> <span class="kc">false</span><span class="p">)</span></code></pre></figure>
<p>Now testing our layout is straightforward:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="nf">property</span><span class="p">(</span><span class="s">"Layout"</span><span class="p">)</span> <span class="o"><-</span> <span class="n">forAll</span> <span class="p">{</span> <span class="p">(</span><span class="nv">viewModel</span><span class="p">:</span> <span class="kt">ViewModel</span><span class="p">)</span> <span class="k">in</span>
<span class="k">let</span> <span class="nv">view</span> <span class="o">=</span> <span class="kt">MyView</span><span class="p">()</span>
<span class="n">view</span><span class="o">.</span><span class="nf">configure</span><span class="p">(</span><span class="nv">with</span><span class="p">:</span> <span class="n">viewModel</span><span class="p">)</span>
<span class="n">view</span><span class="o">.</span><span class="nf">setNeedsLayout</span><span class="p">()</span>
<span class="n">view</span><span class="o">.</span><span class="nf">layoutIfNeeded</span><span class="p">()</span>
<span class="k">return</span> <span class="n">view</span><span class="o">.</span><span class="n">hasNoAutoLayoutIssues</span> <span class="o">&&</span> <span class="n">view</span><span class="o">.</span><span class="n">hasNoFrameOverlap</span>
<span class="p">}</span></code></pre></figure>
<p>The implementation of the methods <code class="language-plaintext highlighter-rouge">hasNoAutoLayoutIssues</code> and <code class="language-plaintext highlighter-rouge">hasNoFrameOverlap</code> is left as an exercise to the reader, but some time ago, LinkedIn created a <a href="https://github.com/linkedin/LayoutTest-iOS">library</a> heavily inspired by this approach (even so they do not provide real random values). You can find the implementations on the repository.</p>
<h2 id="conclusion">Conclusion</h2>
<p>We have seen how powerful property based testing is. Instead of writing one or two example based tests per feature, it allows you to generate thousands of random tests very easily.</p>
<p>The drawback is that this is not always straightforward to find a property that holds for every test you want to write. So even if you can’t use this technique right now, keep it in mind the next time you need it.</p>Pierre FelginesYou will always hear that writing tests is a good thing. But are you 100% sure about the pertinence of your test suite? What if you test only edges cases and miss something? Property based testing let you generate your tests instead of writing them. Let’s see what this is, how to use it, and how we can leverage it to write robust UI tests.UIView styling with functions2019-02-19T00:00:00+00:002019-02-19T00:00:00+00:00https://felginep.github.io/2019-02-19/uiview-styling-with-functions<p>Today I want to talk about <code class="language-plaintext highlighter-rouge">UIView</code> styling. The common approach when we want to customize the display of native UI controls (for instance <code class="language-plaintext highlighter-rouge">UIButton</code> or <code class="language-plaintext highlighter-rouge">UILabel</code>) is to create a subclass that overrides a bunch of properties. That works well most of the time, but some problems may arise.</p>
<h2 id="the-filled-and-rounded-button">The filled and rounded button</h2>
<p>We you use subclasses in Swift to style your views, you loose composition. For example, if you create two <code class="language-plaintext highlighter-rouge">UIButton</code> subclasses, <code class="language-plaintext highlighter-rouge">FilledButton</code> and <code class="language-plaintext highlighter-rouge">RoundedButton</code>, how do you create a button that is both filled and rounded ?</p>
<p>One solution of this problem is to stop using subclasses, but to leverage Swift functions and type system.</p>
<p>We can think about a view style, as a function that consumes a view, and set some properties on it.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">filledButtonStyle</span><span class="p">:</span> <span class="p">(</span><span class="kt">UIButton</span><span class="p">)</span> <span class="o">-></span> <span class="kt">Void</span> <span class="o">=</span> <span class="p">{</span>
<span class="nv">$0</span><span class="o">.</span><span class="nf">setTitleColor</span><span class="p">(</span><span class="o">.</span><span class="n">white</span><span class="p">,</span> <span class="nv">for</span><span class="p">:</span> <span class="o">.</span><span class="n">normal</span><span class="p">)</span>
<span class="nv">$0</span><span class="o">.</span><span class="n">backgroundColor</span> <span class="o">=</span> <span class="o">.</span><span class="n">red</span>
<span class="p">}</span>
<span class="k">let</span> <span class="nv">button</span> <span class="o">=</span> <span class="kt">UIButton</span><span class="p">()</span>
<span class="n">button</span><span class="o">.</span><span class="nf">setTitle</span><span class="p">(</span><span class="s">"My Button"</span><span class="p">,</span> <span class="nv">for</span><span class="p">:</span> <span class="o">.</span><span class="n">normal</span><span class="p">)</span>
<span class="nf">filledButtonStyle</span><span class="p">(</span><span class="n">button</span><span class="p">)</span></code></pre></figure>
<!-- _includes/image.html -->
<div class="image-wrapper">
<img src="https://felginep.github.io/assets/style_filled_button.png" alt="Image 1. Filled button" />
<p class="image-caption">Image 1. Filled button</p>
</div>
<p>We can wrap this function into an object, for more control on it.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">ViewStyle</span><span class="o"><</span><span class="kt">T</span><span class="o">></span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">style</span><span class="p">:</span> <span class="p">(</span><span class="kt">T</span><span class="p">)</span> <span class="o">-></span> <span class="kt">Void</span>
<span class="p">}</span></code></pre></figure>
<p>We can now create some styles for our filled and rounded button.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">filled</span> <span class="o">=</span> <span class="kt">ViewStyle</span><span class="o"><</span><span class="kt">UIButton</span><span class="o">></span> <span class="p">{</span>
<span class="nv">$0</span><span class="o">.</span><span class="nf">setTitleColor</span><span class="p">(</span><span class="o">.</span><span class="n">white</span><span class="p">,</span> <span class="nv">for</span><span class="p">:</span> <span class="o">.</span><span class="n">normal</span><span class="p">)</span>
<span class="nv">$0</span><span class="o">.</span><span class="n">backgroundColor</span> <span class="o">=</span> <span class="o">.</span><span class="n">red</span>
<span class="p">}</span>
<span class="k">let</span> <span class="nv">rounded</span> <span class="o">=</span> <span class="kt">ViewStyle</span><span class="o"><</span><span class="kt">UIButton</span><span class="o">></span> <span class="p">{</span>
<span class="nv">$0</span><span class="o">.</span><span class="n">layer</span><span class="o">.</span><span class="n">cornerRadius</span> <span class="o">=</span> <span class="mf">4.0</span>
<span class="p">}</span>
<span class="k">let</span> <span class="nv">button</span> <span class="o">=</span> <span class="kt">UIButton</span><span class="p">()</span>
<span class="n">filled</span><span class="o">.</span><span class="nf">style</span><span class="p">(</span><span class="n">button</span><span class="p">)</span></code></pre></figure>
<p>Now that we have our two styles for both filled and rounded buttons, we can create a new style for a rounded and filled button very easily.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">extension</span> <span class="kt">ViewStyle</span> <span class="p">{</span>
<span class="kd">func</span> <span class="nf">compose</span><span class="p">(</span><span class="n">with</span> <span class="nv">style</span><span class="p">:</span> <span class="kt">ViewStyle</span><span class="o"><</span><span class="kt">T</span><span class="o">></span><span class="p">)</span> <span class="o">-></span> <span class="kt">ViewStyle</span><span class="o"><</span><span class="kt">T</span><span class="o">></span> <span class="p">{</span>
<span class="k">return</span> <span class="kt">ViewStyle</span><span class="o"><</span><span class="kt">T</span><span class="o">></span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="nf">style</span><span class="p">(</span><span class="nv">$0</span><span class="p">)</span>
<span class="n">style</span><span class="o">.</span><span class="nf">style</span><span class="p">(</span><span class="nv">$0</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">let</span> <span class="nv">roundedAndFilled</span> <span class="o">=</span> <span class="n">filled</span><span class="o">.</span><span class="nf">compose</span><span class="p">(</span><span class="nv">with</span><span class="p">:</span> <span class="n">rounded</span><span class="p">)</span></code></pre></figure>
<p>What was previously impossible with <code class="language-plaintext highlighter-rouge">UIButton</code> subclasses is now very straightforward using simple functions.</p>
<!-- _includes/image.html -->
<div class="image-wrapper">
<img src="https://felginep.github.io/assets/style_rounded_button.png" alt="Image 2. Filled and rounded button" />
<p class="image-caption">Image 2. Filled and rounded button</p>
</div>
<h2 id="improvements">Improvements</h2>
<p>Now that we get the general idea, it’s time for syntactic sugar!</p>
<p>First of all, for now our styles live in the global namespace. That’s not very scalable.</p>
<p>The solution here is to extend <code class="language-plaintext highlighter-rouge">ViewStyle</code> and to constrain the generic type.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">extension</span> <span class="kt">ViewStyle</span> <span class="k">where</span> <span class="kt">T</span><span class="p">:</span> <span class="kt">UIButton</span> <span class="p">{</span>
<span class="kd">static</span> <span class="k">var</span> <span class="nv">filled</span><span class="p">:</span> <span class="kt">ViewStyle</span><span class="o"><</span><span class="kt">UIButton</span><span class="o">></span> <span class="p">{</span>
<span class="k">return</span> <span class="kt">ViewStyle</span><span class="o"><</span><span class="kt">UIButton</span><span class="o">></span> <span class="p">{</span>
<span class="nv">$0</span><span class="o">.</span><span class="nf">setTitleColor</span><span class="p">(</span><span class="o">.</span><span class="n">white</span><span class="p">,</span> <span class="nv">for</span><span class="p">:</span> <span class="o">.</span><span class="n">normal</span><span class="p">)</span>
<span class="nv">$0</span><span class="o">.</span><span class="n">backgroundColor</span> <span class="o">=</span> <span class="o">.</span><span class="n">red</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">static</span> <span class="k">var</span> <span class="nv">rounded</span><span class="p">:</span> <span class="kt">ViewStyle</span><span class="o"><</span><span class="kt">UIButton</span><span class="o">></span> <span class="p">{</span>
<span class="k">return</span> <span class="kt">ViewStyle</span><span class="o"><</span><span class="kt">UIButton</span><span class="o">></span> <span class="p">{</span>
<span class="nv">$0</span><span class="o">.</span><span class="n">layer</span><span class="o">.</span><span class="n">cornerRadius</span> <span class="o">=</span> <span class="mf">4.0</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">static</span> <span class="k">var</span> <span class="nv">roundedAndFilled</span><span class="p">:</span> <span class="kt">ViewStyle</span><span class="o"><</span><span class="kt">UIButton</span><span class="o">></span> <span class="p">{</span>
<span class="k">return</span> <span class="n">filled</span><span class="o">.</span><span class="nf">compose</span><span class="p">(</span><span class="nv">with</span><span class="p">:</span> <span class="n">rounded</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>That’s nice, we have a namespace to list all our styles. But it’s not very handy to style a button yet.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kt">ViewStyle</span><span class="o"><</span><span class="kt">UIButton</span><span class="o">>.</span><span class="n">roundedAndFilled</span><span class="o">.</span><span class="nf">style</span><span class="p">(</span><span class="n">button</span><span class="p">)</span> <span class="c1">// 🙈</span></code></pre></figure>
<p>To improve this, we can define a function that is responsible to apply a style to an object, inferring the type of the style based on the type of the object.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="n">style</span><span class="o"><</span><span class="kt">T</span><span class="o">></span><span class="p">(</span><span class="n">_</span> <span class="nv">object</span><span class="p">:</span> <span class="kt">T</span><span class="p">,</span> <span class="n">with</span> <span class="nv">style</span><span class="p">:</span> <span class="kt">ViewStyle</span><span class="o"><</span><span class="kt">T</span><span class="o">></span><span class="p">)</span> <span class="p">{</span>
<span class="n">style</span><span class="o">.</span><span class="nf">style</span><span class="p">(</span><span class="n">object</span><span class="p">)</span>
<span class="p">}</span>
<span class="nf">style</span><span class="p">(</span><span class="n">button</span><span class="p">,</span> <span class="nv">with</span><span class="p">:</span> <span class="o">.</span><span class="n">roundedAndFilled</span><span class="p">)</span></code></pre></figure>
<h2 id="protocols-to-the-rescue">Protocols to the rescue</h2>
<p>The code looks good and is readable. But we can go one step further! I want to get rid of the global <code class="language-plaintext highlighter-rouge">style(_:with:)</code> function, and to use an instance method of <code class="language-plaintext highlighter-rouge">UIButton</code> instead. For this, let’s define an empty protocol <code class="language-plaintext highlighter-rouge">Stylable</code>, and make <code class="language-plaintext highlighter-rouge">UIView</code> conform to it. That way we will be able to add methods to <code class="language-plaintext highlighter-rouge">Stylable</code> and all the <code class="language-plaintext highlighter-rouge">UIView</code> subclasses will get them for free.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">protocol</span> <span class="kt">Stylable</span> <span class="p">{}</span>
<span class="kd">extension</span> <span class="kt">UIView</span><span class="p">:</span> <span class="kt">Stylable</span> <span class="p">{}</span></code></pre></figure>
<p>That may seem a little odd, but we can now extend <code class="language-plaintext highlighter-rouge">Stylable</code> to add a method to apply a style to any <code class="language-plaintext highlighter-rouge">Stylable</code> instance.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">extension</span> <span class="kt">Stylable</span> <span class="p">{</span>
<span class="kd">func</span> <span class="nf">apply</span><span class="p">(</span><span class="n">_</span> <span class="nv">style</span><span class="p">:</span> <span class="kt">ViewStyle</span><span class="o"><</span><span class="k">Self</span><span class="o">></span><span class="p">)</span> <span class="p">{</span>
<span class="n">style</span><span class="o">.</span><span class="nf">style</span><span class="p">(</span><span class="k">self</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>All the <code class="language-plaintext highlighter-rouge">UIView</code> subclasses gain this <code class="language-plaintext highlighter-rouge">apply(_:)</code> method for free! The code becomes compact and readable.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="n">button</span><span class="o">.</span><span class="nf">apply</span><span class="p">(</span><span class="o">.</span><span class="n">roundedAndFilled</span><span class="p">)</span></code></pre></figure>
<p>What’s more, we can’t misuse our styles because of the Swift type system!</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">labelStyle</span> <span class="o">=</span> <span class="kt">ViewStyle</span><span class="o"><</span><span class="kt">UILabel</span><span class="o">></span> <span class="p">{</span> <span class="nv">$0</span><span class="o">.</span><span class="n">textAlignment</span> <span class="o">=</span> <span class="o">.</span><span class="n">center</span> <span class="p">}</span>
<span class="n">button</span><span class="o">.</span><span class="nf">apply</span><span class="p">(</span><span class="n">labelStyle</span><span class="p">)</span> <span class="c1">// 💣</span>
<span class="c1">// error: cannot convert value of type 'ViewStyle<UILabel>' to expected argument type 'ViewStyle<UIButton>'</span></code></pre></figure>
<h2 id="init-with-style">Init with style</h2>
<p>With the previous <code class="language-plaintext highlighter-rouge">apply(_:)</code> method you will often find yourself writing these two lines:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">button</span> <span class="o">=</span> <span class="kt">UIButton</span><span class="p">()</span>
<span class="n">button</span><span class="o">.</span><span class="nf">apply</span><span class="p">(</span><span class="o">.</span><span class="n">rounded</span><span class="p">)</span></code></pre></figure>
<p>What if we could initialize our button (or any other <code class="language-plaintext highlighter-rouge">UIView</code>) with a predefined style? It would save us one line of code each time.</p>
<p>That is possible, modifying slightly our <code class="language-plaintext highlighter-rouge">Stylable</code> protocol.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">protocol</span> <span class="kt">Stylable</span> <span class="p">{</span>
<span class="nf">init</span><span class="p">()</span>
<span class="p">}</span>
<span class="kd">extension</span> <span class="kt">UIView</span><span class="p">:</span> <span class="kt">Stylable</span> <span class="p">{}</span>
<span class="kd">extension</span> <span class="kt">Stylable</span> <span class="p">{</span>
<span class="nf">init</span><span class="p">(</span><span class="nv">style</span><span class="p">:</span> <span class="kt">ViewStyle</span><span class="o"><</span><span class="k">Self</span><span class="o">></span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="nf">init</span><span class="p">()</span>
<span class="nf">apply</span><span class="p">(</span><span class="n">style</span><span class="p">)</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">apply</span><span class="p">(</span><span class="n">_</span> <span class="nv">style</span><span class="p">:</span> <span class="kt">ViewStyle</span><span class="o"><</span><span class="k">Self</span><span class="o">></span><span class="p">)</span> <span class="p">{</span>
<span class="n">style</span><span class="o">.</span><span class="nf">style</span><span class="p">(</span><span class="k">self</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>We can now use the following syntax:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">button</span> <span class="o">=</span> <span class="kt">UIButton</span><span class="p">(</span><span class="nv">style</span><span class="p">:</span> <span class="o">.</span><span class="n">roundedAndFilled</span><span class="p">)</span> <span class="c1">// 👌</span></code></pre></figure>
<h2 id="conclusion">Conclusion</h2>
<p>With view styles as plain Swift functions, we achieved two things:</p>
<ul>
<li>first, a technical improvement on view subclasses: the composition of two <code class="language-plaintext highlighter-rouge">UIView</code> subclasses was impossible, whereas it becomes very easy using Swift functions.</li>
<li>second, an easier communication between developers and designers. Indeed, designers often work with styles, in order to reuse components and keep a consistent look and feel all around the app. If you can extract their styles and map them in a Swift file, it will become much simpler to develop your UI, and to update these styles in the future.</li>
</ul>
<p>You can find the full gist <a href="https://gist.github.com/felginep/0148b40e26b19d07e81c2e1e4f2ff3d2">here</a>.</p>Pierre FelginesToday I want to talk about UIView styling. The common approach when we want to customize the display of native UI controls (for instance UIButton or UILabel) is to create a subclass that overrides a bunch of properties. That works well most of the time, but some problems may arise.Swinject in practice2019-02-05T00:00:00+00:002019-02-05T00:00:00+00:00https://felginep.github.io/2019-02-05/swinject-in-practice<p>I guess you already have heard of Dependency Injection. Dependency injection (DI) is a software design pattern that implements Inversion of Control for resolving dependencies.</p>
<p>On iOS, one of the popular frameworks you can use for Dependency injection is <a href="https://github.com/Swinject/Swinject">Swinject</a>.</p>
<p>Today we will quickly cover the basics of using Swinject in your app in order to focus on two edge cases you may have been confronted to: <strong>custom object scopes</strong> and <strong>domain specific assemblies</strong>.</p>
<p>Let’s dive in.</p>
<h2 id="the-basics">The basics</h2>
<p><em>Note</em>: I invite you to read the detailed <a href="https://github.com/Swinject/Swinject/tree/master/Documentation">documentation</a> if you want to fully understand what follows.</p>
<p>Swinject let you split your dependencies into logic related objects, called <em>assemblies</em>.</p>
<p>For instance, let’s create an <code class="language-plaintext highlighter-rouge">HelperAssembly</code> that registers few helpers dependencies, that we will need in our project.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">HelperAssembly</span><span class="p">:</span> <span class="kt">Assembly</span> <span class="p">{</span>
<span class="kd">func</span> <span class="nf">assemble</span><span class="p">(</span><span class="nv">container</span><span class="p">:</span> <span class="kt">Container</span><span class="p">)</span> <span class="p">{</span>
<span class="n">container</span><span class="o">.</span><span class="nf">register</span><span class="p">(</span><span class="kt">UIApplication</span><span class="o">.</span><span class="k">self</span><span class="p">)</span> <span class="p">{</span> <span class="n">_</span> <span class="k">in</span>
<span class="kt">UIApplication</span><span class="o">.</span><span class="n">shared</span>
<span class="p">}</span>
<span class="n">container</span><span class="o">.</span><span class="nf">register</span><span class="p">(</span><span class="kt">UserDefaults</span><span class="o">.</span><span class="k">self</span><span class="p">)</span> <span class="p">{</span> <span class="n">_</span> <span class="k">in</span>
<span class="kt">UserDefaults</span><span class="o">.</span><span class="n">standard</span>
<span class="p">}</span>
<span class="n">container</span><span class="o">.</span><span class="nf">register</span><span class="p">(</span><span class="kt">Bundle</span><span class="o">.</span><span class="k">self</span><span class="p">)</span> <span class="p">{</span> <span class="n">_</span> <span class="k">in</span>
<span class="kt">Bundle</span><span class="o">.</span><span class="n">main</span>
<span class="p">}</span>
<span class="n">container</span><span class="o">.</span><span class="nf">register</span><span class="p">(</span><span class="kt">FileManager</span><span class="o">.</span><span class="k">self</span><span class="p">)</span> <span class="p">{</span> <span class="n">_</span> <span class="k">in</span>
<span class="kt">FileManager</span><span class="o">.</span><span class="k">default</span>
<span class="p">}</span>
<span class="o">...</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>Swinject, like any DI framework, works like a key value store (named <em>container</em> here): the keys are types (abstract with protocols or concrete with classes or structs) and the values instances of those types.</p>
<p>Once low level dependencies are registered, we can start using them into higher level dependencies.</p>
<p>Let’s say we need to fetch a list of the user last viewed products in our app. We create an interface (a protocol in Swift) and a concrete implementation of this interface.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">// the interface</span>
<span class="kd">protocol</span> <span class="kt">LastViewedProductsRepository</span> <span class="p">{</span>
<span class="o">...</span>
<span class="kd">func</span> <span class="nf">items</span><span class="p">()</span> <span class="o">-></span> <span class="p">[</span><span class="kt">Product</span><span class="p">]</span>
<span class="p">}</span>
<span class="c1">// the implementation</span>
<span class="kd">class</span> <span class="kt">LastViewedProductsRepositoryImplementation</span><span class="p">:</span> <span class="kt">LastViewedProductsRepository</span> <span class="p">{</span>
<span class="kd">private</span> <span class="k">let</span> <span class="nv">userDefaults</span><span class="p">:</span> <span class="kt">UserDefaults</span>
<span class="nf">init</span><span class="p">(</span><span class="nv">userDefaults</span><span class="p">:</span> <span class="kt">UserDefaults</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">userDefaults</span> <span class="o">=</span> <span class="n">userDefaults</span>
<span class="p">}</span>
<span class="c1">// MARK: - LastViewedProductsRepository</span>
<span class="kd">func</span> <span class="nf">items</span><span class="p">()</span> <span class="o">-></span> <span class="p">[</span><span class="kt">Product</span><span class="p">]</span> <span class="p">{</span>
<span class="c1">// retrieve products from the userDefaults</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>With Swinject, we would register our repository like this:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">RepositoryAssembly</span><span class="p">:</span> <span class="kt">Assembly</span> <span class="p">{</span>
<span class="kd">func</span> <span class="nf">assemble</span><span class="p">(</span><span class="nv">container</span><span class="p">:</span> <span class="kt">Container</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// We register the abstract type as a key ...</span>
<span class="n">container</span><span class="o">.</span><span class="nf">register</span><span class="p">(</span><span class="kt">LastViewedProductsRepository</span><span class="o">.</span><span class="k">self</span><span class="p">)</span> <span class="p">{</span> <span class="n">r</span> <span class="k">in</span>
<span class="c1">// ... and a concrete implementation instance as a value</span>
<span class="kt">LastViewedProductsRepositoryImplementation</span><span class="p">(</span>
<span class="c1">// we don't know how to retrieve a userDefault,</span>
<span class="c1">// we just expect Swinject to give us one at runtime</span>
<span class="nv">userDefaults</span><span class="p">:</span> <span class="n">r</span><span class="o">.</span><span class="nf">resolve</span><span class="p">(</span><span class="kt">UserDefaults</span><span class="o">.</span><span class="k">self</span><span class="p">)</span><span class="o">!</span>
<span class="p">)</span>
<span class="p">}</span><span class="o">.</span><span class="nf">inObjectScope</span><span class="p">(</span><span class="o">.</span><span class="n">container</span><span class="p">)</span>
<span class="o">...</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>The <code class="language-plaintext highlighter-rouge">LastViewedProductsRepository</code> uses an instance of <code class="language-plaintext highlighter-rouge">UserDefaults</code> to save the last viewed products in the application. This dependency will be resolved at runtime using the <code class="language-plaintext highlighter-rouge">HelperAssembly</code>.</p>
<p>Note that we use the object scope <code class="language-plaintext highlighter-rouge">container</code> here to create a single instance of <code class="language-plaintext highlighter-rouge">LastViewedProductsRepositoryImplementation</code> in the whole application. There are several scopes available explained in more details <a href="https://github.com/Swinject/Swinject/blob/master/Documentation/ObjectScopes.md">here</a>.</p>
<p>At the end, we gather all the assemblies into an <em>assembler</em> and use it to create our dependencies.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">DependencyProvider</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">container</span> <span class="o">=</span> <span class="kt">Container</span><span class="p">()</span>
<span class="k">let</span> <span class="nv">assembler</span><span class="p">:</span> <span class="kt">Assembler</span>
<span class="nf">init</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// the assembler gathers all the dependencies in one place</span>
<span class="n">assembler</span> <span class="o">=</span> <span class="kt">Assembler</span><span class="p">(</span>
<span class="p">[</span>
<span class="kt">HelperAssembly</span><span class="p">(),</span> <span class="c1">// from low</span>
<span class="kt">RepositoryAssembly</span><span class="p">(),</span> <span class="c1">// ...</span>
<span class="kt">PresenterAssembly</span><span class="p">(),</span> <span class="c1">// to high level</span>
<span class="p">],</span>
<span class="nv">container</span><span class="p">:</span> <span class="n">container</span>
<span class="p">)</span>
<span class="p">}</span>
<span class="o">...</span>
<span class="p">}</span></code></pre></figure>
<h2 id="custom-object-scopes">Custom object scopes</h2>
<p>We have seen earlier that we have a <code class="language-plaintext highlighter-rouge">LastViewedProductsRepository</code> object, responsible for keeping a cache of the last viewed products in the app.</p>
<p>For now, because we used the <code class="language-plaintext highlighter-rouge">container</code> scope in the assembly, there is a single instance of this class in the whole application. And sadly enough, this instance is the same even if we log out the user, then log in with another id.</p>
<p>That’s a bug…</p>
<p>We want to erase all the last viewed products when the current user logs out.</p>
<p>To overcome this we have a few options:</p>
<ul>
<li>emit a notification when the user logs out and listen to this notification in the repository</li>
<li>provide a public method to clean the products (for instance <code class="language-plaintext highlighter-rouge">cleanCache</code>) in the <code class="language-plaintext highlighter-rouge">LastViewedProductsRepository</code> and call it from the outside when the user logs out.</li>
</ul>
<p>Theses two options are fine, but not very scalable. We want to deport this logic into the DI, meaning creating a scope for the object that:</p>
<ul>
<li>keep the instance alive while the user is logged in</li>
<li>trash the instance when the user logs out</li>
<li>create a new instance when a new user logs in.</li>
</ul>
<p>That’s where custom object scopes shine.</p>
<p>Let’s create a new object scope named <code class="language-plaintext highlighter-rouge">discardedWhenLogout</code>:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">extension</span> <span class="kt">ObjectScope</span> <span class="p">{</span>
<span class="kd">static</span> <span class="k">let</span> <span class="nv">discardedWhenLogout</span> <span class="o">=</span> <span class="kt">ObjectScope</span><span class="p">(</span>
<span class="nv">storageFactory</span><span class="p">:</span> <span class="kt">PermanentStorage</span><span class="o">.</span><span class="kd">init</span><span class="p">,</span>
<span class="nv">description</span><span class="p">:</span> <span class="s">"discardedWhenLogout"</span>
<span class="p">)</span>
<span class="p">}</span></code></pre></figure>
<p>We can use it in our assembly instead of the <code class="language-plaintext highlighter-rouge">container</code> scope:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">RepositoryAssembly</span><span class="p">:</span> <span class="kt">Assembly</span> <span class="p">{</span>
<span class="kd">func</span> <span class="nf">assemble</span><span class="p">(</span><span class="nv">container</span><span class="p">:</span> <span class="kt">Container</span><span class="p">)</span> <span class="p">{</span>
<span class="n">container</span><span class="o">.</span><span class="nf">register</span><span class="p">(</span><span class="kt">LastViewedProductsRepository</span><span class="o">.</span><span class="k">self</span><span class="p">)</span> <span class="p">{</span> <span class="n">r</span> <span class="k">in</span>
<span class="kt">LastViewedProductsRepositoryImplementation</span><span class="p">(</span>
<span class="nv">userDefaults</span><span class="p">:</span> <span class="n">r</span><span class="o">.</span><span class="nf">resolve</span><span class="p">(</span><span class="kt">UserDefaults</span><span class="o">.</span><span class="k">self</span><span class="p">)</span><span class="o">!</span>
<span class="p">)</span>
<span class="p">}</span><span class="o">.</span><span class="nf">inObjectScope</span><span class="p">(</span><span class="o">.</span><span class="n">discardedWhenLogout</span><span class="p">)</span> <span class="c1">// we replace the container scope</span>
<span class="o">...</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>Now we can use the Swinject method <code class="language-plaintext highlighter-rouge">resetObjectScope(_ objectScope: ObjectScope)</code> on a <code class="language-plaintext highlighter-rouge">container</code>. This method discards all the instances registered in the given object scope. That means all the instances will be re-created once they are needed again.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">userDidLogout</span><span class="p">()</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">container</span> <span class="o">=</span> <span class="o">...</span> <span class="c1">// get the container somehow</span>
<span class="n">container</span><span class="o">.</span><span class="nf">resetObjectScope</span><span class="p">(</span><span class="o">.</span><span class="n">discardedWhenLogout</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>
<p>Now the next time a user logs in, all the last viewed products will be cleared.</p>
<h2 id="domain-specific-assemblies">Domain specific assemblies</h2>
<p>In some cases, there are parts of your application that you can access only if some values are downloaded, or set somewhere else in the app. In our case, let’s imagine there is a configuration file downloaded when the user logs in and that this configuration is used intensively in the rest of the app.</p>
<p>This configuration file is mapped into a <code class="language-plaintext highlighter-rouge">LoginConfiguration</code> object, which is just a struct with a bunch of properties.</p>
<p>In the last viewed products page of our application, the number of products displayed is constrained with the value <code class="language-plaintext highlighter-rouge">maxProducts</code> of the configuration. We show a class of <code class="language-plaintext highlighter-rouge">LastViewedProductsPresenter</code> here, keep in mind that a <em>presenter</em> is a just like a <em>viewController</em> but not tied to UIKit.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">LastViewedProductsPresenterImplementation</span><span class="p">:</span> <span class="kt">LastViewedProductsPresenter</span> <span class="p">{</span>
<span class="o">...</span>
<span class="c1">// repository to get the last viewed products</span>
<span class="kd">private</span> <span class="k">let</span> <span class="nv">lastViewedProductsRepository</span><span class="p">:</span> <span class="kt">LastViewedProductsRepository</span>
<span class="c1">// repository to get the saved configuration</span>
<span class="kd">private</span> <span class="k">let</span> <span class="nv">loginConfigurationRepository</span><span class="p">:</span> <span class="kt">LoginConfigurationRepository</span>
<span class="nf">init</span><span class="p">(</span><span class="nv">lastViewedProductsRepository</span><span class="p">:</span> <span class="kt">LastViewedProductsRepository</span><span class="p">,</span>
<span class="nv">loginConfigurationRepository</span><span class="p">:</span> <span class="kt">LoginConfigurationRepository</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">lastViewedProductsRepository</span> <span class="o">=</span> <span class="n">lastViewedProductsRepository</span>
<span class="k">self</span><span class="o">.</span><span class="n">loginConfigurationRepository</span> <span class="o">=</span> <span class="n">loginConfigurationRepository</span>
<span class="p">}</span>
<span class="c1">// MARK: - LastViewedProductsPresenter</span>
<span class="c1">// method called when we want to reload the UI</span>
<span class="kd">func</span> <span class="nf">reload</span><span class="p">()</span> <span class="p">{</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">loginConfiguration</span> <span class="o">=</span> <span class="n">loginConfigurationRepository</span><span class="o">.</span><span class="nf">savedConfiguration</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">let</span> <span class="nv">items</span> <span class="o">=</span> <span class="nf">min</span><span class="p">(</span>
<span class="n">lastViewedProductsRepository</span><span class="o">.</span><span class="nf">items</span><span class="p">(),</span>
<span class="n">loginConfiguration</span><span class="o">.</span><span class="n">maxProducts</span>
<span class="p">)</span>
<span class="c1">// display items somehow</span>
<span class="p">}</span>
<span class="o">...</span>
<span class="p">}</span></code></pre></figure>
<p>The problem here is that we guard against the <code class="language-plaintext highlighter-rouge">loginConfiguration</code> even though the configuration <strong>must</strong> be available in this case, because the user must be logged in to access this page.</p>
<p>But the <code class="language-plaintext highlighter-rouge">loginConfigurationRepository</code> returns an optional (indeed, the configuration does not exist before the user is logged in). But if we think about it, it’s not the right solution. Instead of initializing the presenter object with a repository, we should initialize it with the <code class="language-plaintext highlighter-rouge">loginConfiguration</code> directly, because we are sure at this point that such a value exists.</p>
<p>The <code class="language-plaintext highlighter-rouge">LastViewedProductsPresenterImplementation</code> has two dependencies, so our <code class="language-plaintext highlighter-rouge">PresenterAssembly</code> looks like this:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">PresenterAssembly</span><span class="p">:</span> <span class="kt">Assembly</span> <span class="p">{</span>
<span class="kd">func</span> <span class="nf">assemble</span><span class="p">(</span><span class="nv">container</span><span class="p">:</span> <span class="kt">Container</span><span class="p">)</span> <span class="p">{</span>
<span class="n">container</span><span class="o">.</span><span class="nf">register</span><span class="p">(</span><span class="kt">LastViewedProductsPresenter</span><span class="o">.</span><span class="k">self</span><span class="p">)</span> <span class="p">{</span> <span class="n">r</span> <span class="k">in</span>
<span class="kt">LastViewedProductsPresenterImplementation</span><span class="p">(</span>
<span class="nv">lastViewedProductsRepository</span><span class="p">:</span> <span class="n">r</span><span class="o">.</span><span class="nf">resolve</span><span class="p">(</span><span class="kt">LastViewedProductsRepository</span><span class="o">.</span><span class="k">self</span><span class="p">)</span><span class="o">!</span><span class="p">,</span>
<span class="nv">loginConfigurationRepository</span><span class="p">:</span> <span class="n">r</span><span class="o">.</span><span class="nf">resolve</span><span class="p">(</span><span class="kt">LoginConfigurationRepository</span><span class="o">.</span><span class="k">self</span><span class="p">)</span><span class="o">!</span>
<span class="p">)</span>
<span class="p">}</span>
<span class="o">...</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>What we want is to pass the <code class="language-plaintext highlighter-rouge">loginConfiguration</code> to the presenter at init time, instead of the <code class="language-plaintext highlighter-rouge">loginConfigurationRepository</code>. That means we need to store the <code class="language-plaintext highlighter-rouge">loginConfiguration</code> in the assembly itself to resolve the dependency.</p>
<p>Let’s define a new assembly called <code class="language-plaintext highlighter-rouge">LoggedInPresenterAssembly</code>. This assembly will register types that will be used only once the user is logged in.
As the user will be logged in, we can assume the login configuration is fetched and available in the assembly:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">LoggedInPresenterAssembly</span><span class="p">:</span> <span class="kt">Assembly</span> <span class="p">{</span>
<span class="c1">// we store the configuration directly</span>
<span class="kd">private</span> <span class="k">let</span> <span class="nv">loginConfiguration</span><span class="p">:</span> <span class="kt">LoginConfiguration</span>
<span class="nf">init</span><span class="p">(</span><span class="nv">loginConfiguration</span><span class="p">:</span> <span class="kt">LoginConfiguration</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">loginConfiguration</span> <span class="o">=</span> <span class="n">loginConfiguration</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">assemble</span><span class="p">(</span><span class="nv">container</span><span class="p">:</span> <span class="kt">Container</span><span class="p">)</span> <span class="p">{</span>
<span class="n">container</span><span class="o">.</span><span class="nf">register</span><span class="p">(</span><span class="kt">LastViewedProductsPresenter</span><span class="o">.</span><span class="k">self</span><span class="p">)</span> <span class="p">{</span> <span class="n">r</span> <span class="k">in</span>
<span class="kt">LastViewedProductsPresenterImplementation</span><span class="p">(</span>
<span class="nv">lastViewedProductsRepository</span><span class="p">:</span> <span class="n">r</span><span class="o">.</span><span class="nf">resolve</span><span class="p">(</span><span class="kt">LastViewedProductsRepository</span><span class="o">.</span><span class="k">self</span><span class="p">)</span><span class="o">!</span><span class="p">,</span>
<span class="c1">// and pass it instead of the loginConfigurationRepository</span>
<span class="nv">loginConfiguration</span><span class="p">:</span> <span class="n">loginConfiguration</span>
<span class="p">)</span>
<span class="p">}</span>
<span class="o">...</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>The only thing to do now is to create the <code class="language-plaintext highlighter-rouge">LoggedInPresenterAssembly</code> when the user logs in and that the configuration is fetched, and to give it to the assembler.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">userDidLogin</span><span class="p">(</span><span class="n">with</span> <span class="nv">configuration</span><span class="p">:</span> <span class="kt">LoginConfiguration</span><span class="p">)</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">assembly</span> <span class="o">=</span> <span class="kt">LoggedInPresenterAssembly</span><span class="p">(</span><span class="nv">loginConfiguration</span><span class="p">:</span> <span class="n">configuration</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">assembler</span> <span class="o">=</span> <span class="o">...</span> <span class="c1">// get the assembler somehow</span>
<span class="n">assembler</span><span class="o">.</span><span class="nf">apply</span><span class="p">(</span><span class="n">assembly</span><span class="p">)</span>
<span class="o">...</span>
<span class="p">}</span></code></pre></figure>
<p>That way the presenter now becomes:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">LastViewedProductsPresenterImplementation</span><span class="p">:</span> <span class="kt">LastViewedProductsPresenter</span> <span class="p">{</span>
<span class="o">...</span>
<span class="kd">private</span> <span class="k">let</span> <span class="nv">lastViewedProductsRepository</span><span class="p">:</span> <span class="kt">LastViewedProductsRepository</span>
<span class="c1">// we can use the configuration directly</span>
<span class="kd">private</span> <span class="k">let</span> <span class="nv">loginConfiguration</span><span class="p">:</span> <span class="kt">LoginConfiguration</span>
<span class="nf">init</span><span class="p">(</span><span class="nv">lastViewedProductsRepository</span><span class="p">:</span> <span class="kt">LastViewedProductsRepository</span><span class="p">,</span>
<span class="nv">loginConfiguration</span><span class="p">:</span> <span class="kt">LoginConfiguration</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">lastViewedProductsRepository</span> <span class="o">=</span> <span class="n">lastViewedProductsRepository</span>
<span class="k">self</span><span class="o">.</span><span class="n">loginConfiguration</span> <span class="o">=</span> <span class="n">loginConfiguration</span>
<span class="p">}</span>
<span class="c1">// MARK: - LastViewedProductsPresenter</span>
<span class="kd">func</span> <span class="nf">reload</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// note that the guard is gone!</span>
<span class="k">let</span> <span class="nv">items</span> <span class="o">=</span> <span class="nf">min</span><span class="p">(</span>
<span class="n">lastViewedProductsRepository</span><span class="o">.</span><span class="nf">items</span><span class="p">(),</span>
<span class="n">loginConfiguration</span><span class="o">.</span><span class="n">maxProducts</span>
<span class="p">)</span>
<span class="c1">// display items somehow</span>
<span class="p">}</span>
<span class="o">...</span>
<span class="p">}</span></code></pre></figure>
<h2 id="wrap-up">Wrap up</h2>
<p>Dependency injection is a major concept in programming in order to make your code reusable and testable. The concepts explained in this blog post can apply independently of the framework and the platform.</p>
<p>In particular, we have seen two advanced use cases:</p>
<ul>
<li><strong>custom object scopes</strong> to customize the lifetime of your dependencies</li>
<li><strong>domain specific assemblies</strong> to create dependencies that depend on some external requirements</li>
</ul>Pierre FelginesI guess you already have heard of Dependency Injection. Dependency injection (DI) is a software design pattern that implements Inversion of Control for resolving dependencies.