Crunchy Development Making your Swift code more fun 🎉, magical ✨ and crunchier 👌 en-us https://alisoftware.github.io/ YAML Superpowers, part 2: Multiline Strings <p>This is part 2 of a series of post about lesser-known features of YAML – especially the ones useful in contexts like CI or tools’ config files. In this post we will discover the many ways YAML can represent strings, including multiline strings, keeping or stripping indentation, and more!</p> <p class="small"><a href="/yaml/2021/08/17/yaml-part1-json">Missed part 1? head here!</a></p> <h2 id="simple-strings-flow-scalars">Simple strings (“Flow” scalars)</h2> <p>Let’s start easy with single-line strings. It’s already worth noting that, in YAML:</p> <ul> <li>When using double quotes <code class="language-plaintext highlighter-rouge">""</code> around your strings, it allows you to also escape some special characters using the <code class="language-plaintext highlighter-rouge">\</code> sigil inside that string (see below).</li> <li>When you use single quotes <code class="language-plaintext highlighter-rouge">''</code>, the string won’t be escaped. This means that every character will be interpreted verbatim – including <code class="language-plaintext highlighter-rouge">\</code>. The only exception is if you need to include a literal <code class="language-plaintext highlighter-rouge">'</code> inside a single-quoted string, in which case you can repeat it (i.e. use <code class="language-plaintext highlighter-rouge">''</code> inside the single-quoted string)</li> <li>Quotes can be omitted around strings, unless it’s ambiguous to do so. Omiting quotes is the same as using single quotes (i.e. characters won’t be escaped)</li> </ul> <p>When you use double-quotes, you can then use escape codes for special characters, like the following ones:</p> <ul> <li><code class="language-plaintext highlighter-rouge">"\x12"</code>, <code class="language-plaintext highlighter-rouge">"\u1234"</code> or <code class="language-plaintext highlighter-rouge">"\U00102030"</code> for 8, 16 and 32 bits unicode codepoints respectively</li> <li><code class="language-plaintext highlighter-rouge">"\\"</code> (same as unescaped <code class="language-plaintext highlighter-rouge">'\'</code>) and <code class="language-plaintext highlighter-rouge">"\""</code> (same as <code class="language-plaintext highlighter-rouge">'"'</code>)</li> <li><code class="language-plaintext highlighter-rouge">"\r"</code> and <code class="language-plaintext highlighter-rouge">"\n"</code> for CR and LF, respectively, and <code class="language-plaintext highlighter-rouge">"\t"</code> for tab</li> <li><code class="language-plaintext highlighter-rouge">"\_"</code> for a non-breaking space (NBSP)</li> <li>You can also put a <code class="language-plaintext highlighter-rouge">\</code> at the end of a line (before your newline character) to escape that newline and thus ignore it. This is a nice trick for manually wrapping long strings, though the block scalar syntax below is usually way nicer to use.</li> <li>And some more (see the spec or <a href="http://yaml.org/refcard.html">refcard</a>)</li> </ul> <p>This means that the strings in the following array of strings are all valid:</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="pi">-</span> <span class="s">Hello</span> <span class="pi">-</span> <span class="s1">'</span><span class="s">Hello'</span> <span class="pi">-</span> <span class="s2">"</span><span class="s">Hello"</span> <span class="pi">-</span> <span class="s1">'</span><span class="s">"Hello",</span><span class="nv"> </span><span class="s">they</span><span class="nv"> </span><span class="s">said.'</span> <span class="pi">-</span> <span class="s1">'</span><span class="s">#</span><span class="nv"> </span><span class="s">this</span><span class="nv"> </span><span class="s">is</span><span class="nv"> </span><span class="s">not</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">comment</span><span class="nv"> </span><span class="s">but</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">string'</span> <span class="pi">-</span> <span class="s2">"</span><span class="s">Smile</span><span class="nv"> </span><span class="se">\u263A</span><span class="s">"</span> <span class="pi">-</span> <span class="s2">"</span><span class="s">column1</span><span class="se">\t</span><span class="s">column2</span><span class="se">\t</span><span class="s">column3</span><span class="se">\n</span><span class="s">"</span> <span class="pi">-</span> <span class="s2">"</span><span class="se">\x0d\x0a</span><span class="nv"> </span><span class="s">is</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">same</span><span class="nv"> </span><span class="s">as</span><span class="nv"> </span><span class="se">\r\n</span><span class="s">,</span><span class="nv"> </span><span class="s">namely</span><span class="nv"> </span><span class="s">CRLF"</span> <span class="pi">-</span> <span class="s1">'</span><span class="s">Single</span><span class="nv"> </span><span class="s">quotes</span><span class="nv"> </span><span class="s">means</span><span class="nv"> </span><span class="s">\</span><span class="nv"> </span><span class="s">is</span><span class="nv"> </span><span class="s">not</span><span class="nv"> </span><span class="s">an</span><span class="nv"> </span><span class="s">escape</span><span class="nv"> </span><span class="s">character</span><span class="nv"> </span><span class="s">¯\_(ツ)_/¯'</span> <span class="pi">-</span> <span class="s1">'</span><span class="s">Except</span><span class="nv"> </span><span class="s">there'</span><span class="s1">'</span><span class="s">s</span><span class="nv"> </span><span class="s">still</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">way</span><span class="nv"> </span><span class="s">to</span><span class="nv"> </span><span class="s">include</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">single</span><span class="nv"> </span><span class="s">quote</span><span class="nv"> </span><span class="s">character</span><span class="nv"> </span><span class="s">into</span><span class="nv"> </span><span class="s">those.'</span> </code></pre></div></div> <h2 id="multi-line-blocks-and-long-strings">Multi-line blocks and long strings</h2> <p>YAML also supports writing strings on multiple lines in your YAML, with two different styles: literal and folded. Those are represented by a <code class="language-plaintext highlighter-rouge">|</code> (for literal style) or <code class="language-plaintext highlighter-rouge">&gt;</code> (for folded style) header line (see examples below), and we will go into their differences soon.</p> <p>But first, note that any leading indentation in those multi-line blocks is stripped by YAML, so you don’t have to worry about it; in fact, let’s talk a bit about indentation first before going further.</p> <h3 id="indentation-in-multi-line-blocks">Indentation in multi-line blocks</h3> <p>In a multi-line block, YAML first determines the indentation level of the whole block:</p> <ul> <li>Either by guessing it from the number of leading spaces of the first non-empty line, if you don’t specify anything (most common case)</li> <li>Or by using the explicit value you provide after the <code class="language-plaintext highlighter-rouge">|</code> or <code class="language-plaintext highlighter-rouge">&gt;</code>, to indicate the number of <em>additional</em> spaces compared to the parent node.</li> </ul> <p>The leading spaces corresponding to (implicit or explicit) indentation are then ignored in the final interpreted string.</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">multi-line-yaml-blocks</span><span class="pi">:</span> <span class="na">examples</span><span class="pi">:</span> <span class="na">string1</span><span class="pi">:</span> <span class="pi">&gt;</span> <span class="s">This is a long string, where the indentation</span> <span class="s">was guessed to be 6 leading spaces (aka 2 more</span> <span class="s">leading spaces than the parent node). Also note that</span> <span class="s">this long string will be folded into a single-line string with no newline.</span> <span class="na">string2</span><span class="pi">:</span> <span class="pi">&gt;2</span> <span class="s">This is another long string, where the indentation was explicitly</span> <span class="s">indicated to be 2 more spaces than the parent node.</span> <span class="s">Which means that the 1st and 3rd lines, which happens to start with</span> <span class="s">5 additional leading spaces relative to the parent node's existing indentation,</span> <span class="s">will start with 3 literal space characters (as the first 2 are the indentation).</span> </code></pre></div></div> <p>In most cases you’ll use implicit indentation (I’ve rarely seen the explicit indentation value being used in concrete use cases to be honest), but it’s good to know you can make it explicit if needed, especially if your string is supposed to start with spaces that are not to be interpreted as indentation.</p> <h3 id="literal-style---keep-the-newlines">Literal style: <code class="language-plaintext highlighter-rouge">|</code> – keep the newlines</h3> <p>When using the block syntax, the literal style, denoted by <code class="language-plaintext highlighter-rouge">|</code> header, is the simplest.</p> <p>As the name suggests, it keeps the content as is, including rendering newlines as actual newlines in the end content. The only thing that is stripped is the leading indentation.</p> <p>This is most useful when you want to embed strings with multiple lines in your YAML; a typical example being the content of a bash script with multiple commands defined in a step of your CI config file.</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">steps</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">label</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Build</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">app"</span> <span class="na">key</span><span class="pi">:</span> <span class="s2">"</span><span class="s">build"</span> <span class="na">command</span><span class="pi">:</span> <span class="pi">|</span> <span class="s">echo "--- Install gems"</span> <span class="s">bundle install</span> <span class="s">echo "--- Build the app"</span> <span class="s">bundle exec fastlane build</span> </code></pre></div></div> <h3 id="folded-style---fold-the-newlines-into-spaces">Folded style: <code class="language-plaintext highlighter-rouge">&gt;</code> – fold the newlines into spaces</h3> <p>On the other hand, the folded style, denoted by a <code class="language-plaintext highlighter-rouge">&gt;</code> header, replaces newlines by a space character – unless it ends an empty or more-indented line.</p> <p>This is very useful when you want to break long lines of text into smaller ones (i.e. manual hard-wrapping) for readability. Note that empty (or all-spaces) lines, as well as more-indented lines, are not affected by this folding.</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">notify</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">slack</span><span class="pi">:</span> <span class="na">channels</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">#build-notifs"</span><span class="pi">]</span> <span class="na">message</span><span class="pi">:</span> <span class="pi">&gt;</span> <span class="s">Your build have failed. You might want to check your</span> <span class="s">CI logs for more details about the failure, or ping</span> <span class="s">your friendly neighbourhood Infrastructure Engineer</span> <span class="s">on call to ask for help.</span> <span class="na">if</span><span class="pi">:</span> <span class="s">build.state == "failed"</span> </code></pre></div></div> <h3 id="chomp-mode">Chomp mode</h3> <p>In addition to the style (literal <code class="language-plaintext highlighter-rouge">|</code> or folded <code class="language-plaintext highlighter-rouge">&gt;</code>), and optional explicit indentation number, you can also specify a chomp mode in the block header. The chomp mode defines how YAML will interpret trailing newlines and spaces at the end of your block, and can be one of the following:</p> <ul> <li>“Clip” mode: this is the default behavior, the one used when you don’t specify any specific chomp indicator in your header. In this mode, the final newline is preserved, but any additional trailing empty line are ignored.</li> <li>“Strip” mode: indicated by a <code class="language-plaintext highlighter-rouge">-</code> in the block header, this will strip not only trailing empty lines like in clip mode, but also the final newline at the end of the string.</li> <li>“Keep” mode: indicated by a <code class="language-plaintext highlighter-rouge">+</code> in the block header, this will keep both the final newline and any potential trailing empty lines too.</li> </ul> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">examples</span><span class="pi">:</span> <span class="na">clip</span><span class="pi">:</span> <span class="pi">&gt;</span> <span class="s">This content will end with a LF character</span> <span class="s">but not include the final empty lines.</span> <span class="na">strip</span><span class="pi">:</span> <span class="pi">&gt;-</span> <span class="s">This content will neither contain a trailing LF character</span> <span class="s">nor the trailing empty line.</span> <span class="na">keep</span><span class="pi">:</span> <span class="pi">&gt;+</span> <span class="s">This content will keep both the LF</span> <span class="s">and the trailing empty lines.</span> <span class="na">equivalent-output</span><span class="pi">:</span> <span class="na">clip</span><span class="pi">:</span> <span class="s2">"</span><span class="s">This</span><span class="nv"> </span><span class="s">content</span><span class="nv"> </span><span class="s">will</span><span class="nv"> </span><span class="s">end</span><span class="nv"> </span><span class="s">with</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">LF</span><span class="nv"> </span><span class="s">character</span><span class="nv"> </span><span class="s">but</span><span class="nv"> </span><span class="s">not</span><span class="nv"> </span><span class="s">include</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">final</span><span class="nv"> </span><span class="s">empty</span><span class="nv"> </span><span class="s">lines.</span><span class="se">\n</span><span class="s">"</span> <span class="na">strip</span><span class="pi">:</span> <span class="s2">"</span><span class="s">This</span><span class="nv"> </span><span class="s">content</span><span class="nv"> </span><span class="s">will</span><span class="nv"> </span><span class="s">neither</span><span class="nv"> </span><span class="s">contain</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">trailing</span><span class="nv"> </span><span class="s">LF</span><span class="nv"> </span><span class="s">character</span><span class="nv"> </span><span class="s">nor</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">trailing</span><span class="nv"> </span><span class="s">empty</span><span class="nv"> </span><span class="s">line."</span> <span class="na">keep</span><span class="pi">:</span> <span class="s2">"</span><span class="s">This</span><span class="nv"> </span><span class="s">content</span><span class="nv"> </span><span class="s">will</span><span class="nv"> </span><span class="s">keep</span><span class="nv"> </span><span class="s">both</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">LF</span><span class="nv"> </span><span class="s">and</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">trailing</span><span class="nv"> </span><span class="s">empty</span><span class="nv"> </span><span class="s">lines.</span><span class="se">\n\n\n</span><span class="s">"</span> </code></pre></div></div> <h3 id="optional-block-comment">Optional block Comment</h3> <p>So every block syntax starts with a header (with <code class="language-plaintext highlighter-rouge">|</code> or <code class="language-plaintext highlighter-rouge">&gt;</code>, optional indentation number, and optional chomp indicator). The only other thing that is also allowed on that header line is a single-line <code class="language-plaintext highlighter-rouge"># comment</code>; the rest (the content of the block literal) has to start on the next line.</p> <p>Adding a single-line comment can be nice to provide some context:</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">notify</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">slack</span><span class="pi">:</span> <span class="na">channels</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">#build-notifs"</span><span class="pi">]</span> <span class="na">message</span><span class="pi">:</span> <span class="pi">&gt;</span> <span class="c1"># This message will be folded, i.e. newlines will be replaced by spaces.</span> <span class="s">Your build have failed. You might want to check your</span> <span class="s">CI logs for more details about the failure, or ping</span> <span class="s">your friendly neighbourhood Infrastructure Engineer</span> <span class="s">on call to ask for help.</span> <span class="na">if</span><span class="pi">:</span> <span class="s">build.state == "failed"</span> </code></pre></div></div> <h2 id="conclusion">Conclusion</h2> <p>And here you thought strings would be a pretty simple topic in YAML… 😅</p> <p>In this post we saw how YAML allows us to easily represent long strings, as well as strings containing special characters and newlines, without having to escape all the newlines manually, and making those long, potentially-multiline strings to be way more readable.</p> <p>I hope you learned something and you will be able to put this to good use in your YAML config files 😊</p> <p>Next up will be an even more powerful and lesser-known concept of YAML: anchors and aliases. Meet me in part 3 to discover how you can avoid repeating yourself, and how to mutualise common, repeated values in your YAML files!</p> Thu, 19 Aug 2021 00:00:00 +0000 https://alisoftware.github.io/yaml/2021/08/19/yaml-part2-strings/ https://alisoftware.github.io/yaml/2021/08/19/yaml-part2-strings/ YAML Superpowers, part 1: JSON is YAML <p>This post is part 1 of a series of posts where I plan to focus on little-known features of YAML like multiline string processing, aliases and anchors, base64 support, tags and more.</p> <h2 id="why-all-those-posts-yaml-is-not-that-complicated">Why all those posts? YAML is not that complicated!</h2> <p>Amongst other things, YAML is widely used as a configuration format for different tools, including configuration files for tools like <a href="https://github.com/realm/SwiftLint">SwiftLint</a> or <a href="https://github.com/SwiftGen/SwiftGen">SwiftGen</a>, but also notoriously to configure most of the Continuous Integration providers, like <a href="https://github.com/features/actions">GitHub Actions</a>, <a href="https://circleci.com">CircleCI</a>, <a href="https://buildkite.com">BuildKite</a>, and many more.</p> <p>But most people only know the basics of YAML, while <strong>many less-known features of this format are powerful and could prove very useful</strong> to improve your config files readability and ease of maintenance.</p> <p>In this series, I’ll focus on examples related to configuring a CI like BuildKite (because that’s what we are currently migrating to where I work at <a href="https://automattic.com">Automattic</a>) but those features apply to other CI providers and use cases of YAML.</p> <h3 id="back-to-basics">Back to Basics</h3> <p>I’m sure most of you already know the basics of the YAML syntax; they are not that complicated after all. But even about the basics, it’s worth making sure we’re all on the same page first:</p> <ul> <li>YAML is a format to represent structured data. It can especially represent dictionaries (what YAML calls “maps”), lists/arrays (what YAML calls “sequences”), and literals (like strings, numbers, booleans, …)</li> <li>One thing that most people might not know is that <strong>YAML is a superset of JSON</strong>. This means that any JSON is a valid YAML file! YAML just extends the JSON syntax to provide more features (like comments etc) and alternatives to represent the same data structures.</li> </ul> <p>For example, in YAML, you often see a list (aka “sequence” in YAML parlance) represented like this:</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="pi">-</span> <span class="s2">"</span><span class="s">item1"</span> <span class="pi">-</span> <span class="s2">"</span><span class="s">item2"</span> <span class="pi">-</span> <span class="s2">"</span><span class="s">item3"</span> <span class="pi">-</span> <span class="s2">"</span><span class="s">etc"</span> </code></pre></div></div> <p>But did you know you could also represent a list using the JSON “square brackets” syntax too?</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">[</span><span class="s2">"</span><span class="s">item1"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">item2"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">item3"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">etc"</span><span class="pi">]</span> </code></pre></div></div> <p>That’s right, that syntax is the same as what you use to represent arrays in JSON. And it’s no coincidence, because JSON is a subset of YAML, so that’s also valid YAML, and both syntaxes are interchangeable in YAML!</p> <p>Likewise, you often see dictionaries (aka “maps” in YAML parlance) represented like this in most YAML files:</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s2">"</span><span class="s">key1"</span><span class="pi">:</span> <span class="s2">"</span><span class="s">value1"</span> <span class="s2">"</span><span class="s">key2"</span><span class="pi">:</span> <span class="s2">"</span><span class="s">value2"</span> <span class="s2">"</span><span class="s">key3"</span><span class="pi">:</span> <span class="s2">"</span><span class="s">value3"</span> </code></pre></div></div> <p>But another, totally valid way to represent a map in YAML is to use this alternative syntax, which is the syntax you’re already used to in JSON:</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">{</span> <span class="s2">"</span><span class="s">key1"</span><span class="pi">:</span> <span class="s2">"</span><span class="s">value1"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">key2"</span><span class="pi">:</span> <span class="s2">"</span><span class="s">value2"</span><span class="pi">,</span> <span class="s2">"</span><span class="s">key3"</span><span class="pi">:</span> <span class="s2">"</span><span class="s">value3"</span> <span class="pi">}</span> </code></pre></div></div> <p>Both those syntaxes represent the same thing and are interchangeable. You can even write your file with purely JSON-compatible syntax and add it a <code class="language-plaintext highlighter-rouge">.yml</code> extension, and that would be accepted by any YAML parser. Just try it: take any JSON file that you might have around, and paste its content to <a href="http://www.yamllint.com">a YAML linter</a> and it will gladly accept it!</p> <p>What makes YAML usually more attractive as a config file format over JSON is that those alternative syntaxes are meant to make the structure more human-readable than JSON (by representing lists a bit like bullet points, etc), while JSON is meant to be more machine-oriented.</p> <p>The fact that it also allows adding comments, unlike JSON<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>, and that quotes around strings are optional<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>, also helps make your YAML config files easier to read and write for a human.</p> <h3 id="a-concrete-example-a-ci-config">A concrete example: a CI config</h3> <p>At that point I feel it can also be useful to take a concrete example, especially because when used in the context of CI config files, YAML structures can become a bit more complex and specific.</p> <p>For example, it’s common in most CIs to have YAML nodes that appear as “lists of <strong>single-key dictionaries</strong>”, which might be confusing at first – and not always straighforward to realize what those really are at first glance. Those look like regular dictionaries, but are not. This pattern of “sequence of single-key dictionaries” is in fact the way YAML represents an <strong>ordered map</strong> (while a regular dictionary/map is unordered by definition). Here’s an example:</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">steps</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">label</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Build</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">app"</span> <span class="na">key</span><span class="pi">:</span> <span class="s">build</span> <span class="na">plugins</span><span class="pi">:</span> <span class="pi">-</span> <span class="s">automattic/bash-cache#v1.5.0</span><span class="pi">:</span> <span class="na">bucket</span><span class="pi">:</span> <span class="s2">"</span><span class="s">a8c-cache"</span> <span class="pi">-</span> <span class="s">automattic/git-s3-cache#v1.1.0</span><span class="pi">:</span> <span class="na">bucket</span><span class="pi">:</span> <span class="s2">"</span><span class="s">a8c-repo-mirrors"</span> <span class="na">repo</span><span class="pi">:</span> <span class="s2">"</span><span class="s">wordpress-mobile/wordpress-ios/"</span> <span class="na">env</span><span class="pi">:</span> <span class="na">IMAGE_ID</span><span class="pi">:</span> <span class="s">xcode-12.5.1</span> <span class="na">command</span><span class="pi">:</span> <span class="s2">"</span><span class="s">bundle</span><span class="nv"> </span><span class="s">exec</span><span class="nv"> </span><span class="s">fastlane</span><span class="nv"> </span><span class="s">build_for_testing"</span> <span class="pi">-</span> <span class="na">label</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Run</span><span class="nv"> </span><span class="s">Tests"</span> <span class="na">key</span><span class="pi">:</span> <span class="s">test</span> <span class="na">plugins</span><span class="pi">:</span> <span class="pi">-</span> <span class="s">automattic/bash-cache#v1.5.0</span> <span class="pi">-</span> <span class="s">automattic/git-s3-cache#v1.1.0</span><span class="pi">:</span> <span class="na">bucket</span><span class="pi">:</span> <span class="s2">"</span><span class="s">a8c-repo-mirrors"</span> <span class="na">repo</span><span class="pi">:</span> <span class="s2">"</span><span class="s">wordpress-mobile/wordpress-ios/"</span> <span class="na">env</span><span class="pi">:</span> <span class="na">IMAGE_ID</span><span class="pi">:</span> <span class="s">xcode-12.5.1</span> <span class="na">command</span><span class="pi">:</span> <span class="s2">"</span><span class="s">bundle</span><span class="nv"> </span><span class="s">exec</span><span class="nv"> </span><span class="s">fastlane</span><span class="nv"> </span><span class="s">tests"</span> </code></pre></div></div> <p>This extract of a typical BuildKite config file<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup> defines:</p> <ul> <li>A top-level dictionary with the key <code class="language-plaintext highlighter-rouge">steps</code>. The value associated with that key is an array of 2 elements – as you can see by the two <code class="language-plaintext highlighter-rouge">-</code> that are at indentation level 2</li> <li>Each of these 2 items is a dictionary, which both happen to contain the same 5 keys: <code class="language-plaintext highlighter-rouge">label</code>, <code class="language-plaintext highlighter-rouge">key</code>, <code class="language-plaintext highlighter-rouge">plugins</code>, <code class="language-plaintext highlighter-rouge">env</code> and <code class="language-plaintext highlighter-rouge">command</code>.</li> <li>The value for the <code class="language-plaintext highlighter-rouge">label</code>, <code class="language-plaintext highlighter-rouge">key</code> and <code class="language-plaintext highlighter-rouge">command</code> keys are strings in both cases.</li> <li>The value of the <code class="language-plaintext highlighter-rouge">env</code> key is itself another dictionary (map) to define a list of environment variables for that step; in our case we only define a single <code class="language-plaintext highlighter-rouge">IMAGE_ID</code> key/env var, be we could have added more there.</li> <li>The value of the <code class="language-plaintext highlighter-rouge">plugins</code> key is what might look the most unusual, as it is such a so-called ordered map, aka an <em>array of single-key dictionaries</em>. <ul> <li>In fact, for the first “step” described in this YAML, the value for <code class="language-plaintext highlighter-rouge">plugins</code> is an array of 2 items, each of them being a dictionary with only a single key – so deep down it’s in fact 2 single-key dictionaries and not a single dictionary with 2 keys as one might think… even if in practice for all intents and purposes you will probably read it as an ordered dictionary with 2 keys for interpretation of the config file.</li> <li>The first single-key dictionary has the key <code class="language-plaintext highlighter-rouge">automattic/bash-cache#v1.5.0</code>, and its value is yet another dictionary (intended to define the “options” of that “plugin”) which happens to only have the <code class="language-plaintext highlighter-rouge">bucket:</code> key</li> <li>The second single-key dictionary has the key <code class="language-plaintext highlighter-rouge">automattic/git-s3-cache#v1.1.0</code> and its value is yet another dictionary, this time with 2 keys.</li> <li>For the second step though (the one to configure the test step), the value of the <code class="language-plaintext highlighter-rouge">plugins</code> key is in fact an array of mixed values, the first one being a single string, while the second one being a single-key dictionary like in the first step.</li> </ul> </li> </ul> <p><details><summary class="small">💡 More comfortable with JSON? Open to see the same structure using only JSON-compatible syntax.</summary> <!-- Editor's note: triple-backticks in markdown source seems to not be parsed by Jekyll's Kramdown renderer when inside a <details> node… so no syntax highlighting for this one :( --> <pre class="language-json"><code>{ "steps": [ { "label": "Build the app", "key": "build", "plugins": [ { "automattic/bash-cache#v1.5.0": { "bucket": "a8c-cache" } }, { "automattic/git-s3-cache#v1.1.0": { "bucket": "a8c-repo-mirrors", "repo": "wordpress-mobile/wordpress-ios/" } } ], "env": { "IMAGE_ID": "xcode-12.5.1" }, "command": "bundle exec fastlane build_for_testing" }, { "label": "Run Tests", "key": "test", "plugins": [ "automattic/bash-cache#v1.5.0", { "automattic/git-s3-cache#v1.1.0": { "bucket": "a8c-repo-mirrors", "repo": "wordpress-mobile/wordpress-ios/" } } ], "env": { "IMAGE_ID": "xcode-12.5.1" }, "command": "bundle exec fastlane tests" } ] }</code></pre> </details></p> <p>These 2 ways of listing the various <code class="language-plaintext highlighter-rouge">plugins</code> in a CI <code class="language-plaintext highlighter-rouge">step</code> is actually common in most CIs (this is an example from a BuildKite config file, but e.g. CircleCI has similar use cases of arrays of mixed types, with Strings and single-key dictionaries too).</p> <p>This is a common way in YAML to describe an ordered list of items (here BuildKite plugins) while allowing some of them to define “options” (by making the plugin “name” be the key of a single-key dictionary, and providing the options as the value for that key), while others might not need any option (and most CI config syntaxes allow you to use simple strings mentioning the plugin “name” for those cases instead of single-key dict with no value<sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">4</a></sup>).</p> <p>Already having your head spinning a bit with that not-so-basic example 😅? Yeah, that’s why I wanted to make sure we were all on the same page with “basic” syntax – because even that can lead to twisted YAML structures as you saw – even before going further to introduce lesser known YAML features 😉.</p> <h3 id="whats-next-advanced-features-of-yaml">What’s Next: Advanced features of YAML</h3> <p>Ready to go further? That’s the whole goal of this article series after all! 😁</p> <p>In addition to all that above, YAML also provides quite powerful features that will be the focus of the next parts of this article series 🥳. Here’s our program:</p> <ul> <li>Multiline strings (<a href="/yaml/2021/08/19/yaml-part2-strings">part 2</a>), including various ways to process indentation</li> <li>Anchors &amp; aliases (part 3), to avoid repeating yourself</li> <li>Merging dictionaries (part 4), which is especially useful when used with anchors</li> <li>Hex/Binary/Octal numbers, Booleans and the Null value (part 5)</li> <li>Tags (part 6), to add type information and help disambiguate values</li> <li>Base64 representation of binary data (part 7)</li> </ul> <p>To be honest, parts 2,3 and 4 are probably the ones that will be the most useful for CI configs, and the rest is mostly some bonus 😜. There’s even more to YAML<sup id="fnref:5" role="doc-noteref"><a href="#fn:5" class="footnote" rel="footnote">5</a></sup>, but this will hopefully already give you an extensive-enough tour to what will likely be the features you might find the most useful in the context of using YAML as config files.</p> <p>See you in <a href="/yaml/2021/08/19/yaml-part2-strings">part 2</a>!</p> <div class="footnotes" role="doc-endnotes"> <ol> <li id="fn:1" role="doc-endnote"> <p>Technically <a href="https://json5.org">JSON5</a>, which is a successor of JSON, does support comments; but the more common JSON that you’ve seen around for ages and that we still find in most places doesn’t. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:2" role="doc-endnote"> <p>This is why in most cases people omit quotes in both single string literals, but also in arrays of strings and dictionary keys. Quotes are still required/useful if disambiguation is needed, e.g. using <code class="language-plaintext highlighter-rouge">"42"</code> to represent the <em>string</em> “42” as opposed to the number 42. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:3" role="doc-endnote"> <p>Borrowed from our Wordpress-iOS source code <a href="https://github.com/wordpress-mobile/WordPress-iOS/blob/develop/.buildkite/pipeline.yml">here</a> <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:4" role="doc-endnote"> <p>In most CIs, including BuildKite, it would also be valid to use a single-key dictionary with a <code class="language-plaintext highlighter-rouge">null</code> value for items which don’t need options to be provided as values, instead of using a single String. So we could have used <code class="language-plaintext highlighter-rouge">- automattic/bash-cache#v1.5.0: null</code> as well here. But let’s keep the talk about null values for part 5 of this article series ☺️. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> <li id="fn:5" role="doc-endnote"> <p>I’ll probably not go into preprocessor directives, or defining your own tags, or putting multiple YAML documents into a single YAML file, etc… because they are way less useful to the context of using YAML as CI or tools’ config files, but feel free to search for those feature if you’re curious about YAML in general! <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> </ol> </div> Tue, 17 Aug 2021 00:00:00 +0000 https://alisoftware.github.io/yaml/2021/08/17/yaml-part1-json/ https://alisoftware.github.io/yaml/2021/08/17/yaml-part1-json/ StringInterpolation in Swift 5 — AttributedStrings <p>In <a href="/swift/2018/12/15/swift5-stringinterpolation-part1/">the previous post</a>, we introduced the new StringInterpolation design coming to Swift 5. In this second part, I’ll focus on one application of that new <code class="language-plaintext highlighter-rouge">ExpressibleByStringInterpolation</code>, to make <code class="language-plaintext highlighter-rouge">NSAttributedString</code> prettier.</p> <h2 id="the-goal">The goal</h2> <p>One of the first application I thought about when seeing that <a href="https://github.com/apple/swift-evolution/blob/master/proposals/0228-fix-expressiblebystringinterpolation.md">new StringInterpolation design in Swift 5</a> was to make it easy to build an <code class="language-plaintext highlighter-rouge">NSAttributedString</code>.</p> <p>My goal was to be able to create an attributed string using a syntax like this<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">username</span> <span class="o">=</span> <span class="s">"AliGator"</span> <span class="k">let</span> <span class="nv">str</span><span class="p">:</span> <span class="kt">AttrString</span> <span class="o">=</span> <span class="s">""" Hello </span><span class="se">\(</span><span class="n">username</span><span class="p">,</span> <span class="o">.</span><span class="nf">color</span><span class="p">(</span><span class="o">.</span><span class="n">red</span><span class="p">)</span><span class="se">)</span><span class="s">, isn't this </span><span class="se">\(</span><span class="s">"cool"</span><span class="p">,</span> <span class="o">.</span><span class="nf">color</span><span class="p">(</span><span class="o">.</span><span class="n">blue</span><span class="p">),</span> <span class="o">.</span><span class="n">oblique</span><span class="p">,</span> <span class="o">.</span><span class="nf">underline</span><span class="p">(</span><span class="o">.</span><span class="n">purple</span><span class="p">,</span> <span class="o">.</span><span class="n">single</span><span class="p">)</span><span class="se">)</span><span class="s">? </span><span class="se">\(</span><span class="nv">wrap</span><span class="p">:</span> <span class="s">""" </span><span class="se">\(</span><span class="s">" Merry Xmas! "</span><span class="p">,</span> <span class="o">.</span><span class="nf">font</span><span class="p">(</span><span class="o">.</span><span class="nf">systemFont</span><span class="p">(</span><span class="nv">ofSize</span><span class="p">:</span> <span class="mi">36</span><span class="p">)),</span> <span class="o">.</span><span class="nf">color</span><span class="p">(</span><span class="o">.</span><span class="n">red</span><span class="p">),</span> <span class="o">.</span><span class="nf">bgColor</span><span class="p">(</span><span class="o">.</span><span class="n">yellow</span><span class="p">)</span><span class="se">)</span><span class="s"> </span><span class="se">\(</span><span class="nv">image</span><span class="p">:</span> <span class="err">#</span><span class="nf">imageLiteral</span><span class="p">(</span><span class="nv">resourceName</span><span class="p">:</span> <span class="s">"santa.jpg"</span><span class="p">),</span> <span class="nv">scale</span><span class="p">:</span> <span class="mf">0.2</span><span class="se">)</span><span class="s"> """</span><span class="p">,</span> <span class="o">.</span><span class="nf">alignment</span><span class="p">(</span><span class="o">.</span><span class="n">center</span><span class="p">)</span><span class="se">)</span><span class="s"> Go there to </span><span class="se">\(</span><span class="s">"learn more about String Interpolation"</span><span class="p">,</span> <span class="o">.</span><span class="nf">link</span><span class="p">(</span><span class="s">"https://github.com/apple/swift-evolution/blob/master/proposals/0228-fix-expressiblebystringinterpolation.md"</span><span class="p">),</span> <span class="o">.</span><span class="nf">underline</span><span class="p">(</span><span class="o">.</span><span class="n">blue</span><span class="p">,</span> <span class="o">.</span><span class="n">single</span><span class="p">)</span><span class="se">)</span><span class="s">! """</span> </code></pre></div></div> <p>That big String uses the multi-line string literals syntax (<a href="https://github.com/apple/swift-evolution/blob/master/proposals/0168-multi-line-string-literals.md">new in Swift 4.0, in case you missed it</a>) — and even goes as far as wrapping another multi-line String literal inside another (see the <code class="language-plaintext highlighter-rouge">\(wrap: …)</code> segment)! — and contains interpolations to add some styling to parts of that big String… so a lot of new features of Swift coming together!</p> <p>The result <code class="language-plaintext highlighter-rouge">NSAttributedString</code>, once rendered in a <code class="language-plaintext highlighter-rouge">UILabel</code> or <code class="language-plaintext highlighter-rouge">NSTextView</code>, should then look like this:</p> <p style="text-align: center"><img src="/assets/StringInterpolation-AttrString.png" alt="rendering of the AttributedString created by the code above" /></p> <p>☝️ Yes, that above with the text and image… is really <strong>just</strong> an <code class="language-plaintext highlighter-rouge">NSAttributedString</code> (and not a complex view with layout or anything)! 🤯</p> <h2 id="first-implementation">First implementation</h2> <p>So how are we going to implement this? Well, similar to how we implemented <code class="language-plaintext highlighter-rouge">GitHubComment</code> <a href="/swift/2018/12/15/swift5-stringinterpolation-part1/">in part 1</a> of course!</p> <p>So, let’s start with the declaration of the dedicated type first, before even tackling string interpolation:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">AttrString</span> <span class="p">{</span> <span class="k">let</span> <span class="nv">attributedString</span><span class="p">:</span> <span class="kt">NSAttributedString</span> <span class="p">}</span> <span class="kd">extension</span> <span class="kt">AttrString</span><span class="p">:</span> <span class="kt">ExpressibleByStringLiteral</span> <span class="p">{</span> <span class="nf">init</span><span class="p">(</span><span class="nv">stringLiteral</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span> <span class="k">self</span><span class="o">.</span><span class="n">attributedString</span> <span class="o">=</span> <span class="kt">NSAttributedString</span><span class="p">(</span><span class="nv">string</span><span class="p">:</span> <span class="n">stringLiteral</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> <span class="kd">extension</span> <span class="kt">AttrString</span><span class="p">:</span> <span class="kt">CustomStringConvertible</span> <span class="p">{</span> <span class="k">var</span> <span class="nv">description</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span> <span class="k">return</span> <span class="kt">String</span><span class="p">(</span><span class="nv">describing</span><span class="p">:</span> <span class="k">self</span><span class="o">.</span><span class="n">attributedString</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>Simple enough, right? That’s just a wrapper around <code class="language-plaintext highlighter-rouge">NSAttributedString</code>. Now, let’s add support for <code class="language-plaintext highlighter-rouge">ExpressibleByStringInterpolation</code> that would allow both literals but also strings annotated with <code class="language-plaintext highlighter-rouge">NSAttributedString</code> attributes:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">AttrString</span><span class="p">:</span> <span class="kt">ExpressibleByStringInterpolation</span> <span class="p">{</span> <span class="nf">init</span><span class="p">(</span><span class="nv">stringInterpolation</span><span class="p">:</span> <span class="kt">StringInterpolation</span><span class="p">)</span> <span class="p">{</span> <span class="k">self</span><span class="o">.</span><span class="n">attributedString</span> <span class="o">=</span> <span class="kt">NSAttributedString</span><span class="p">(</span><span class="nv">attributedString</span><span class="p">:</span> <span class="n">stringInterpolation</span><span class="o">.</span><span class="n">attributedString</span><span class="p">)</span> <span class="p">}</span> <span class="kd">struct</span> <span class="kt">StringInterpolation</span><span class="p">:</span> <span class="kt">StringInterpolationProtocol</span> <span class="p">{</span> <span class="k">var</span> <span class="nv">attributedString</span><span class="p">:</span> <span class="kt">NSMutableAttributedString</span> <span class="nf">init</span><span class="p">(</span><span class="nv">literalCapacity</span><span class="p">:</span> <span class="kt">Int</span><span class="p">,</span> <span class="nv">interpolationCount</span><span class="p">:</span> <span class="kt">Int</span><span class="p">)</span> <span class="p">{</span> <span class="k">self</span><span class="o">.</span><span class="n">attributedString</span> <span class="o">=</span> <span class="kt">NSMutableAttributedString</span><span class="p">()</span> <span class="p">}</span> <span class="kd">func</span> <span class="nf">appendLiteral</span><span class="p">(</span><span class="n">_</span> <span class="nv">literal</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">astr</span> <span class="o">=</span> <span class="kt">NSAttributedString</span><span class="p">(</span><span class="nv">string</span><span class="p">:</span> <span class="n">literal</span><span class="p">)</span> <span class="k">self</span><span class="o">.</span><span class="n">attributedString</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="n">astr</span><span class="p">)</span> <span class="p">}</span> <span class="kd">func</span> <span class="nf">appendInterpolation</span><span class="p">(</span><span class="n">_</span> <span class="nv">string</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span> <span class="nv">attributes</span><span class="p">:</span> <span class="p">[</span><span class="kt">NSAttributedString</span><span class="o">.</span><span class="kt">Key</span><span class="p">:</span> <span class="kt">Any</span><span class="p">])</span> <span class="p">{</span> <span class="k">let</span> <span class="nv">astr</span> <span class="o">=</span> <span class="kt">NSAttributedString</span><span class="p">(</span><span class="nv">string</span><span class="p">:</span> <span class="n">string</span><span class="p">,</span> <span class="nv">attributes</span><span class="p">:</span> <span class="n">attributes</span><span class="p">)</span> <span class="k">self</span><span class="o">.</span><span class="n">attributedString</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="n">astr</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>At that stage, we’re already able to use it that way to easily build an <code class="language-plaintext highlighter-rouge">NSAttributedString</code>:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">user</span> <span class="o">=</span> <span class="s">"AliSoftware"</span> <span class="k">let</span> <span class="nv">str</span><span class="p">:</span> <span class="kt">AttrString</span> <span class="o">=</span> <span class="s">""" Hello </span><span class="se">\(</span><span class="n">user</span><span class="p">,</span> <span class="nv">attributes</span><span class="p">:</span> <span class="p">[</span><span class="o">.</span><span class="nv">foregroundColor</span><span class="p">:</span> <span class="kt">NSColor</span><span class="o">.</span><span class="n">blue</span><span class="p">]</span><span class="se">)</span><span class="s">! """</span> </code></pre></div></div> <p>That’s already nice as it is, right?</p> <h2 id="adding-styling-convenience">Adding styling convenience</h2> <p>But dealing with attributes as a dictionary <code class="language-plaintext highlighter-rouge">[NAttributedString.Key: Any]</code> isn’t really nice. Especially since that <code class="language-plaintext highlighter-rouge">Any</code> isn’t typed, and forces us to know the expected type of the value for each key…</p> <p>So let’s make that nicer by creating a dedicated <code class="language-plaintext highlighter-rouge">Style</code> type<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup> to help us building attributes dictionaries:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">AttrString</span> <span class="p">{</span> <span class="kd">struct</span> <span class="kt">Style</span> <span class="p">{</span> <span class="k">let</span> <span class="nv">attributes</span><span class="p">:</span> <span class="p">[</span><span class="kt">NSAttributedString</span><span class="o">.</span><span class="kt">Key</span><span class="p">:</span> <span class="kt">Any</span><span class="p">]</span> <span class="kd">static</span> <span class="kd">func</span> <span class="nf">font</span><span class="p">(</span><span class="n">_</span> <span class="nv">font</span><span class="p">:</span> <span class="kt">NSFont</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Style</span> <span class="p">{</span> <span class="k">return</span> <span class="kt">Style</span><span class="p">(</span><span class="nv">attributes</span><span class="p">:</span> <span class="p">[</span><span class="o">.</span><span class="nv">font</span><span class="p">:</span> <span class="n">font</span><span class="p">])</span> <span class="p">}</span> <span class="kd">static</span> <span class="kd">func</span> <span class="nf">color</span><span class="p">(</span><span class="n">_</span> <span class="nv">color</span><span class="p">:</span> <span class="kt">NSColor</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Style</span> <span class="p">{</span> <span class="k">return</span> <span class="kt">Style</span><span class="p">(</span><span class="nv">attributes</span><span class="p">:</span> <span class="p">[</span><span class="o">.</span><span class="nv">foregroundColor</span><span class="p">:</span> <span class="n">color</span><span class="p">])</span> <span class="p">}</span> <span class="kd">static</span> <span class="kd">func</span> <span class="nf">bgColor</span><span class="p">(</span><span class="n">_</span> <span class="nv">color</span><span class="p">:</span> <span class="kt">NSColor</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Style</span> <span class="p">{</span> <span class="k">return</span> <span class="kt">Style</span><span class="p">(</span><span class="nv">attributes</span><span class="p">:</span> <span class="p">[</span><span class="o">.</span><span class="nv">backgroundColor</span><span class="p">:</span> <span class="n">color</span><span class="p">])</span> <span class="p">}</span> <span class="kd">static</span> <span class="kd">func</span> <span class="nf">link</span><span class="p">(</span><span class="n">_</span> <span class="nv">link</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Style</span> <span class="p">{</span> <span class="k">return</span> <span class="o">.</span><span class="nf">link</span><span class="p">(</span><span class="kt">URL</span><span class="p">(</span><span class="nv">string</span><span class="p">:</span> <span class="n">link</span><span class="p">)</span><span class="o">!</span><span class="p">)</span> <span class="p">}</span> <span class="kd">static</span> <span class="kd">func</span> <span class="nf">link</span><span class="p">(</span><span class="n">_</span> <span class="nv">link</span><span class="p">:</span> <span class="kt">URL</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Style</span> <span class="p">{</span> <span class="k">return</span> <span class="kt">Style</span><span class="p">(</span><span class="nv">attributes</span><span class="p">:</span> <span class="p">[</span><span class="o">.</span><span class="nv">link</span><span class="p">:</span> <span class="n">link</span><span class="p">])</span> <span class="p">}</span> <span class="kd">static</span> <span class="k">let</span> <span class="nv">oblique</span> <span class="o">=</span> <span class="kt">Style</span><span class="p">(</span><span class="nv">attributes</span><span class="p">:</span> <span class="p">[</span><span class="o">.</span><span class="nv">obliqueness</span><span class="p">:</span> <span class="mf">0.1</span><span class="p">])</span> <span class="kd">static</span> <span class="kd">func</span> <span class="nf">underline</span><span class="p">(</span><span class="n">_</span> <span class="nv">color</span><span class="p">:</span> <span class="kt">NSColor</span><span class="p">,</span> <span class="n">_</span> <span class="nv">style</span><span class="p">:</span> <span class="kt">NSUnderlineStyle</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Style</span> <span class="p">{</span> <span class="k">return</span> <span class="kt">Style</span><span class="p">(</span><span class="nv">attributes</span><span class="p">:</span> <span class="p">[</span> <span class="o">.</span><span class="nv">underlineColor</span><span class="p">:</span> <span class="n">color</span><span class="p">,</span> <span class="o">.</span><span class="nv">underlineStyle</span><span class="p">:</span> <span class="n">style</span><span class="o">.</span><span class="n">rawValue</span> <span class="p">])</span> <span class="p">}</span> <span class="kd">static</span> <span class="kd">func</span> <span class="nf">alignment</span><span class="p">(</span><span class="n">_</span> <span class="nv">alignment</span><span class="p">:</span> <span class="kt">NSTextAlignment</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Style</span> <span class="p">{</span> <span class="k">let</span> <span class="nv">ps</span> <span class="o">=</span> <span class="kt">NSMutableParagraphStyle</span><span class="p">()</span> <span class="n">ps</span><span class="o">.</span><span class="n">alignment</span> <span class="o">=</span> <span class="n">alignment</span> <span class="k">return</span> <span class="kt">Style</span><span class="p">(</span><span class="nv">attributes</span><span class="p">:</span> <span class="p">[</span><span class="o">.</span><span class="nv">paragraphStyle</span><span class="p">:</span> <span class="n">ps</span><span class="p">])</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>This allows us to use <code class="language-plaintext highlighter-rouge">Style.color(.blue)</code> to create a <code class="language-plaintext highlighter-rouge">Style</code> wrapping <code class="language-plaintext highlighter-rouge">[.foregroundColor: NSColor.blue]</code> easily.</p> <p>But let’s not stop there then, and make our <code class="language-plaintext highlighter-rouge">StringInterpolation</code> handle such <code class="language-plaintext highlighter-rouge">Style</code> attributes now!</p> <p>So the idea is to be able to write this:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">str</span><span class="p">:</span> <span class="kt">AttrString</span> <span class="o">=</span> <span class="s">""" Hello </span><span class="se">\(</span><span class="n">user</span><span class="p">,</span> <span class="o">.</span><span class="nf">color</span><span class="p">(</span><span class="o">.</span><span class="n">blue</span><span class="p">)</span><span class="se">)</span><span class="s">, how do you like this? """</span> </code></pre></div></div> <p>Wouldn’t it be nice? Well, let’s just implement the right <code class="language-plaintext highlighter-rouge">appendInterpolation</code> for that!</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">AttrString</span><span class="o">.</span><span class="kt">StringInterpolation</span> <span class="p">{</span> <span class="kd">func</span> <span class="nf">appendInterpolation</span><span class="p">(</span><span class="n">_</span> <span class="nv">string</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span> <span class="n">_</span> <span class="nv">style</span><span class="p">:</span> <span class="kt">AttrString</span><span class="o">.</span><span class="kt">Style</span><span class="p">)</span> <span class="p">{</span> <span class="k">let</span> <span class="nv">astr</span> <span class="o">=</span> <span class="kt">NSAttributedString</span><span class="p">(</span><span class="nv">string</span><span class="p">:</span> <span class="n">string</span><span class="p">,</span> <span class="nv">attributes</span><span class="p">:</span> <span class="n">style</span><span class="o">.</span><span class="n">attributes</span><span class="p">)</span> <span class="k">self</span><span class="o">.</span><span class="n">attributedString</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="n">astr</span><span class="p">)</span> <span class="p">}</span> </code></pre></div></div> <p>And here you have it! But… this only supports one <code class="language-plaintext highlighter-rouge">Style</code> at a time then. Why not allow passing multiple <code class="language-plaintext highlighter-rouge">Style</code> as parameters there? And to do so, instead of allowing a <code class="language-plaintext highlighter-rouge">[Style]</code> parameter, forcing us to wrap the list of styles in brackets at call site… why not use variadic parameters here?</p> <p>So instead of the previous implementation, let’s instead implement it that way:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">AttrString</span><span class="o">.</span><span class="kt">StringInterpolation</span> <span class="p">{</span> <span class="kd">func</span> <span class="nf">appendInterpolation</span><span class="p">(</span><span class="n">_</span> <span class="nv">string</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span> <span class="n">_</span> <span class="nv">style</span><span class="p">:</span> <span class="kt">AttrString</span><span class="o">.</span><span class="kt">Style</span><span class="o">...</span><span class="p">)</span> <span class="p">{</span> <span class="k">var</span> <span class="nv">attrs</span><span class="p">:</span> <span class="p">[</span><span class="kt">NSAttributedString</span><span class="o">.</span><span class="kt">Key</span><span class="p">:</span> <span class="kt">Any</span><span class="p">]</span> <span class="o">=</span> <span class="p">[:]</span> <span class="n">style</span><span class="o">.</span><span class="n">forEach</span> <span class="p">{</span> <span class="n">attrs</span><span class="o">.</span><span class="nf">merge</span><span class="p">(</span><span class="nv">$0</span><span class="o">.</span><span class="n">attributes</span><span class="p">,</span> <span class="nv">uniquingKeysWith</span><span class="p">:</span> <span class="p">{</span><span class="nv">$1</span><span class="p">})</span> <span class="p">}</span> <span class="k">let</span> <span class="nv">astr</span> <span class="o">=</span> <span class="kt">NSAttributedString</span><span class="p">(</span><span class="nv">string</span><span class="p">:</span> <span class="n">string</span><span class="p">,</span> <span class="nv">attributes</span><span class="p">:</span> <span class="n">attrs</span><span class="p">)</span> <span class="k">self</span><span class="o">.</span><span class="n">attributedString</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="n">astr</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>And now we can mix multiple styles!</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">str</span><span class="p">:</span> <span class="kt">AttrString</span> <span class="o">=</span> <span class="s">""" Hello </span><span class="se">\(</span><span class="n">user</span><span class="p">,</span> <span class="o">.</span><span class="nf">color</span><span class="p">(</span><span class="o">.</span><span class="n">blue</span><span class="p">),</span> <span class="o">.</span><span class="nf">underline</span><span class="p">(</span><span class="o">.</span><span class="n">red</span><span class="p">,</span> <span class="o">.</span><span class="n">single</span><span class="p">)</span><span class="se">)</span><span class="s">, how do you like this? """</span> </code></pre></div></div> <h2 id="supporting-images">Supporting Images</h2> <p>Another capability of <code class="language-plaintext highlighter-rouge">NSAttributedString</code> is to add images as part of the string, by using <code class="language-plaintext highlighter-rouge">NSAttributedString(attachment: NSTextAttachment)</code>. To do that, it’s just a matter of implementing <code class="language-plaintext highlighter-rouge">appendInterpolation(image: NSImage)</code> to use it.</p> <p>For that feature, I wanted to add the ability to rescale the image as well. And because I tried all that in a macOS playground, which has its graphic context flipped, I also had to draw the image flipped. (Note that this detail might be different for the iOS implementation supporting <code class="language-plaintext highlighter-rouge">UIImage</code>). So here’s what I came up with:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">AttrString</span><span class="o">.</span><span class="kt">StringInterpolation</span> <span class="p">{</span> <span class="kd">func</span> <span class="nf">appendInterpolation</span><span class="p">(</span><span class="nv">image</span><span class="p">:</span> <span class="kt">NSImage</span><span class="p">,</span> <span class="nv">scale</span><span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="mf">1.0</span><span class="p">)</span> <span class="p">{</span> <span class="k">let</span> <span class="nv">attachment</span> <span class="o">=</span> <span class="kt">NSTextAttachment</span><span class="p">()</span> <span class="k">let</span> <span class="nv">size</span> <span class="o">=</span> <span class="kt">NSSize</span><span class="p">(</span> <span class="nv">width</span><span class="p">:</span> <span class="n">image</span><span class="o">.</span><span class="n">size</span><span class="o">.</span><span class="n">width</span> <span class="o">*</span> <span class="n">scale</span><span class="p">,</span> <span class="nv">height</span><span class="p">:</span> <span class="n">image</span><span class="o">.</span><span class="n">size</span><span class="o">.</span><span class="n">height</span> <span class="o">*</span> <span class="n">scale</span> <span class="p">)</span> <span class="n">attachment</span><span class="o">.</span><span class="n">image</span> <span class="o">=</span> <span class="kt">NSImage</span><span class="p">(</span><span class="nv">size</span><span class="p">:</span> <span class="n">size</span><span class="p">,</span> <span class="nv">flipped</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nv">drawingHandler</span><span class="p">:</span> <span class="p">{</span> <span class="p">(</span><span class="nv">rect</span><span class="p">:</span> <span class="kt">NSRect</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Bool</span> <span class="k">in</span> <span class="kt">NSGraphicsContext</span><span class="o">.</span><span class="n">current</span><span class="p">?</span><span class="o">.</span><span class="n">cgContext</span><span class="o">.</span><span class="nf">translateBy</span><span class="p">(</span><span class="nv">x</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="nv">y</span><span class="p">:</span> <span class="n">size</span><span class="o">.</span><span class="n">height</span><span class="p">)</span> <span class="kt">NSGraphicsContext</span><span class="o">.</span><span class="n">current</span><span class="p">?</span><span class="o">.</span><span class="n">cgContext</span><span class="o">.</span><span class="nf">scaleBy</span><span class="p">(</span><span class="nv">x</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nv">y</span><span class="p">:</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="n">image</span><span class="o">.</span><span class="nf">draw</span><span class="p">(</span><span class="nv">in</span><span class="p">:</span> <span class="n">rect</span><span class="p">)</span> <span class="k">return</span> <span class="kc">true</span> <span class="p">})</span> <span class="k">self</span><span class="o">.</span><span class="n">attributedString</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="kt">NSAttributedString</span><span class="p">(</span><span class="nv">attachment</span><span class="p">:</span> <span class="n">attachment</span><span class="p">))</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <h2 id="wrapping-styles-in-one-another">Wrapping styles in one another</h2> <p>Finally, sometimes, you want to apply a style to a large section of text, which itself might contain styles inside subsections of that text. Like <code class="language-plaintext highlighter-rouge">"&lt;b&gt;Hello &lt;i&gt;world&lt;/i&gt;&lt;/b&gt;"</code> in HTML where the whole section is bold but contains sub-parts in oblique.</p> <p>Our API doesn’t support that yet, so let’s add it. The idea there is to be able to apply a list of <code class="language-plaintext highlighter-rouge">Style...</code> to something that’s not just a <code class="language-plaintext highlighter-rouge">String</code> but already an <code class="language-plaintext highlighter-rouge">AttrString</code> with already existing attributes.</p> <p>The implementation will be similar to <code class="language-plaintext highlighter-rouge">appendInterpolation(_ string: String, _ style: Style...)</code>, but will mutate the <code class="language-plaintext highlighter-rouge">AttrString.attributedString</code> to <em>add</em> attributes to it, intead of creating a brand new <code class="language-plaintext highlighter-rouge">NSAttributedString</code> from a plain <code class="language-plaintext highlighter-rouge">String</code>.</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">AttrString</span><span class="o">.</span><span class="kt">StringInterpolation</span> <span class="p">{</span> <span class="kd">func</span> <span class="nf">appendInterpolation</span><span class="p">(</span><span class="n">wrap</span> <span class="nv">string</span><span class="p">:</span> <span class="kt">AttrString</span><span class="p">,</span> <span class="n">_</span> <span class="nv">style</span><span class="p">:</span> <span class="kt">AttrString</span><span class="o">.</span><span class="kt">Style</span><span class="o">...</span><span class="p">)</span> <span class="p">{</span> <span class="k">var</span> <span class="nv">attrs</span><span class="p">:</span> <span class="p">[</span><span class="kt">NSAttributedString</span><span class="o">.</span><span class="kt">Key</span><span class="p">:</span> <span class="kt">Any</span><span class="p">]</span> <span class="o">=</span> <span class="p">[:]</span> <span class="n">style</span><span class="o">.</span><span class="n">forEach</span> <span class="p">{</span> <span class="n">attrs</span><span class="o">.</span><span class="nf">merge</span><span class="p">(</span><span class="nv">$0</span><span class="o">.</span><span class="n">attributes</span><span class="p">,</span> <span class="nv">uniquingKeysWith</span><span class="p">:</span> <span class="p">{</span><span class="nv">$1</span><span class="p">})</span> <span class="p">}</span> <span class="k">let</span> <span class="nv">mas</span> <span class="o">=</span> <span class="kt">NSMutableAttributedString</span><span class="p">(</span><span class="nv">attributedString</span><span class="p">:</span> <span class="n">string</span><span class="o">.</span><span class="n">attributedString</span><span class="p">)</span> <span class="k">let</span> <span class="nv">fullRange</span> <span class="o">=</span> <span class="kt">NSRange</span><span class="p">(</span><span class="n">mas</span><span class="o">.</span><span class="n">string</span><span class="o">.</span><span class="n">startIndex</span><span class="o">..&lt;</span><span class="n">mas</span><span class="o">.</span><span class="n">string</span><span class="o">.</span><span class="n">endIndex</span><span class="p">,</span> <span class="nv">in</span><span class="p">:</span> <span class="n">mas</span><span class="o">.</span><span class="n">string</span><span class="p">)</span> <span class="n">mas</span><span class="o">.</span><span class="nf">addAttributes</span><span class="p">(</span><span class="n">attrs</span><span class="p">,</span> <span class="nv">range</span><span class="p">:</span> <span class="n">fullRange</span><span class="p">)</span> <span class="k">self</span><span class="o">.</span><span class="n">attributedString</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="n">mas</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>And with all that, we have achieved our goal and are finally able to create an AttributedString using this single string with interpolations:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">username</span> <span class="o">=</span> <span class="s">"AliGator"</span> <span class="k">let</span> <span class="nv">str</span><span class="p">:</span> <span class="kt">AttrString</span> <span class="o">=</span> <span class="s">""" Hello </span><span class="se">\(</span><span class="n">username</span><span class="p">,</span> <span class="o">.</span><span class="nf">color</span><span class="p">(</span><span class="o">.</span><span class="n">red</span><span class="p">)</span><span class="se">)</span><span class="s">, isn't this </span><span class="se">\(</span><span class="s">"cool"</span><span class="p">,</span> <span class="o">.</span><span class="nf">color</span><span class="p">(</span><span class="o">.</span><span class="n">blue</span><span class="p">),</span> <span class="o">.</span><span class="n">oblique</span><span class="p">,</span> <span class="o">.</span><span class="nf">underline</span><span class="p">(</span><span class="o">.</span><span class="n">purple</span><span class="p">,</span> <span class="o">.</span><span class="n">single</span><span class="p">)</span><span class="se">)</span><span class="s">? </span><span class="se">\(</span><span class="nv">wrap</span><span class="p">:</span> <span class="s">""" </span><span class="se">\(</span><span class="s">" Merry Xmas! "</span><span class="p">,</span> <span class="o">.</span><span class="nf">font</span><span class="p">(</span><span class="o">.</span><span class="nf">systemFont</span><span class="p">(</span><span class="nv">ofSize</span><span class="p">:</span> <span class="mi">36</span><span class="p">)),</span> <span class="o">.</span><span class="nf">color</span><span class="p">(</span><span class="o">.</span><span class="n">red</span><span class="p">),</span> <span class="o">.</span><span class="nf">bgColor</span><span class="p">(</span><span class="o">.</span><span class="n">yellow</span><span class="p">)</span><span class="se">)</span><span class="s"> </span><span class="se">\(</span><span class="nv">image</span><span class="p">:</span> <span class="err">#</span><span class="nf">imageLiteral</span><span class="p">(</span><span class="nv">resourceName</span><span class="p">:</span> <span class="s">"santa.jpg"</span><span class="p">),</span> <span class="nv">scale</span><span class="p">:</span> <span class="mf">0.2</span><span class="se">)</span><span class="s"> """</span><span class="p">,</span> <span class="o">.</span><span class="nf">alignment</span><span class="p">(</span><span class="o">.</span><span class="n">center</span><span class="p">)</span><span class="se">)</span><span class="s"> Go there to </span><span class="se">\(</span><span class="s">"learn more about String Interpolation"</span><span class="p">,</span> <span class="o">.</span><span class="nf">link</span><span class="p">(</span><span class="s">"https://github.com/apple/swift-evolution/blob/master/proposals/0228-fix-expressiblebystringinterpolation.md"</span><span class="p">),</span> <span class="o">.</span><span class="nf">underline</span><span class="p">(</span><span class="o">.</span><span class="n">blue</span><span class="p">,</span> <span class="o">.</span><span class="n">single</span><span class="p">)</span><span class="se">)</span><span class="s">! """</span> </code></pre></div></div> <p style="text-align: center"><img src="/assets/StringInterpolation-AttrString.png" alt="rendering of the AttributedString created by the code above" /></p> <h2 id="conclusion">Conclusion</h2> <p>I hope that you enjoyed this series on <code class="language-plaintext highlighter-rouge">StringInterpolation</code> and that it gave you a glimpse at the power brought by that new design.</p> <p>You can <a href="/assets/StringInterpolation.playground.zip">download my Playground here</a><sup id="fnref:1:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> with the full implementation for <code class="language-plaintext highlighter-rouge">GitHubComment</code> (see <a href="/swift/2018/12/15/swift5-stringinterpolation-part1/">part 1</a>), <code class="language-plaintext highlighter-rouge">AttrString</code>, and even some fun I tried with an simplistic implementation for <code class="language-plaintext highlighter-rouge">RegEx</code>.</p> <p>There are plenty more nice ideas around here to make use of that new <code class="language-plaintext highlighter-rouge">ExpressibleByStringInterpolation</code> API coming in Swift 5 — including some from <a href="https://ericasadun.com/2018/12/12/the-beauty-of-swift-5-string-interpolation/">Erica Sadun’s blog here</a>, <a href="https://ericasadun.com/2018/12/14/more-fun-with-swift-5-string-interpolation-radix-formatting/">here</a> and <a href="https://ericasadun.com/2018/12/16/swift-5-interpolation-part-3-dates-and-number-formatters/">here</a> — so don’t hesitate to read more about it… and even have fun with it yourself!</p> <div class="footnotes" role="doc-endnotes"> <ol> <li id="fn:1" role="doc-endnote"> <p>For the code in that post &amp; playground, you’ll need to use Swift 5, which is now shipped with Xcode 10.2. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:1:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p> </li> <li id="fn:2" role="doc-endnote"> <p>Of course I’ve only implemented a limited list of styles there, for demo purposes. The idea would be to extend that <code class="language-plaintext highlighter-rouge">Style</code> type to support way more styles in the future, and ideally cover all possible <code class="language-plaintext highlighter-rouge">NSAttributedString.Key</code> that exists. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> </ol> </div> Sun, 16 Dec 2018 00:00:00 +0000 https://alisoftware.github.io/swift/2018/12/16/swift5-stringinterpolation-part2/ https://alisoftware.github.io/swift/2018/12/16/swift5-stringinterpolation-part2/ StringInterpolation in Swift 5 — Introduction <p>In Swift 4, the <code class="language-plaintext highlighter-rouge">StringInterpolation</code> protocol got deprecated, because its original design was inefficient and inflexible, with the goal of redisigning it entirely after Swift 4. Since then, <a href="https://github.com/apple/swift-evolution/blob/master/proposals/0228-fix-expressiblebystringinterpolation.md">SE-0228</a> introduced a new design for <code class="language-plaintext highlighter-rouge">StringInterpolation</code>, which is going to be part of Swift 5, and opens a whole lot of powerful possibilities.</p> <p>This feature is part of Swift 5, so you will need Xcode 10.2 and Swift 5 to use it.</p> <p>Note: This article is an introduction to the new <code class="language-plaintext highlighter-rouge">StringInterpolation</code>. A followup article with a more advanced example is available in <a href="/swift/2018/12/16/swift5-stringinterpolation-part2/">part 2 of this article</a>.</p> <h2 id="the-new-stringinterpolation-design">The new StringInterpolation design</h2> <p>I really encourage you to read the <a href="https://github.com/apple/swift-evolution/blob/master/proposals/0228-fix-expressiblebystringinterpolation.md">SE-0228</a> proposal to get an idea of the design and motivations behind the new API.</p> <p>Basically, to make a type conform to <code class="language-plaintext highlighter-rouge">ExpressibleByStringInterpolation</code>, you have to:</p> <ul> <li>Make this type have a subtype <code class="language-plaintext highlighter-rouge">StringInterpolation</code>, that must itself conform to <code class="language-plaintext highlighter-rouge">StringInterpolationProtocol</code> and will be responsible to handle the interpretation of the interpolation</li> <li>That subtype just have to implement <code class="language-plaintext highlighter-rouge">appendLiteral(_ literal: String)</code> and one or more <code class="language-plaintext highlighter-rouge">appendInterpolation(…)</code> method, with the signature of your choosing depending on what you want to support</li> <li>This <code class="language-plaintext highlighter-rouge">StringInterpolation</code> subtype will serve as a “Builder Pattern” for your main type, and the compiler will call those <code class="language-plaintext highlighter-rouge">append…</code> methods to build the object step by step</li> <li>Then your main type needs to implement <code class="language-plaintext highlighter-rouge">init(stringInterpolation: StringInterpolation)</code> to instantiate itself with the result of those incremental steps.</li> </ul> <p>The fact that you can implement whatever <code class="language-plaintext highlighter-rouge">appendInterpolation(…)</code> method you like means that you can choose what interpolation to support. This is a super powerful feature that opens a large range of possibilities!</p> <p>For example, if you implement <code class="language-plaintext highlighter-rouge">func appendInterpolation(_ string: String, pad: Int)</code> that means that you’ll be able to build your type using an interpolation like: <code class="language-plaintext highlighter-rouge">"Hello \(name, pad: 10), how are you?"</code>. The interpolation just has to match one of the <code class="language-plaintext highlighter-rouge">appendInterpolation</code> signatures that your <code class="language-plaintext highlighter-rouge">StringInterpolation</code> subtype support.</p> <h2 id="simple-example">Simple example</h2> <p>Let’s start with a simple type to demonstrate how it goes. Let’s build a <code class="language-plaintext highlighter-rouge">GitHubComment</code> type that would allow you to reference issue numbers and users.</p> <p>The goal of that example is to be then able to write something like this:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">comment</span><span class="p">:</span> <span class="kt">GitHubComment</span> <span class="o">=</span> <span class="s">""" See </span><span class="se">\(</span><span class="nv">issue</span><span class="p">:</span> <span class="mi">123</span><span class="se">)</span><span class="s"> where </span><span class="se">\(</span><span class="nv">user</span><span class="p">:</span> <span class="s">"alisoftware"</span><span class="se">)</span><span class="s"> explains the steps to reproduce. """</span> </code></pre></div></div> <p>So, how do we implement this?</p> <p>First let’s declare the basic <code class="language-plaintext highlighter-rouge">struct GitHubComment</code> and make it <code class="language-plaintext highlighter-rouge">ExpressibleByStringLiteral</code> (because <code class="language-plaintext highlighter-rouge">ExpressibleByStringInterpolation</code> inherits that protocol so let’s get that implementation out of the way) and <code class="language-plaintext highlighter-rouge">CustomStringConvertible</code> (for nice debugging when printing in the console)</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">GitHubComment</span> <span class="p">{</span> <span class="k">let</span> <span class="nv">markdown</span><span class="p">:</span> <span class="kt">String</span> <span class="p">}</span> <span class="kd">extension</span> <span class="kt">GitHubComment</span><span class="p">:</span> <span class="kt">ExpressibleByStringLiteral</span> <span class="p">{</span> <span class="nf">init</span><span class="p">(</span><span class="n">stringLiteral</span> <span class="nv">value</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span> <span class="k">self</span><span class="o">.</span><span class="n">markdown</span> <span class="o">=</span> <span class="n">value</span> <span class="p">}</span> <span class="p">}</span> <span class="kd">extension</span> <span class="kt">GitHubComment</span><span class="p">:</span> <span class="kt">CustomStringConvertible</span> <span class="p">{</span> <span class="k">var</span> <span class="nv">description</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span> <span class="k">return</span> <span class="k">self</span><span class="o">.</span><span class="n">markdown</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>Then, we’ll make <code class="language-plaintext highlighter-rouge">GitHubComment</code> conform to <code class="language-plaintext highlighter-rouge">ExpressibleByStringInterpolation</code>, which means having a <code class="language-plaintext highlighter-rouge">StringInterpolation</code> subtype that will handle what to do when:</p> <ul> <li>initializing itself: <code class="language-plaintext highlighter-rouge">init(literalCapacity: Int, interpolationCount: Int)</code> lets you the possibility to reserve some capacity to the buffer you’ll use while building the type step by step. In our case, we could simply have used a <code class="language-plaintext highlighter-rouge">String</code> and append the segments to it while building the instance, but I instead chose to use a <code class="language-plaintext highlighter-rouge">parts: [String]</code>, that we’ll assemble together later</li> <li>implement <code class="language-plaintext highlighter-rouge">appendLiteral(_ string: String)</code> to append the literal text to the <code class="language-plaintext highlighter-rouge">parts</code></li> <li>implement <code class="language-plaintext highlighter-rouge">appendInterpolation(user: String)</code> to append the markdown representation of a link to that user’s profile when encountering <code class="language-plaintext highlighter-rouge">\(user: xxx)</code></li> <li>implement <code class="language-plaintext highlighter-rouge">appendInterpolation(issue: Int)</code> to append the markdown representation of a link to that issue</li> <li>then implement <code class="language-plaintext highlighter-rouge">init(stringInterpolation: StringInterpolation)</code> on <code class="language-plaintext highlighter-rouge">GitHubComment</code> to build a comment from those <code class="language-plaintext highlighter-rouge">parts</code></li> </ul> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">GitHubComment</span><span class="p">:</span> <span class="kt">ExpressibleByStringInterpolation</span> <span class="p">{</span> <span class="kd">struct</span> <span class="kt">StringInterpolation</span><span class="p">:</span> <span class="kt">StringInterpolationProtocol</span> <span class="p">{</span> <span class="k">var</span> <span class="nv">parts</span><span class="p">:</span> <span class="p">[</span><span class="kt">String</span><span class="p">]</span> <span class="nf">init</span><span class="p">(</span><span class="nv">literalCapacity</span><span class="p">:</span> <span class="kt">Int</span><span class="p">,</span> <span class="nv">interpolationCount</span><span class="p">:</span> <span class="kt">Int</span><span class="p">)</span> <span class="p">{</span> <span class="k">self</span><span class="o">.</span><span class="n">parts</span> <span class="o">=</span> <span class="p">[]</span> <span class="c1">// - literalCapacity is the number of characters in literal segments (L)</span> <span class="c1">// - interpolationCount is the number of interpolation segments (I)</span> <span class="c1">// We estimate that we generally have a structure like "LILILIL"</span> <span class="c1">// — e.g. "Hello \(world, .color(.blue))!" — hence the 2n+1</span> <span class="k">self</span><span class="o">.</span><span class="n">parts</span><span class="o">.</span><span class="nf">reserveCapacity</span><span class="p">(</span><span class="mf">2*</span><span class="n">interpolationCount</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span> <span class="p">}</span> <span class="k">mutating</span> <span class="kd">func</span> <span class="nf">appendLiteral</span><span class="p">(</span><span class="n">_</span> <span class="nv">literal</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span> <span class="k">self</span><span class="o">.</span><span class="n">parts</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="n">literal</span><span class="p">)</span> <span class="p">}</span> <span class="k">mutating</span> <span class="kd">func</span> <span class="nf">appendInterpolation</span><span class="p">(</span><span class="n">user</span> <span class="nv">name</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span> <span class="k">self</span><span class="o">.</span><span class="n">parts</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="s">"[</span><span class="se">\(</span><span class="n">name</span><span class="se">)</span><span class="s">](https://github.com/</span><span class="se">\(</span><span class="n">name</span><span class="se">)</span><span class="s">)"</span><span class="p">)</span> <span class="p">}</span> <span class="k">mutating</span> <span class="kd">func</span> <span class="nf">appendInterpolation</span><span class="p">(</span><span class="n">issue</span> <span class="nv">number</span><span class="p">:</span> <span class="kt">Int</span><span class="p">)</span> <span class="p">{</span> <span class="k">self</span><span class="o">.</span><span class="n">parts</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="s">"[#</span><span class="se">\(</span><span class="n">number</span><span class="se">)</span><span class="s">](issues/</span><span class="se">\(</span><span class="n">number</span><span class="se">)</span><span class="s">)"</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> <span class="nf">init</span><span class="p">(</span><span class="nv">stringInterpolation</span><span class="p">:</span> <span class="kt">StringInterpolation</span><span class="p">)</span> <span class="p">{</span> <span class="k">self</span><span class="o">.</span><span class="n">markdown</span> <span class="o">=</span> <span class="n">stringInterpolation</span><span class="o">.</span><span class="n">parts</span><span class="o">.</span><span class="nf">joined</span><span class="p">()</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>And that’s it! we’re done!</p> <p>Notice how, because of the signatures of the <code class="language-plaintext highlighter-rouge">appendInterpolation</code> methods we’ve implemented, we’re allowed to use <code class="language-plaintext highlighter-rouge">Hello \(user: "alisoftware")</code> but not <code class="language-plaintext highlighter-rouge">Hello \(user: 123)</code>, as <code class="language-plaintext highlighter-rouge">appendInterpolation(user:)</code> expects a <code class="language-plaintext highlighter-rouge">String</code> as parameter. Similarly, <code class="language-plaintext highlighter-rouge">\(issue: 123)</code> in your string will only allow an <code class="language-plaintext highlighter-rouge">Int</code> because <code class="language-plaintext highlighter-rouge">appendInterpolation(issue:)</code> takes an <code class="language-plaintext highlighter-rouge">Int</code> as parameter.</p> <p>In fact, if you try to use interpolations that are not supported by your <code class="language-plaintext highlighter-rouge">StringInterpolation</code> subtype, the compiler will give you nice errors:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">comment</span><span class="p">:</span> <span class="kt">GitHubComment</span> <span class="o">=</span> <span class="s">""" See </span><span class="se">\(</span><span class="nv">issue</span><span class="p">:</span> <span class="s">"bob"</span><span class="se">)</span><span class="s"> where </span><span class="se">\(</span><span class="nv">username</span><span class="p">:</span> <span class="s">"alisoftware"</span><span class="se">)</span><span class="s"> explains the steps to reproduce. """</span> <span class="c1">// ^~~~~ ^~~~~~~~~</span> <span class="c1">// error: cannot convert value of type 'String' to expected argument type 'Int'</span> <span class="c1">// error: incorrect argument label in call (have 'username:', expected 'user:')</span> </code></pre></div></div> <h2 id="its-only-the-beginning">It’s only the beginning</h2> <p>This new design opens a large range of possibilities for making your own types <code class="language-plaintext highlighter-rouge">ExpressibleByStringInterpolation</code>. Some ideas include:</p> <ul> <li>Making a <code class="language-plaintext highlighter-rouge">HTML</code> type conform so that you can write HTML using interpolations</li> <li>Making a <code class="language-plaintext highlighter-rouge">SQLStatement</code> type conform so you can write SQL statements easily</li> <li>Using string interpolation to support more custom formatting, like formatting <code class="language-plaintext highlighter-rouge">Double</code> or <code class="language-plaintext highlighter-rouge">Date</code> values inside your interpolated string</li> <li>Making a <code class="language-plaintext highlighter-rouge">RegEx</code> type conform so that you can write regular expressions with a fancy syntax</li> <li>Making a <code class="language-plaintext highlighter-rouge">AttributedString</code> type conform to build an <code class="language-plaintext highlighter-rouge">NSAttributedString</code> using string interpolation</li> </ul> <p><a href="https://github.com/brentdax">Brent Royal-Gordon</a>, who came up with that awesome design for the new String Interpolation alongside <a href="https://github.com/milseman">Michael Ilseman</a>, provided some more example in <a href="https://gist.github.com/brentdax/0b46ce25b7da1049e61b4669352094b6">this gist</a></p> <p>Personally I gave a shot at building NSAttributedStrings using StringInterpolation, and found the result really beautiful 🤩 So I am really excited to share it with you in <a href="/swift/2018/12/16/swift5-stringinterpolation-part2/">part 2 of this post</a> 🙂</p> <p>See you there! 👋</p> Sat, 15 Dec 2018 00:00:00 +0000 https://alisoftware.github.io/swift/2018/12/15/swift5-stringinterpolation-part1/ https://alisoftware.github.io/swift/2018/12/15/swift5-stringinterpolation-part1/ Private properties in protocols <p>In Swift, protocols can’t specify access control to the properties they declare. If a property is listed in a protocol, you have to make conforming types declare those properties explicitly.<br /> But sometimes, even if you need those properties in order to provide your implementations, you don’t want those properties to be used outside the type. Let’s see how to workaround that problem.</p> <h3 id="a-simplified-example">A simplified example</h3> <p>Let’s see that you want to create a dedicated object to manage your ViewControllers navigation, like a Coordinator.</p> <p>Every coordinator is going to have a root <code class="language-plaintext highlighter-rouge">UINavigationController</code>, and share some common capabilities, like pushing and poping other ViewControllers on it. So at first it might look like this<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Coordinator.swift</span> <span class="kd">protocol</span> <span class="kt">Coordinator</span> <span class="p">{</span> <span class="k">var</span> <span class="nv">navigationController</span><span class="p">:</span> <span class="kt">UINavigationController</span> <span class="p">{</span> <span class="k">get</span> <span class="p">}</span> <span class="k">var</span> <span class="nv">childCoordinator</span><span class="p">:</span> <span class="kt">Coordinator</span><span class="p">?</span> <span class="p">{</span> <span class="k">get</span> <span class="k">set</span> <span class="p">}</span> <span class="kd">func</span> <span class="nf">push</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="nv">animated</span><span class="p">:</span> <span class="kt">Bool</span><span class="p">)</span> <span class="kd">func</span> <span class="nf">present</span><span class="p">(</span><span class="nv">childViewController</span><span class="p">:</span> <span class="kt">UIViewController</span><span class="p">,</span> <span class="nv">animated</span><span class="p">:</span> <span class="kt">Bool</span><span class="p">)</span> <span class="kd">func</span> <span class="nf">pop</span><span class="p">(</span><span class="nv">animated</span><span class="p">:</span> <span class="kt">Bool</span><span class="p">)</span> <span class="p">}</span> <span class="kd">extension</span> <span class="kt">Coordinator</span> <span class="p">{</span> <span class="kd">func</span> <span class="nf">push</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="nv">animated</span><span class="p">:</span> <span class="kt">Bool</span> <span class="o">=</span> <span class="kc">true</span><span class="p">)</span> <span class="p">{</span> <span class="k">self</span><span class="o">.</span><span class="n">navigationController</span><span class="o">.</span><span class="nf">pushViewController</span><span class="p">(</span><span class="n">viewController</span><span class="p">,</span> <span class="nv">animated</span><span class="p">:</span> <span class="n">animated</span><span class="p">)</span> <span class="p">}</span> <span class="kd">func</span> <span class="nf">present</span><span class="p">(</span><span class="nv">childCoordinator</span><span class="p">:</span> <span class="kt">Coordinator</span><span class="p">,</span> <span class="nv">animated</span><span class="p">:</span> <span class="kt">Bool</span><span class="p">)</span> <span class="p">{</span> <span class="k">self</span><span class="o">.</span><span class="n">navigationController</span><span class="o">.</span><span class="nf">present</span><span class="p">(</span><span class="n">childCoordinator</span><span class="o">.</span><span class="n">navigationController</span><span class="p">,</span> <span class="nv">animated</span><span class="p">:</span> <span class="n">animated</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="k">in</span> <span class="k">self</span><span class="p">?</span><span class="o">.</span><span class="n">childCoordinator</span> <span class="o">=</span> <span class="n">childCoordinator</span> <span class="p">}</span> <span class="p">}</span> <span class="kd">func</span> <span class="nf">pop</span><span class="p">(</span><span class="nv">animated</span><span class="p">:</span> <span class="kt">Bool</span> <span class="o">=</span> <span class="kc">true</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="k">let</span> <span class="nv">childCoordinator</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="n">childCoordinator</span> <span class="p">{</span> <span class="k">self</span><span class="o">.</span><span class="nf">dismissViewController</span><span class="p">(</span><span class="nv">animated</span><span class="p">:</span> <span class="n">animated</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="k">in</span> <span class="k">self</span><span class="p">?</span><span class="o">.</span><span class="n">childCoordinator</span> <span class="o">=</span> <span class="kc">nil</span> <span class="p">}</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="k">self</span><span class="o">.</span><span class="n">navigationController</span><span class="o">.</span><span class="nf">popViewController</span><span class="p">(</span><span class="nv">animated</span><span class="p">:</span> <span class="n">animated</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>And then when we want to declare a new <code class="language-plaintext highlighter-rouge">Coordinator</code> object, we’d do something like this:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// MainCoordinator.swift</span> <span class="kd">class</span> <span class="kt">MainCoordinator</span><span class="p">:</span> <span class="kt">Coordinator</span> <span class="p">{</span> <span class="k">let</span> <span class="nv">navigationController</span><span class="p">:</span> <span class="kt">UINavigationController</span> <span class="o">=</span> <span class="kt">UINavigationController</span><span class="p">()</span> <span class="k">var</span> <span class="nv">childCoordinator</span><span class="p">:</span> <span class="kt">Coordinator</span><span class="p">?</span> <span class="kd">func</span> <span class="nf">showTutorialPage1</span><span class="p">()</span> <span class="p">{</span> <span class="k">let</span> <span class="nv">vc</span> <span class="o">=</span> <span class="nf">makeTutorialPage</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="nv">coordinator</span><span class="p">:</span> <span class="k">self</span><span class="p">)</span> <span class="k">self</span><span class="o">.</span><span class="nf">push</span><span class="p">(</span><span class="nv">viewController</span><span class="p">:</span> <span class="n">vc</span><span class="p">)</span> <span class="p">}</span> <span class="kd">func</span> <span class="nf">showTutorialPage2</span><span class="p">()</span> <span class="p">{</span> <span class="k">let</span> <span class="nv">vc</span> <span class="o">=</span> <span class="nf">makeTutorialPage</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="nv">coordinator</span><span class="p">:</span> <span class="k">self</span><span class="p">)</span> <span class="k">self</span><span class="o">.</span><span class="nf">push</span><span class="p">(</span><span class="nv">viewController</span><span class="p">:</span> <span class="n">vc</span><span class="p">)</span> <span class="p">}</span> <span class="kd">private</span> <span class="kd">func</span> <span class="nf">makeTutorialPage</span><span class="p">(</span><span class="n">_</span> <span class="nv">num</span><span class="p">:</span> <span class="kt">Int</span><span class="p">,</span> <span class="nv">coordinator</span><span class="p">:</span> <span class="kt">Coordinator</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">UIViewController</span> <span class="p">{</span> <span class="err">…</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <h3 id="the-problem-leaking-implementation-details">The problem: leaking implementation details</h3> <p>There are two problems with this solution regarding <code class="language-plaintext highlighter-rouge">protocol</code> visibility:</p> <ul> <li>When we want to declare a new <code class="language-plaintext highlighter-rouge">Coordinator</code> object, we have to explicitly declare a <code class="language-plaintext highlighter-rouge">let navigationController: UINavigationController</code> property AND a <code class="language-plaintext highlighter-rouge">var childCoordinator: Coordinator?</code> every time. <strong>Even if we don’t use them explicitly</strong> in implementations of our conforming types — they are just there because we need them for the default implementations provided by the <code class="language-plaintext highlighter-rouge">protocol Coordinator</code> to work.</li> <li>Those two properties we have to declare have to be of the same visibility (the implicit <code class="language-plaintext highlighter-rouge">internal</code> access control level in our case) as our <code class="language-plaintext highlighter-rouge">MainCoordinator</code>, because that’s a requirement of our <code class="language-plaintext highlighter-rouge">protocol Coordinator</code>. That makes them also visible to the outside, i.e. to code using <code class="language-plaintext highlighter-rouge">MainCoordinator</code></li> </ul> <p>So the problem is that we have to declare some properties every time while it’s only some implementation details, but also that these implementation details are leaked to the outside interface, allowing consumers of that class to do things they shouldn’t be allowed to do, like:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">mainCoord</span> <span class="o">=</span> <span class="kt">MainCoordinator</span><span class="p">()</span> <span class="c1">// Consumers shouldn't be allowed to access the navigationController directly but they can</span> <span class="n">mainCoord</span><span class="o">.</span><span class="n">navigationController</span><span class="o">.</span><span class="nf">dismissViewController</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="c1">// and neither should they be allowed to do stuff like this</span> <span class="n">mainCoord</span><span class="o">.</span><span class="n">childCoordinator</span> <span class="o">=</span> <span class="n">mainCoord</span> </code></pre></div></div> <p>You might think that we could just not declare those two properties in the <code class="language-plaintext highlighter-rouge">protocol</code> in the first place, as we don’t want them to be visible. But if we do that, we wouldn’t be able to provide default implementations via our <code class="language-plaintext highlighter-rouge">extension Coordinator</code>, as those default implementations need those properties to exist in order for their code to compile.</p> <p>One could also hope that Swift would allow declaring those properties <code class="language-plaintext highlighter-rouge">fileprivate</code> in the protocol, but as of Swift 4, you can’t specify any access control attributes inside <code class="language-plaintext highlighter-rouge">protocols</code>.</p> <p>So how could we solve that, to both provide those default implementations which require those properties, and not letting them leak to the outside interface?</p> <h3 id="a-solution">A solution</h3> <p>A trick to achieve that is to hide those properties inside an intermediate object, and make the properties of that object <code class="language-plaintext highlighter-rouge">fileprivate</code>.</p> <p>That way, even if we’ll still have conforming types to declare that property in their public interface, consumers of that interface won’t be able to access internal properties of that object. While our default implementation of the protocol will be able to access them — as long as it’s declared in the same file (as they’ll be <code class="language-plaintext highlighter-rouge">fileprivate</code>).</p> <p>This would look like this:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Coordinator.swift</span> <span class="kd">class</span> <span class="kt">CoordinatorComponents</span> <span class="p">{</span> <span class="kd">fileprivate</span> <span class="k">let</span> <span class="nv">navigationController</span><span class="p">:</span> <span class="kt">UINavigationController</span> <span class="o">=</span> <span class="kt">UINavigationController</span><span class="p">()</span> <span class="kd">fileprivate</span> <span class="k">var</span> <span class="nv">childCoordinator</span><span class="p">:</span> <span class="kt">Coordinator</span><span class="p">?</span> <span class="o">=</span> <span class="kc">nil</span> <span class="p">}</span> <span class="kd">protocol</span> <span class="kt">Coordinator</span><span class="p">:</span> <span class="kt">AnyObject</span> <span class="p">{</span> <span class="k">var</span> <span class="nv">coordinatorComponents</span><span class="p">:</span> <span class="kt">CoordinatorComponents</span> <span class="p">{</span> <span class="k">get</span> <span class="p">}</span> <span class="kd">func</span> <span class="nf">push</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="nv">animated</span><span class="p">:</span> <span class="kt">Bool</span><span class="p">)</span> <span class="kd">func</span> <span class="nf">present</span><span class="p">(</span><span class="nv">childCoordinator</span><span class="p">:</span> <span class="kt">Coordinator</span><span class="p">,</span> <span class="nv">animated</span><span class="p">:</span> <span class="kt">Bool</span><span class="p">)</span> <span class="kd">func</span> <span class="nf">pop</span><span class="p">(</span><span class="nv">animated</span><span class="p">:</span> <span class="kt">Bool</span><span class="p">)</span> <span class="p">}</span> <span class="kd">extension</span> <span class="kt">Coordinator</span> <span class="p">{</span> <span class="kd">func</span> <span class="nf">push</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="nv">animated</span><span class="p">:</span> <span class="kt">Bool</span> <span class="o">=</span> <span class="kc">true</span><span class="p">)</span> <span class="p">{</span> <span class="k">self</span><span class="o">.</span><span class="n">coordinatorComponents</span><span class="o">.</span><span class="n">navigationController</span><span class="o">.</span><span class="nf">pushViewController</span><span class="p">(</span><span class="n">viewController</span><span class="p">,</span> <span class="nv">animated</span><span class="p">:</span> <span class="n">animated</span><span class="p">)</span> <span class="p">}</span> <span class="kd">func</span> <span class="nf">present</span><span class="p">(</span><span class="nv">childCoordinator</span><span class="p">:</span> <span class="kt">Coordinator</span><span class="p">,</span> <span class="nv">animated</span><span class="p">:</span> <span class="kt">Bool</span> <span class="o">=</span> <span class="kc">true</span><span class="p">)</span> <span class="p">{</span> <span class="k">let</span> <span class="nv">childVC</span> <span class="o">=</span> <span class="n">childCoordinator</span><span class="o">.</span><span class="n">coordinatorComponents</span><span class="o">.</span><span class="n">navigationController</span> <span class="k">self</span><span class="o">.</span><span class="n">coordinatorComponents</span><span class="o">.</span><span class="n">navigationController</span><span class="o">.</span><span class="nf">present</span><span class="p">(</span><span class="n">childVC</span><span class="p">,</span> <span class="nv">animated</span><span class="p">:</span> <span class="n">animated</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="k">in</span> <span class="k">self</span><span class="p">?</span><span class="o">.</span><span class="n">coordinatorComponents</span><span class="o">.</span><span class="n">childCoordinator</span> <span class="o">=</span> <span class="n">childCoordinator</span> <span class="c1">// retain the child strongly</span> <span class="p">}</span> <span class="p">}</span> <span class="kd">func</span> <span class="nf">pop</span><span class="p">(</span><span class="nv">animated</span><span class="p">:</span> <span class="kt">Bool</span> <span class="o">=</span> <span class="kc">true</span><span class="p">)</span> <span class="p">{</span> <span class="k">let</span> <span class="nv">privateAPI</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="n">coordinatorComponents</span> <span class="k">if</span> <span class="n">privateAPI</span><span class="o">.</span><span class="n">childCoordinator</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span> <span class="n">privateAPI</span><span class="o">.</span><span class="n">navigationController</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="n">animated</span><span class="p">)</span> <span class="p">{</span> <span class="p">[</span><span class="k">weak</span> <span class="n">privateAPI</span><span class="p">]</span> <span class="k">in</span> <span class="n">privateAPI</span><span class="p">?</span><span class="o">.</span><span class="n">childCoordinator</span> <span class="o">=</span> <span class="kc">nil</span> <span class="p">}</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="n">privateAPI</span><span class="o">.</span><span class="n">navigationController</span><span class="o">.</span><span class="nf">popViewController</span><span class="p">(</span><span class="nv">animated</span><span class="p">:</span> <span class="n">animated</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>And the conforming type <code class="language-plaintext highlighter-rouge">MainCoordinator</code> would now:</p> <ul> <li>Only have to declare a single <code class="language-plaintext highlighter-rouge">let coordinatorComponents = CoordinatorComponents()</code> property, not having to know what’s inside that <code class="language-plaintext highlighter-rouge">CoordinatorComponents</code> type (hiding the implementation details)</li> <li>Wouldn’t be able to access any of the <code class="language-plaintext highlighter-rouge">coordinatorComponents</code> properties from its <code class="language-plaintext highlighter-rouge">MainCoordinator.swift</code> file, as they are declared <code class="language-plaintext highlighter-rouge">fileprivate</code></li> </ul> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="kt">MainCoordinator</span><span class="p">:</span> <span class="kt">Coordinator</span> <span class="p">{</span> <span class="k">let</span> <span class="nv">coordinatorComponents</span> <span class="o">=</span> <span class="kt">CoordinatorComponents</span><span class="p">()</span> <span class="kd">func</span> <span class="nf">showTutorialPage1</span><span class="p">()</span> <span class="p">{</span> <span class="k">let</span> <span class="nv">vc</span> <span class="o">=</span> <span class="nf">makeTutorialPage</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="nv">coordinator</span><span class="p">:</span> <span class="k">self</span><span class="p">)</span> <span class="k">self</span><span class="o">.</span><span class="nf">push</span><span class="p">(</span><span class="nv">viewController</span><span class="p">:</span> <span class="n">vc</span><span class="p">)</span> <span class="p">}</span> <span class="kd">func</span> <span class="nf">showTutorialPage2</span><span class="p">()</span> <span class="p">{</span> <span class="k">let</span> <span class="nv">vc</span> <span class="o">=</span> <span class="nf">makeTutorialPage</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="nv">coordinator</span><span class="p">:</span> <span class="k">self</span><span class="p">)</span> <span class="k">self</span><span class="o">.</span><span class="nf">push</span><span class="p">(</span><span class="nv">viewController</span><span class="p">:</span> <span class="n">vc</span><span class="p">)</span> <span class="p">}</span> <span class="kd">private</span> <span class="kd">func</span> <span class="nf">makeTutorialPage</span><span class="p">(</span><span class="n">_</span> <span class="nv">num</span><span class="p">:</span> <span class="kt">Int</span><span class="p">,</span> <span class="nv">coordinator</span><span class="p">:</span> <span class="kt">Coordinator</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">UIViewController</span> <span class="p">{</span> <span class="err">…</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>Sure, you still need to declare <code class="language-plaintext highlighter-rouge">let coordinatorComponents</code> in the conforming type to provide the storage, and this declaration has to be visible (can’t be made <code class="language-plaintext highlighter-rouge">private</code>) as it’s part of the requirement for conforming to <code class="language-plaintext highlighter-rouge">protocol Coordinator</code>. But:</p> <ul> <li>that’s only one property to declare instead of 2 (or more in more complex cases)</li> <li>and more importantly, even if it’s accessible from the conforming type’s implementation, and also from the outside interface, you can’t do anything with it.</li> </ul> <p>Sure you can still access <code class="language-plaintext highlighter-rouge">myMainCoordinator.coordinatorComponents</code> but you won’t be able to do anything with it as all its properties are <code class="language-plaintext highlighter-rouge">fileprivate</code>!</p> <h3 id="conclusion">Conclusion</h3> <p>Swift might not provide all the features you want right outside of the box. One might hope that some day <code class="language-plaintext highlighter-rouge">protocols</code> would be able to allow declaring access control attributes to the properties and function requirements they declare, or some way to make those hidden to the public API.</p> <p>But in the meantime, having those kind of tricks and workaround up your sleeve can make your public API nicer and more safe, avoiding to leak implementation details or to give access to properties that shouldn’t be modified outside of the implementation while still using the <a href="/swift/protocol/2015/11/08/mixins-over-inheritance/">Mixin pattern</a> and providing default implementations.</p> <div class="footnotes" role="doc-endnotes"> <ol> <li id="fn:1" role="doc-endnote"> <p>That’s some simplified example ; don’t focus on the implementation of the Coordinator pattern here — that’s not really the point of that example, which focuses more about the need to have publicly accessible properties declared in the protocol. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> </ol> </div> Sun, 02 Sep 2018 00:00:00 +0000 https://alisoftware.github.io/swift/protocols/2018/09/02/protocols-private-properties/ https://alisoftware.github.io/swift/protocols/2018/09/02/protocols-private-properties/ Exploring @dynamicMemberLookup <p>With Xcode 10 and Swift 4.2, the new <code class="language-plaintext highlighter-rouge">@dynamicMemberLookup</code> proposal is now available in Swift. Let’s have some fun with it.</p> <h2 id="welcome-xcode-10">Welcome Xcode 10</h2> <p>With the WWDC’18 just finished, the first beta of Xcode 10 is already available and really pleasant with a lots of very welcome improvements, including:</p> <ul> <li>way better autocompletion: more contextual completion, more reactive, more accurate, also works in auxiliary files in Playgrounds, …</li> <li>better Playgrounds: errors and warnings properly shown inline in red/yellow ribbons, new REPL mode is really cool, way more stable, … they are actually usable again!</li> </ul> <p>But Xcode 10 also comes with Swift 4.2 included. Which brings some interesting new features recently implemented through the Swift-Evolution process and included in this release of Swift</p> <h2 id="welcome-swift-42-and-dynamicmemberlookup">Welcome Swift 4.2… and @dynamicMemberLookup</h2> <p>One of the new features included in Swift 4.2 is <a href="https://github.com/apple/swift-evolution/blob/master/proposals/0195-dynamic-member-lookup.md">dynamicMemberLookup</a>. This allows to call object properties that will be dynamically resolved at runtime.</p> <p>This proposal had some controversy, and one thing I didn’t personally like on this new feature is that it meant that typed annotated with <code class="language-plaintext highlighter-rouge">@dynamicMemberLookup</code> would not, by design, show any potential compilation error. Which is understandable, as the whole need for that proposal was to be able to call properties which we didn’t know at compile-time.</p> <p>But this also meant that once the type was annotated with <code class="language-plaintext highlighter-rouge">@dynamicMemberLookup</code>, you wouldn’t be able to choose at call-site if you wanted an expression to allow dynamic member lookup or wanted the expression on that type to be type-checked</p> <h3 id="example-of-a-problematic-dynamicmemberlookup">Example of a problematic dynamicMemberLookup</h3> <p>Let’s take the example given in the SE-0195 proposal:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@dynamicMemberLookup</span> <span class="kd">enum</span> <span class="kt">JSON</span> <span class="p">{</span> <span class="k">case</span> <span class="nf">intValue</span><span class="p">(</span><span class="kt">Int</span><span class="p">)</span> <span class="k">case</span> <span class="nf">stringValue</span><span class="p">(</span><span class="kt">String</span><span class="p">)</span> <span class="k">case</span> <span class="nf">arrayValue</span><span class="p">(</span><span class="kt">Array</span><span class="o">&lt;</span><span class="kt">JSON</span><span class="o">&gt;</span><span class="p">)</span> <span class="k">case</span> <span class="nf">dictionaryValue</span><span class="p">(</span><span class="kt">Dictionary</span><span class="o">&lt;</span><span class="kt">String</span><span class="p">,</span> <span class="kt">JSON</span><span class="o">&gt;</span><span class="p">)</span> <span class="nf">subscript</span><span class="p">(</span><span class="n">dynamicMember</span> <span class="nv">member</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">JSON</span><span class="p">?</span> <span class="p">{</span> <span class="k">if</span> <span class="k">case</span> <span class="o">.</span><span class="nf">dictionaryValue</span><span class="p">(</span><span class="k">let</span> <span class="nv">dict</span><span class="p">)</span> <span class="o">=</span> <span class="k">self</span> <span class="p">{</span> <span class="k">return</span> <span class="n">dict</span><span class="p">[</span><span class="n">member</span><span class="p">]</span> <span class="p">}</span> <span class="k">return</span> <span class="kc">nil</span> <span class="p">}</span> <span class="k">var</span> <span class="nv">count</span><span class="p">:</span> <span class="kt">Int</span> <span class="p">{</span> <span class="k">switch</span> <span class="k">self</span> <span class="p">{</span> <span class="k">case</span> <span class="o">.</span><span class="n">intValue</span><span class="p">,</span> <span class="o">.</span><span class="nv">stringValue</span><span class="p">:</span> <span class="k">return</span> <span class="mi">1</span> <span class="k">case</span> <span class="o">.</span><span class="nf">arrayValue</span><span class="p">(</span><span class="k">let</span> <span class="nv">a</span><span class="p">):</span> <span class="k">return</span> <span class="n">a</span><span class="o">.</span><span class="n">count</span> <span class="k">case</span> <span class="o">.</span><span class="nf">dictionaryValue</span><span class="p">(</span><span class="k">let</span> <span class="nv">d</span><span class="p">):</span> <span class="k">return</span> <span class="n">d</span><span class="o">.</span><span class="n">count</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>The problem I have with this example is that we can call whatever property we want on a <code class="language-plaintext highlighter-rouge">JSON</code> instance, because it’s <code class="language-plaintext highlighter-rouge">@dynamicMemberLookup</code> this property lookup will always compile, but sometimes returns the real property and sometimes doing a dynamic lookup, without any distinction at call site:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">j</span> <span class="o">=</span> <span class="kt">JSON</span><span class="o">.</span><span class="nf">dictionaryValue</span><span class="p">([</span> <span class="s">"comment"</span><span class="p">:</span> <span class="o">.</span><span class="nf">stringValue</span><span class="p">(</span><span class="s">"Not being able to tell the difference at call site is confusing"</span><span class="p">),</span> <span class="s">"count"</span><span class="p">:</span> <span class="o">.</span><span class="nf">intValue</span><span class="p">(</span><span class="mi">42</span><span class="p">),</span> <span class="s">"count2"</span><span class="p">:</span> <span class="o">.</span><span class="nf">intValue</span><span class="p">(</span><span class="mi">1337</span><span class="p">)</span> <span class="p">])</span> <span class="c1">// Show the issue: hard to know when this is solved dynamically vs at compile time</span> <span class="n">j</span><span class="o">.</span><span class="n">count</span> <span class="c1">// This one will return 3 — from `var count: Int` in `enum JSON`. And not 42 from the dictionary</span> <span class="n">j</span><span class="o">.</span><span class="n">count2</span> <span class="c1">// While this one will return the 1337 value from the dictionary</span> <span class="n">j</span><span class="o">.</span><span class="n">cuont</span> <span class="c1">// And this will compile despite the typo (and return nil because no such key exists in the JSON)</span> </code></pre></div></div> <h2 id="improving-the-situation-make-it-explicit-at-call-site">Improving the situation: make it explicit at call site</h2> <p>What I want to be able to do is to distinguish explicitly between calls that are checked at compile time and those that are dynamically looked up (and are not compile-time checked)</p> <p>An example of the call site I’d find more clear would be like this:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">j</span><span class="o">.</span><span class="n">count</span> <span class="c1">// compile-time checked. Compiles, calls the property on Dictionary, returns 3</span> <span class="n">j</span><span class="o">.</span><span class="n">cuont</span> <span class="c1">// compile-time error</span> <span class="n">j</span><span class="o">^.</span><span class="n">count2</span> <span class="c1">// explicit that this is dynamically looked-up and not compile-time checked. Dynamically lookup inside the dictionary at runtime, and returns the value of key "count2" (which is 1337)</span> </code></pre></div></div> <h2 id="lets-build-it">Let’s build it!</h2> <p>The solution is actually quite short to implement. We’ll create a <code class="language-plaintext highlighter-rouge">^</code> postfix operator that will wrap the instance it’s applied on into some proxy object that is the one being <code class="language-plaintext highlighter-rouge">@dynamicMemberLookup</code>. That proxy object will just wrap the dictionary we applied <code class="language-plaintext highlighter-rouge">^</code> on, and on dynamic lookup, will search the key in the dictionary to return the corresponding value (if it exists).</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@dynamicMemberLookup</span> <span class="kd">public</span> <span class="kd">struct</span> <span class="kt">DynamicLookupProxy</span> <span class="p">{</span> <span class="k">let</span> <span class="nv">dict</span><span class="p">:</span> <span class="p">[</span><span class="kt">String</span><span class="p">:</span> <span class="kt">Any</span><span class="p">]</span> <span class="kd">public</span> <span class="nf">subscript</span><span class="p">(</span><span class="n">dynamicMember</span> <span class="nv">member</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Any</span><span class="p">?</span> <span class="p">{</span> <span class="k">return</span> <span class="n">dict</span><span class="p">[</span><span class="n">member</span><span class="p">]</span> <span class="p">}</span> <span class="p">}</span> <span class="k">postfix</span> <span class="k">operator</span> <span class="o">^</span> <span class="kd">public</span> <span class="k">postfix</span> <span class="kd">func</span> <span class="o">^</span> <span class="p">(</span><span class="nv">lhs</span><span class="p">:</span> <span class="p">[</span><span class="kt">String</span><span class="p">:</span> <span class="kt">Any</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="kt">DynamicLookupProxy</span> <span class="p">{</span> <span class="k">return</span> <span class="kt">DynamicLookupProxy</span><span class="p">(</span><span class="n">lhs</span><span class="p">)</span> <span class="p">}</span> </code></pre></div></div> <p>And now if we test this on our example:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">j</span><span class="p">:</span> <span class="p">[</span><span class="kt">String</span><span class="p">:</span> <span class="kt">Any</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span> <span class="s">"comment"</span><span class="p">:</span> <span class="s">"Being able to tell the difference at call site is explicitly is nicer"</span><span class="p">,</span> <span class="s">"count"</span><span class="p">:</span> <span class="mi">42</span><span class="p">,</span> <span class="s">"count2"</span><span class="p">:</span> <span class="mi">1337</span> <span class="p">]</span> <span class="n">j</span><span class="o">.</span><span class="n">count</span> <span class="c1">// checked at compile time. Returns 3</span> <span class="n">j</span><span class="o">^.</span><span class="n">count</span> <span class="c1">// dynamic lookup. Returns 42</span> <span class="n">j</span><span class="o">.</span><span class="n">cuont</span> <span class="c1">// compile-time error</span> <span class="n">j</span><span class="o">^.</span><span class="n">cuont</span> <span class="c1">// dynamic lookup, but key not found. Returns nil.</span> </code></pre></div></div> <p>Success!</p> <h2 id="chain-all-the-things">Chain all the things!</h2> <p>The only problem with this is that our dynamic lookup returns Any. This means we have to cast it to the desired object, and we can’t chain those calls:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">j2</span><span class="p">:</span> <span class="p">[</span><span class="kt">String</span><span class="p">:</span> <span class="kt">Any</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span> <span class="s">"name"</span><span class="p">:</span> <span class="s">"Olivier"</span><span class="p">,</span> <span class="s">"address"</span><span class="p">:</span> <span class="p">[</span> <span class="s">"street"</span><span class="p">:</span> <span class="s">"Swift Street"</span><span class="p">,</span> <span class="s">"number"</span><span class="p">:</span> <span class="mi">1337</span><span class="p">,</span> <span class="s">"city"</span><span class="p">:</span> <span class="s">"PlaygroundVille"</span> <span class="p">]</span> <span class="p">]</span> <span class="n">j2</span><span class="o">^.</span><span class="n">name</span> <span class="n">j2</span><span class="o">^.</span><span class="n">address</span><span class="o">^.</span><span class="n">street</span> <span class="c1">// 🛑 Cannot convert value of type 'Any?' to expected argument type '[String : Any]'. Fix: Insert ' as! ([String : Any])'</span> </code></pre></div></div> <p>How do we solve that? Surely <a href="/swift/2015/09/06/thinking-in-swift-1/">not by the force-cast</a> suggested by that Fix-It!</p> <p>Well, if instead of returning <code class="language-plaintext highlighter-rouge">Any?</code> we make the <code class="language-plaintext highlighter-rouge">subscript</code> generic (yes, that’s also a recent addition, in Swift 4!) to make it be able to return some typed value, we could make it infer it being <code class="language-plaintext highlighter-rouge">[String: Any]</code> in some contexts?</p> <p>So let’s add that generic subscript to our <code class="language-plaintext highlighter-rouge">DynamicLookupProxy</code> struct:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@dynamicMemberLookup</span> <span class="kd">public</span> <span class="kd">struct</span> <span class="kt">DynamicLookupProxy</span> <span class="p">{</span> <span class="kd">private</span> <span class="k">let</span> <span class="nv">dict</span><span class="p">:</span> <span class="p">[</span><span class="kt">String</span><span class="p">:</span> <span class="kt">Any</span><span class="p">]</span> <span class="kd">public</span> <span class="nf">init</span><span class="p">(</span><span class="n">_</span> <span class="nv">dict</span><span class="p">:</span> <span class="p">[</span><span class="kt">String</span><span class="p">:</span> <span class="kt">Any</span><span class="p">])</span> <span class="p">{</span> <span class="k">self</span><span class="o">.</span><span class="n">dict</span> <span class="o">=</span> <span class="n">dict</span> <span class="p">}</span> <span class="kd">public</span> <span class="kd">subscript</span><span class="o">&lt;</span><span class="kt">T</span><span class="o">&gt;</span><span class="p">(</span><span class="n">dynamicMember</span> <span class="nv">member</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">T</span><span class="p">?</span> <span class="p">{</span> <span class="k">return</span> <span class="n">dict</span><span class="p">[</span><span class="n">member</span><span class="p">]</span> <span class="k">as?</span> <span class="kt">T</span> <span class="p">}</span> <span class="kd">public</span> <span class="nf">subscript</span><span class="p">(</span><span class="n">dynamicMember</span> <span class="nv">member</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Any</span><span class="p">?</span> <span class="p">{</span> <span class="k">return</span> <span class="n">dict</span><span class="p">[</span><span class="n">member</span><span class="p">]</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p><em>Note: I still kept the <code class="language-plaintext highlighter-rouge">-&gt; Any?</code> implementation so that if the return type is not specified/inferable, it’ll falls back to <code class="language-plaintext highlighter-rouge">Any?</code> instead of forcing you to specify the type explicitly</em></p> <p>And now look at that!</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">j2</span><span class="o">^.</span><span class="n">name</span> <span class="c1">// Returns "Olivier"</span> <span class="n">j2</span><span class="o">^.</span><span class="n">address</span><span class="p">?</span><span class="o">^.</span><span class="n">street</span> <span class="c1">// Returns "Swift Street"</span> <span class="n">j2</span><span class="o">^.</span><span class="n">address</span><span class="p">?</span><span class="o">^.</span><span class="n">zipcode</span> <span class="c1">// Returns nil, as there's no zipcode</span> <span class="n">j2</span><span class="o">.</span><span class="n">count</span> <span class="c1">// Calls the compile-time property count on Dictionary, returns 2</span> </code></pre></div></div> <p>The line <code class="language-plaintext highlighter-rouge">j2^.address</code> is actually equivalent to <code class="language-plaintext highlighter-rouge">j2["address"]</code>… so why bother having all that? It’s not that different after all, and maybe it’s better to keep strings explicit? Maybe… but now we can also chain those properties to access subkeys without having to do explicit casts, as opposed to using string subscripts!</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Nice call site with our new solution, but still explicit that it's dynamic lookup:</span> <span class="n">j2</span><span class="o">^.</span><span class="n">address</span><span class="p">?</span><span class="o">^.</span><span class="n">street</span> <span class="c1">// What we'd have to write without `@dynamicMemberLookup` to have the same:</span> <span class="p">(</span><span class="n">j2</span><span class="p">[</span><span class="s">"address"</span><span class="p">]</span> <span class="k">as?</span> <span class="p">[</span><span class="kt">String</span><span class="p">:</span> <span class="kt">Any</span><span class="p">])?[</span><span class="s">"street"</span><span class="p">]</span> </code></pre></div></div> <p>And even better, since we’re now using generics, we can even let the compiler infer the return type of the dynamic member lookup by hinting the type of the variable we store the result to:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">addr</span><span class="p">:</span> <span class="p">[</span><span class="kt">String</span><span class="p">:</span> <span class="kt">Any</span><span class="p">]?</span> <span class="o">=</span> <span class="n">j2</span><span class="o">^.</span><span class="n">address</span> <span class="n">addr</span><span class="p">?</span><span class="o">^.</span><span class="n">street</span> <span class="c1">// Returns "Swift Street"</span> <span class="n">addr</span><span class="p">?[</span><span class="s">"street"</span><span class="p">]</span> <span class="c1">// same thing, really, but arguably less nice</span> <span class="n">addr</span><span class="p">?</span><span class="o">.</span><span class="n">count</span> <span class="c1">// Still no ambiguity: no '^' here, this one is compile-time checked (returns 3)</span> </code></pre></div></div> <h2 id="avoid-the-question-mark">Avoid the question mark?</h2> <p>The last thing we could consider doing is to get rid of the <code class="language-plaintext highlighter-rouge">?</code> and consider that dynamic member lookup on a <code class="language-plaintext highlighter-rouge">nil</code> value should directly return <code class="language-plaintext highlighter-rouge">nil</code> — without having to rely on optional chaining to do that.</p> <p>Indeed, having to use <code class="language-plaintext highlighter-rouge">?</code> in front of every <code class="language-plaintext highlighter-rouge">^</code> in the chain (besides the first one) can feel repetitive.</p> <p>The solution actually consists of a simple change: just allow our <code class="language-plaintext highlighter-rouge">^</code> operator to be applied on optional dictionaries. If our dictionary isn’t optional, the compiler will lift it to optional for us, and if it’s optional, we can imagine just return our <code class="language-plaintext highlighter-rouge">DynamicLookupProxy</code> but with an empty dictionary — so that any following dynamic lookup will always return a <code class="language-plaintext highlighter-rouge">nil</code> value anyway.</p> <p>So just change our <code class="language-plaintext highlighter-rouge">func ^</code> signature and implementation to this:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="k">postfix</span> <span class="kd">func</span> <span class="o">^</span> <span class="p">(</span><span class="nv">lhs</span><span class="p">:</span> <span class="p">[</span><span class="kt">String</span><span class="p">:</span> <span class="kt">Any</span><span class="p">]?)</span> <span class="o">-&gt;</span> <span class="kt">DynamicLookupProxy</span> <span class="p">{</span> <span class="k">return</span> <span class="kt">DynamicLookupProxy</span><span class="p">(</span><span class="n">lhs</span> <span class="p">??</span> <span class="p">[:])</span> <span class="p">}</span> </code></pre></div></div> <p>And we’re good to go! Now we can update our call sites to look like this:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// No need for the extra `?` before the second `^` anymore.</span> <span class="k">let</span> <span class="nv">street</span><span class="p">:</span> <span class="kt">String</span><span class="p">?</span> <span class="o">=</span> <span class="n">j2</span><span class="o">^.</span><span class="n">address</span><span class="o">^.</span><span class="n">street</span> <span class="c1">// Still returns "Swift Street"</span> </code></pre></div></div> <p>Was that a good idea? Wasn’t it better to keep explicit the fact that the return type of any dynamic lookup was optional by requiring the optional chaining syntax and that <code class="language-plaintext highlighter-rouge">?</code> before continuing chaining with <code class="language-plaintext highlighter-rouge">^.street</code>? That’s another debate, and I’ll let you decide. But at least you know that’s possible 😉</p> <h2 id="more-exploration-creating-a-contextscope">More exploration: creating a context/scope</h2> <p>If you’ve made it so far, maybe you’re even ready to take it a step further? (if not, you can skip this last part, I won’t blame you 😄)</p> <p>In the playground attached below, I’ve explored that idea a bit more, by allowing another syntax. This one looks more like creating a context or scope in which everything we call is dynamically looked up (instead of looking like chaining <code class="language-plaintext highlighter-rouge">^</code> calls):</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">street</span><span class="p">:</span> <span class="kt">String</span><span class="p">?</span> <span class="o">=</span> <span class="n">j2</span><span class="o">.</span><span class="n">dynamicLookup</span> <span class="p">{</span> <span class="nv">$0</span><span class="o">.</span><span class="n">address</span><span class="p">?</span><span class="o">.</span><span class="n">street</span> <span class="p">}</span> </code></pre></div></div> <p>To implement this, I’ve created a separate proxy type called <code class="language-plaintext highlighter-rouge">DynamicLookupContext</code> similar to our first <code class="language-plaintext highlighter-rouge">DynamicLookupProxy</code>, but this time, our subscript on <code class="language-plaintext highlighter-rouge">DynamicLookupContext</code> does not returns the value found in the dictionary, but instead wraps that value in a new <code class="language-plaintext highlighter-rouge">DynamicLookupContext</code> and return that. Which means we can chain them (without having to re-lift them at every step like we had to do by repeating <code class="language-plaintext highlighter-rouge">^</code> at each step in our previous solution).</p> <p>Then the <code class="language-plaintext highlighter-rouge">func dynamicLookup&lt;T&gt;</code> I’ve added on <code class="language-plaintext highlighter-rouge">[String: Any]</code> takes care of wrapping that initial dictionary we want to query into a <code class="language-plaintext highlighter-rouge">DynamicLookupContext</code>, let you execute your chain of dynamic lookups on this (via a closure you provide), and extract the output from the result returned by the end of that chain:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@dynamicMemberLookup</span> <span class="kd">public</span> <span class="kd">struct</span> <span class="kt">DynamicLookupContext</span> <span class="p">{</span> <span class="k">let</span> <span class="nv">value</span><span class="p">:</span> <span class="kt">Any</span> <span class="kd">public</span> <span class="nf">subscript</span><span class="p">(</span><span class="n">dynamicMember</span> <span class="nv">member</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">DynamicLookupContext</span><span class="p">?</span> <span class="p">{</span> <span class="k">let</span> <span class="nv">dict</span> <span class="o">=</span> <span class="n">value</span> <span class="k">as?</span> <span class="p">[</span><span class="kt">String</span><span class="p">:</span> <span class="kt">Any</span><span class="p">]</span> <span class="k">guard</span> <span class="k">let</span> <span class="nv">value</span> <span class="o">=</span> <span class="n">dict</span><span class="p">?[</span><span class="n">member</span><span class="p">]</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">return</span> <span class="kt">DynamicLookupContext</span><span class="p">(</span><span class="nv">value</span><span class="p">:</span> <span class="n">value</span><span class="p">)</span> <span class="p">}</span> <span class="kd">public</span> <span class="nf">subscript</span><span class="p">(</span><span class="nv">index</span><span class="p">:</span> <span class="kt">Int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">DynamicLookupContext</span><span class="p">?</span> <span class="p">{</span> <span class="k">guard</span> <span class="k">let</span> <span class="nv">array</span> <span class="o">=</span> <span class="n">value</span> <span class="k">as?</span> <span class="p">[</span><span class="kt">Any</span><span class="p">]</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">return</span> <span class="kt">DynamicLookupContext</span><span class="p">(</span><span class="nv">value</span><span class="p">:</span> <span class="n">array</span><span class="p">[</span><span class="n">index</span><span class="p">])</span> <span class="p">}</span> <span class="p">}</span> <span class="kd">public</span> <span class="kd">extension</span> <span class="kt">Dictionary</span> <span class="k">where</span> <span class="kt">Key</span> <span class="o">==</span> <span class="kt">String</span><span class="p">,</span> <span class="kt">Value</span><span class="p">:</span> <span class="kt">Any</span> <span class="p">{</span> <span class="kd">func</span> <span class="n">dynamicLookup</span><span class="o">&lt;</span><span class="kt">T</span><span class="o">&gt;</span><span class="p">(</span><span class="nv">execute</span><span class="p">:</span> <span class="p">(</span><span class="kt">DynamicLookupContext</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">DynamicLookupContext</span><span class="p">?)</span> <span class="o">-&gt;</span> <span class="kt">T</span><span class="p">?</span> <span class="p">{</span> <span class="k">let</span> <span class="nv">wrapped</span> <span class="o">=</span> <span class="kt">DynamicLookupContext</span><span class="p">(</span><span class="nv">value</span><span class="p">:</span> <span class="k">self</span><span class="p">)</span> <span class="k">let</span> <span class="nv">result</span> <span class="o">=</span> <span class="nf">execute</span><span class="p">(</span><span class="n">wrapped</span><span class="p">)</span> <span class="k">return</span> <span class="n">result</span><span class="p">?</span><span class="o">.</span><span class="n">value</span> <span class="k">as?</span> <span class="kt">T</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>As a result, you can now choose between either syntax, whichever you like best:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// With our DynamicLookupProxy and ^ operator we saw before:</span> <span class="k">let</span> <span class="nv">street1</span><span class="p">:</span> <span class="kt">String</span><span class="p">?</span> <span class="o">=</span> <span class="n">j2</span><span class="o">^.</span><span class="n">address</span><span class="o">^.</span><span class="n">street</span> <span class="c1">// With our new DynamicLookupContext and dynamicLookup method above:</span> <span class="k">let</span> <span class="nv">street2</span><span class="p">:</span> <span class="kt">String</span><span class="p">?</span> <span class="o">=</span> <span class="n">j2</span><span class="o">.</span><span class="n">dynamicLookup</span> <span class="p">{</span> <span class="nv">$0</span><span class="o">.</span><span class="n">address</span><span class="p">?</span><span class="o">.</span><span class="n">street</span> <span class="p">}</span> <span class="c1">// Without any usage of those @dynamicMemberLookup tricks, we'd have to do this instead:</span> <span class="k">let</span> <span class="nv">addr</span> <span class="o">=</span> <span class="n">j2</span><span class="p">[</span><span class="s">"address"</span><span class="p">]</span> <span class="k">as?</span> <span class="p">[</span><span class="kt">String</span><span class="p">:</span> <span class="kt">Any</span><span class="p">]</span> <span class="k">let</span> <span class="nv">street3</span> <span class="o">=</span> <span class="n">addr</span><span class="p">[</span><span class="s">"street"</span><span class="p">]</span> <span class="k">as?</span> <span class="kt">String</span> </code></pre></div></div> <blockquote> <p>I’ve <a href="/assets/DynamicMemberLookup.playground.zip">attached an Xcode 10 playground</a> with my experimentation around this and the code presented above.</p> </blockquote> <h2 id="conclusion">Conclusion</h2> <p>What do you think of this solution?</p> <p>To be honest, I am torn as to consider this really useful in production code or just a toy idea.</p> <ul> <li> <p>I like the fact that it’s now clear at call site when we intend to do a dynamic lookup and when we intend to do a real property access that is checked at compile time. We can choose when we want one or the other.</p> </li> <li> <p>On the other hand, the fact that the code still looks like real Swift instructions — without explicit String-typed keys clearly appearing — can make this confusing; people have to know what this <code class="language-plaintext highlighter-rouge">^</code> operator we created does and that it allows part of the expression to not be checked by the compiler.</p> </li> </ul> <p>As always when you create a custom operator — and even more so here where it kind of “disable the compile-time checks on some parts of the expression” — it can be hard for newcomers on your code to understand how that works, so even if this is fun and powerful, we also have to keep that in mind.</p> <p>What do you all think? Do you like the idea? Would you use that in production to allow you to quickly parse parts of an arbitrary JSON or Plist or dictionary? Or should it just be kept as a fun experiment in a playground?</p> Thu, 14 Jun 2018 00:00:00 +0000 https://alisoftware.github.io/swift/exploration/2018/06/14/exploring-dynamicmemberlookup/ https://alisoftware.github.io/swift/exploration/2018/06/14/exploring-dynamicmemberlookup/ Using Dedicated Objects as Delegates & Datasources <p>Today for my come-back post (hopefully), I’ll be talking about a tip for your <code class="language-plaintext highlighter-rouge">UITableViewDataSource</code> and <code class="language-plaintext highlighter-rouge">UITableViewDelegate</code>. The idea is in fact applicable to a lot of other things following the same <code class="language-plaintext highlighter-rouge">delegate</code> pattern, but the use case of <code class="language-plaintext highlighter-rouge">UITableView</code> will hopefully be the most concrete example for most people.</p> <h2 id="the-idea">The idea</h2> <p>A lot of developers, me included, have taken the habit of making your <code class="language-plaintext highlighter-rouge">UIViewController</code> the <code class="language-plaintext highlighter-rouge">delegate</code> and <code class="language-plaintext highlighter-rouge">dataSource</code> of their <code class="language-plaintext highlighter-rouge">UITableView</code>. That’s all good and fine, but in effort to minimize the code size of your <code class="language-plaintext highlighter-rouge">UIViewController</code> (you know, that Massive-View-Controller thing all we want to avoid) and limit the responsibility of your <code class="language-plaintext highlighter-rouge">UIViewController</code> (separation of concern), there’s a better solution.</p> <p>The general idea is in fact very simple: extract your <code class="language-plaintext highlighter-rouge">UITableViewDataSource</code> and <code class="language-plaintext highlighter-rouge">UITableViewDelegate</code> into a separate object. And make e.g. your <code class="language-plaintext highlighter-rouge">UIViewController</code> retain that object and set it as the <code class="language-plaintext highlighter-rouge">tableView</code>’s <code class="language-plaintext highlighter-rouge">delegate</code> and <code class="language-plaintext highlighter-rouge">dataSource</code>.</p> <p>In practice, you can move all the responsibility to a single object (making it both the delegate and dataSource, and making it the responsibility to provide the cells), or you can make that separate objects (one for delegate, one for dataSource), or you can make that separate object just be a proxy, that will be reusable for multiple ViewControllers, and delegate more simple queries to each <code class="language-plaintext highlighter-rouge">UIViewController</code> using it to customize just the right things.</p> <h2 id="concrete-example">Concrete example</h2> <p>Let’s start with a very simple example: a list of products.</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">Product</span> <span class="p">{</span> <span class="k">let</span> <span class="nv">name</span><span class="p">:</span> <span class="kt">String</span> <span class="k">let</span> <span class="nv">category</span><span class="p">:</span> <span class="kt">String</span> <span class="k">let</span> <span class="nv">price</span><span class="p">:</span> <span class="kt">Double</span> <span class="p">}</span> </code></pre></div></div> <p>Splitting your DataSource into a dedicated type would look like that:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="kt">ListDataSource</span><span class="p">:</span> <span class="kt">NSObject</span> <span class="p">{</span> <span class="k">let</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="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">product</span><span class="p">(</span><span class="n">at</span> <span class="nv">indexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Product</span> <span class="p">{</span> <span class="k">return</span> <span class="k">self</span><span class="o">.</span><span class="n">products</span><span class="p">[</span><span class="n">indexPath</span><span class="o">.</span><span class="n">row</span><span class="p">]</span> <span class="p">}</span> <span class="p">}</span> <span class="kd">extension</span> <span class="kt">ListDataSource</span><span class="p">:</span> <span class="kt">UITableViewDataSource</span> <span class="p">{</span> <span class="kd">func</span> <span class="nf">numberOfSections</span><span class="p">(</span><span class="k">in</span> <span class="nv">tableView</span><span class="p">:</span> <span class="kt">UITableView</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Int</span> <span class="p">{</span> <span class="k">return</span> <span class="mi">1</span> <span class="p">}</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="n">_</span> <span class="nv">tableView</span><span class="p">:</span> <span class="kt">UITableView</span><span class="p">,</span> <span class="n">numberOfRowsInSection</span> <span class="nv">section</span><span class="p">:</span> <span class="kt">Int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Int</span> <span class="p">{</span> <span class="k">return</span> <span class="k">self</span><span class="o">.</span><span class="n">products</span><span class="o">.</span><span class="n">count</span> <span class="p">}</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="n">_</span> <span class="nv">tableView</span><span class="p">:</span> <span class="kt">UITableView</span><span class="p">,</span> <span class="n">cellForRowAt</span> <span class="nv">indexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">UITableViewCell</span> <span class="p">{</span> <span class="k">return</span> <span class="nf">cell</span><span class="p">(</span><span class="nv">for</span><span class="p">:</span> <span class="nf">product</span><span class="p">(</span><span class="nv">at</span><span class="p">:</span> <span class="n">indexPath</span><span class="p">))</span> <span class="p">}</span> <span class="kd">func</span> <span class="nf">cell</span><span class="p">(</span><span class="k">for</span> <span class="nv">product</span><span class="p">:</span> <span class="kt">Product</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">UITableViewCell</span> <span class="p">{</span> <span class="c1">// Of course in a real project we'd dequeue a custom cell from tableView, etc.</span> <span class="k">let</span> <span class="nv">cell</span> <span class="o">=</span> <span class="kt">UITableViewCell</span><span class="p">()</span> <span class="n">cell</span><span class="o">.</span><span class="n">textLabel</span><span class="p">?</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">product</span><span class="o">.</span><span class="n">name</span> <span class="k">return</span> <span class="n">cell</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>And then you’ll use it this way in your <code class="language-plaintext highlighter-rouge">UIViewController</code>:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="kt">DemoViewController</span> <span class="p">:</span> <span class="kt">UIViewController</span> <span class="p">{</span> <span class="k">var</span> <span class="nv">tableView</span><span class="p">:</span> <span class="kt">UITableView</span><span class="o">!</span> <span class="k">var</span> <span class="nv">currentDataSource</span><span class="p">:</span> <span class="kt">UITableViewDataSource</span><span class="p">?</span> <span class="p">{</span> <span class="k">didSet</span> <span class="p">{</span> <span class="k">self</span><span class="o">.</span><span class="n">tableView</span><span class="o">.</span><span class="n">dataSource</span> <span class="o">=</span> <span class="n">currentDataSource</span> <span class="k">self</span><span class="o">.</span><span class="n">tableView</span><span class="o">.</span><span class="nf">reloadData</span><span class="p">()</span> <span class="p">}</span> <span class="p">}</span> <span class="k">override</span> <span class="kd">func</span> <span class="nf">viewDidAppear</span><span class="p">(</span><span class="n">_</span> <span class="nv">animated</span><span class="p">:</span> <span class="kt">Bool</span><span class="p">)</span> <span class="p">{</span> <span class="k">super</span><span class="o">.</span><span class="nf">viewDidAppear</span><span class="p">(</span><span class="n">animated</span><span class="p">)</span> <span class="k">let</span> <span class="nv">products</span> <span class="o">=</span> <span class="nf">loadProducts</span><span class="p">()</span> <span class="k">self</span><span class="o">.</span><span class="n">currentDataSource</span> <span class="o">=</span> <span class="kt">ListDataSource</span><span class="p">(</span><span class="nv">products</span><span class="p">:</span> <span class="n">products</span><span class="p">)</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>In that very simplistic example, the benefits might not seem very visible at first, especially because for now our <code class="language-plaintext highlighter-rouge">DemoViewController</code> doesn’t do much yet. But in a real world example where that <code class="language-plaintext highlighter-rouge">DemoViewController</code> probably does a lot more — like some logic specific to your app, or handling any <code class="language-plaintext highlighter-rouge">@IBAction</code> you have on the rest of your screen, etc — I hope you can see how at least extracting all the <code class="language-plaintext highlighter-rouge">UITableView</code> display logic would help here.</p> <p><em>It’s also generally easier to reason about, as all the logic about the display of that DataSource is now extracted in its dedicated file &amp; class, instead of it being mixed with the rest of the code of your <code class="language-plaintext highlighter-rouge">UIViewController</code>, and that separation will probably help you when tweaking or editing the way the <code class="language-plaintext highlighter-rouge">UITableView</code> should display its data, by focusing on one specific and isolated file, instead of scrolling amongst other unrelated code.</em></p> <h2 id="retain-your-datasources">Retain your DataSources!</h2> <p>One tricky thing however when you put your dataSource in a dedicated, separate object, is to not forget to retain it.</p> <p>The <code class="language-plaintext highlighter-rouge">dataSource</code> property on <code class="language-plaintext highlighter-rouge">UITableView</code> is <code class="language-plaintext highlighter-rouge">weak</code> (as all dataSources and delegates should be), so if you just affect <code class="language-plaintext highlighter-rouge">tableView.dataSource</code> to a newly created <code class="language-plaintext highlighter-rouge">ListDataSource(…)</code> without retaining it, that <code class="language-plaintext highlighter-rouge">ListDataSource</code> instance will then be released from memory and your <code class="language-plaintext highlighter-rouge">UITableView</code> will go back to being empty.</p> <p>So when you use that trick, just don’t forget to retain your object serving as DataSource. In the example above, I made the <code class="language-plaintext highlighter-rouge">DemoViewController</code> retain it by using a <code class="language-plaintext highlighter-rouge">var currentDataSource</code> property, and used that occasion to use a <code class="language-plaintext highlighter-rouge">didSet</code> on it to propagate it to the <code class="language-plaintext highlighter-rouge">tableView</code> and reload it afterwards.</p> <h2 id="improving-the-datasource">Improving the DataSource</h2> <p>Now, imagine that without changing your app’s logic itself, you just wanted to adjust the way the list of products were displayed, e.g. group your products in sections according to their category.</p> <p>All you’d have to do is to modify the implementation of your <code class="language-plaintext highlighter-rouge">DataSource</code> class, without changing anything in your <code class="language-plaintext highlighter-rouge">DemoViewController</code>!</p> <p>But let’s take that one step further: instead of modifying the existing DataSource, let’s keep it but also duplicate it to create a new one (<code class="language-plaintext highlighter-rouge">SectionDataSource</code> below). By keeping both of them, we’ll then be able to switch between the two at runtime later!</p> <p>So let’s create our new <code class="language-plaintext highlighter-rouge">SectionDataSource</code>, very similar to the first one, but which now handles sections:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="kt">SectionsDataSource</span><span class="p">:</span> <span class="kt">NSObject</span> <span class="p">{</span> <span class="kd">struct</span> <span class="kt">Section</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">items</span><span class="p">:</span> <span class="p">[</span><span class="kt">Product</span><span class="p">]</span> <span class="p">}</span> <span class="k">let</span> <span class="nv">sections</span><span class="p">:</span> <span class="p">[</span><span class="kt">Section</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="p">{</span> <span class="c1">// Dispatch products into a dictionary according to their category</span> <span class="k">let</span> <span class="nv">groups</span> <span class="o">=</span> <span class="kt">Dictionary</span><span class="p">(</span><span class="nv">grouping</span><span class="p">:</span> <span class="n">products</span><span class="p">,</span> <span class="nv">by</span><span class="p">:</span> <span class="p">{</span> <span class="n">product</span> <span class="k">in</span> <span class="n">product</span><span class="o">.</span><span class="n">category</span> <span class="p">})</span> <span class="c1">// Convert that dictionary into an array of Sections, then sort it by section title</span> <span class="k">self</span><span class="o">.</span><span class="n">sections</span> <span class="o">=</span> <span class="n">groups</span><span class="o">.</span><span class="nf">map</span><span class="p">(</span><span class="kt">Section</span><span class="o">.</span><span class="kd">init</span><span class="p">)</span><span class="o">.</span><span class="nf">sorted</span><span class="p">(</span><span class="nv">by</span><span class="p">:</span> <span class="p">{</span> <span class="nv">$0</span><span class="o">.</span><span class="n">title</span> <span class="o">&lt;</span> <span class="nv">$1</span><span class="o">.</span><span class="n">title</span> <span class="p">})</span> <span class="p">}</span> <span class="kd">func</span> <span class="nf">product</span><span class="p">(</span><span class="n">at</span> <span class="nv">indexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Product</span> <span class="p">{</span> <span class="k">return</span> <span class="k">self</span><span class="o">.</span><span class="n">sections</span><span class="p">[</span><span class="n">indexPath</span><span class="o">.</span><span class="n">section</span><span class="p">]</span><span class="o">.</span><span class="n">items</span><span class="p">[</span><span class="n">indexPath</span><span class="o">.</span><span class="n">row</span><span class="p">]</span> <span class="p">}</span> <span class="p">}</span> <span class="kd">extension</span> <span class="kt">SectionsDataSource</span><span class="p">:</span> <span class="kt">UITableViewDataSource</span> <span class="p">{</span> <span class="kd">func</span> <span class="nf">numberOfSections</span><span class="p">(</span><span class="k">in</span> <span class="nv">tableView</span><span class="p">:</span> <span class="kt">UITableView</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Int</span> <span class="p">{</span> <span class="k">return</span> <span class="k">self</span><span class="o">.</span><span class="n">sections</span><span class="o">.</span><span class="n">count</span> <span class="p">}</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="n">_</span> <span class="nv">tableView</span><span class="p">:</span> <span class="kt">UITableView</span><span class="p">,</span> <span class="n">titleForHeaderInSection</span> <span class="nv">section</span><span class="p">:</span> <span class="kt">Int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">String</span><span class="p">?</span> <span class="p">{</span> <span class="k">return</span> <span class="k">self</span><span class="o">.</span><span class="n">sections</span><span class="p">[</span><span class="n">section</span><span class="p">]</span><span class="o">.</span><span class="n">title</span> <span class="p">}</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="n">_</span> <span class="nv">tableView</span><span class="p">:</span> <span class="kt">UITableView</span><span class="p">,</span> <span class="n">numberOfRowsInSection</span> <span class="nv">section</span><span class="p">:</span> <span class="kt">Int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Int</span> <span class="p">{</span> <span class="k">return</span> <span class="k">self</span><span class="o">.</span><span class="n">sections</span><span class="p">[</span><span class="n">section</span><span class="p">]</span><span class="o">.</span><span class="n">items</span><span class="o">.</span><span class="n">count</span> <span class="p">}</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="n">_</span> <span class="nv">tableView</span><span class="p">:</span> <span class="kt">UITableView</span><span class="p">,</span> <span class="n">cellForRowAt</span> <span class="nv">indexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">UITableViewCell</span> <span class="p">{</span> <span class="k">return</span> <span class="nf">cell</span><span class="p">(</span><span class="nv">for</span><span class="p">:</span> <span class="nf">product</span><span class="p">(</span><span class="nv">at</span><span class="p">:</span> <span class="n">indexPath</span><span class="p">))</span> <span class="p">}</span> <span class="kd">func</span> <span class="nf">cell</span><span class="p">(</span><span class="k">for</span> <span class="nv">product</span><span class="p">:</span> <span class="kt">Product</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">UITableViewCell</span> <span class="p">{</span> <span class="k">let</span> <span class="nv">cell</span> <span class="o">=</span> <span class="kt">UITableViewCell</span><span class="p">()</span> <span class="n">cell</span><span class="o">.</span><span class="n">textLabel</span><span class="p">?</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="n">product</span><span class="o">.</span><span class="n">name</span> <span class="k">return</span> <span class="n">cell</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>And now that we have our new <code class="language-plaintext highlighter-rouge">SectionsDataSource</code> class, all we have to do in our <code class="language-plaintext highlighter-rouge">DemoViewController</code> is… replace the <code class="language-plaintext highlighter-rouge">self.currentDataSource = ListDataSource(products: products)</code> line by <code class="language-plaintext highlighter-rouge">self.currentDataSource = SectionsDataSource(products: products)</code>, and that’s all!</p> <p>See how everything continues to work, while the majority of your changes was located in a dedicated file and your ViewController was barely affected. That also helps to focus your editing in one specific class in a single file, instead of finding in a Massive ViewController which lines to modify.</p> <h2 id="switching-datasources">Switching DataSources</h2> <p>Now that we have both <code class="language-plaintext highlighter-rouge">ListDataSource</code> and <code class="language-plaintext highlighter-rouge">SectionsDataSource</code>, we can easily imagine switching them at runtime. You could just add a <code class="language-plaintext highlighter-rouge">UISegmentedControl</code> or similar to your UI, and depending on which segment gets selected, you’d set <code class="language-plaintext highlighter-rouge">self.currentDataSource</code> to either <code class="language-plaintext highlighter-rouge">ListDataSource(products: products)</code> or <code class="language-plaintext highlighter-rouge">SectionsDataSource(products: products)</code>, and that’s all!</p> <p><img src="/assets/ListDataSource.png" alt="ListDataSource" /> <img src="/assets/SectionsDataSource.png" alt="SectionsDataSource" /></p> <p>Imagine if you had to do all that logic in your <code class="language-plaintext highlighter-rouge">DemoViewController</code> directly instead of having separate objects… That would very likely mean:</p> <ul> <li>A lot of <code class="language-plaintext highlighter-rouge">if</code> tests inside <code class="language-plaintext highlighter-rouge">numberOfRowsInSection</code> and <code class="language-plaintext highlighter-rouge">cellForRowAtIndexPath</code> methods to return different things depending on the mode (flat list or sections by categories)</li> <li>Be sure to make those <code class="language-plaintext highlighter-rouge">if</code> conditions consistent (have the same conditions and branches in <code class="language-plaintext highlighter-rouge">numberOfRowsInSection</code> and <code class="language-plaintext highlighter-rouge">cellForRowAtIndexPath</code> so that the count returned in one matches the object index used in the other)</li> <li>Either two properties to hold the data (an array of Products for the flat list, and an array of Sections for the sectionned list mode), or just use an array of Sections and trick the logic by using an array with only one section in the case of the flat list… not very consistent and readable months later anyway…</li> </ul> <p>A lot could go wrong in this setup, from mismatching the conditions in different methods you have to implement to having to focus on understanding the logic as a whole and understand the big picture at once to see how everything works together, with risks of inconsistencies along the way.</p> <p>But instead, because we separated the concerns of the <code class="language-plaintext highlighter-rouge">UITableViewDataSource</code> in their own classes, it’s all easier to read and reason about!</p> <h2 id="going-further">Going further</h2> <p>From there we can go way further and improve this a lot:</p> <ol> <li>Of course we can imagine adding more and more classes for displaying your <code class="language-plaintext highlighter-rouge">UITableView</code> differently</li> <li>But we could also imagine make those DataSources generic over a type <code class="language-plaintext highlighter-rouge">T</code> (instead of using them only for the <code class="language-plaintext highlighter-rouge">Product</code> type) to reuse them to display other types elsewhere in our application</li> <li>We could then accept a closure in the <code class="language-plaintext highlighter-rouge">init</code> of <code class="language-plaintext highlighter-rouge">SectionsDataSource&lt;T&gt;</code> to tell how to extract the section name (<code class="language-plaintext highlighter-rouge">String</code>) for each <code class="language-plaintext highlighter-rouge">T</code> (where before we hardcoded that it was <code class="language-plaintext highlighter-rouge">product.category</code>)</li> <li>We could extend the idea to <code class="language-plaintext highlighter-rouge">UITableViewDelegate</code></li> <li>We could allow to customize the cells</li> </ol> <h3 id="uitableviewdelegate">UITableViewDelegate</h3> <p>For extending the idea to <code class="language-plaintext highlighter-rouge">UITableViewDelegate</code>, you could go multiple ways:</p> <ul> <li>You could either imagine making your existing classes extend <code class="language-plaintext highlighter-rouge">UITableViewDelegate</code>, so that you’d use the same dedicated object for both being the <code class="language-plaintext highlighter-rouge">dataSource</code> and the <code class="language-plaintext highlighter-rouge">delegate</code> of your <code class="language-plaintext highlighter-rouge">tableView</code>: <code class="language-plaintext highlighter-rouge">extension SectionsDataSource: UITableViewDelegate</code>, etc.</li> <li>Or you could decide to use an entirely separate object again, so you’d have one dedicated object for your <code class="language-plaintext highlighter-rouge">tableView.dataSource</code>, and a different one to use as your <code class="language-plaintext highlighter-rouge">tableView.delegate</code></li> </ul> <p>Both options are valid, it depends of your needs and what your dedicated implementations are doing. The fact that for <code class="language-plaintext highlighter-rouge">UITableView</code>, the delegate and dataSource are often closely related might make you go towards the first solution though <sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>.</p> <p>Also, imagining you go with the first route (use the same object for dataSource and delegate), you could give closures to that object on <code class="language-plaintext highlighter-rouge">init</code> to tell it what to do especially when a <code class="language-plaintext highlighter-rouge">Product</code> is selected, then forward delegate method to call that closure:</p> <div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// let onProductSelected: (Product) -&gt; Void</span> <span class="kd">extension</span> <span class="kt">ListDataSource</span><span class="p">:</span> <span class="kt">UITableViewDelegate</span> <span class="p">{</span> <span class="kd">func</span> <span class="nf">tableView</span><span class="p">(</span><span class="n">_</span> <span class="nv">tableView</span><span class="p">:</span> <span class="kt">UITableView</span><span class="p">,</span> <span class="n">didSelectRowAt</span> <span class="nv">indexPath</span><span class="p">:</span> <span class="kt">IndexPath</span><span class="p">)</span> <span class="p">{</span> <span class="k">self</span><span class="o">.</span><span class="nf">onProductSelected</span><span class="p">(</span><span class="nf">product</span><span class="p">(</span><span class="nv">at</span><span class="p">:</span> <span class="n">indexPath</span><span class="p">))</span> <span class="p">}</span> <span class="p">}</span> </code></pre></div></div> <p>Once again, the benefit of that is, if you do the same on the <code class="language-plaintext highlighter-rouge">SectionsDataSource</code>, then just switching your <code class="language-plaintext highlighter-rouge">currentDataSource</code> from <code class="language-plaintext highlighter-rouge">ListDataSource</code> to <code class="language-plaintext highlighter-rouge">SectionsDataSource</code> wouldn’t require to change your <code class="language-plaintext highlighter-rouge">ViewController</code>, which would still work as expected, even if in one case the list is flat and in the other it’s structured in sections!</p> <p>That’s because now your DataSource object, whichever one is used, is responsible for translating your <code class="language-plaintext highlighter-rouge">IndexPath</code> into <code class="language-plaintext highlighter-rouge">Product</code> instances, so that you don’t have to care about how those products are organized in the DataSource, all you have to implement in your <code class="language-plaintext highlighter-rouge">ViewController</code> is what to do when a given <code class="language-plaintext highlighter-rouge">Product</code> is selected (instead of when a given <code class="language-plaintext highlighter-rouge">IndexPath</code> is selected), abstracting away all that <code class="language-plaintext highlighter-rouge">IndexPath-&gt;Product</code> translation logic out of your ViewController.</p> <h3 id="allowing-custom-cells">Allowing custom cells</h3> <p>For allowing custom cells to be provided by the outside (i.e. being able to use the same <code class="language-plaintext highlighter-rouge">ListDataSource</code> for different <code class="language-plaintext highlighter-rouge">tableViews</code> which are using different cells, without having to duplicate <code class="language-plaintext highlighter-rouge">ListDataSource</code> for each cell type), once again you have multiple solutions, some of them being:</p> <ul> <li>Provide a <code class="language-plaintext highlighter-rouge">cellFactory</code> closure to your DataSource classes at <code class="language-plaintext highlighter-rouge">init</code>, to tell how to build and return an <code class="language-plaintext highlighter-rouge">UITableViewCell</code> from a <code class="language-plaintext highlighter-rouge">Product</code></li> <li>Provide the cell class to use to instantiate each cell (alongside its <code class="language-plaintext highlighter-rouge">cellIdentifier</code>), but make it sure that cell class conforms to some protocol declaring a method like <code class="language-plaintext highlighter-rouge">func fill(with product: Product)</code> that your cell class would have to implement, and that your <code class="language-plaintext highlighter-rouge">ListDataSource</code> and <code class="language-plaintext highlighter-rouge">SectionsDataSource</code> would call after instantiating the cell to populate it</li> <li>Make your DataSource classes themselves have a <code class="language-plaintext highlighter-rouge">delegate</code> property of some custom protocol type (e.g. <code class="language-plaintext highlighter-rouge">protocol CellProvider</code>), and conform to that protocol to implement how to build and fill a cell with a <code class="language-plaintext highlighter-rouge">Product</code></li> </ul> <p>There are probably other ways to let your <code class="language-plaintext highlighter-rouge">UIViewController</code> tell how to build each cell for cases where you want that flexibility, all being quite similar in concept, but I hope that gives you some interesting ideas.</p> <h2 id="explore--playground">Explore &amp; Playground</h2> <p>It’s time for you to play with all those concepts yourself! Especially the ones I didn’t detail much in that last part (Generics, etc) could be interesting to explore on your own. But if you’re curious about my take on one possible solution, I’ve <a href="/assets/DedicatedDataSource.playground.zip">attached a playground</a> for you to browse the code we discussed — and even a solution using Generics so those DataSources are reusable for types other that <code class="language-plaintext highlighter-rouge">Products</code> in other places in the same app.</p> <p>Also, keep in mind that this idea we just applied is indeed well suited for <code class="language-plaintext highlighter-rouge">UITableViewData</code> &amp; <code class="language-plaintext highlighter-rouge">UITableViewDelegate</code>, but can also be used for other <code class="language-plaintext highlighter-rouge">delegate</code> patterns as well 😉</p> <p>Happy Swifting!</p> <div class="footnotes" role="doc-endnotes"> <ol> <li id="fn:1" role="doc-endnote"> <p>For example, the <code class="language-plaintext highlighter-rouge">titleForHeaderInSection</code> function is in the <code class="language-plaintext highlighter-rouge">UITableViewDataSource</code> protocol, but the <code class="language-plaintext highlighter-rouge">viewForHeaderInSection</code> function is in the <code class="language-plaintext highlighter-rouge">UITableViewDelegate</code> protocol… go figure; also, they both expect the cells to be indexed the same way by your <code class="language-plaintext highlighter-rouge">IndexPaths</code>, so using a delegate which assumes one way of organizing your data (e.g. in a flat list) and a dataSource which assumes another (e.g. in sections) probably wouldn’t fit well together <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p> </li> </ol> </div> Sun, 20 May 2018 00:00:00 +0000 https://alisoftware.github.io/architecture/2018/05/20/dedicated-datasources/ https://alisoftware.github.io/architecture/2018/05/20/dedicated-datasources/ SwiftGen 4.2 and other news <p>SwiftGen — my tool to generate Swift code so you can use your images, localized strings, fonts, storyboards and other assets in a type-safe way — has just been released in version 4.2 after a big internal refactoring. I’ve also been working on other OSS projects lately.</p> <p>This article intends to give you some news on all those various OSS projects as well as what has been going on lately and what’s next to come.</p> <p class="note">Table of Contents: <a href="#swiftgen">SwiftGen</a> • <a href="#reusable">Reusable</a> • <a href="#fastlane">fastlane</a> • <a href="#appdevcon-amsterdam-march-16-17">AppDevCon</a> • <a href="#nsbudapest--craftconf-budapest-april-25-28">NSBudapest &amp; CraftConf</a> • <a href="#blog-articles">Blog Articles</a></p> <h2 id="swiftgen">SwiftGen</h2> <p>The thing that took most of my time lately is probably the new SwiftGen release, which just went through a big internal refactoring, consisting of moving <code class="language-plaintext highlighter-rouge">SwiftGen</code> to its own GitHub organization and splitting it into multiple repositories.</p> <p>We even have a brand new logo now!</p> <h3 align="center"> <a href="https://github.com/SwiftGen/SwiftGen"> <img src="https://raw.githubusercontent.com/SwiftGen/Eve/master/logo/logo-256.png" alt="SwiftGen's new logo" height="256" /> </a> </h3> <h3 id="why-splitting-swiftgen-into-multiple-repositories">Why splitting SwiftGen into multiple repositories?</h3> <ul> <li>Since the <a href="https://github.com/krzysztofzablocki/Sourcery">Sourcery</a> project came to life — and because this new tool also uses <a href="https://github.com/kylef/Stencil">Stencil</a> the same as we always did at <code class="language-plaintext highlighter-rouge">SwiftGen</code> — Krzysztof asked us to share the code we use to drive <code class="language-plaintext highlighter-rouge">Stencil</code>, to use it with his new tool too. This way <code class="language-plaintext highlighter-rouge">SwiftGen</code> and <code class="language-plaintext highlighter-rouge">Sourcery</code> could use the same tags, filters, and extensions to write their templates similarly, an users would have consistent understanding of the template language between both tools. That meant splitting that common code in its own framework, to make it independent of <code class="language-plaintext highlighter-rouge">SwiftGen</code> so that <code class="language-plaintext highlighter-rouge">Sourcery</code> could depend on it too.</li> <li>Having separate repositories also helps separate the code of <code class="language-plaintext highlighter-rouge">SwiftGen</code> into clear modules with clear responsibilities. <ul> <li>It should also help potential contributors to understand the code better, as each part of the <code class="language-plaintext highlighter-rouge">SwiftGen</code> pipeline will be clearly separated and documented.</li> <li>This also allows those modules to be used independently of SwiftGen’s CLI, if other tools want to build something around them.</li> </ul> </li> <li>It allows those modules, as well as templates, to evolve with their own versions lifecycles, for example making it possible to release a new module or tag new templates without having to release a new version of <code class="language-plaintext highlighter-rouge">SwiftGen</code> as a whole.</li> </ul> <h3 id="why-move-swiftgen-into-its-own-github-organization">Why move SwiftGen into its own GitHub organization?</h3> <ul> <li>Since <a href="https://github.com/djbe">David Jennes</a> has become a core contributor (thanks again to him as he did most of the work on the refactoring PRs!), I felt like <code class="language-plaintext highlighter-rouge">SwiftGen</code> has more and more become a community than just my own project</li> <li>This use of a GitHub organization will hopefully help making the <code class="language-plaintext highlighter-rouge">SwiftGen</code> community grow, by attaching <code class="language-plaintext highlighter-rouge">SwiftGen</code> to an organization rather than an individual (me)</li> <li>Since we needed to split <code class="language-plaintext highlighter-rouge">SwiftGen</code> in multiple repositories anyway, having a GitHub organizaton to host them all was the logical choice</li> </ul> <h3 id="what-changed-for-users">What changed for users?</h3> <p>Actually, not that much. That big refactoring was necessary internally, but that shouldn’t change anything for the end users. That’s why this release is a minor one (4.1 -&gt; 4.2) and not a major version bump.</p> <p>This release also fixes some bugs, mainly the warning on <code class="language-plaintext highlighter-rouge">import YourAppModule</code> that was present for storyboards, and adds some new features, mainly support for <code class="language-plaintext highlighter-rouge">--param X=Y</code> to pass arbitrary parameters to your templates. See <a href="https://github.com/SwiftGen/SwiftGen/blob/4.2.0/CHANGELOG.md">the CHANGELOGs</a> for more details.</p> <h3 id="whats-next">What’s next?</h3> <p>We already have the next milestores for <code class="language-plaintext highlighter-rouge">SwiftGen</code> planned:</p> <ul> <li>Version 4.2.1 will focus on improving documentation: <ul> <li>Update the global documentation explaining the new organization in the repo (what’s the responsibility of each module)</li> <li>A Markdown per template to document them each in detail and help you choose the right template</li> <li>Providing a <code class="language-plaintext highlighter-rouge">CONTRIBUTING.md</code> and a <code class="language-plaintext highlighter-rouge">CODE_OF_CONDUCT.md</code> and documentation to help contributors feel even more welcome and help them contribute</li> </ul> </li> <li>Version 5.0.0 will clean some stuff up: <ul> <li>Removing old legacy code and template variables (that were only present because Stencil wasn’t as advanced back then as it is now)</li> <li>Re-organize and rename the templates bundled with <code class="language-plaintext highlighter-rouge">SwiftGen</code> (especially so that <code class="language-plaintext highlighter-rouge">SwiftGen</code> won’t default to using Swift 2 templates, providing more alternatives, and organizing them in directories to overcome their growing number)</li> </ul> </li> </ul> <p><strong>If you have created your own custom templates</strong>, as some templates variables will be removed in SwiftGen 5.0, I highly suggest that you start adapting them as soon as possible so they’ll be ready and still working for SwiftGen 5.0 when it’s released.<br /> This simply consists of using the new variables instead of the old ones. Both the deprecated variables and the new ones are present in SwiftGen 4.2, so this transition should go smoothly.</p> <p>To help you migrate your templates to be ready for the upcoming SwiftGen 5.0, you can follow the dedicated <a href="https://github.com/SwiftGen/SwiftGen/wiki/migration-guides">Migration Guide</a>.</p> <h2 id="reusable">Reusable</h2> <h3 align="center"> <a href="https://github.com/AliSoftware/Reusable"> <img src="https://raw.githubusercontent.com/AliSoftware/Reusable/master/Logo.png" alt="Reusable logo" height="128" /> </a> </h3> <p>During my absence from this blog, we also worked on my <a href="https://github.com/AliSoftware/Reusable">Reusable</a> pod, which is a “Mixin” to help you work with reusable views (like <code class="language-plaintext highlighter-rouge">UITableViewCell</code> and <code class="language-plaintext highlighter-rouge">UICollectionViewCell</code> subclasses), views designed in a XIB, and VCs loaded via a Storyboard, all in a type-safe way.</p> <p>This pod is a great complement to <code class="language-plaintext highlighter-rouge">SwiftGen</code> to make your code even safer and continue getting rid of all String-based APIs.</p> <p>If you want to know more about how it works, you can read my blog post about <a href="http://alisoftware.github.io/swift/protocol/2015/11/08/mixins-over-inheritance/">Mixins</a> as well as the <a href="http://alisoftware.github.io/swift/generics/2016/01/06/generic-tableviewcells/">dedicated blog post about using Generics to dequeue cells safely</a>.</p> <p>We recently released <a href="https://github.com/AliSoftware/Reusable/releases/tag/4.0.0">Reusable 4</a> which improves the API and makes it even safer, so take a look and update your projects!</p> <h2 id="fastlane">fastlane</h2> <h3 align="center"> <a href="https://github.com/fastlane/fastlane"> <img src="https://github.com/fastlane/fastlane/raw/master/fastlane/assets/fastlane_text.png" alt="fastlane Logo" height="128" /> </a> </h3> <p>I also have the great pleasure to now <a href="https://twitter.com/FastlaneTools/status/824286337415135232">be part of the <em>fastlane</em> Core Contributor team</a> since recently.</p> <p>As a Core Contributor, I plan to contribute to <em>fastlane</em> more, but also help other people wanting to contribute to <em>fastlane</em> to feel welcome, help them do their first ruby PRs and make the <em>fastlane</em> community grow even more!</p> <h2 id="appdevcon-amsterdam-march-16-17">AppDevCon (Amsterdam, March 16-17)</h2> <h3 align="center"> <a href="http://appdevcon.nl"> <img src="https://pbs.twimg.com/profile_images/802779268631658496/N5IYeRr3.jpg" alt="AppDevCon Conference Logo" height="128" /> </a> </h3> <p>Next month, I’ll be talking at the <a href="http://appdevcon.nl">AppDevCon</a> conference (formerly known as “mDevCon”) in Amsterdam.</p> <p>This is a wonderful and popular conference in Europe, and I encourage you to go if you can. And Amsterdam is such a beautiful city, that would make such a good excuse to visit anyway!</p> <p>Among other talks at AppDevCon are:</p> <ul> <li>A tutorial session about <code class="language-plaintext highlighter-rouge">Sourcery</code> and <code class="language-plaintext highlighter-rouge">SwiftGen</code> by Paweł Brągoszewski: <a href="http://appdevcon.nl/session/pawel-bragoszewski-adding-magic-to-swift-with-swiftgen-and-sourcery/">“Adding magic to Swift with SwiftGen and Sourcery”</a></li> <li><a href="http://appdevcon.nl/session/olivier-halligon-mixins-over-inheritance/">My talk about Mixins over Inheritance</a></li> <li>A talk by Andrea Falcone, member of the <em>fastlane</em> team, about <a href="http://appdevcon.nl/session/andrea-falcone-fabric-fastlane/">“Supercharging your mobile app release with fastlane”</a></li> </ul> <h2 id="nsbudapest--craftconf-budapest-april-25-28">NSBudapest &amp; CraftConf (Budapest, April 25-28)</h2> <h3 align="center"> <a href="https://www.meetup.com/NSBudapest/"> <img src="https://pbs.twimg.com/profile_images/664400531687874560/m2uY7_RL_400x400.jpg" alt="NSBudapest Logo" height="128" /> </a> <a href="https://craft-conf.com"> <img src="https://pbs.twimg.com/profile_images/378800000824012775/97bb1d972974a336814f088a932add3f_400x400.png" alt="CraftConf Logo" height="128" /> </a> </h3> <p>At the end of April I’ll also be talking at the <a href="https://www.meetup.com/NSBudapest/">NSBudapest meetup</a> that will take place during the <a href="https://craft-conf.com">Craft Conference</a> in Budapest, Hungary.</p> <p>I’ll be talking about SwiftGen here, as well as code generation in general, but there are also plenty of other very interesting talks there, including about <em>fastlane</em> and Sourcery too (and I hear Budapest is a wonderful city to visit as well!), so I’m definitely looking forward to it… and maybe meet some of you too!</p> <h2 id="blog-articles">Blog Articles</h2> <p>I know that I’ve let you down since a little while here, as I haven’t been writing since a long time.</p> <p>If you read all those news above, you probably understand how busy I have been these past few month (and that’s just about my free OSS work, but my full-time paid work has been pretty full too) and why I didn’t have much time for blogging!</p> <p>Now that all those big tasks are mostly behind (even if I’ll still continue to be busy with <em>fastlane</em> and my talks, I expect to have a little more free time), it’s about time I start writing again — starting with that part 2 of my capture semantics article that is long overdue!</p> <p>In the meantime, I hope you’ll enjoy all my hard work on my OSS tools and talks, and will see you soon for some technical articles again!</p> Sun, 19 Feb 2017 00:00:00 +0000 https://alisoftware.github.io/news/oss/2017/02/19/swiftgen-4-2-and-other-news/ https://alisoftware.github.io/news/oss/2017/02/19/swiftgen-4-2-and-other-news/ Still Alive <p>This is a quick post to reassure you that this blog is still alive 😜 And announcing the subjects that I plan to write about in the upcoming articles.</p> <hr /> <p>I’ve been quite swamped lately, not having much free time to do OpenSource work on my various OSS projects and others’, let alone writing in this blog.</p> <p>Having to do the transition of all my pods and tools (<a href="https://github.com/AliSoftware/OHHTTPStubs"><code class="language-plaintext highlighter-rouge">OHHTTPStubs</code></a>, <a href="https://github.com/AliSoftware/Reusable"><code class="language-plaintext highlighter-rouge">Reusable</code></a>, <a href="https://github.com/AliSoftware/SwiftGen"><code class="language-plaintext highlighter-rouge">SwiftGen</code></a>, …) to Swift 2.3 then Swift 3.0 was very time-consuming and didn’t help having much time for anything else. All the time I had to work on my side projects was dedicated to Swift 3 conversion, triaging issues and reviewing Pull Requests.</p> <p>On this note, I’d like to thank <a href="https://twitter.com/phatblat">Ben Chatelain</a> and <a href="https://github.com/ceyhuno">Ceyhun Ozugur</a> for helping a lot porting <a href="https://github.com/AliSoftware/Reusable">Reusable</a> to Swift 3, and a huge thanks to <a href="https://github.com/djbe">David Jennes</a> and <a href="https://twitter.com/Adam_Tierney">Adam Tierney</a> for helping a lot with <a href="https://github.com/AliSoftware/SwiftGen">SwiftGen</a> lately.</p> <hr /> <p>But fret not, I haven’t forgotten about the part two of my “Closures Capture Semantics” article and already have plenty of subjects waiting in my drafts about Phantom Types, Type Safety, Networking Architectures, ViewController LifeCycle Behaviors, Functional ViewControllers, Coordinators and much more!</p> <center><iframe width="560" height="315" src="https://www.youtube.com/embed/Y6ljFaKRTrI" frameborder="0" allowfullscreen=""></iframe></center> <hr /> <p><em>PS: For those of you who like video games and music, I strongly encourage you to go see the <a href="http://videogameslive.com">VideoGames Live</a> show. It’s a concert of game music played by an orchestra, really stunning. You can see the videos I’ve filmed from it <a href="https://twitter.com/search?q=from%3A%40aligatr%20%20%23VideoGamesLive">here on my Twitter</a>.</em></p> Sun, 27 Nov 2016 00:00:00 +0000 https://alisoftware.github.io/news/2016/11/27/still-alive/ https://alisoftware.github.io/news/2016/11/27/still-alive/ Talking at conferences <p>This month, I’m going to talk at (<a href="http://2016.nsspain.com">NSSpain</a> and <a href="http://frenchkit.fr">FrenchKit</a>) about Swift 🎉 I’m looking forward to speak for the first time at such great conferences, and I really hope to see you there!</p> <h2 id="talking-at-nsspain--frenchkit">Talking at NSSpain &amp; FrenchKit</h2> <p>This post is just a quickie to do a small announcement, in case you didn’t see me announce it on Twitter a few month ago.</p> <p>This month, I’m going to give my first big conference talks. People who know me already know I don’t have any problem talking a lot in public 😇 and I’ve already talked in meetings like CocoaHeads, but that will be the first time I’ll speak at big conferences.</p> <p>And for my first time, I already chose quite a challenge:</p> <ul> <li>My first talk will be at <a href="http://2016.nsspain.com">NSSpain</a>, on Sept 15-16, 2016</li> <li>My second talk will be at <a href="http://frenchkit.fr">FrenchKit</a>, on the week right after (Sept 23-24)</li> </ul> <p>The first talk will be quite a challenge for me, because it will be my first big conference, speaking in English (not my language), in a foreign country, but more importantly doing not just slides but mostly live-coding — with Swift 3… so in Xcode 8 beta 😱 yeah I too like to live dangerously! — and about a subject that I haven’t talked about in my blog or elsewhere yet 😓. Yeah, that’s a lot of potential obstacles for my first talk, but hey, I’m up for the challenge!</p> <p>The second one will probably be easier, first because I’ll already have th experience from the first one, but also because it will be in my country 🇫🇷, with slides and not live-coding, and on a subject I already talked about. But hey, you never know how events could turn once on stage, and especially given how talkative I can be, I hope I’ll fit into the 30 minutes window that I’ve been allocated!</p> <p>I’m not sure I’m entirely ready for those 😰 but nevertheless I really can’t wait to meet a lot of people there and be part of that adventure 🎉! I really enjoyed going to conferences last year as an attendee, so this time going as a speaker I really think this is going to be even more amazing!</p> <p><em>This also explains why I haven’t been very present the last few weeks, quite busy preparing my talks… but worry not, I haven’t forgotten about the part 2 of my last article about <a href="/swift/closures/2016/07/25/closure-capture-1/">Closure Capture Semantics</a> and will hopefully resume my blogging around october after that!</em></p> <p>I can’t wait to see a lot of awesome people at NSSpain and FrenchKit, and if you come to one of these conferences, don’t hesitate to come and say hello! 👋</p> Tue, 06 Sep 2016 00:00:00 +0000 https://alisoftware.github.io/news/conferences/2016/09/06/conferences/ https://alisoftware.github.io/news/conferences/2016/09/06/conferences/