Jekyll2019-12-22T23:42:58+00:00https://rudybermudez.io/Rudy BermudezPersonal website and blog of Rudy BermudezHandling Network Problems Gracefully with Alamofire2018-07-28T21:12:00+00:002018-07-28T21:12:00+00:00https://rudybermudez.io/handing-network-problems-gracefully-with-alamofire<p>Learning to gracefully handle network failures is an essential part of HTTP networking. In this post I will discuss how I use the popular networking library, Alamofire, to handle network failures in my iOS applications.</p>
<h2 id="a-prelude">A Prelude</h2>
<p>Mobile devices allow us to remain ever connected to the world around us while on the go, but with this comes the sporadic issues of wireless communication such as signal loss. For this reason, when developing for iOS it is especially important to anticipate that your users are constantly on the move, and that network conditions are constantly changing. It is up to you, the developer, to decide how applications should operate when network connectivity states change. The most common networking issue that I experience on an almost daily basis is connection loss due to moving outside of the range of a connected Wi-Fi network. However my users may live in a major metropolitan area so I may also have to worry about the intermittent signal of cellular data while traveling in a subway. Luckily, the Alamofire <code class="highlighter-rouge">RequestRetrier</code> protocol will alleviate both of these issues.</p>
<h2 id="the-solution">The Solution</h2>
<p>Alamofire created a protocol called the <code class="highlighter-rouge">RequestRetrier</code> that allows for requests to be retried when an error occurs. They also mention how it could be adapted to refresh access tokens, but that will be another post. In order to get the retrier to work, you must first create a class that conforms to the RequestRetrier protocol.</p>
<h4 id="note">Note:</h4>
<blockquote>
<p>This is a quick and rough implementation of a <code class="highlighter-rouge">RequestRetrier</code> that should be further configured to uniquely handle different errors (401, 404, 500, etc.) before it is used in a production application.</p>
</blockquote>
<h3 id="the-code">The Code</h3>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">import</span> <span class="kt">Alamofire</span>
<span class="kd">class</span> <span class="kt">NetworkRequestRetrier</span><span class="p">:</span> <span class="kt">RequestRetrier</span> <span class="p">{</span>
<span class="c1">// [Request url: Number of times retried]</span>
<span class="kd">private</span> <span class="k">var</span> <span class="nv">retriedRequests</span><span class="p">:</span> <span class="p">[</span><span class="kt">String</span><span class="p">:</span> <span class="kt">Int</span><span class="p">]</span> <span class="o">=</span> <span class="p">[:]</span>
<span class="kd">internal</span> <span class="kd">func</span> <span class="nf">should</span><span class="p">(</span><span class="n">_</span> <span class="nv">manager</span><span class="p">:</span> <span class="kt">SessionManager</span><span class="p">,</span>
<span class="n">retry</span> <span class="nv">request</span><span class="p">:</span> <span class="kt">Request</span><span class="p">,</span>
<span class="n">with</span> <span class="nv">error</span><span class="p">:</span> <span class="kt">Error</span><span class="p">,</span>
<span class="nv">completion</span><span class="p">:</span> <span class="kd">@escaping</span> <span class="kt">RequestRetryCompletion</span><span class="p">)</span> <span class="p">{</span>
<span class="k">guard</span>
<span class="n">request</span><span class="o">.</span><span class="n">task</span><span class="p">?</span><span class="o">.</span><span class="n">response</span> <span class="o">==</span> <span class="kc">nil</span><span class="p">,</span>
<span class="k">let</span> <span class="nv">url</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">request</span><span class="p">?</span><span class="o">.</span><span class="n">url</span><span class="p">?</span><span class="o">.</span><span class="n">absoluteString</span>
<span class="k">else</span> <span class="p">{</span>
<span class="nf">removeCachedUrlRequest</span><span class="p">(</span><span class="nv">url</span><span class="p">:</span> <span class="n">request</span><span class="o">.</span><span class="n">request</span><span class="p">?</span><span class="o">.</span><span class="n">url</span><span class="p">?</span><span class="o">.</span><span class="n">absoluteString</span><span class="p">)</span>
<span class="nf">completion</span><span class="p">(</span><span class="kc">false</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">)</span> <span class="c1">// don't retry</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">retryCount</span> <span class="o">=</span> <span class="n">retriedRequests</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="n">retriedRequests</span><span class="p">[</span><span class="n">url</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span>
<span class="nf">completion</span><span class="p">(</span><span class="kc">true</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">)</span> <span class="c1">// retry after 1 second</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">if</span> <span class="n">retryCount</span> <span class="o"><=</span> <span class="mi">3</span> <span class="p">{</span>
<span class="n">retriedRequests</span><span class="p">[</span><span class="n">url</span><span class="p">]</span> <span class="o">=</span> <span class="n">retryCount</span> <span class="o">+</span> <span class="mi">1</span>
<span class="nf">completion</span><span class="p">(</span><span class="kc">true</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">)</span> <span class="c1">// retry after 1 second</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nf">removeCachedUrlRequest</span><span class="p">(</span><span class="nv">url</span><span class="p">:</span> <span class="n">url</span><span class="p">)</span>
<span class="nf">completion</span><span class="p">(</span><span class="kc">false</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">)</span> <span class="c1">// don't retry</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">private</span> <span class="kd">func</span> <span class="nf">removeCachedUrlRequest</span><span class="p">(</span><span class="nv">url</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="k">let</span> <span class="nv">url</span> <span class="o">=</span> <span class="n">url</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="n">retriedRequests</span><span class="o">.</span><span class="nf">removeValue</span><span class="p">(</span><span class="nv">forKey</span><span class="p">:</span> <span class="n">url</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Now once you have created your NetworkRequestRetrier, the next thing you need to do is create a session manager so that you can use the request retrier.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">import</span> <span class="kt">Alamofire</span>
<span class="k">let</span> <span class="nv">sessionManager</span> <span class="o">=</span> <span class="kt">SessionManager</span><span class="p">()</span> <span class="c1">// Create a session manager</span>
<span class="k">let</span> <span class="nv">requestRetrier</span> <span class="o">=</span> <span class="kt">NetworkRequestRetrier</span><span class="p">()</span> <span class="c1">// Create a request retrier</span>
<span class="n">sessionManager</span><span class="o">.</span><span class="n">retrier</span> <span class="o">=</span> <span class="n">requestRetrier</span> <span class="c1">// Set the retrier</span>
<span class="c1">// Now when you make a request, use the session manager you just created</span>
<span class="n">sessionManager</span><span class="o">.</span><span class="nf">request</span><span class="p">(</span><span class="s">"https://httpbin.org/get"</span><span class="p">)</span><span class="o">.</span><span class="nf">validate</span><span class="p">()</span><span class="o">.</span><span class="n">responseJSON</span> <span class="p">{</span> <span class="n">response</span> <span class="k">in</span>
<span class="k">switch</span> <span class="n">response</span><span class="o">.</span><span class="n">result</span> <span class="p">{</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">success</span><span class="p">:</span>
<span class="nf">print</span><span class="p">(</span><span class="s">"Validation Successful"</span><span class="p">)</span>
<span class="k">case</span> <span class="o">.</span><span class="nf">failure</span><span class="p">(</span><span class="k">let</span> <span class="nv">error</span><span class="p">):</span>
<span class="nf">print</span><span class="p">(</span><span class="n">error</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="thats-it">That’s it!</h2>
<p>It doesn’t take much to get the <code class="highlighter-rouge">RequestRetrier</code> working, and that’s a good thing because it is a huge necessity for mobile communication.</p>
<p><br /></p>
<h2 id="further-reading--additional-resources">Further Reading & Additional Resources</h2>
<p>The apple developer documentation has an incredible section on <a href="https://developer.apple.com/library/archive/documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/WhyNetworkingIsHard/WhyNetworkingIsHard.html#//apple_ref/doc/uid/TP40010220-CH13-SW1">designing for real-world networks</a> that I would highly recommend familiarizing yourself with that applies to more than just AppleOS development.</p>
<p>I also recommend checking out the Alamofire documentation that covers <a href="https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md#adapting-and-retrying-requests">adapting and retrying requests</a>.</p>Learning to gracefully handle network failures is an essential part of HTTP networking. In this post I will discuss how I use the popular networking library, Alamofire, to handle network failures in my iOS applications.Getting Started with Fastlane and TravisCI2018-03-08T21:19:00+00:002018-03-08T21:19:00+00:00https://rudybermudez.io/getting-started-with-fastlane-and-travisci<p><a href="https://fastlane.tools">Fastlane</a> is a build automation tool for both iOS and
Android developed by <a href="https://krausefx.com/about">Felix Krause</a>.
Its tooling provides the ability to automate screenshots, run tests, manage code-signing,
distribute beta builds, publish applications, and so much more. Needless to say,
it is a toolset that any mobile developer should become familiar with. In this blog post,
I am going to talk about how I use Fastlane and TravisCI in one of my upcoming projects.</p>
<h4 id="note">Note:</h4>
<blockquote>
<p>I am going to assume that you are already familiar with TravisCI and know how
to configure it to automatically build your iOS project.</p>
</blockquote>
<h2 id="how-it-works">How it works</h2>
<p>Fastlane allows you to configure workflows that you would like to automate.
These workflows are called lanes and are defined as a script called the Fastfile.
The lanes are run by calling <code class="highlighter-rouge">fastlane {name of the lane}</code> from the command line in the directory of the project.
For example, <code class="highlighter-rouge">fastlane beta</code>.</p>
<h2 id="my-fastfile">My Fastfile</h2>
<p>Here is a snippet of my Fastfile so that you can get a general idea of how I configured my workflows.
For those who wish to view the entirety of the Fastfile, I have published it as a
<a href="https://gist.github.com/RudyB/4cbbc6af091ec2e9b7055e2270945d19">gist</a>.</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">default_platform</span><span class="p">(</span><span class="ss">:ios</span><span class="p">)</span>
<span class="n">platform</span> <span class="ss">:ios</span> <span class="k">do</span>
<span class="n">setup_travis</span>
<span class="n">desc</span> <span class="s2">"Run Unit and UI Tests"</span>
<span class="n">lane</span> <span class="ss">:test</span> <span class="k">do</span>
<span class="c1"># Install Pods</span>
<span class="n">cocoapods</span><span class="p">(</span><span class="ss">clean: </span><span class="kp">true</span><span class="p">,</span> <span class="ss">repo_update: </span><span class="kp">true</span> <span class="p">)</span>
<span class="n">run_tests</span><span class="p">(</span><span class="ss">workspace: </span><span class="s2">"Tempo.xcworkspace"</span><span class="p">,</span>
<span class="ss">devices: </span><span class="p">[</span><span class="s2">"iPhone 6s"</span><span class="p">],</span>
<span class="ss">scheme: </span><span class="s2">"Tempo"</span><span class="p">,</span>
<span class="ss">slack_channel: </span><span class="s2">"#general"</span><span class="p">)</span>
<span class="c1"># Compute the code coverage</span>
<span class="n">slather</span><span class="p">(</span><span class="ss">cobertura_xml: </span><span class="kp">true</span><span class="p">,</span>
<span class="ss">scheme: </span><span class="s2">"Tempo"</span><span class="p">,</span>
<span class="ss">simple_output: </span><span class="kp">true</span><span class="p">,</span>
<span class="ss">travis_pro: </span><span class="kp">true</span><span class="p">,</span>
<span class="ss">workspace: </span><span class="s2">"Tempo.xcworkspace"</span>
<span class="p">)</span>
<span class="c1"># Report Coverage to Codecov.io</span>
<span class="n">codecov_reporter</span><span class="p">(</span><span class="ss">token: </span><span class="s2">"$CODECOV_TOKEN"</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">desc</span> <span class="s2">"Push a new beta build to TestFlight"</span>
<span class="n">lane</span> <span class="ss">:beta</span> <span class="k">do</span>
<span class="nb">test</span>
<span class="n">ensure_git_status_clean</span>
<span class="c1"># Increment the build number (not the version number)</span>
<span class="n">build_number</span> <span class="o">=</span> <span class="n">increment_build_number</span><span class="p">(</span><span class="ss">xcodeproj: </span><span class="s2">"Tempo.xcodeproj"</span><span class="p">)</span>
<span class="c1"># Commit the version bump, skipping CI Build</span>
<span class="n">commit_version_bump</span><span class="p">(</span><span class="ss">xcodeproj: </span><span class="s2">"Tempo.xcodeproj"</span><span class="p">,</span>
<span class="n">message</span><span class="ss">:"[ci skip] Version Bump to </span><span class="si">#{</span><span class="n">build_number</span><span class="si">}</span><span class="ss">"</span><span class="p">)</span>
<span class="c1"># Push new build number</span>
<span class="n">push_to_git_remote</span><span class="p">(</span>
<span class="ss">remote: </span><span class="s2">"origin"</span><span class="p">,</span> <span class="c1"># optional, default: "origin"</span>
<span class="ss">remote_branch: </span><span class="s2">"develop"</span><span class="p">,</span> <span class="c1"># optional, default is set to local_branch</span>
<span class="ss">tags: </span><span class="kp">false</span> <span class="c1"># optional, default: true</span>
<span class="p">)</span>
<span class="n">cocoapods</span><span class="p">(</span><span class="ss">clean: </span><span class="kp">true</span><span class="p">,</span> <span class="ss">repo_update: </span><span class="kp">true</span><span class="p">)</span>
<span class="n">sync_code_signing</span><span class="p">(</span><span class="ss">type: </span><span class="s2">"appstore"</span><span class="p">)</span>
<span class="n">build_app</span>
<span class="n">upload_to_testflight</span><span class="p">(</span> <span class="ss">demo_account_required: </span><span class="kp">true</span><span class="p">,</span> <span class="ss">groups: </span><span class="s2">"Tempo Testers"</span><span class="p">,</span>
<span class="ss">distribute_external: </span><span class="kp">true</span><span class="p">,</span> <span class="ss">changelog: </span><span class="s2">"Here is what's new with Tempo:"</span> <span class="p">)</span>
<span class="n">slack</span><span class="p">(</span><span class="ss">message: </span><span class="s2">"Successfully distributed a new beta build"</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<h2 id="getting-fancy-with-travisci">Getting Fancy with TravisCI</h2>
<p>I created the following shell script that uses the TravisCI environment variables
in order to determine which lane to run:</p>
<h5 id="travissh">travis.sh</h5>
<hr />
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#! /bin/sh</span>
<span class="k">if</span> <span class="o">[[</span> <span class="nv">$TRAVIS_COMMIT_MESSAGE</span> <span class="o">==</span> <span class="k">*</span><span class="s2">"[fastlane deploy beta]"</span><span class="k">*</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
./fastlane/setupGit.sh
bundle <span class="nb">exec </span>fastlane beta
<span class="nb">exit</span> <span class="nv">$?</span>
<span class="k">elif</span> <span class="o">[[</span> <span class="nv">$TRAVIS_COMMIT_MESSAGE</span> <span class="o">==</span> <span class="k">*</span><span class="s2">"[fastlane deploy release]"</span><span class="k">*</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
./fastlane/setupGit.sh
bundle <span class="nb">exec </span>fastlane release
<span class="nb">exit</span> <span class="nv">$?</span>
<span class="k">elif</span> <span class="o">[[</span> <span class="nv">$TRAVIS_COMMIT_MESSAGE</span> <span class="o">==</span> <span class="k">*</span><span class="s2">"[fastlane capture screenshots]"</span><span class="k">*</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
</span>bundle <span class="nb">exec </span>fastlane screenshots
<span class="nb">exit</span> <span class="nv">$?</span>
<span class="k">elif</span> <span class="o">[[</span> <span class="s2">"</span><span class="nv">$TRAVIS_PULL_REQUEST</span><span class="s2">"</span> <span class="o">!=</span> <span class="s2">"false"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
</span>bundle <span class="nb">exec </span>fastlane <span class="nb">test
exit</span> <span class="nv">$?</span>
<span class="k">else
</span>bundle <span class="nb">exec </span>fastlane <span class="nb">test
exit</span> <span class="nv">$?</span>
<span class="k">fi</span>
</code></pre></div></div>
<p>Basically what is going on here is that I tell the TravisCI build system to check
the commit message for a command that I define in the git commit message; such as
<code class="highlighter-rouge">[fastlane deploy beta]</code>. This allows me the flexibility of being able to
dynamically select a workflow for each git commit, and then run it on a remote
continuous integration server. Running Fastlane on TravisCI takes build automation to the next level.
You won’t have to wait by your computer or keep it from sleeping to complete a build.
You can take a break because you’ll receive a notification from the TravisCI when the build completes.</p>
<h4 id="note-1">Note:</h4>
<blockquote>
<p>You may notice that I list a command <code class="highlighter-rouge">.fastlane/setupGit.sh</code>.
This script simply sets the git properties <code class="highlighter-rouge">--global user.email</code> and <code class="highlighter-rouge">--global user.name</code>.
I do this to allow TravisCI to push version bumps to my repo, however it is not
required if you do not want to push in your workflow.</p>
</blockquote>
<h2 id="configuring-the-shell-script-to-run-on-travisci">Configuring the Shell Script to run on TravisCI</h2>
<p>Getting TravisCI to run the shell script is actually very straightforward.
I call <code class="highlighter-rouge">travis.sh</code> from the <code class="highlighter-rouge">.travis.yml</code> as so:</p>
<h5 id="travisyml">travis.yml</h5>
<hr />
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>os: osx
osx_image: xcode9
language: objective-c
install:
- bundle install
script:
- ./fastlane/travis.sh
</code></pre></div></div>
<h2 id="thats-it">That’s It!</h2>
<p>That is all it takes to configure TravisCI to dynamically run Fastlane workflows.
I hope you found this post useful and I recommend you try it with your iOS Projects.</p>Fastlane is a build automation tool for both iOS and Android developed by Felix Krause. Its tooling provides the ability to automate screenshots, run tests, manage code-signing, distribute beta builds, publish applications, and so much more. Needless to say, it is a toolset that any mobile developer should become familiar with. In this blog post, I am going to talk about how I use Fastlane and TravisCI in one of my upcoming projects.Modularized, Customizable Alerts in Swift2018-03-07T20:23:21+00:002018-03-07T20:23:21+00:00https://rudybermudez.io/modularized-customizable-alerts-in-swift<p>In iOS Development, it is frequently required to provide alerts to users.
This is a code snippet that I developed that I now use in all of my applications.
Feel free to use and adapt it to your needs.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">import</span> <span class="kt">UIKit</span>
<span class="cm">/** Easily Create, Customize, and Present an UIAlertController on a UIViewController
- Parameters:
- target: The instance of a UIViewController that you would like to present the UIAlertController upon.
- title: The `title` for the UIAlertController.
- message: Optional `message` field for the UIAlertController. nil by default
- style: The `preferredStyle` for the UIAlertController. UIAlertControllerStyle.alert by default
- actionList: A list of `UIAlertAction`. If no action is added, `[UIAlertAction(title: "OK", style: .default, handler: nil)]` will be added.
*/</span>
<span class="kd">func</span> <span class="nf">showAlert</span><span class="p">(</span><span class="nv">target</span><span class="p">:</span> <span class="kt">UIViewController</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="o">=</span> <span class="kc">nil</span><span class="p">,</span> <span class="nv">style</span><span class="p">:</span> <span class="kt">UIAlertControllerStyle</span> <span class="o">=</span> <span class="o">.</span><span class="n">alert</span><span class="p">,</span> <span class="nv">actionList</span><span class="p">:[</span><span class="kt">UIAlertAction</span><span class="p">]</span> <span class="o">=</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="k">default</span><span class="p">,</span> <span class="nv">handler</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)]</span> <span class="p">)</span> <span class="p">{</span>
<span class="kt">DispatchQueue</span><span class="o">.</span><span class="n">main</span><span class="o">.</span><span class="n">async</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="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">actionList</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="n">action</span><span class="p">)</span>
<span class="p">}</span>
<span class="c1">// Check to see if the target viewController current is currently presenting a ViewController</span>
<span class="k">if</span> <span class="n">target</span><span class="o">.</span><span class="n">presentedViewController</span> <span class="o">==</span> <span class="kc">nil</span> <span class="p">{</span>
<span class="n">target</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="nv">completion</span><span class="p">:</span> <span class="kc">nil</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>In iOS Development, it is frequently required to provide alerts to users. This is a code snippet that I developed that I now use in all of my applications. Feel free to use and adapt it to your needs.