Jekyll2022-03-24T12:45:36+00:00http://dpgao.cn/feed.xmldpgao.github.ioIf— (for Programmers)2016-08-11T00:00:00+00:002016-08-11T00:00:00+00:00http://dpgao.cn/archive/if-for-programmers<p>If you can keep your HEAD when all about you<br />
Are losing theirs and blaming it on you,<br />
If you can trust yourself when all men doubt you,<br />
But make allowance for their doubting too;<br />
If you can wait and not be tired by waiting,<br />
Or, being locked away, don’t deal in locks,<br />
Or, being raced, don’t give way to racing,<br />
And yet don’t loop too good, nor thread too wise:</p>
<!-- excerpt -->
<p>If you can branch—and not make branches your master;<br />
If you can commit—and not make commits your aim;<br />
If you can meet with pass and fail<br />
And treat those two test cases just the same;<br />
If you can bear to watch the code you’ve written<br />
Twisted by knaves on Stack Overflow,<br />
Or watch the things you gave your life to broken,<br />
And stoop and build ‘em up with debugging tools:</p>
<p>If you can make one heap of all your memory<br />
And leak it on one turn of pitch-and-toss,<br />
And crash, and start again at your beginnings<br />
And never breathe a word about your loss;<br />
If you can force your heart and nerve and sinew<br />
To serve your turn long after they are gone,<br />
And so hold on when there is NULL in you<br />
Except the Coffee which says to them: ‘Hold on!’</p>
<p>If you can write PHP and keep your virtue,<br />
Or talk in Lisp—nor lose the common touch,<br />
If neither monads nor functors can hurt you,<br />
If all bugs count with you, but none too much;<br />
If you can fill the unforgiving second<br />
With a thousand milliseconds’ worth of instructions run,<br />
Yours is the Machine and everything that’s in it,<br />
And—which is more—you’ll be a Programmer, my son!</p>If you can keep your HEAD when all about you Are losing theirs and blaming it on you, If you can trust yourself when all men doubt you, But make allowance for their doubting too; If you can wait and not be tired by waiting, Or, being locked away, don’t deal in locks, Or, being raced, don’t give way to racing, And yet don’t loop too good, nor thread too wise:Asynchronous Image Downloading with Caching in Swift2016-06-17T00:00:00+00:002016-06-17T00:00:00+00:00http://dpgao.cn/archive/asynchronous-image-downloading-with-caching-in-swift<p>Downloading images asynchronously is <a href="https://www.natashatherobot.com/how-to-download-images-asynchronously-and-make-your-uitableview-scroll-fast-in-ios/">hard</a> due to complexities involving concurrency, speed, and user experience. In this post, I will implement a simple yet effective image downloader with memory and disk caches.</p>
<!-- excerpt -->
<h3 id="structure-of-the-class">Structure of the Class</h3>
<p>The downloader consists of a class named <code>ImageDownloader</code>. It provides a method that asynchronously downloads an image with a given URL and calls a handler when the task is completed. Under the hood, this class is also responsible for scheduling incoming download tasks and caching downloaded images.</p>
<div id="gist1">
<script>embed_gist('71d9be43c972a9e4baeba2e0e0b2eea8', '', [[0,1],[2,3],[60,61],[81,82],[82,83],], 'gist1');</script>
</div>
<p>The rest of this post will be organised into three parts, each dealing with one important feature of the downloader.</p>
<ol>
<li><a href="#memory-and-disk-caching">Memory and disk caching</a></li>
<li><a href="#lifo-task-scheduling">LIFO task scheduling</a></li>
<li><a href="#duplicate-requests-coalescing">Duplicate requests coalescing</a></li>
</ol>
<h3 id="memory-and-disk-caching">Memory and Disk Caching</h3>
<p>For simplicity, the downloader uses the <code>NSURLCache</code> class provided by Apple. During initialisation, we create the cache with the specified memory and disk capacities (in bytes). We then assign it to an <code>NSURLSessionConfiguration</code> object with which we create a custom <code>NSURLSession</code> object.</p>
<div id="gist2">
<script>embed_gist('71d9be43c972a9e4baeba2e0e0b2eea8', '', [[18,31],], 'gist2');</script>
</div>
<p>Note that we set the <code>requestCachePolicy</code> of the configuration object to <code>.ReturnCacheDataElseLoad</code>. This enables more aggressive caching and is optional.</p>
<p>From now on, whenever you start a data task using this custom <code>NSURLSession</code> object, the response will be cached if it meets the conditions outlined in the <a href="https://developer.apple.com/reference/foundation/urlsessiondatadelegate/1411612-urlsession">documentation</a>.</p>
<h3 id="lifo-task-scheduling">LIFO Task Scheduling</h3>
<p>By default, network tasks are treated in a FIFO fashion—earlier requests get processed first. LIFO scheduling, however, gives the latest request the highest priority. Why do we need such a behaviour?</p>
<p>Imaging an app that displays rows of images downloaded from the Internet. As the user scrolls down, more and more images are fetched from the server to populate the rows. Assume that the app initially displays rows 1 to 5, and the user quickly scrolls to rows 15 to 20. With FIFO task scheduling, the app has to finish downloading the first five rows of images before it can move on to what the user most urgently wants, causing high latency. LIFO task scheduling solves this problem: the images that are immediately visible to the user are always downloaded first, delivering a responsive experience.</p>
<p>Of course, the scheduler itself needs to be efficient, and the key to this is the data structure we choose to store the tasks. Referring our needs, we know that it should have the following characteristics:</p>
<ol>
<li>Ordered</li>
<li>Fast insertion</li>
<li>Fast removal</li>
<li>Fast lookup</li>
</ol>
<p>Dictionaries and sets are ruled out instantly, because they are unordered collections. Neither can we use arrays, because arbitrary lookup and removals run at <code>O(n)</code>. The obscure <code>NSMutableOrderedSet</code> seems like a good candidate—it is a hybrid between a set and an array, providing the best of both worlds. Of course, these benefits come at the cost of higher memory usage, but it should rarely be a concern nowadays.</p>
<p>Let’s create an <code>NSMutableOrderedSet</code> instance and add it as a property.</p>
<div id="gist3">
<script>embed_gist('71d9be43c972a9e4baeba2e0e0b2eea8', '', [[11,12],], 'gist3');</script>
</div>
<p>We also need a counter to keep track of the number of active tasks.</p>
<div id="gist4">
<script>embed_gist('71d9be43c972a9e4baeba2e0e0b2eea8', '', [[12,13],], 'gist4');</script>
</div>
<p>Since these variables will be mutated on multiple threads, a lock is used for protection.</p>
<div id="gist5">
<script>embed_gist('71d9be43c972a9e4baeba2e0e0b2eea8', '', [[9,10],], 'gist5');</script>
</div>
<p>Here comes the complicated part. To ensure that the scheduler works correctly, we have to state the invariants in our data structure:</p>
<ol>
<li><code>dataTasks[0..<activeDataTaskCount]</code> are tasks that are actively running. The rest of the tasks are in a suspended state.</li>
<li><code>activeDataTaskCount</code> falls in the range <code>0...min(dataTasks.count, maxActiveDataTaskCount)</code> and should be kept as large as possible within this range.</li>
</ol>
<p>To achieve LIFO behaviour, new data tasks should be inserted at index 0. After insertion, we also have to increment <code>activeDataTaskCount</code>.</p>
<div id="gist6">
<script>embed_gist('71d9be43c972a9e4baeba2e0e0b2eea8', '', [[32,36],], 'gist6');</script>
</div>
<p>Then we run a loop to trim down the number of active tasks so that the invariants are kept.</p>
<div id="gist7">
<script>embed_gist('71d9be43c972a9e4baeba2e0e0b2eea8', '', [[36,41],], 'gist7');</script>
</div>
<p>Removing a task is a bit trickier. Since we do not know whether the task we are about to remove is active or not, a check must be performed. If the task is active, we have to decrement <code>activeDataTaskCount</code> before removing it.</p>
<div id="gist8">
<script>embed_gist('71d9be43c972a9e4baeba2e0e0b2eea8', '', [[42,47],], 'gist8');</script>
</div>
<p>Then we run a loop to ensure that <code>activeDataTaskCount</code> is as large as possible.</p>
<div id="gist9">
<script>embed_gist('71d9be43c972a9e4baeba2e0e0b2eea8', '', [[47,52],], 'gist9');</script>
</div>
<p>Voila! This is all you need to implement a LIFO task scheduler.</p>
<h3 id="duplicate-requests-coalescing">Duplicate Requests Coalescing</h3>
<p>Returning to our hypothetical app, an astute reader may discover that if the user scrolls back up while the images there are still loading, duplicate network requests for the same URL will be issued, generating unnecessary traffic. Hence we need a way to detect such requests and combine them with the original one.</p>
<p>But this coalescing behaviour violates our LIFO rule: if a new, duplicate request is combined into an old one, it will not gain the highest priority! A direct way to solve this is to “promote” the data task to the front of the queue.</p>
<div id="gist10">
<script>embed_gist('71d9be43c972a9e4baeba2e0e0b2eea8', '', [[53,59],], 'gist10');</script>
</div>
<p>In order to reduce overhead, however, the above implementation deviates from the prescribed behaviour in that it does nothing if the task is already actively running.</p>
<p>Back to the issue. When talking about combining network requests, what we really mean is the ability to modify the completion handler of a data task on the fly. We can achieve this by adding another level of indirection—a helper struct <code>ResponseHandler</code> that stores a data task and all the completion handlers it is going to execute upon completion.</p>
<div id="gist11">
<script>embed_gist('71d9be43c972a9e4baeba2e0e0b2eea8', '', [[4,8],], 'gist11');</script>
</div>
<p>We also need another property in <code>ImageDownloader</code> that maps a URL to a <code>ResponseHandler</code>.</p>
<div id="gist12">
<script>embed_gist('71d9be43c972a9e4baeba2e0e0b2eea8', '', [[13,14],], 'gist12');</script>
</div>
<p>Upon receiving a new request, check the <code>responseHandlers</code> dictionary first to find out whether this is a duplicate request. If yes, simply extract the <code>ResponseHandler</code> instance, promote the data task, and append the completion handler.</p>
<div id="gist13">
<script>embed_gist('71d9be43c972a9e4baeba2e0e0b2eea8', '', [[60,67],], 'gist13');</script>
</div>
<p>Otherwise, we need to create a new data task. In its completion handler, extract the dictionary entry for the URL, remove the task from the queue, and execute all the completion handlers.</p>
<div id="gist14">
<script>embed_gist('71d9be43c972a9e4baeba2e0e0b2eea8', '', [[67,76],], 'gist14');</script>
</div>
<p>Lastly, do not forget to enqueue the data task and set the dictionary entry for the URL.</p>
<div id="gist15">
<script>embed_gist('71d9be43c972a9e4baeba2e0e0b2eea8', '', [[76,82],], 'gist15');</script>
</div>
<p>And here ends the entire implementation of <code>ImageDownloader</code>.</p>
<h3 id="conclusion">Conclusion</h3>
<p>Admittedly, <code>ImageDownloader</code> has several limitations such as the inability to cancel a request. Nevertheless, we can still see that implementing asynchronous image downloading is, at least on a conceptual level, a fun exercise.</p>Downloading images asynchronously is hard due to complexities involving concurrency, speed, and user experience. In this post, I will implement a simple yet effective image downloader with memory and disk caches.Flattening the “Pyramid of Doom” in Swift with Custom Operators2015-08-18T00:00:00+00:002015-08-18T00:00:00+00:00http://dpgao.cn/archive/flattening-the-pyramid-of-doom-in-swift-with-custom-operators<p><strong>Update</strong>: The solution in this post is effectively obsoleted by the <code>guard</code> statement available since Swift 2.</p>
<p>Swift 1.2 introduces a feature that allows us to condense nested <code>if</code> statements into a single one, eliminating the so-called “<a href="https://en.wikipedia.org/wiki/Pyramid_of_doom_(programming)">pyramid of doom</a>”. However, there are still a few edge cases not covered by such an improvement, one of which appears in the <a href="https://chris.eidhof.nl/post/tiny-networking-in-swift/">Tiny Networking</a> library by Chris Eidhof:</p>
<div id="gist1">
<script>embed_gist('26bda788f13b3e8a279c', 'tiny-networking.swift', [[41,60],], 'gist1');</script>
</div>
<p>What prevents us from using a single <code>if</code> statement is the fact that there is a corresponding <code>else</code> after each <code>if</code>. In fact, this is a very common pattern in data serialisation and error handling, so we need a more elegant way to represent it.</p>
<!-- excerpt -->
<h3 id="solution">Solution</h3>
<p>We start out by defining an operator:</p>
<div id="gist2">
<script>embed_gist('799521f251119ce8bbdf832a1ca1dbb8', 'pyramid-of-doom-1.swift', [], 'gist2');</script>
</div>
<p>The <code>rhs</code> parameter of this operator is a closure that acts exactly like an <code>else</code> block—it will only be executed if <code>lhs</code> is <code>nil</code>. Here is an example:</p>
<div id="gist3">
<script>embed_gist('799521f251119ce8bbdf832a1ca1dbb8', 'pyramid-of-doom-2.swift', [], 'gist3');</script>
</div>
<p>The operator we defined, having higher precedence than the assignment operator, will be evaluated before assignment takes place. The implementation first checks <code>lhs</code> to decide whether the closure should be called or not. In the above example, the first string is unwrapped successfully, so its corresponding closure is not called. However, the second string is <code>nil</code>, which triggers the execution of its accompanying closure and causes the entire condition to fail.</p>
<p>Similarly, we can overload this operator to handle Boolean values:</p>
<div id="gist4">
<script>embed_gist('799521f251119ce8bbdf832a1ca1dbb8', 'pyramid-of-doom-3.swift', [], 'gist4');</script>
</div>
<p>We can even add the <code>@autoclosure</code> attribute to the <code>rhs</code> parameter so that you can pass in an expression without explicit curly braces around it:</p>
<div id="gist5">
<script>embed_gist('799521f251119ce8bbdf832a1ca1dbb8', 'pyramid-of-doom-4.swift', [], 'gist5');</script>
</div>
<p>Equipped with the new operator, we can now rewrite the code at the beginning of this article as follows. Note that you can chain expressions together with the operator to avoid using any curly braces, as shown in the <code>where</code> clause below:</p>
<div id="gist6">
<script>embed_gist('799521f251119ce8bbdf832a1ca1dbb8', 'pyramid-of-doom-5.swift', [], 'gist6');</script>
</div>
<p>As you can see, by defining a simple operator, we can completely flatten the “pyramid of doom” and make our code much more readable.</p>Update: The solution in this post is effectively obsoleted by the guard statement available since Swift 2. Swift 1.2 introduces a feature that allows us to condense nested if statements into a single one, eliminating the so-called “pyramid of doom”. However, there are still a few edge cases not covered by such an improvement, one of which appears in the Tiny Networking library by Chris Eidhof: What prevents us from using a single if statement is the fact that there is a corresponding else after each if. In fact, this is a very common pattern in data serialisation and error handling, so we need a more elegant way to represent it.