<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Scott Berrevoets - blog</title><link href="https://scottberrevoets.com/" rel="alternate"/><link href="https://scottberrevoets.com/feeds/blog.atom.xml" rel="self"/><id>https://scottberrevoets.com/</id><updated>2026-03-20T00:00:00-07:00</updated><entry><title>Review your own AI-generated code</title><link href="https://scottberrevoets.com/2026/03/20/review-your-own-ai-generated-code/" rel="alternate"/><published>2026-03-20T00:00:00-07:00</published><updated>2026-03-20T00:00:00-07:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2026-03-20:/2026/03/20/review-your-own-ai-generated-code/</id><summary type="html">&lt;p&gt;Years ago, when we started to have 5+ contributors to our codebase at Lyft, we
had to establish a code review process with high enough value to justify the
friction it caused. However, "value" is very subjective, as not everyone cares
about the same qualities of a pull request:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Catching …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;Years ago, when we started to have 5+ contributors to our codebase at Lyft, we
had to establish a code review process with high enough value to justify the
friction it caused. However, "value" is very subjective, as not everyone cares
about the same qualities of a pull request:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Catching runtime problems like bugs, security vulnerabilities, or performance
  issues&lt;/li&gt;
&lt;li&gt;Knowledge and context sharing, mentoring, or general collaboration&lt;/li&gt;
&lt;li&gt;Code quality and maintainability&lt;/li&gt;
&lt;li&gt;Upholding code consistency, conventions, style, naming, grammar, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Over the last 10-15 years, the industry has seen its &lt;strong&gt;engineering foundations&lt;/strong&gt;
greatly improved: programming languages, CI systems, build tools, internal
architectures and processes, and engineering principles have improved so much
that human review of many of these potential issues has lost a lot of their
value. Linters keep code consistent as much as possible, standardized
architectures should enable reliability and testability, modern programming
languages and paradigms enable low code complexity, and CI validates all these
conditions before deploying.&lt;/p&gt;
&lt;p&gt;With these advancements over the years, I find myself leaving significantly
fewer comments on PRs than before. Any comments I do leave are usually about
local, low-severity code quality improvements, avoiding suppressing linter
violations, asking for specs/context, double-checking intention, etc. Whenever
possible, I try to instead invest in some of these foundations: an additional
linter rule, updating CI, providing better architectural patterns. That time
investment scales much better than me leaving comments on individual PRs.&lt;/p&gt;
&lt;p&gt;And that brings us to 2026, where we get to re-evaluate the "value" of code
review because of AI agents. The impact of AI on code review is threefold:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Output quality&lt;/strong&gt;: a year ago we called it vibe-coding, today it's much more
   difficult to tell apart agent code from human code if the human(s) behind the
   agent code put real effort into getting high-quality output from the agent&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Agentic review&lt;/strong&gt;: many teams have agents review other agents' code. Codex
   has &lt;code&gt;/review&lt;/code&gt;, Claude Code has &lt;a href="https://code.claude.com/docs/en/code-review"&gt;Code Review&lt;/a&gt;, and Cursor, Copilot, and
   various observability and SRE tools all have code review agents as well.&lt;/p&gt;
&lt;p&gt;I've made code review part of my own workflow as well: write a plan, have
   an agent refactor a bunch of code, and have other agents review it for
   issues. &lt;a href="https://openai.com/index/harness-engineering/"&gt;OpenAI&lt;/a&gt; is, unsurprisingly, pushing the boundaries with an
   internal project where all code is reviewed by agents and mostly not by
   humans at all.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Code volume&lt;/strong&gt;: engineers are "writing" a lot more code than before and, if
   we stick to our old practices, the burden of code review increases more too&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We have gotten faster at producing high-quality code, but if it can't be
reviewed at higher speed as well, we've just found ourselves a &lt;em&gt;new&lt;/em&gt; bottleneck.&lt;/p&gt;
&lt;p&gt;The status quo is that engineers review &lt;em&gt;each other&lt;/em&gt;'s code to avoid &lt;strong&gt;author
bias&lt;/strong&gt;, which could lead to overestimating the quality, performance, and clarity
of the code written by the author. But in a world where the entire development
workflow is changing and code is increasingly written by agents, we're no longer
actually reviewing &lt;em&gt;each other&lt;/em&gt;'s code, we're reviewing &lt;em&gt;agents'&lt;/em&gt; code. The bias
no longer exists at the code level; it moves to the plan level.&lt;/p&gt;
&lt;p&gt;Human accountability and oversight are still important, but &lt;strong&gt;the primary
responsibility of code review can shift to the engineer that drove the agent in
the first place&lt;/strong&gt;. That engineer is coming in with all the context and can
review the code much faster than other engineers. For bigger changes, their
author bias could still apply to the &lt;em&gt;plan&lt;/em&gt;, so it's good to have other
engineers review that, but the code itself is just execution.&lt;/p&gt;
&lt;p&gt;With the right plan and LLM guidance in place, agents have gotten excellent at
implementing exactly according to spec. When an agent introduces a bug, it's
less likely to be an implementation issue and more likely an underspecification
of the plan, guidance, or documentation. The correctness of these documents is
still a shared team responsibility.&lt;/p&gt;
&lt;p&gt;The outcome is more than just removing the new bottleneck in writing code.
Engineers are more autonomous in shipping because they don't have to wait for
others, possibly in different time zones, and can fully unblock themselves in
all the work they are doing. With strong PR checks, such as linters and code
quality gates, in place to prevent old-fashioned vibe-coding, the bar remains
high while increasing velocity.&lt;/p&gt;
&lt;p&gt;There is one notable downside to this approach: the reduced context sharing and
mentoring. It's a real benefit of "traditional" code review, but not the most
effective time to break knowledge silos. As I mentioned earlier, &lt;em&gt;plans&lt;/em&gt; still
warrant review by others, and plans are usually derivatives of tech specs or
PRDs. A well-written document provides the necessary context, the whys, the
thought process, and the alternatives considered. It's also easier to ask
questions and act on feedback in this stage because it's much earlier in the
development lifecycle.&lt;/p&gt;
&lt;p&gt;In a world where agentic engineering is the norm, teams review plans, specs, and
agent guidance together so they capture the intended design and behavior; the
code becomes an implementation detail of the plan that can be self-reviewed by
the driver of the agent.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>All in on Interface Builder: 10 years later</title><link href="https://scottberrevoets.com/2026/03/02/all-in-on-interface-builder-10-years-later/" rel="alternate"/><published>2026-03-02T00:00:00-08:00</published><updated>2026-03-02T00:00:00-08:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2026-03-02:/2026/03/02/all-in-on-interface-builder-10-years-later/</id><summary type="html">&lt;p&gt;Back in 2017, I wrote about how we used &lt;a href="https://scottberrevoets.com/2017/03/06/using-interface-builder-at-lyft/"&gt;Interface Builder&lt;/a&gt; as our primary way
of building UI at Lyft. Interestingly, Speak chose to use Interface Builder to
build UI too before SwiftUI came along, though without the same tooling we had
at Lyft. With Interface Builder mostly forgotten, including …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Back in 2017, I wrote about how we used &lt;a href="https://scottberrevoets.com/2017/03/06/using-interface-builder-at-lyft/"&gt;Interface Builder&lt;/a&gt; as our primary way
of building UI at Lyft. Interestingly, Speak chose to use Interface Builder to
build UI too before SwiftUI came along, though without the same tooling we had
at Lyft. With Interface Builder mostly forgotten, including by Apple itself, I
think it's worth sharing some lessons learned after all this time.&lt;/p&gt;
&lt;p&gt;I didn't explicitly share this in the original post, but we didn't take the
decision to use Interface Builder lightly. The main benefits we saw:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Swift toolchain was very young and not fully developed yet. The compiler
  was slow and autocomplete was much less advanced, so the more code we could
  shove in Interface Builder the less code that had to be compiled. This had a
  significant impact on build times and build failures for us.&lt;/li&gt;
&lt;li&gt;We were hoping for what Swift Previews eventually got much closer to: a quick
  way to understand what a view looks like at runtime without spinning up a
  simulator&lt;/li&gt;
&lt;li&gt;It saved developers a lot of typing manual, tedious Auto Layout code.
  Interface Builder provide immediate feedback about whether your constraints
  resolved cleanly&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We were also aware of the pain points of Interface Builder and built good
tooling around managing those. We got to meet with Apple engineers to voice IB's
weaknesses that we couldn't tool our way out of, and were generally happy to not
be writing super verbose Auto Layout code. Merge conflicts still never were a
problem because we distributed the UI over many individual storyboards and xibs.&lt;/p&gt;
&lt;p&gt;We felt we'd found a way to make Interface Builder scale pretty well, and it did
for a long time. But cracks started to form in the strategy:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We had primarily invested in storyboards, but a stronger focus on code
  testability was directly in conflict with that investment&lt;/li&gt;
&lt;li&gt;New team members never fully got the hang of some of these more nuanced and
  advanced ways of using Interface Builder&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@IBDesignable&lt;/code&gt; never really lived up to its expectations, being buggy, slow,
  and unreliable&lt;/li&gt;
&lt;li&gt;Routing complexity changed our strategy over time to not use segues anymore,
  losing some of the benefits of using IB&lt;/li&gt;
&lt;li&gt;UIs became a lot more complicated and constraint management became difficult
  in Interface Builder (this likely would've also been hard in programmatic UI)&lt;/li&gt;
&lt;li&gt;Building Lyft's internal design system was more difficult while supporting
  Interface Builder&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All these problems came with scale none of us had really seen before. At the
same time, the original problem of writing programmatic Auto Layout also still
was a concern, so a potential migration was costly with unclear benefits.&lt;/p&gt;
&lt;p&gt;Until SwiftUI came along. With SwiftUI, we finally felt like the cons started to
outweigh the pros. We embarked on what ended up being a five year migration
journey away from Interface Builder, finally completing the effort mid-2024.
This multi-step migration was the largest we had undertaken on iOS (on Android
the Dagger 1 to Dagger 2 migration was of similar scale), and immediately
justified the investment into &lt;a href="https://www.scottberrevoets.com/2021/10/14/ios-architecture-at-lyft/#declarative-ui"&gt;DeclarativeUI&lt;/a&gt; as a prelude to SwiftUI adoption.&lt;/p&gt;
&lt;p&gt;Joining Speak also exposed me to the newcomer perspective of having so much UI
defined in Interface Builder. It's hard to understand the scope, complexity, and
context of a screen when all its configuration is tucked away in the right
sidebar, with many implicit behaviors from checkboxes, dropdowns, and long lists
of constraints and their properties.&lt;/p&gt;
&lt;p&gt;We're also dealing with the same testability problem (even though dependency
injection with storyboards is a little easier nowadays), and the storyboards in
the Speak codebase are more like wireframes than actual designs, so navigating
them isn't any easier than code. Refactoring is harder because the UI definition is
split into two (some in code, some in Interface Builder), and the weakly typed
IB/code connections between &lt;code&gt;@IBOutlet&lt;/code&gt;s and &lt;code&gt;@IBAction&lt;/code&gt;s make it hard and risky
to understand which code is still used and which isn't.&lt;/p&gt;
&lt;p&gt;A more modern problem is that because Interface Builder was not the popular
choice in an ecosystem where open source was already much less valued than in
other development domains, there is very little Interface Builder XML that AI
agents are trained on. This means their effectiveness is also much less compared
to plain Swift, which is doubly annoying when AI agents would otherwise reduce
the migration cost tremendously.&lt;/p&gt;
&lt;p&gt;Let it be clear that I wouldn't recommend Interface Builder as a tool for
building UI anymore, but despite my change of heart, I can't say I regret the
choice of introducing it at the time we did. There were no signs a replacement
UI framework would come along, teams were reasonably productive with it, and we
benefited from its use for a good 4-5 years. Some of the tools and
implementations were arguably too clever for their own good, and that's a
valuable lesson learned.&lt;/p&gt;
&lt;p&gt;But I think the bigger lesson is that there's a cost to architectural decisions
like these, and in an industry that's always changing, the pros and cons need
constant weighing. It's easy to adopt a sunk cost mentality and keep investing
more time and resources because the migration cost is high, but like other tech
debt it just increases the cost of migration at a later stage.&lt;/p&gt;
&lt;p&gt;Relating this to my own post about &lt;a href="https://www.scottberrevoets.com/2022/09/28/human-factors-in-choosing-technologies/"&gt;human factors in choosing technologies&lt;/a&gt;, the
adoption, ramp-up, and support factors were all pretty favorable. However, in
retrospect, IB scored low on the ability to undo (as many architectures would),
and after declarative UIs came along there was a downward trend in whether
engineers &lt;em&gt;liked&lt;/em&gt; using IB. Once that trend started, Interface Builder's days
were numbered, even if the path to obsolescence was long and painful. &lt;/p&gt;</content><category term="blog"/></entry><entry><title>Scaling my git/tmux workflows for AI agents</title><link href="https://scottberrevoets.com/2026/02/17/scaling-my-gittmux-workflows-for-ai-agents/" rel="alternate"/><published>2026-02-17T00:00:00-08:00</published><updated>2026-02-17T00:00:00-08:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2026-02-17:/2026/02/17/scaling-my-gittmux-workflows-for-ai-agents/</id><summary type="html">&lt;p&gt;After I started to ramp up on agentic coding, I found my workflow lagging behind
how I ended up working. Previously I would clone my project's repo twice and
switch back and forth between the clones. I used stack-based git workflow
&lt;a href="https://github.com/keith/git-pile"&gt;git-pile&lt;/a&gt;, which eliminated the overhead of switching branches and …&lt;/p&gt;</summary><content type="html">&lt;p&gt;After I started to ramp up on agentic coding, I found my workflow lagging behind
how I ended up working. Previously I would clone my project's repo twice and
switch back and forth between the clones. I used stack-based git workflow
&lt;a href="https://github.com/keith/git-pile"&gt;git-pile&lt;/a&gt;, which eliminated the overhead of switching branches and stashing
changes.&lt;/p&gt;
&lt;p&gt;When I was occasionally in the middle of something and needed to context switch
and have a clean repo, I would have the other clone and that was about as much
as I could handle at a time. I knew of git worktrees but never needed it so
didn't bother upgrading my workflow.&lt;/p&gt;
&lt;p&gt;In the age of AI I found myself needing a lot more. Understanding various parts
of the codebase is one prompt away and making changes requires a much smaller
context switch than before so I do it much more frequently. However, the two
clones weren't really sufficient for this anymore, as it became more painful to
sync changes back and forth before they landed on &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So I had Claude design me a new workflow that scaled better and allowed more
parallelization. I wanted:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;one repo with &lt;code&gt;n&lt;/code&gt; worktrees with no prior set up required&lt;/li&gt;
&lt;li&gt;tmux integration so each worktree gets its own tmux window&lt;/li&gt;
&lt;li&gt;no dangling worktrees and branches if I abandon work&lt;/li&gt;
&lt;li&gt;compatibility with git-pile, but no direct integration&lt;/li&gt;
&lt;li&gt;packaged in an easy to use CLI with powerful commands that capture a lot of
  complexity&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I wanted to use worktrees under the hood, but also have that be an
implementation detail as I'd prefer to not deal with them directly.&lt;/p&gt;
&lt;p&gt;The end result is a Python script I called &lt;code&gt;agents&lt;/code&gt; that has 6 commands:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;new [name]&lt;/code&gt; - create a new worktree&lt;/li&gt;
&lt;li&gt;&lt;code&gt;list&lt;/code&gt; - lists all existing tasks/worktrees&lt;/li&gt;
&lt;li&gt;&lt;code&gt;merge&lt;/code&gt; - merge the current changes into the parent repo on &lt;code&gt;main&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rm&lt;/code&gt; - delete this worktree and close its tmux window&lt;/li&gt;
&lt;li&gt;&lt;code&gt;open&lt;/code&gt; - create a tmux window with the specified worktree (in case it was
  closed)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;clean&lt;/code&gt; - delete all stale worktrees and branches, and closes their tmux
  windows&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This provides a very simple workflow:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Initialize worktree&lt;/span&gt;
&lt;span class="c1"&gt;# No subcommand is shorthand for `new`&lt;/span&gt;
agents&lt;span class="w"&gt; &lt;/span&gt;find-bug

&lt;span class="c1"&gt;# Run some agents&lt;/span&gt;
codex&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;fix the race condition in DataManager&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="c1"&gt;# switch to another agent/tmux window, do some work there, then return&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="c1"&gt;# Codex output looks good - let&amp;#39;s upstream (or have the agent do it)&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Fix race condition&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Merge commit back to main repo&lt;/span&gt;
agents&lt;span class="w"&gt; &lt;/span&gt;merge

&lt;span class="c1"&gt;# Delete worktree and close tmux window&lt;/span&gt;
agents&lt;span class="w"&gt; &lt;/span&gt;rm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;main&lt;/code&gt; branch on my repo clone now has this commit, and I can use &lt;code&gt;git-pile&lt;/code&gt;
to submit a pull request with this change. This workflow works in any git repo
out of the box, no special setup required.&lt;/p&gt;
&lt;p&gt;While Codex is running, I can switch back to the previous tmux window and start
another agent, or I can even branch off that particular worktree into a new one.
I'm not sure I want that from the workflow since I prefer working on the repo's
&lt;code&gt;main&lt;/code&gt; branch in the first place, so time will tell if I end up using that
branching behavior.&lt;/p&gt;
&lt;p&gt;Ideally these worktrees are shortlived and will just get their work merged in.
However, there are situations where there's longer running work or work I end up
not moving forward with, and for that I had Claude implement an &lt;code&gt;agents clean&lt;/code&gt;
command. It deletes all stale worktrees, with some protections for unmerged work
to avoid losing uncommitted changes.&lt;/p&gt;
&lt;p&gt;To understand what work is in progress, &lt;code&gt;agents list&lt;/code&gt; gives a nicely-formatted
output that's piped to &lt;code&gt;fzf&lt;/code&gt; for easy selection:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of Terminal output after listing agents" src="/images/agents-list.png"&gt;&lt;/p&gt;
&lt;p&gt;The arrows indicate commit status, which is helpful to understand if any
worktrees may need rebasing.&lt;/p&gt;
&lt;p&gt;After building and working this tool a bit I realized the workflow works even
when not using an agentic programming workflow, so &lt;code&gt;agents&lt;/code&gt; is probably not the
best name, but since I developed it with that use case in mind I'll probably
continue to use that for now.&lt;/p&gt;
&lt;p&gt;The full script is in my &lt;a href="https://github.com/sberrevoets/dotfiles/blob/master/bin/agents"&gt;dotfiles&lt;/a&gt;' &lt;code&gt;./bin&lt;/code&gt; folder, which is automatically
accessible anywhere from my Terminal. I've also written a Claude/Codex skill to
run this automatically for me in case I forget to do so myself, and I'll
probably keep tweaking the workflow to my liking as I work with it more.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Localization troubles with Swift PM</title><link href="https://scottberrevoets.com/2025/09/19/localization-troubles-with-swift-pm/" rel="alternate"/><published>2025-09-19T00:00:00-07:00</published><updated>2025-09-19T00:00:00-07:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2025-09-19:/2025/09/19/localization-troubles-with-swift-pm/</id><summary type="html">&lt;p&gt;A few weeks ago we got bit by a piece of Apple magic in its localization
workflow when combined with Swift PM. The problem we ran into was that as soon
as we moved all our &lt;code&gt;Localizable.strings&lt;/code&gt; files to an SPM package, none of the
localizations would be picked …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A few weeks ago we got bit by a piece of Apple magic in its localization
workflow when combined with Swift PM. The problem we ran into was that as soon
as we moved all our &lt;code&gt;Localizable.strings&lt;/code&gt; files to an SPM package, none of the
localizations would be picked up anymore and the app would display in
English&amp;mdash;but &lt;strong&gt;only for first-time installations&lt;/strong&gt;. We followed the
documentation guidelines for how to localize an app in an Swift PM environment
and have no custom tooling in place, so it was a bit of a headscratcher.&lt;/p&gt;
&lt;p&gt;The modern way of adding support for another language/region in Xcode is through
the project's &lt;em&gt;Info&lt;/em&gt; tab. Simply click the + and add a language and the app now
supports localizing in that language.&lt;/p&gt;
&lt;p&gt;Adding localizations for any resource is done by clicking the &lt;em&gt;Localize...&lt;/em&gt;
button in the Attributes inspector. After confirming the language, Xcode creates
a new &lt;code&gt;.lproj&lt;/code&gt; folder for the selected language and moves the resource into that
folder. The end result is the project will have a &lt;code&gt;.lproj&lt;/code&gt; folder for each
localization as well as the &lt;code&gt;Base.lproj&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This &lt;em&gt;just works&lt;/em&gt;. The &lt;code&gt;.lproj&lt;/code&gt; folders get copied over to the &lt;code&gt;.app&lt;/code&gt; and at
runtime the SDK knows how to read and use these folders. Xcode also gives visual
confirmation in the &lt;em&gt;Info&lt;/em&gt; tab by indicating for how many files it has picked up
a localized version per language/region.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Xcode sees three localized files" src="/images/xcode-localizations-3.png"&gt;&lt;/p&gt;
&lt;p&gt;Getting started with adding some SPM packages in our app, we moved the &lt;code&gt;.lproj&lt;/code&gt;
folders into our &lt;code&gt;Localization&lt;/code&gt; package at
&lt;code&gt;Localization/Sources/Localization/Resources/&lt;/code&gt;, which follows the process
outlined in the &lt;a href="https://developer.apple.com/documentation/xcode/localizing-package-resources"&gt;documentation&lt;/a&gt;. Locally this seemed to work and we shipped the
change, only for QA to report localizations were broken throughout the app.&lt;/p&gt;
&lt;p&gt;As it turns out, any &lt;code&gt;.lproj&lt;/code&gt; folders in &lt;strong&gt;SPM packages&lt;/strong&gt; aren't considered when
determining what localizations are available. At first this was a bit confusing,
but it makes sense for two reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Through various layers of abstraction, the system looks up the available
   localizations using methods on &lt;code&gt;Bundle&lt;/code&gt;. &lt;code&gt;Bundle.localizations&lt;/code&gt; simply finds
   all &lt;code&gt;.lproj&lt;/code&gt; folders in that bundle. SPM package bundles aren't included in
   &lt;code&gt;Bundle.main&lt;/code&gt;, which is the bundle that's used to determine app-wide
   localizations.&lt;/li&gt;
&lt;li&gt;An SPM package can be localized in different languages than the app that
   integrates the package, especially if the package is from an external vendor.
   It could be weird behavior to have the app's selected localization be
   determined by some SPM package.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;There is an Info.plist key that would allow SPM packages to use a different
localization than the main app: &lt;code&gt;CFBundleAllowMixedLocalizations&lt;/code&gt;.
I haven't tried for myself, but setting this to &lt;code&gt;YES&lt;/code&gt; allows different
localizations for the app and for every package.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;When moving all &lt;code&gt;.lproj&lt;/code&gt; folders into the SPM package, the app didn't have any
localized files left and Xcode indicated as much. &lt;/p&gt;
&lt;p&gt;&lt;img alt="Xcode sees no localized files" src="/images/xcode-localizations-0.png"&gt;&lt;/p&gt;
&lt;p&gt;As a result, the app would always default to English. This explained why things
were broken but there were still a few questions left.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How come the correct localization was used for returning users?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This issue only happened to users that freshly installed the app. Upon first
launch, we store the user's locale in &lt;code&gt;UserDefaults&lt;/code&gt; (later giving users the
ability to override this manually). Instead of reading strings from
&lt;code&gt;Bundle.main&lt;/code&gt; we read them from a new &lt;code&gt;Bundle&lt;/code&gt; instance specifically scoped to
that locale:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;defaultLocale&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;preferredLocalizations&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;userLanguage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UserDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt; &lt;span class="c1"&gt;// e.g. &amp;quot;nl&amp;quot; for Dutch&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pathForResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="n"&gt;defaultLocale&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ofType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;lproj&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;customBundle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;localized&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;customBundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;localizedString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;forKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This setup has some interesting results:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;❌ &lt;code&gt;Bundle.main.preferredLocalizations&lt;/code&gt; only contains the development default
  locale &lt;code&gt;en&lt;/code&gt; as the main bundle doesn't have any localizations&lt;/li&gt;
&lt;li&gt;❌ &lt;code&gt;customBundle.preferredLocalizations&lt;/code&gt; only contains the localization it was
  scoped to&lt;/li&gt;
&lt;li&gt;✅ &lt;code&gt;customBundle.localizedString(forKey:value:table:)&lt;/code&gt; works as the SPM
  package's resources still get copied into the main app as a separate bundle&lt;/li&gt;
&lt;li&gt;✅ &lt;code&gt;Bundle.module.preferredLocalizations&lt;/code&gt; correctly returns all the supported
  localizations since they're defined in the SPM package&lt;/li&gt;
&lt;li&gt;✅ &lt;code&gt;Bundle.module.localizedString(forKey:value:table:)&lt;/code&gt; works for the same
  reason&lt;/li&gt;
&lt;li&gt;❌ &lt;code&gt;Bundle.main.localizedString(forKey:value:table:)&lt;/code&gt; does not work as the
  main bundle doesn't have any localizations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For first-time users we would read &lt;code&gt;en&lt;/code&gt; as their preferred language through
&lt;code&gt;Bundle.main.preferredLocalizations&lt;/code&gt;, but for returning users we'd read what
language they were using before from &lt;code&gt;UserDefaults&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Conclusion: we should be using &lt;code&gt;Bundle.module.preferredLocalizations&lt;/code&gt; instead.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why was it so difficult to figure this out?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The build cache in Xcode messed with our ability to reproduce the issue. In
understanding what commit introduced the problem, we would often switch branches
but that didn't invalidate the build cache.&lt;/p&gt;
&lt;p&gt;So building any commit before the offending change would copy the &lt;code&gt;.lproj&lt;/code&gt;
folders into the main app's build cache, and those folders would stay there
after checking out and building the offending commit.&lt;/p&gt;
&lt;p&gt;At first we thought the issue was only intermittently reproducible, but once we
realized the build cache was "polluted" we started to clean it every time. This
made it much easier to reproduce but much slower to test due to constantly doing
full builds.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What am I supposed to do in a fully modularized app where there are no files
to localize in the main app?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In theory, using &lt;code&gt;Bundle.module&lt;/code&gt; everywhere would work as highlighted above.
However, that's pretty cumbersome and there's a nicer way:
&lt;code&gt;CFBundleLocalizations&lt;/code&gt;. This is another &lt;a href="https://developer.apple.com/documentation/bundleresources/information-property-list/cfbundlelocalizations"&gt;Info.plist key&lt;/a&gt; that lets you
&lt;em&gt;explicitly&lt;/em&gt; define what localizations the app handles, instead of looking for
&lt;code&gt;.lproj&lt;/code&gt; folders and implicitly supporting the localizations for the folders
Xcode finds. With that key set, Xcode still does look for these folders but now
it also considers SPM bundles, fixing the issue.&lt;/p&gt;
&lt;p&gt;Finally, there's another way to be explicit about the localizations a project
supports: by adding a localization through Xcode's &lt;em&gt;Info&lt;/em&gt; tab. This sets the
&lt;code&gt;knownRegions&lt;/code&gt; value in the Xcode project file, which is not only considered for
determining what localizations to consider at build time, but is also used to
export and import &lt;code&gt;xliff&lt;/code&gt; files.&lt;/p&gt;
&lt;p&gt;That would have fixed our issue as well, but I personally prefer to not touch
the project file as much as possible and be explicit about what localizations
are supported through &lt;code&gt;CFBundleLocalizations&lt;/code&gt;.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Managing code deprecations on iOS</title><link href="https://scottberrevoets.com/2025/08/20/managing-code-deprecations-on-ios/" rel="alternate"/><published>2025-08-20T00:00:00-07:00</published><updated>2025-08-20T00:00:00-07:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2025-08-20:/2025/08/20/managing-code-deprecations-on-ios/</id><summary type="html">&lt;p&gt;A great manager I once worked with had been at Google for a while and poked fun
at an aspect of its engineering culture: any given service was either deprecated
or not yet ready for adoption. I spend a lot of my time working on mobile
platform development and over …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A great manager I once worked with had been at Google for a while and poked fun
at an aspect of its engineering culture: any given service was either deprecated
or not yet ready for adoption. I spend a lot of my time working on mobile
platform development and over the years have been exposed to both sides of this:
needing to get rid of code that's deprecated in new OS versions, and deprecating
technologies many internal teams depend on.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Vendor deprecations&lt;/strong&gt; usually involve the platform vendor releasing new APIs
  or technologies that replace older ones and they want their developer base to
  put in the work to use this new tech&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Internal deprecations&lt;/strong&gt; come with a lot of non-technical work and a long
  migration path, often in the order of quarters or years, on top of the
  technical challenge and often ugly tradeoffs in making the old and systems
  compatible during the transition period&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both types of deprecations are a way of life for platform engineering, and yet
on Apple platforms we have very little tooling available to make these easier.
In fact, the only real tool we have is &lt;code&gt;@available()&lt;/code&gt; to mark a type as having
been deprecated:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deprecated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;renamed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;myNewFunction()&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;myFunction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="c1"&gt;// deprecated and renamed to myNewFunction&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iOS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deprecated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;26.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;myFunction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="c1"&gt;// deprecated starting in iOS 26&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deprecated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Please use myOtherNewFunction instead&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;myOtherFunction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="c1"&gt;// deprecated with a custom warning message&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Apple uses this pattern extensively throughout their frameworks and every new OS
version introduces more deprecation annotations exactly like the examples above.
This makes raising the deployment target in a sufficiently large project a much
bigger initiative than it might seem at first, because suddenly you're facing a
large number of deprecation warnings.&lt;/p&gt;
&lt;p&gt;Handling the deprecation of a single class can in extreme cases be a project on
its own. If you're looking to raise the deployment target to use the latest
SwiftUI APIs, it can be a bummer to first have to spend a bunch of time
resolving deprecation warnings, especially when &lt;code&gt;warnings-as-errors&lt;/code&gt; is on and
the app doesn't even build.&lt;/p&gt;
&lt;p&gt;Since &lt;code&gt;@available&lt;/code&gt;'s only real logic is gating on OS and Swift versions, it
isn't helpful for internal deprecations. If I write a new database class that
replaces an old one that's used in 50+ places, I don't know what OS version to
specify because it's not related to OS-specific system APIs. It's more important
to think about how to manage the existing call sites, which in most cases means
accepting the 50+ instances, ensuring it doesn't grow any further, and working
with the team to migrate them over time.&lt;/p&gt;
&lt;p&gt;Annotating the old class with &lt;code&gt;@available(*, deprecated)&lt;/code&gt; would immediately
cause build errors, so that's not an option. At Lyft we often resorted to the
ugly workaround of renaming the old class to &lt;code&gt;DeprecatedDatabase&lt;/code&gt; or even
&lt;code&gt;DEPRECATEDDatabase&lt;/code&gt;. This worked surprisingly well, but it also very clearly
highlights a gap in tooling.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0443-warning-control-flags.md"&gt;SE-0443: Precise Control Flags over Compiler Warnings&lt;/a&gt; gives developers more
granular control over compiler warnings, even mentioning the deprecation use
case specifically. In short, there are now build settings that enable upgrading
and downgrading warnings of a specific type to and from errors. The proposal
gives this example to turn warnings-as-errors on except for deprecations:
&lt;code&gt;-warnings-as-errors -Wwarning Deprecated&lt;/code&gt;. This leaves &lt;code&gt;warnings-as-errors&lt;/code&gt; on
for everything except deprecations.&lt;/p&gt;
&lt;p&gt;It's an OK first step, but not really sufficient:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;There might be multiple deprecations happening at once, but you can only
   specify whether to enable or disable deprecation warnings as a whole, not per
   type&lt;/li&gt;
&lt;li&gt;Even if per-type warning disables were available, that still means it's too
   easy to accidentally or unknowingly introduce new call sites of a deprecated
   type&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The most ideal solution is bringing back a relic from the Objective-C past: C
compiler directives. In C-based languages, &lt;code&gt;#pragma&lt;/code&gt; blocks could be used to
ignore specific instances of a warning:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#pragma GCC diagnostic ignored &amp;quot;-Wdeprecated-declarations&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;myDeprecatedFunction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// no warning&lt;/span&gt;

&lt;span class="cp"&gt;#pragma GCC diagnostic pop&lt;/span&gt;

&lt;span class="n"&gt;myDeprecatedFunction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// warning: myDeprecatedFunction is deprecated&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Although the syntax is clearly not very "2025", the idea is still good and
exactly what should happen to improve this bit of API tooling. The deprecation
warning on the call site that's "wrapped" in the &lt;code&gt;diagnostic&lt;/code&gt; directive would be
ignored, and introducing a new caller of &lt;code&gt;myDeprecatedFunction&lt;/code&gt; generates a
warning (or more ideally, a build error). You can choose to wrap that new call
in the same &lt;code&gt;#pragma&lt;/code&gt; block, but then it's an explicit choice and not something
the developer was just unaware of.&lt;/p&gt;
&lt;p&gt;Incidentally, having multiple levels of granularity is also exactly how e.g.
SwiftLint and linters on other platforms work: you can disable rules entirely,
per-file, per code-section, per-line, etc. It's too bad SE-443 didn't go that
far, but discussions in the &lt;a href="https://forums.swift.org/t/se-0443-precise-control-flags-over-compiler-warnings/74116"&gt;developer forums&lt;/a&gt; have pointed out these same
problems so hopefully those will be considered in future improvements.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Flywheel of tech debt</title><link href="https://scottberrevoets.com/2025/07/02/flywheel-of-tech-debt/" rel="alternate"/><published>2025-07-02T00:00:00-07:00</published><updated>2025-07-02T00:00:00-07:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2025-07-02:/2025/07/02/flywheel-of-tech-debt/</id><summary type="html">&lt;p&gt;Tech debt is often compared to financial debt, in that you're borrowing
development time now to pay it off with more development time later. When
"later" comes around, the development time needed to fix the issues introduced
earlier is too high so the new thing is also not built "correctly …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Tech debt is often compared to financial debt, in that you're borrowing
development time now to pay it off with more development time later. When
"later" comes around, the development time needed to fix the issues introduced
earlier is too high so the new thing is also not built "correctly", and the
pattern repeats itself: tech debt begets tech debt.&lt;/p&gt;
&lt;p&gt;A few years ago I came across an article about the &lt;a href="https://technology.riotgames.com/news/taxonomy-tech-debt"&gt;Taxonomy of Tech Debt&lt;/a&gt;,
which talks about the contagion of tech debt: the easier a piece of tech debt
spreads, the higher its impact because fixing that fragile code means
preemptively fixing it in future cases as well.&lt;/p&gt;
&lt;p&gt;A more extreme version of this is a &lt;strong&gt;flywheel of tech debt&lt;/strong&gt;&amp;trade;: tech debt
that doesn't just expand linearly because of a high contagion factor, but that
multiplies at an excessive rate. This tends to happen in one specific part of
the codebase: the infrastructure. Any libraries, frameworks, architectures,
foundational patterns, etc. with tech debt have this flywheel and once it's
rotating it's very difficult to stop.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;With a sufficient number of users of an API, it does not matter what you
promise in the contract: all observable behaviours of your system will be
depended on by somebody.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://xkcd.com/1172/"&gt;Hyrum's Law&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Hyrum's Law is the primary reason why it's so hard to remove any tech debt from
infrastructure code: even if the tech debt is internal to the architecture to
account for one edge case, the behavior is there and &lt;em&gt;will&lt;/em&gt; be depended on by
somebody. If the tech debt is more severe and affects the API, the flywheel goes
brrr and now &lt;em&gt;requires&lt;/em&gt; all consumers of that API to incur the tech debt as
well, unless they specifically understand (how) to avoid it. &lt;/p&gt;
&lt;p&gt;What's worse is that after some time passes and people are using these APIs,
it's much more difficult to remove because it also requires removing it at all
the call sites that API. For a large team/codebase, this requires a lot of
coordination between teams and can take years of time and effort. One team that
doesn't pull its weight impacts everyone else.&lt;/p&gt;
&lt;p&gt;This is ironic because in many situations that foundational layer with good
abstractions and nice developer ergonomics is specifically created to &lt;em&gt;avoid&lt;/em&gt;
tech debt. The more this happens, the more brittle the foundations get until we
get to the &lt;a href="https://xkcd.com/2347/"&gt;other xkcd&lt;/a&gt;. As such, I tend to advocate for moving the fragile code
downstream, to the feature code as much as possible. This reduces chances of it
impacting other features, even if that means the fragile code is worse or more
risky.&lt;/p&gt;
&lt;p&gt;The cost of fragile infrastructure code is so high because that code is
generally long-lived, where the feature may be scrapped before it even launches.
The team that introduces this code is now also directly responsible for it
without impacting everybody else in the process. And, if the tech debt finally
becomes too painful to deal with, fixing it is much simpler and easier to roll
out than a fundamental change in the foundational layer of an app.&lt;/p&gt;
&lt;p&gt;In the past I reluctantly accepted a hack here and there in some foundational
libraries, but I intend to push back harder going forward. Spending years
working out a single parameter from a public method in some part of our
architecture is a valuable lesson learned...&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Art of the state</title><link href="https://scottberrevoets.com/2025/06/02/art-of-the-state/" rel="alternate"/><published>2025-06-02T00:00:00-07:00</published><updated>2025-06-02T00:00:00-07:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2025-06-02:/2025/06/02/art-of-the-state/</id><summary type="html">&lt;p&gt;&lt;code&gt;@State&lt;/code&gt; plays a prominent role in any SwiftUI app, and for good reason: app
state is what ultimately drives the UI and how the user interacts with your app.
This has always been true, but SwiftUI embraces this reality with specific APIs
and a unidirectional data flow. UIKit-based apps or …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;code&gt;@State&lt;/code&gt; plays a prominent role in any SwiftUI app, and for good reason: app
state is what ultimately drives the UI and how the user interacts with your app.
This has always been true, but SwiftUI embraces this reality with specific APIs
and a unidirectional data flow. UIKit-based apps or features were &lt;em&gt;also&lt;/em&gt; driven
by state, but this state would often live in the UI layer and was much more
hidden as a result.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;This is actually true for frontend/web apps as well, as React works in a
very similar way as SwiftUI and also uses a unidirectional data flow. In
fact, I suspect SwiftUI drew some inspiration from React in terms of API
design, but we probably won't ever get that confirmed :)&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;SwiftUI improves the data flow in applications, but there is much less
discussion on best practices for defining what that data looks like. This is too
bad, because thoughtful data and state modeling has cascading effects in API
design and testability of code. Suboptimal definitions of state in the the model
layer means that view models, view controllers, views, etc. all also suffer from
that suboptimal design.&lt;/p&gt;
&lt;p&gt;No matter the app, state plays a critical role (even if that state largely comes
from the server) and I wanted to write down best practices and common pitfalls
I've seen when writing state models.&lt;/p&gt;
&lt;h2 id="prefer-value-types"&gt;Prefer value types&lt;/h2&gt;
&lt;p&gt;State is often passed down or otherwise accessed by different parts of the app.
In some cases you might want to mutate it or operate on a separate copy of that
state, without immediately affecting every other part of the app that relies on
that same state. Using value types provides the necessary guard rails to make
sure those changes don't unintentionally also impact other parts of the app,
while still making it possible, to also globally mutate state.&lt;/p&gt;
&lt;p&gt;Making the main state type a struct or an enum is a good start but not always
sufficient: &lt;em&gt;all&lt;/em&gt; state in a tree is ideally using value types. This avoids
misusing a state type to be more than just the basic values that represent the
current condition of the app.&lt;/p&gt;
&lt;p&gt;It also inherently means that almost all state should be a collection of
"primitives" like &lt;code&gt;String&lt;/code&gt;, &lt;code&gt;Int&lt;/code&gt;, &lt;code&gt;Float&lt;/code&gt;/&lt;code&gt;Double&lt;/code&gt;, and &lt;code&gt;Bool&lt;/code&gt; that are used
either directly or wrapped in other types (e.g. &lt;code&gt;CGRect&lt;/code&gt;,
&lt;code&gt;NumberFormatter.Style&lt;/code&gt;, a custom type, etc.).&lt;/p&gt;
&lt;p&gt;Basically any other type is out. No classes, closures, actors, or other more
complex types as they typically aren't good candidates to represent state.
Unfortunately, &lt;a href="https://forums.swift.org/t/status-of-se-0283-tuples-conform-to-equatable-comparable-and-hashable/46942/3"&gt;tuples&lt;/a&gt; are also not an option for now, but using a named
struct is usually worth that hassle.&lt;/p&gt;
&lt;p&gt;I've found that a good way to validate this is by making all state types conform
to &lt;code&gt;Equatable&lt;/code&gt; (or possibly &lt;code&gt;Sendable&lt;/code&gt;). Types whose implementation of those
&lt;code&gt;Equatable&lt;/code&gt; don't require a custom implementation of &lt;code&gt;==&lt;/code&gt; are good to go, and if
the compiler complains about non-conformance there's some critical thinking to
do.&lt;/p&gt;
&lt;h2 id="avoid-singletons"&gt;Avoid singletons&lt;/h2&gt;
&lt;p&gt;If you needed more reasons to dislike singletons, here is another one: it makes
data modeling more difficult. Take this &lt;code&gt;UserManager&lt;/code&gt; example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserManager&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;shared&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UserManager&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Ignoring all other singleton-related issues, the problem here is that since
&lt;code&gt;UserManager&lt;/code&gt; can be accessed even if the user isn't logged in, it gets hard to
model the &lt;code&gt;user&lt;/code&gt; property:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Make it optional and you're dealing with optionality everywhere, which is also
  not ideal (more on that below)&lt;/li&gt;
&lt;li&gt;Make it "empty" (e.g. &lt;code&gt;var user = User(id: UUID(), name: "")&lt;/code&gt;, and it doesn't
  properly reflect the state the app could actually be in since a &lt;code&gt;user&lt;/code&gt; isn't
  required for the app to function&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It's better to architect this in a different way such that you can guarantee the
&lt;code&gt;user&lt;/code&gt; and its properties are valid (i.e. non-nil and not just some default
value) when asking for it.&lt;/p&gt;
&lt;h2 id="separate-dtos-from-domain-models"&gt;Separate DTOs from domain models&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Data Transfer Objects&lt;/strong&gt; are the objects you get back directly from, typically,
an API. DTOs are directly bound to the data the API sends, which could have all
kinds of problems: missing or unused fields, wrong data types, optionality in
some cases but not in others, unexpected or inconsistent formats, different
versions, etc.&lt;/p&gt;
&lt;p&gt;It's tempting to use these DTOs directly, especially when using mechanisms like
Codable to create them easily and to cover some of those flaws. However, it
might also be tempting to &lt;em&gt;not&lt;/em&gt; optimize the domain model for your app because
Codable isn't always the easiest to work it. This could handicap your data
models as they now have to follow the API design and all its flaws everywhere in
your app.&lt;/p&gt;
&lt;p&gt;Admittedly, it is a bit annoying to write the data model "twice", but the work
often pays off and the decoupling is worth it. Perhaps AI or other tools can
help with this as well.&lt;/p&gt;
&lt;h2 id="avoid-state-impossibilities"&gt;Avoid state impossibilities&lt;/h2&gt;
&lt;p&gt;Imagine a &lt;code&gt;User&lt;/code&gt; object that captures the user's email and its verification
status:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;emailVerified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Bool&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;verified&lt;/code&gt; status is required regardless of whether there is an email
address. But the combination &lt;code&gt;email = nil&lt;/code&gt; and &lt;code&gt;emailVerified = true&lt;/code&gt; means
nothing, so the state is modeling an impossible scenario. It's better to model
this as a tuple or a struct:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;verified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Bool&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
    &lt;span class="c1"&gt;// or&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;)?&lt;/span&gt;

    &lt;span class="c1"&gt;// with optional conveniences:&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;emailAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;emailVerified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;verified&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now either both fields are set, or neither are and the app doesn't have to
handle that "impossible" state.&lt;/p&gt;
&lt;h2 id="minimize-redundant-state"&gt;Minimize redundant state&lt;/h2&gt;
&lt;p&gt;Try to use the minimum amount of state possible to represent a scenario. For
example, you might start out with a simple flag to choose whether an account has
been selected, but the app's requirements later change to also capture &lt;em&gt;which&lt;/em&gt;
account:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;AccountSelection&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;hasSelectedAccount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Bool&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;selectedAccount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Not only does this lead to another impossible state (&lt;code&gt;hasSelectedAccount =
true&lt;/code&gt; and &lt;code&gt;selectedAccount = nil&lt;/code&gt;), but &lt;code&gt;hasSelectedAccount&lt;/code&gt; can also be
inferred from &lt;code&gt;selectedAccount&lt;/code&gt;: if it's &lt;code&gt;nil&lt;/code&gt;, no account has been selected.&lt;/p&gt;
&lt;p&gt;Don't be lazy and let these redundancies build up in your app because the core
business logic and UI layers (and tests!) will have to deal with this
unnecessary complexity.&lt;/p&gt;
&lt;h2 id="reduce-optionals-when-possible"&gt;Reduce optionals when possible&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The power of optionals is not using them&lt;/p&gt;
&lt;p&gt;- Someone, at some point&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In languages that have an &lt;code&gt;Optional&lt;/code&gt; type, use them as little as possible. An
optional immediately forks a type into two possible values (&lt;code&gt;some&lt;/code&gt; or &lt;code&gt;none&lt;/code&gt;),
which incurs an additional code branch to test.&lt;/p&gt;
&lt;p&gt;Note that "as little as possible" doesn't mean "never", so here are a few common
pitfalls I've encountered when an optional is the best tool available:&lt;/p&gt;
&lt;h3 id="handle-optionality-as-early-as-possible"&gt;Handle optionality as early as possible&lt;/h3&gt;
&lt;p&gt;If you're calling an API that returns an optional value that you expect to
always be there anyway, try to eliminate it as early as possible instead of
letting that optional permeate your entire tech stack. Define your model with
the value as a non-optional and assert on its existence in your model (or even
networking) layer.&lt;/p&gt;
&lt;p&gt;In line with the DTO use above, that could look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;

    &lt;span class="kd"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;UserDTO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;throws&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// UserDTO might not have a user&amp;#39;s name for some reason&lt;/span&gt;
        &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;ParsingError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;missingField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This eliminates the need to test for what happens at every other layer (UI,
business logic, etc.) if that value were to be nil.&lt;/p&gt;
&lt;h3 id="optional-vs-empty-arrays"&gt;Optional vs. empty arrays&lt;/h3&gt;
&lt;p&gt;A pattern I see a lot, unfortunately also in Apple's code, is an array defined
like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;myStrings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;]?&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The optional here makes the code more difficult to deal with, since now you have
to account for the &lt;code&gt;nil&lt;/code&gt; case everywhere, in addition to the array being empty.&lt;/p&gt;
&lt;p&gt;Hot take: in the vast majority of cases, code doesn't handle a &lt;code&gt;nil&lt;/code&gt; array
differently from an empty array (search your codebase for &lt;code&gt;?? []&lt;/code&gt; and you'll see
what I mean), so you might as well model it like that too.&lt;/p&gt;
&lt;p&gt;In the situations where that difference &lt;em&gt;is&lt;/em&gt; meaningful, making the property
optional is fine:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;friends&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;]?&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In this case, &lt;code&gt;nil&lt;/code&gt; could mean we don't know who the user's friends are, and
an empty array could mean the user has no friends ☹️&lt;/p&gt;
&lt;h3 id="consider-enums-instead-of-optionals"&gt;Consider enums instead of optionals&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;Result&lt;/code&gt; type was introduced in Swift's standard library to eliminate the
awkward situation where both the value and the error are optional types yet one
of them will always be set. (Another example of state impossibility: what does
it mean if both the value &lt;em&gt;and&lt;/em&gt; the error are set? Or neither?)&lt;/p&gt;
&lt;p&gt;I've often seen this awkwardness in state as well, and similar to &lt;code&gt;Result&lt;/code&gt;, the
solution is often to model that state as an enum with associated values. As a
simplified example, instead of a bunch of optionals in an onboarding flow where
data is collected step by step, that state could be modeled as:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;OnboardingState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;askForName&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;askForBirthday&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;askForEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;birthday&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;askForPhone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;birthday&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="closing"&gt;Closing&lt;/h2&gt;
&lt;p&gt;Not all of the techniques above will be useful all the time, but I've often
found myself adjusting the data model when something was annoying to deal with
in layers that consumed it, and over time realized the importance of getting the
details there right to make the rest of the application easier to deal with too.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Composing a resume in markdown</title><link href="https://scottberrevoets.com/2025/03/15/composing-a-resume-in-markdown/" rel="alternate"/><published>2025-03-15T00:00:00-07:00</published><updated>2025-03-15T00:00:00-07:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2025-03-15:/2025/03/15/composing-a-resume-in-markdown/</id><summary type="html">&lt;p&gt;I recently updated my resume, and after a few iterations of tweaking the same
Pages file I started more than a decade ago, I decided that I really wanted
source control to make it easier to keep track of changes and to have different
versions of the same resume. Additionally …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I recently updated my resume, and after a few iterations of tweaking the same
Pages file I started more than a decade ago, I decided that I really wanted
source control to make it easier to keep track of changes and to have different
versions of the same resume. Additionally, the very consistent structure and
simplicity of a resume makes it a perfect candidate to be written in markdown,
which is a text-based format and easy to have in source control.&lt;/p&gt;
&lt;p&gt;Some quick searching confirmed my suspicion that many people were already doing
this and that there are various tools available to make this easier. The
primary tool is &lt;a href="http://pandoc.org/"&gt;pandoc&lt;/a&gt;, which is a universal document
converter. It can convert between many different formats, including markdown and
PDF.&lt;/p&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;The other tool that makes for a very structured resume is &lt;a href="https://jsonresume.org"&gt;JSON
Resume&lt;/a&gt;. It's a JSON schema with standard fields for
common resume sections like 'experience' and 'education' that can then
easily be themed. I found this a little too limiting and not as readable in
plain text as markdown.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;In my case, I'm converting from markdown to HTML, injecting some CSS to make it
look the way I want it to, and then converting that to PDF. LaTeX was another
option but I wasn't familiar with either LaTeX or ConTeXt and didn't want to
bother learning it just for this exercise.&lt;/p&gt;
&lt;p&gt;Converting to HTML was generally easy.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pandoc&lt;span class="w"&gt; &lt;/span&gt;--standalone&lt;span class="w"&gt; &lt;/span&gt;--include-in-header&lt;span class="w"&gt; &lt;/span&gt;resume.css&lt;span class="w"&gt; &lt;/span&gt;--output&lt;span class="w"&gt; &lt;/span&gt;resume.html&lt;span class="w"&gt; &lt;/span&gt;resume.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This gives a pretty good starting point even with no CSS at all. &lt;code&gt;pandoc&lt;/code&gt; uses a
custom markdown dialect with some extensions and other features, which makes it
easy to add ids and classes to &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;-&lt;code&gt;&amp;lt;h6&amp;gt;&lt;/code&gt; elements. This is useful as it
makes applying the right style to the right element easy. The resume itself is
simple and so is the resulting HTML. &lt;code&gt;pandoc&lt;/code&gt; does inject its own CSS as well,
which is easy to override in the custom CSS file.&lt;/p&gt;
&lt;p&gt;After tweaking the CSS to my liking, the next step was to convert the HTML to
PDF format. This was a bit more complicated as there were multiple ways to do
that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Safari&lt;/strong&gt; (or another browser): There is an export as PDF option or the
  option to print and then save as PDF. This worked but required manual steps I
  wanted to automate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;wkhtmltopdf&lt;/strong&gt;: a command line tool that converts HTML to PDF using a WebKit
  engine&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;pandoc&lt;/strong&gt;: can convert to PDF directly with some options that, supposedly,
  affect the final PDF output. I tried using it with &lt;code&gt;wkhtmltopdf&lt;/code&gt; as its
  engine, and it can also use LaTeX or ConTeXt as an intermediate format that
  bypasses HTML generation altogether.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I struggled a bit with which to choose, as the format for all of them wasn't
quite right except for the print to PDF option in Safari. I ultimately settled
on using &lt;code&gt;wkhtmltopdf&lt;/code&gt; directly (without using &lt;code&gt;pandoc&lt;/code&gt; for this step), after
experimenting with these options and seeing which output I liked best.
Converting the HTML to PDF was as simple as:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;wkhtmltopdf&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;letter&lt;span class="w"&gt; &lt;/span&gt;resume.html&lt;span class="w"&gt; &lt;/span&gt;resume.pdf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;-s letter&lt;/code&gt; option sets the paper size to US Letter since the default is A4.
Nowadays it doesn't really matter as resumes are only ever used in digital
format so the size of the PDF doesn't really matter anyway.&lt;/p&gt;
&lt;p&gt;I then wrapped both commands in a simple shell script to make it easier to run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pandoc&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--standalone&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--include-in-header&lt;span class="w"&gt; &lt;/span&gt;resume.css&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;--output&lt;span class="w"&gt; &lt;/span&gt;resume.html&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;resume.md

wkhtmltopdf&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;letter&lt;span class="w"&gt; &lt;/span&gt;resume.html&lt;span class="w"&gt; &lt;/span&gt;resume.pdf

open&lt;span class="w"&gt; &lt;/span&gt;resume.pdf&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;open&lt;span class="w"&gt; &lt;/span&gt;resume.html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After some modifications to the markdown, running this script will quickly
generate the HTML and PDF files and open them both so I can validate the result.&lt;/p&gt;
&lt;p&gt;This setup ends up working pretty well for me for 3 reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;With everything in a git repo I can make changes as I like, undo them easily,
   and use all of git's features on my resume. I push the markdown, HTML, and
   PDF versions to GitHub so I always have the latest version handy in a few
   different formats.&lt;/li&gt;
&lt;li&gt;The format and the content are for the most part separate, making it easier
   to focus on one or the other.&lt;/li&gt;
&lt;li&gt;Since everything is now text-based and not in proprietary binary formats,
   it's also easy to overengineer this in the future by including certain
   sections when applying to different jobs, including certain information only
   when certain flags are set, differentiating between a CV and a resume, etc.&lt;/li&gt;
&lt;/ol&gt;</content><category term="blog"/></entry><entry><title>Incremental complexity in iOS development</title><link href="https://scottberrevoets.com/2025/02/03/incremental-complexity-in-ios-development/" rel="alternate"/><published>2025-02-03T00:00:00-08:00</published><updated>2025-02-03T00:00:00-08:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2025-02-03:/2025/02/03/incremental-complexity-in-ios-development/</id><summary type="html">&lt;p&gt;I wrote my first app, a map to navigate my college campus, in the winter of
2011, on iOS 4.x and with Xcode 4.x. There were 25 frameworks, iOS 7 hadn't been
invented yet, there was only 1 phone size, and we wrote apps with Objective-C.&lt;/p&gt;
&lt;p&gt;But things …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I wrote my first app, a map to navigate my college campus, in the winter of
2011, on iOS 4.x and with Xcode 4.x. There were 25 frameworks, iOS 7 hadn't been
invented yet, there was only 1 phone size, and we wrote apps with Objective-C.&lt;/p&gt;
&lt;p&gt;But things have gotten much more complex over the years: within iOS you can
specialize in build engineering, architecture, performance &amp;amp; observability,
UIKit or SwiftUI, game development, security and privacy, Core Data or
SwiftData, iCloud, cross-platform apps with Catalyst, machine learning... The
list goes on and on, and that's on top of the already powerful frameworks from
the early days.&lt;/p&gt;
&lt;p&gt;There are frameworks that enable entire categories of apps that weren't possible
in the early days, or if they were with much less OS integration than today.
HealthKit, WalletKit, StoreKit, RealityKit, ARKit all enable apps that were
pretty much unimaginable 15 years ago. And that's just iOS without even thinking
about watchOS, visionOS, or Apple's other platforms.&lt;/p&gt;
&lt;p&gt;Swift went through a similar evolution. It started with a more functional
programming language compared to Objective-C, but quickly added new features:
protocol-oriented programming, property wrappers, function builders,
concurrency, macros, and a billion new keywords.&lt;/p&gt;
&lt;p&gt;In my first few years, I was eager to learn everything there was to learn about
iOS. This was in the days when there was still an NDA anything related to iOS
APIs and the developer forums required a paid membership, so only anything
published by Apple was readily available. One of the first things I checked with
new platform versions was the API changes in UIKit and Foundation, and the iOS
and Xcode release notes were next. WWDC keynote days were overwhelming because a
year's worth of changes just shipped and I felt like I needed to learn all of it
in a few hours' time.&lt;/p&gt;
&lt;p&gt;There are so many more sources to learn some part of the platform now that's
impossible to keep up with it all. You can easily be an iOS developer without
knowing 90% of its technologies. A "generalist" iOS developer will know many of
the UIKit/SwiftUI and Foundation APIs, as well as commonly used Swift features.
But if you're building an iOS app for an audio company that requires deep
knowledge of AVFoundation, you won't care or be able to keep up with
advancements in Core Location simply because they're irrelevant.&lt;/p&gt;
&lt;p&gt;This feels like a pretty big difference from the first few years of the App
Store, when experienced developers went "broad" because "deep" didn't really
exist yet. There were no UI experts, iOS game developers, Swift enthusiasts, or
multiplatform lobbyists, so it was fun to learn more about all the frameworks
even if you weren't going to use them.&lt;/p&gt;
&lt;p&gt;For beginning developers that increase in complexity isn't super obvious, as
everyone is "expected" to know basic UI and Swift knowledge and it takes some
time to learn that. But more senior developers looking for a new job where
experience with specific frameworks (sometimes not even Apple's own, like React
Native, RxSwift, or The Composable Architecture) is desired may feel like they
are not experienced enough. In reality they have plenty of great experience,
just in other parts of iOS.&lt;/p&gt;
&lt;p&gt;Most companies that hire for a niche iOS specialty realize this and are
generally OK with someone who's willing to learn the technologies that specific
job requires. But how do those companies interview for that? For hands-on coding
exercises, there are a few options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ask more basic questions&lt;/strong&gt; that all people should know at least to some
  extent. This works OK but doesn't really distinguish senior from junior
  engineers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ask deeper questions&lt;/strong&gt; but run the risk the candidate happens to not have
  deep knowledge on the topic they're being asked about.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Let candidates choose the topic&lt;/strong&gt; they have deep knowledge of and talk about
  that, but run the risk of the interviewer knowing nothing about topic.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Having been deeply involved in designing an interview process, I'm growing more
convinced that evaluating candidates based on 60 minutes of hands-on coding
isn't going to get you the best developers and will primarily weed out the below
average ones. What I find more interesting for these positions is if candidates
have enough transferable knowledge in one framework to quickly ramp up with
another. You can learn a ton about someone's experience through questions like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How does Apple approach privacy and how is that reflected in its APIs? What is
  generally involved in requiring user permission to use sensitive user data and
  what is the best way to handle that data?&lt;/li&gt;
&lt;li&gt;What hardware and environmental constraints do iPhones have? How does the
  system deal with those limitations and in what ways are developers affected by
  that? What precautions can you take and how can you know if your app performs
  well?&lt;/li&gt;
&lt;li&gt;How would you do the research for a lightning talk about an iOS technology you
  currently know nothing about (e.g. data persistence)? What sources of
  information are there, how reliable are they, and which do you prefer?&lt;/li&gt;
&lt;li&gt;Think of an API, language feature, or pattern you've used recently that can be
  improved. What did you not like about it, how would you improve it, and why do
  you think Apple might have designed it this way?&lt;/li&gt;
&lt;li&gt;Apple introduces a new technology at WWDC and your boss hears about it and
  wants you to implement it in the company's app. What problems do you foresee?&lt;/li&gt;
&lt;li&gt;What App Store Review guideline would you get rid of and why? Which one would
  you introduce?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These questions feel decidedly more involved than typical questions like "how
does memory management work" and "what's the difference between a class and a
struct", and they give a lot more insight into someone's (lack of) experience
with the platform.&lt;/p&gt;
&lt;p&gt;Being a senior iOS developer went from breadth of knowledge to depth of
knowledge, and I expect more defined specialties, perhaps iOS Security Engineer
or iOS Camera Engineer, to crystallize in the future. It's no longer about
watching every WWDC video and learning the latest UIKit tricks, knowing exactly
what APIs became available in what iOS version, and finding ways to use the
latest Swift features in your own code.&lt;/p&gt;
&lt;p&gt;Instead, more experienced developers understand how Apple designs their
platforms and technologies, have surface-level knowledge of what is available to
them, and are able to take their existing knowledge and apply it to something
new to ramp up quickly and effectively. The rest they know they can learn on the
fly. Hopefully companies realize this too in their interviewing and onboarding
processes, because you might be onboarding an iOS developer who doesn't know a
single thing about the tech stack your app is running on.&lt;/p&gt;
&lt;div class="admonition update"&gt;
&lt;p class="admonition-title"&gt;Update (February 2026)&lt;/p&gt;
&lt;p&gt;This post is only a year old but many shifts have already happened. AI
impacts the profession in a profound way and I no longer think more
specialized jobs will exist. Instead, companies will have their engineers
leverage AI to ramp up on new technologies quickly instead of trying to find
their unicorn engineer that happens to be an expert on Bluetooth audio.&lt;/p&gt;
&lt;p&gt;Senior engineers with a baseline knowledge will need to upskill to learn how
to use AI tools but will be fine in the end. Junior engineers will have a
harder time as companies seem to be unwilling to train them, a bad trend in
my opinion because the junior engineers are the senior engineers of the
future.&lt;/p&gt;
&lt;/div&gt;</content><category term="blog"/></entry><entry><title>Looking up words in a dictionary with Neovim</title><link href="https://scottberrevoets.com/2024/12/08/looking-up-words-in-a-dictionary-with-neovim/" rel="alternate"/><published>2024-12-08T00:00:00-08:00</published><updated>2024-12-08T00:00:00-08:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2024-12-08:/2024/12/08/looking-up-words-in-a-dictionary-with-neovim/</id><summary type="html">&lt;p&gt;When writing code in neovim, I frequently use &lt;code&gt;K&lt;/code&gt; to look up documentation,
function signatures, variable definitions, etc. In markdown files that doesn't
make too much sense, but I wanted to use it to look up words in the dictionary
instead. This didn't appear to be possible out of the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;When writing code in neovim, I frequently use &lt;code&gt;K&lt;/code&gt; to look up documentation,
function signatures, variable definitions, etc. In markdown files that doesn't
make too much sense, but I wanted to use it to look up words in the dictionary
instead. This didn't appear to be possible out of the box but the power of
(neo)vim is its extensibility so I decided to build it myself.&lt;/p&gt;
&lt;p&gt;The steps to build this roughly were:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Write a dictionary lookup tool&lt;/li&gt;
&lt;li&gt;Parse this tool's output and show it in neovim&lt;/li&gt;
&lt;li&gt;Configure neovim's &lt;code&gt;K&lt;/code&gt; functionality&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="dict"&gt;&lt;code&gt;dict&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;dict&lt;/code&gt; seemed like an appropriate, easy name to look up a word in the
dictionary. As I'm a macOS user I first wanted to use the built-in dictionary
through
&lt;a href="https://gist.github.com/lambdamusic/bdd56b25a5f547599f7f"&gt;&lt;code&gt;pyobjc-framework-CoreServices&lt;/code&gt;&lt;/a&gt;
but &lt;code&gt;DCSCopyTextDefinition&lt;/code&gt; doesn't return dictionary entries in a
well-structured or consistent way, so I ultimately decided to write a quick
lookup using &lt;a href="https://dictionaryapi.dev"&gt;dictionaryapi.dev&lt;/a&gt;. I also added some
other formatting and ANSI coloring to improve the CLI output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/usr/bin/env python3&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;argparse&lt;/span&gt;

&lt;span class="n"&gt;_TERMINAL_RESET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\033&lt;/span&gt;&lt;span class="s2"&gt;[0m&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;_TERMINAL_BOLD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\033&lt;/span&gt;&lt;span class="s2"&gt;[1m&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;_TERMINAL_DIM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\033&lt;/span&gt;&lt;span class="s2"&gt;[2m&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;_TERMINAL_RED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\033&lt;/span&gt;&lt;span class="s2"&gt;[91m&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;_TERMINAL_GREEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\033&lt;/span&gt;&lt;span class="s2"&gt;[92m&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;_TERMINAL_BLUE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\033&lt;/span&gt;&lt;span class="s2"&gt;[94m&amp;quot;&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_fetch_word_definition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://api.dictionaryapi.dev/api/v2/entries/en/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Handle API errors&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_format_definition_output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word_data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;word_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_TERMINAL_RED&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;error: word not found&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_TERMINAL_RESET&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

    &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;word_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;word&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;meanings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;word_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;meanings&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;

    &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_TERMINAL_GREEN&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;_TERMINAL_BOLD&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;_TERMINAL_RESET&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;part_of_speech&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;meanings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;part_of_speech&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;partOfSpeech&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_TERMINAL_BLUE&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;_TERMINAL_RESET&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

        &lt;span class="n"&gt;synonyms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;, &amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;part_of_speech&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;synonyms&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_TERMINAL_DIM&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;synonyms&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_TERMINAL_RESET&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;synonyms&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;definition&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;part_of_speech&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;definitions&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;definition_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;definition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;definition&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;N/A&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;definition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;example&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;definition_text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot; (e.g., &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)&amp;quot;&lt;/span&gt;
            &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_get_word_definition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;word_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_fetch_word_definition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_format_definition_output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Fetch word definitions from API dictionary.dev.&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;word&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Word to look up&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_get_word_definition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This file needs to be in the &lt;code&gt;PATH&lt;/code&gt; which I did by putting it in
&lt;code&gt;~/.bin/dict.py&lt;/code&gt; and setting &lt;code&gt;export PATH="$HOME/.bin:$PATH"&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dict ambiguous&lt;/code&gt; now gives a nicely formatted definition:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Dictionary lookup of the word ambiguous in the
terminal" src="/images/dictionary-lookup-cli.png"&gt;&lt;/p&gt;
&lt;h2 id="neovim-integration"&gt;Neovim integration&lt;/h2&gt;
&lt;p&gt;I wanted to invoke &lt;code&gt;dict&lt;/code&gt; using &lt;code&gt;K&lt;/code&gt; passing in the word under the cursor as the
trigger and showing the output in a popup window. Other than the plain vim
configuration this required some text manipulation so I put all that in
&lt;code&gt;~/.vim/plugin/dictionary.lua&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Create a new buffer with the given message and apply ANSI highlighting&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;create_buf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_create_buf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;plain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="kr"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="kr"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;ipairs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
    &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;stripped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\27&lt;/span&gt;&lt;span class="s2"&gt;%[[0-9;]+m&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_buf_set_lines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;stripped&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;apply_ansi_highlighting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kr"&gt;end&lt;/span&gt;

  &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;-- Show a popup window with the given buffer&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;show_popup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;popup_window&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_open_win&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;relative&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;cursor&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;anchor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;NW&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;style&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;minimal&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;border&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;single&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;-- Close the popup window&lt;/span&gt;
&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ClosePopup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;window&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;popup_window&lt;/span&gt;
  &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;popup_window&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_win_is_valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
    &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_win_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;popup_window&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
  &lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;-- Look up the definition of the word under the cursor&lt;/span&gt;
&lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;DictionaryLookup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;lt;cword&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;io.popen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dict &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;r&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;definition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;*a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;show_popup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;create_buf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This creates 2 functions, &lt;code&gt;DictionaryLookup()&lt;/code&gt; and &lt;code&gt;ClosePopup()&lt;/code&gt; that show and
dismiss the popup, stripping the ANSI codes from the output as they were
rendered raw in the popup.&lt;/p&gt;
&lt;p&gt;However, note the call to &lt;code&gt;apply_ansi_highlighting(buf, i - 1, line)&lt;/code&gt; near the
end of &lt;code&gt;create_buf()&lt;/code&gt;, which replaces the ANSI codes with neovim specific
highlight groups. This turned
out to be quite involved but I wanted the formatted output and used this as an
opportunity to learn lua a bit more. &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_set_hl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Normal&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;fg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;fg&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;underline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;italic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_set_hl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Bold&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;bold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;underline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_set_hl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Dim&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;fg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;gray&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;italic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_set_hl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Red&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;fg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;red&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_set_hl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Green&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;fg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;green&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_set_hl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Blue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;fg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;blue&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;ansi_patterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\27&lt;/span&gt;&lt;span class="s2"&gt;%[0m&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;highlight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Normal&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\27&lt;/span&gt;&lt;span class="s2"&gt;%[1m&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;highlight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Bold&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\27&lt;/span&gt;&lt;span class="s2"&gt;%[2m&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;highlight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Dim&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\27&lt;/span&gt;&lt;span class="s2"&gt;%[91m&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;highlight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Red&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\27&lt;/span&gt;&lt;span class="s2"&gt;%[92m&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;highlight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Green&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\27&lt;/span&gt;&lt;span class="s2"&gt;%[94m&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;highlight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Blue&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;-- Find the highlight group for a given ANSI escape code&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;find_ansi_pattern&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kr"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="kr"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;pairs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ansi_patterns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
      &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;highlight&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;
  &lt;span class="kr"&gt;end&lt;/span&gt;
  &lt;span class="kr"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;-- Apply ANSI highlighting to a line of text in the given buffer&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="kr"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;apply_ansi_highlighting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line_num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;gsub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\27&lt;/span&gt;&lt;span class="s2"&gt;%[0m$&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\27&lt;/span&gt;&lt;span class="s2"&gt;%[[0-9]+m&amp;quot;&lt;/span&gt;

  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;start_pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;pos_in_stripped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="nb"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;
  &lt;span class="kr"&gt;while&lt;/span&gt; &lt;span class="n"&gt;start_pos&lt;/span&gt; &lt;span class="kr"&gt;do&lt;/span&gt;
    &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;^&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
      &lt;span class="n"&gt;start_pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;end_pos&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
      &lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_buf_add_highlight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;find_ansi_pattern&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Normal&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;line_num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;pos_in_stripped&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kr"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;start_pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="kr"&gt;if&lt;/span&gt; &lt;span class="n"&gt;start_pos&lt;/span&gt; &lt;span class="kr"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;pos_in_stripped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pos_in_stripped&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;start_pos&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start_pos&lt;/span&gt;
      &lt;span class="kr"&gt;end&lt;/span&gt;
    &lt;span class="kr"&gt;end&lt;/span&gt;

    &lt;span class="nb"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kr"&gt;end&lt;/span&gt;
&lt;span class="kr"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="configuring-k"&gt;Configuring &lt;code&gt;K&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Opening any file and calling &lt;code&gt;:lua DictionaryLookup()&lt;/code&gt; already works (and &lt;code&gt;:lua
ClosePopup()&lt;/code&gt; to dismiss it) but that's a lot of typing and it better fits &lt;code&gt;K&lt;/code&gt;
in markdown files since that's otherwise unused anyway.&lt;/p&gt;
&lt;p&gt;I mapped the first to a vim command &lt;code&gt;:Dict&lt;/code&gt; and wanted to hide the window with
any cursor movement, similar to how this works for other documentation lookups. 
&lt;code&gt;keywordprg&lt;/code&gt; is the command that gets invoked through &lt;code&gt;K&lt;/code&gt; (and can also be set
through &lt;code&gt;set keywordprg&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;I put this in &lt;code&gt;~/.vim/ftplugin/markdown.lua&lt;/code&gt; so that it automatically is applied
to markdown files:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_create_user_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Dict&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DictionaryLookup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;nargs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;keywordprg&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;:Dict&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_create_autocmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;CursorMoved&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;*&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;callback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ClosePopup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With the end result being:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Dictionary lookup of the word ambiguous in
neovim" src="/images/dictionary-lookup-neovim.png"&gt;&lt;/p&gt;
&lt;p&gt;Dictionary lookup also still works in non-markdown files by directly invoking
&lt;code&gt;:Dict&lt;/code&gt;, which is a bit more typing but also much less common. That's all that
was needed, now &lt;code&gt;K&lt;/code&gt; performs a dictionary lookup and shows it nicely formatted
in-line and is automatically hidden when needed. This may be nice as a generic
plugin as well so I might do that at some point in the future as well.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Shifting the testing culture: Code coverage</title><link href="https://scottberrevoets.com/2024/11/20/shifting-the-testing-culture-code-coverage/" rel="alternate"/><published>2024-11-20T00:00:00-08:00</published><updated>2024-11-20T00:00:00-08:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2024-11-20:/2024/11/20/shifting-the-testing-culture-code-coverage/</id><summary type="html">&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;This is the third post in a series on shifting our testing culture.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://scottberrevoets.com/2024/11/18/shifting-the-testing-culture-motivation/"&gt;Motivation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://scottberrevoets.com/2024/11/19/shifting-the-testing-culture-infrastructure/"&gt;Infrastructure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Code coverage&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;p&gt;Most people will say that code coverage is a measure of how well-tested your
code is. I prefer to say it's a measure of how &lt;em&gt;untested&lt;/em&gt; a codebase is. Take
this example …&lt;/p&gt;</summary><content type="html">&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;This is the third post in a series on shifting our testing culture.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://scottberrevoets.com/2024/11/18/shifting-the-testing-culture-motivation/"&gt;Motivation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://scottberrevoets.com/2024/11/19/shifting-the-testing-culture-infrastructure/"&gt;Infrastructure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Code coverage&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;p&gt;Most people will say that code coverage is a measure of how well-tested your
code is. I prefer to say it's a measure of how &lt;em&gt;untested&lt;/em&gt; a codebase is. Take
this example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;myFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;zero&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;one&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;other&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;testMyunction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;myFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;myFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This yields 66.7% code coverage, but although the test passes, it doesn't
validate any business logic because the test has no assertion. The example is a
bit derived, but in a more realistic scenario where tools report you have 66.7%
code coverage, you don't actually know if that code is actively tested or just
happens to be executed while running tests. What you do know is that the other
33.3% is &lt;em&gt;definitely not&lt;/em&gt; tested.&lt;/p&gt;
&lt;h2 id="llvm-cov"&gt;&lt;code&gt;llvm-cov&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Pedantries aside, code coverage is still important even if the coverage level is
already high and there isn't much untested code. That's because code coverage
isn't just a number, it also comes with a detailed &lt;strong&gt;source overlay&lt;/strong&gt;: the
source code with highlights that indicate which lines, functions, and code
branches were invoked while running tests, and how often:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Source overlay as generated by llvm-cov" src="/images/source-overlay.png"&gt;&lt;/p&gt;
&lt;p&gt;It's clear that although this method has a test, the &lt;code&gt;input == 0&lt;/code&gt; branch wasn't
executed while running them. The &lt;code&gt;return "one"&lt;/code&gt; and &lt;code&gt;return "other"&lt;/code&gt; lines are
executed once, and the rest of the method is executed twice. Getting full
coverage of this function requires adding a test that calls it with &lt;code&gt;input ==
0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;On iOS, Xcode has built-in support for reporting code coverage. It presumably
uses &lt;a href="https://llvm.org/docs/CommandGuide/llvm-cov.html"&gt;llvm-cov&lt;/a&gt; for user friendliness, but invoking &lt;code&gt;llvm-cov&lt;/code&gt; directly gives a
ton of extra options, including exporting in different formats like JSON and
HTML.&lt;/p&gt;
&lt;p&gt;Since &lt;code&gt;llvm-cov&lt;/code&gt; operates on a test bundle and we have a test bundle for each of
our modules, we can get detailed information about a module's code coverage:
which file and which lines within a file, how many total lines are testable and
how many are covered, etc.&lt;/p&gt;
&lt;p&gt;In the past we used the various output formats to integrate with &lt;a href="https://codecov.io"&gt;codecov&lt;/a&gt;, but
eventually moved to an in-house web portal where this data is stored and easily
accessible. We've also built tooling integration so developers can run a simple
command on their own machine to get &lt;code&gt;llvm-cov&lt;/code&gt;'s output generated for their
modules, though viewing it in Xcode is often the better choice for local
development. The only part that's missing is seeing the source overlays in
GitHub so you can immediately tell which code changes require more tests.&lt;/p&gt;
&lt;h2 id="tracking-coverage"&gt;Tracking coverage&lt;/h2&gt;
&lt;p&gt;Every time a module's source code or tests change, we capture the new coverage
metrics in a database to keep track of a full history of each module's code
coverage. We aggregate the data by team and for the codebase as a whole, and
display it on the web portal. This gives anyone immediate access to answer
common questions about their team's testing progress.&lt;/p&gt;
&lt;p&gt;Developers are often interested in more than just the raw code coverage numbers
- they also want to understand what code isn't tested so they can add more
coverage. To make this easier, the web portal also displays the source overlays
for all source files in a module for easy viewing without having to open Xcode
for it.&lt;/p&gt;
&lt;h2 id="minimum-coverage"&gt;Minimum coverage&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;When a measure becomes a target, it ceases to be a good measure&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;- Goodhart's law&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Many companies enforce a minimum coverage level all code has to hit. Although it
sounds enticing, it could also turn into exactly what Goodhart's law warns
about. Developers will likely work around it if they feel they need to, or write
low-quality tests just to hit the minimum. It's also not clear what number to
pick and what to base that on. &lt;/p&gt;
&lt;p&gt;So instead we took a slightly different approach: each module gets to set &lt;em&gt;its
own minimum coverage percentage&lt;/em&gt; in the &lt;code&gt;BUILD&lt;/code&gt; file, and that percentage is
then enforced by our CI system. It's a good balance between establishing a
minimum while giving developers enough control to not make it feel overbearing.&lt;/p&gt;
&lt;p&gt;Developers are free to set that minimum to any percentage they want through a
pull request, but lowering it too drastically requires a good justification and
comes with social friction. Increases are often celebrated as a job well done.&lt;/p&gt;
&lt;p&gt;We also bump the minimum for a given module automatically if it's too low
compared to its actual coverage. A module whose minimum coverage is set to 10%
but actually has 50% coverage, would get its minimum increased to 45%. This
makes it harder to accidentally drop coverage.&lt;/p&gt;
&lt;p&gt;Other ideas we've played around with but haven't implemented:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;not allowing decreases in minimum coverage below a certain threshold&lt;/li&gt;
&lt;li&gt;enabling a global minimum but setting it very low (e.g. 20%), in hopes of
  having to write &lt;em&gt;some&lt;/em&gt; tests will lead to writing more&lt;/li&gt;
&lt;li&gt;higher minimum coverage for code that uses highly testable architectural
  components (as opposed to legacy code that's harder to test)&lt;/li&gt;
&lt;li&gt;tech lead approval for dropping a module's minimum coverage&lt;/li&gt;
&lt;li&gt;requiring adding/updating tests on PRs (changing code without needing to
  change tests generally means the code is not tested)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="raising-the-bar"&gt;Raising the bar&lt;/h2&gt;
&lt;p&gt;Having a long history of coverage data available has been a big factor in
raising the quality bar. During biannual planning, teams get a clear overview of
their average coverage and can quickly see which of their modules have the most
room for improvement, and attach a measurable goal to it.&lt;/p&gt;
&lt;p&gt;We look at overall trends and average coverage levels per module type, and set
goals for them for as guidance for other teams. Nothing big, just a couple
percent points per quarter, but it makes testing a talking point. When we
introduce a new tool, e.g. snapshot tests, we can see how that affects the trend
(spoiler alert: our developers strongly prefer snapshot tests) and whether to
invest more time/effort.&lt;/p&gt;
&lt;p&gt;Developers ask for more or better tests during code review. There has been a
noticeable uptick in code changes that just introduce a bunch more tests for
existing code. More refactors start out with ensuring code coverage is solid
or by closing any testing gaps. If an incident happens, the fix is asked to
include a test, and the post-mortem documents the code coverage of the code
that was at fault, with an action item to increase coverage if it's too low.&lt;/p&gt;
&lt;p&gt;Just like a more testable architecture and better tooling, having insightful
code coverage data played a big role in the gradual shift of our iOS testing
culture. We started with near 0% coverage, and all the improvements over the
years have gotten us to an overall code coverage of 60% and climbing.&lt;/p&gt;
&lt;p&gt;To me it's one of the most fascinating leaps in maturity I've seen in my time at
Lyft with a ton of learnings along the way.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Shifting the testing culture: Infrastructure</title><link href="https://scottberrevoets.com/2024/11/19/shifting-the-testing-culture-infrastructure/" rel="alternate"/><published>2024-11-19T00:00:00-08:00</published><updated>2024-11-19T00:00:00-08:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2024-11-19:/2024/11/19/shifting-the-testing-culture-infrastructure/</id><summary type="html">&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;This is the second post in a series on shifting our testing culture.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://scottberrevoets.com/2024/11/18/shifting-the-testing-culture-motivation/"&gt;Motivation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Infrastructure&lt;/li&gt;
&lt;li&gt;&lt;a href="https://scottberrevoets.com/2024/11/20/shifting-the-testing-culture-code-coverage/"&gt;Code coverage&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;p&gt;This second article goes a bit deeper into the testing infrastructure we built
over the years to optimize the ergonomics and developer experience as much as
possible.&lt;/p&gt;
&lt;p&gt;Our initial test suites only …&lt;/p&gt;</summary><content type="html">&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;This is the second post in a series on shifting our testing culture.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://scottberrevoets.com/2024/11/18/shifting-the-testing-culture-motivation/"&gt;Motivation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Infrastructure&lt;/li&gt;
&lt;li&gt;&lt;a href="https://scottberrevoets.com/2024/11/20/shifting-the-testing-culture-code-coverage/"&gt;Code coverage&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;p&gt;This second article goes a bit deeper into the testing infrastructure we built
over the years to optimize the ergonomics and developer experience as much as
possible.&lt;/p&gt;
&lt;p&gt;Our initial test suites only had basic tests for some foundational code in
shared modules. Initially these were written in &lt;a href="https://github.com/Quick/Quick"&gt;Quick and Nimble&lt;/a&gt;, but we moved
to the default &lt;code&gt;XCTest&lt;/code&gt; to reduce the learning curve. When we started
writing UI tests, we immediately used &lt;code&gt;XCUITest&lt;/code&gt; and still use that today.&lt;/p&gt;
&lt;p&gt;(&lt;code&gt;swift-testing&lt;/code&gt; looks very nice and we're exploring how we can start using this
in our own codebase.)&lt;/p&gt;
&lt;h2 id="modules"&gt;Modules&lt;/h2&gt;
&lt;p&gt;When we modularized the codebase (which I wrote a little bit about &lt;a href="https://scottberrevoets.com/2021/10/14/ios-architecture-at-lyft/#modules"&gt;before&lt;/a&gt;), we
did so specifically with testing in mind. All modules got their own test bundle
to test that module's code.&lt;/p&gt;
&lt;p&gt;Later on, we also specialized each module to the type of code it contained. A
module with primarily UI code in it is a &lt;code&gt;UI&lt;/code&gt; module, and a module with
core business logic is a &lt;code&gt;logic&lt;/code&gt; module. This approach has three (testing)
benefits:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It separates business logic from UI logic at a higher level&lt;/li&gt;
&lt;li&gt;You can now establish the level of testing that's expected per module type
   (e.g. higher for &lt;code&gt;logic&lt;/code&gt; modules than for &lt;code&gt;UI&lt;/code&gt; modules)&lt;/li&gt;
&lt;li&gt;The module type defines what type of tests should be written for that module
   (e.g. snapshot tests for &lt;code&gt;UI&lt;/code&gt; modules, unit tests for &lt;code&gt;logic&lt;/code&gt; modules)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Since each module is owned and maintained by a specific team, that team is now
fully in control over the tests they want to write for their own features. For
example, a UI-heavy feature is more likely to have snapshot tests than unit
tests. Complicated flows might have both or use UI tests for integration-like
tests.&lt;/p&gt;
&lt;h2 id="avoiding-flakiness"&gt;Avoiding flakiness&lt;/h2&gt;
&lt;p&gt;Test flakiness, tests that sometimes pass and sometimes fail, became more common
as we grew the number tests. This is a problem for multiple reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;people lose confidence in the system if tests fail when they shouldn't&lt;/li&gt;
&lt;li&gt;it slows engineers down&lt;/li&gt;
&lt;li&gt;it's disruptive when tests that have been around for months suddenly start
  failing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The source of flakiness was generally an infrastructure problem, a test problem,
or a code problem. Infrastructure problems were usually not actionable by
product engineers and was something related to CI, Xcode, the test runner, the
simulator, or the intersection between any of these.&lt;/p&gt;
&lt;p&gt;If the code was flaky that may have actually been a bug (yay tests!). Often this
meant some globally mutable state was involved, and was different than what the
tests expected for some reason.&lt;/p&gt;
&lt;p&gt;If the test itself was flaky, it wasn't always obvious why and how that could be
avoided. The code is hard to debug because the issue doesn't always reproduce
(sometimes only on CI) and if there didn't seem to be any bugs people would
understandably give up on that test if tracking the issue down took too long.&lt;/p&gt;
&lt;p&gt;On the infrastructure side we've made many improvements over the years to reduce
flakiness to a minimum:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;retry a failing test to reduce the impact of infrastructure failures&lt;/li&gt;
&lt;li&gt;disable a test that failed multiple times in a row and report it to the
  maintainer of the module that has the failing test&lt;/li&gt;
&lt;li&gt;increase the timeouts for test suites to run to avoid timing out slow-running,
  but passing, tests&lt;/li&gt;
&lt;li&gt;keep track of common errors out of our control (e.g. bugs in Xcode) and
  explicitly ignore them, try again, or work around them&lt;/li&gt;
&lt;li&gt;ensure tests always ran in the same environment: iOS version, device simulator,
  locale, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Infrastructure flakiness still happens on rare occasion, but the sytems are much
more robust and forgiving of intermittent issues than when we first started.&lt;/p&gt;
&lt;h2 id="making-simple-things-simple"&gt;Making simple things simple&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Simple things should be simple, complex things should be possible.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;- Alan Kay&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Further architecture improvements led us to standardize on a Unidirectional
Data Flow using RxSwift. These patterns are very testable, but required quite a
bit of boilerplate and significant knowledge of best testing practices within
those systems.&lt;/p&gt;
&lt;p&gt;With a more RxSwift-centric architecture, more asynchronousness code patterns
emerged as well. Initially we used &lt;code&gt;XCTestExpectation&lt;/code&gt;s to deal with that since
that's what Apple recommended, but regularly having to increase the timeout on
these expectations felt bad and we figured there had to be better ways.&lt;/p&gt;
&lt;p&gt;We first adopted RxBlocking, but later realized RxTest was where the real money
was for us. Having two extra libraries just for testing felt heavy-handed at the
time, but have become invaluable for us.&lt;/p&gt;
&lt;p&gt;Because the new standard for features was that they were all written using
in-house architectural components without too much deviation, we could also
standardize the test-writing part through a thin glaze of syntax sugar. Testing
a single action's outcome in a reducer now looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;testAcknowledgingError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reducer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="bp"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="c1"&gt;// easy error state setup with mock data&lt;/span&gt;
        &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;acknowledgeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// acknowledge the error&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;activeError&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// error has been acknowledged&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With many conveniences like these in place, many tests are easy to read and take
just a few lines of code. I'm optimistic that once we adopt &lt;code&gt;swift-testing&lt;/code&gt;'s
parameterized testing we can probably reduce this even more.&lt;/p&gt;
&lt;h2 id="mocking"&gt;Mocking&lt;/h2&gt;
&lt;p&gt;Our &lt;a href="https://scottberrevoets.com/2021/10/14/ios-architecture-at-lyft/#dependency-injection"&gt;dependency injection system&lt;/a&gt; enables mocking with protocols which carries a
bit more boilerplate than I'd prefer but isn't too bad overall. We've also built
"automock" tooling: a pre-compile tool that generates mock classes for all
protocols it found in a module. That looks something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// handwritten by a developer:&lt;/span&gt;

&lt;span class="kd"&gt;protocol&lt;/span&gt; &lt;span class="nc"&gt;MyAPI&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;getDataFromServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// json&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;postDataToServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// autogenerated by automock:&lt;/span&gt;

&lt;span class="n"&gt;open&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyAPIMock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MyAPI&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;getDataFromServerReturn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[:]&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="n"&gt;getDataFromServerCalls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kr"&gt;get&lt;/span&gt; &lt;span class="n"&gt;postDataToServerParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;getDataFromServer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;getDataFromServerCalls&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;getDataFromServerReturn&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;postDataToServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;postDataToServerParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;MyAPIMock&lt;/code&gt; can be used in tests to verify that the right methods are called at
the right time, and with the right parameters.&lt;/p&gt;
&lt;p&gt;Automock supports more complicated use cases like closures, and more complex
parameter and return types as well. This saves developer time writing mock
classes and promotes consistency in naming and mocking usage.&lt;/p&gt;
&lt;p&gt;It even works for model data, but it's less used and I'm more interested in a
macro approach that looks something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;Mock&lt;/span&gt;
&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Bool&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// @Mock expands to:&lt;/span&gt;

&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Generating mock data is now as simple as &lt;code&gt;User.mock()&lt;/code&gt; (or even &lt;code&gt;.mock()&lt;/code&gt; if the
type can be inferred) while still having the ability to set parameters manually
if needed.&lt;/p&gt;
&lt;h2 id="other-improvements"&gt;Other improvements&lt;/h2&gt;
&lt;p&gt;We've made a plethora of other improvements as well: sharding to parallelize
tests, only running test bundles for changed modules, a &lt;code&gt;TestKit&lt;/code&gt; module to
remove common &lt;code&gt;setUp()&lt;/code&gt; boilerplate, and a bunch of small things I'm probably
forgetting right now.&lt;/p&gt;
&lt;p&gt;But the most meaningful and impactful improvements have been related to how we
handle code coverage. So much so that I thought it deserved a whole separate
post.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Shifting the testing culture: Motivation</title><link href="https://scottberrevoets.com/2024/11/18/shifting-the-testing-culture-motivation/" rel="alternate"/><published>2024-11-18T00:00:00-08:00</published><updated>2024-11-18T00:00:00-08:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2024-11-18:/2024/11/18/shifting-the-testing-culture-motivation/</id><summary type="html">&lt;p&gt;Five years ago I began promoting automated testing heavily within Lyft even
though I didn't have any real experience in writing big test suites myself. Many
hands make light work, so I found a few people who shared this thinking and we
started on what turned out a multi-year testing …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Five years ago I began promoting automated testing heavily within Lyft even
though I didn't have any real experience in writing big test suites myself. Many
hands make light work, so I found a few people who shared this thinking and we
started on what turned out a multi-year testing journey. What did we do?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We optimized the architecture in a much more profound way than just "you can
   write tests more easily"&lt;/li&gt;
&lt;li&gt;We built a ton of tooling to improve the developer experience and to hold
   ourselves more accountable&lt;/li&gt;
&lt;li&gt;We started measuring and reporting progress on our efforts regularly&lt;/li&gt;
&lt;li&gt;We included testing in company-wide processes like incident review and
   biannual planning&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The end result of all the major efforts, minor quality-of-life improvements, and
fixing tiny paper cuts has led to test-writing now being a huge part of our
engineering culture. That's not to say we're there—there is definitely still
more work to be done. But the progress is pretty remarkable and I wanted to
share more about it.&lt;/p&gt;
&lt;p&gt;I'll do this in a series of blog posts, this being the first one:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Motivation&lt;/strong&gt;: why we thought this was an important area to invest in&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://scottberrevoets.com/2024/11/19/shifting-the-testing-culture-infrastructure/"&gt;Infrastructure&lt;/a&gt;&lt;/strong&gt;: how we upgraded our infrastructure to optimize for
   testability&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://scottberrevoets.com/2024/11/20/shifting-the-testing-culture-code-coverage/"&gt;Code coverage&lt;/a&gt;&lt;/strong&gt;: how we measure, report, and act on our code coverage&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="motivation"&gt;Motivation&lt;/h2&gt;
&lt;p&gt;After experiencing the hypergrowth of both our team and codebase in 2017-2019,
it became clear that the only way to maintain code and product quality was
through automated testing. Prior to that, a lot of testing would happen through
knowledgeable developers, QA processes, and dogfooding, but that wasn't going to
hold up indefinitely for a few reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;QA time is costly because a human hour is much more expensive than a computer
  hour. Writing a unit test means you can now validate any code change with that
  test in a fraction of a second, but a QA engineer needs to spend that time
  again and again.&lt;/li&gt;
&lt;li&gt;QA processes themselves are expensive: test plan management and maintenance
  costs time and money and the process itself needs to be developed, documented,
  and updated&lt;/li&gt;
&lt;li&gt;People leave, and with them institutional knowledge leaves as well. It takes a
  long time for new people to build that knowledge, if they ever get there.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In short, computers can handle 80-90% of this work faster and more accurately
than developers can. As with all infrastructure, it takes some time to set it up
in an effective way, but since that's a one-time effort it should pay itself off
over time.&lt;/p&gt;
&lt;h2 id="testing-methodologies"&gt;Testing methodologies&lt;/h2&gt;
&lt;p&gt;Unit testing, acceptance testing, integration testing, system testing,
regression testing, UI testing... fanatics will tell you they're all important.
I don't necessarily disagree, but how hard you go also depends on how you need
to balance quality and velocity. Aviation software leans much more toward
quality, a rideshare app comparably more toward velocity.&lt;/p&gt;
&lt;p&gt;So we initially focused on two types of testing: unit testing and UI testing.
After snapshot testing became popular on both iOS and Android we added that to
our arsenal as well.&lt;/p&gt;
&lt;p&gt;Unit and snapshot tests are written by mobile developers for their own code, UI
tests cover larger flows and are written by SETs. (In my opinion UI tests, if
you choose to invest in them, should also be written by the developers that
wrote the original code, but that is not the setup we have today.)&lt;/p&gt;
&lt;h2 id="benefits"&gt;Benefits&lt;/h2&gt;
&lt;p&gt;It feels a bit weird to be writing these articles pretending it's some novel
idea that writing tests benefits an engineering organization like that isn't
already common knowledge. However, testing culture in the iOS community, and I
suspect all "frontend" disciplines (web, mobile, TVs, etc.), is weak compared to
that in other communities. For one because UI is hard to test, but also because
Apple itself doesn't promote it much as a best practice.&lt;/p&gt;
&lt;p&gt;To not just speak in hypotheticals, the benefits we've experienced firsthand
are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Behavior validation&lt;/strong&gt;: The most obvious benefit is that tests validate how
  code behaves. Bug fixes will see fewer regressions, refactors are safer, and
  the app is generally of higher quality because every bug happens only once (in
  theory) and many are prevented from happening at all.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Behavior documentation&lt;/strong&gt;: Code becomes more self-documenting as a lot of the
  behavior is prescribed in tests. You can skim test names/descriptions to
  understand how a class is supposed to behave, with little risk of the
  documentation becoming outdated because the tests will fail if they don't
  match the actual behavior.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Code readability&lt;/strong&gt;: in order to write reasonable tests, code has to be
  structured reasonably well too. Test setup is hard with spaghetti code, so
  there's an incentive to write cleaner code. (Unfortunately, the opposite is
  true too: poorly written code can often lead to poorly written or no tests.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Engineering satisfaction&lt;/strong&gt;: a well-tested codebase is more satisfying to
  work in because developers have higher confidence in the correctness of their
  code. It "feels" easier to make changes because you can rely on the tests to
  catch any mistakes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="drawbacks"&gt;Drawbacks&lt;/h2&gt;
&lt;p&gt;I don't really like writing these articles without also mentioning the
drawbacks. The main ones we've experienced are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Time cost&lt;/strong&gt;: it simply takes time to write tests, and there's strong
  correlation between a codebase that &lt;em&gt;desperately needs tests&lt;/em&gt; and the time it
  takes to write those tests&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;False sense of security&lt;/strong&gt;: it's easy to become overly confident in the test
  suite and assume a passing test suite means there are no bugs, when in reality
  the test suite might be incomplete or of low quality&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Learning curve&lt;/strong&gt;: there's a learning curve involved, especially in a large
  codebase, and not everyone is interested in learning this&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;More code to maintain&lt;/strong&gt;: code requires maintenance and test code is no
  different&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Over time we've tried to minimize these drawbacks as best as we could, but most
of them will likely always be a factor. In the next articles I'll go in a
bit more depth on the mitigation strategies we've used and how effective they've
been.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Migration strategies in large codebases</title><link href="https://scottberrevoets.com/2022/11/15/migration-strategies-in-large-codebases/" rel="alternate"/><published>2022-11-15T00:00:00-08:00</published><updated>2022-11-15T00:00:00-08:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2022-11-15:/2022/11/15/migration-strategies-in-large-codebases/</id><summary type="html">&lt;p&gt;Code migrations are a fact of life for large codebases. New technologies pop up,
platforms see improvements, and programming languages get new features you might
want to adopt. Not performing these migrations and keeping up with the times is
simply not an option in most cases. While some migrations are …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Code migrations are a fact of life for large codebases. New technologies pop up,
platforms see improvements, and programming languages get new features you might
want to adopt. Not performing these migrations and keeping up with the times is
simply not an option in most cases. While some migrations are easy 1 for 1
replacements that can/should be scripted, that’s not true for the more
“semantic” migrations—the new code accomplishes the same but in an entirely
different manner.&lt;/p&gt;
&lt;p&gt;We’ve done a number of these semantic migrations in Lyft’s mobile codebases and
while we’ve learned something from every one of them (and applied the learnings
the next time), they only get harder as time goes on.&lt;/p&gt;
&lt;p&gt;What makes migrations so difficult? This generally comes down to how development
works in large codebases: there is no 1 mobile team, but rather a lot of
vertical teams (e.g. payments, new user experience, etc.) that all build their
own features, using shared foundational code and architectural patterns. The
coordination of dozens of teams on the progress of a migration is what makes
this difficult:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Codebases tend to grow ad infinitum, so there is simply more code to migrate
  each time and there are more “weird” cases that make automating a migration
  difficult.&lt;/li&gt;
&lt;li&gt;Getting (and keeping) buy-in and commitment. Teams all have their own plans
  and priorities, and migrating code from pattern A to pattern B is often not
  high on the priority list. If it is, that could change before the migration is
  completed.&lt;/li&gt;
&lt;li&gt;Senior engineers on those teams tend to have good judgment on the importance
  of reducing tech debt and can weigh it against other priorities coming from
  product managers and designers. Junior engineers that get pulled in 4
  different directions are less likely to push back against their direct
  teammates, so tech debt tickets are moved to an ever-expanding Jira backlog.&lt;/li&gt;
&lt;li&gt;Teams are not static. You might inherit code written years ago that still
  works and is in use, but you didn’t even know it existed until last week. And
  now it needs to be migrated? Or it’s that one feature that is somehow mission
  critical but no one knows how to even get in the state to show it, let alone
  test new code in it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So how do we get these migrations done anyway? There is no silver bullet, but we
have started using a number of strategies to make these migrations less
painful—and almost all of them have to do with improving the communication and
expectations everyone involved has.&lt;/p&gt;
&lt;h2 id="migration-tracker"&gt;Migration tracker&lt;/h2&gt;
&lt;p&gt;The biggest leap forward for us was the rollout of an in-house web dashboard
that shows progress on all ongoing migrations in our iOS and Android codebases.
It shows all the important information anyone could want to know: the start
date, (projected) completion date, links to documentation and support channels,
relative priority (more on that below), etc.&lt;/p&gt;
&lt;p&gt;But the real win is automatic progress tracking. Every migration is defined as
the presence of a certain pattern in the codebase we want to get rid of. A cron
job records all instances of that pattern on the main branch once a day, so we
know exactly how much code has been migrated and how much is left.&lt;/p&gt;
&lt;p&gt;Furthermore, our codebase is heavily modularized, and through CODEOWNERS every
module is required to define what team owns it. This data is shared with the
Migration Tracker, so it can give a detailed breakdown by module &lt;em&gt;and by team&lt;/em&gt;
of how much unmigrated code is left. This is extremely helpful in understanding
which teams might need extra time, help, or nudging in getting everything done.&lt;/p&gt;
&lt;h2 id="timeline"&gt;Timeline&lt;/h2&gt;
&lt;p&gt;The “deadline” of a migration needs to be carefully chosen. Not giving people
enough time makes them not do the work at all, giving them too much time means
people will procrastinate and needlessly extend the migration time. If it looks
like teams are not going to make the deadline (as projected by the Migration
Tracker), we ask them when they &lt;em&gt;can&lt;/em&gt; get it done. We usually accept whatever
answer the team gives, but hold them accountable to that since they themselves
picked that timeframe.&lt;/p&gt;
&lt;h2 id="priorities"&gt;Priorities&lt;/h2&gt;
&lt;p&gt;Teams that are far behind in code modernization might have to migrate their code
in multiple ways. We give each migration a priority (P0/P1/P2/P3) to give teams
a sense of where to focus their tech debt energy first.&lt;/p&gt;
&lt;p&gt;A P0 migration is a “must-do-or-else” (externally mandated changes, tight
deadlines, extremely high impact on overall trajectory of the codebase or
product, etc.), a P1 is high impact but not as urgent, P2 is a nice to have, and
P3 is almost more of an FYI.&lt;/p&gt;
&lt;p&gt;This helps everyone understand where they should focus their energy, instead of
seeing a potentially long list of items that are all seemingly equally important
migrations.&lt;/p&gt;
&lt;h2 id="incremental-value"&gt;Incremental value&lt;/h2&gt;
&lt;p&gt;Where possible (which is most cases), we try to bring incremental value as a
migration is being performed. For example, if we set out to replace all uses of
class A with class B because class B performs better, the places where class B
is used should see that performance increase before the rest of the code has
been migrated. This has a few benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It rewards teams that have done their part of the overall migration&lt;/li&gt;
&lt;li&gt;It’s easier to convince people doing their part has value since they
  themselves immediately see that value&lt;/li&gt;
&lt;li&gt;It de-risks long-running migrations—if it gets to 95% completion we realize
  95% of the benefits and not 0%&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="only-invest-in-the-new"&gt;Only invest in the new&lt;/h2&gt;
&lt;p&gt;The initial value (like performance improvements in the example above) teams get
from migrating their own code might not be enough to convince them to do it. But
as more and more improvements are made to “class B” (e.g. API ergonomics,
integrated analytics, compatibility with other systems) that “class A” doesn’t
have, the value proposition for the migration changes with time. We no longer
update A, which means there's some amount of bit rot and it becomes more
cumbersome to use.&lt;/p&gt;
&lt;p&gt;In extreme cases we could even remove features of class A that would necessitate
a migration, but we haven’t done that so far and would like to avoid it to avoid
losing goodwill with teams.&lt;/p&gt;
&lt;h2 id="develop-great-partnerships"&gt;Develop great partnerships&lt;/h2&gt;
&lt;p&gt;Getting adoption of a new pattern or tool is difficult even if it doesn’t
involve a new migration, because there is no precedent. Early adopters will hit
all the rough edges and have often incomplete documentation and no sample code.
The developer experience isn’t great (yet), so we make ourselves very available
to their needs. We take their feedback seriously and address it promptly, sit
down with them to help solve their issues, and generally spend a lot of time
with them working out the kinks. We do this 1) to build more confidence that
what we built is production-ready and 2) to recognize/reward people that want to
try new things by reducing as much of the growing pains as possible.&lt;/p&gt;
&lt;p&gt;Of course, good developer experience is important for all teams and not just
early adopters, but piloting teams can help us get the ball rolling with other
teams as well. Positive reviews from users of Great New Thing is different than
hearing it from the developers of Great New Thing.&lt;/p&gt;
&lt;h2 id="avoid-new-uses"&gt;Avoid new uses&lt;/h2&gt;
&lt;p&gt;To avoid creating a moving target, as soon as we start a new migration we lock
down the uses of that pattern to only the current ones and no longer accept new
uses of this pattern. Depending on the situation we have a few different ways of
accomplishing this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We add a linter rule or sometimes entirely new type of (custom) linter and
  introduce exceptions to all existing use cases&lt;/li&gt;
&lt;li&gt;We rename a class or method by prepending the current name with &lt;code&gt;DEPRECATED&lt;/code&gt;.
  This isn’t particularly elegant, but it alerts the author and PR reviewers
  that new legacy code is being introduced, which usually has them dig deeper
  and figure out what to do instead.&lt;/li&gt;
&lt;li&gt;If an entire module is being deprecated, we don’t easily let new modules
  depend on it. Exceptions are always possible, but engineers have to talk to us
  first so we can guide them in the right direction.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These measures are just to generate awareness that a pattern is outdated, it
doesn’t always communicate why it’s being deprecated and what’s replacing it.
But if people come to us asking for an exception we can explain it or link to
docs, an opportunity we wouldn’t have had if we didn’t do any of the things
above.&lt;/p&gt;
&lt;h2 id="support-support-support"&gt;Support, support, support&lt;/h2&gt;
&lt;p&gt;And that brings me to the most critical and time-consuming piece: support. This
means helping people with questions, plugging gaps new patterns have compared to
the old ones, checking in with people on progress, celebrating the completion of
major (or all!) migrations with everyone that helped contribute, assisting teams
that are having difficulty prioritizing the work, and anything else that gets
the work done.&lt;/p&gt;
&lt;p&gt;Migrations are a very specific type of codebase maintenance due to the large
footprint they often have in a lot of the codebase. It’s an all-hands on deck
situation and requires proper coordination and communication, sometimes for
years on end. It’s hard to get them right and we’re still fine-tuning our
strategies but applying the learnings I described here have made us become
better at them. I can only hope one day refactoring tools become smart enough to
automate the work for us.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Human factors in choosing technologies</title><link href="https://scottberrevoets.com/2022/09/28/human-factors-in-choosing-technologies/" rel="alternate"/><published>2022-09-28T00:00:00-07:00</published><updated>2022-09-28T00:00:00-07:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2022-09-28:/2022/09/28/human-factors-in-choosing-technologies/</id><summary type="html">&lt;p&gt;I recently saw a thread where someone wanted to introduce a more capable
architecture pattern than what most apps start out with in a small team, but
received some pushback from teammates and was looking for help in countering
their arguments.&lt;/p&gt;
&lt;p&gt;The thread for the most part focused on the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I recently saw a thread where someone wanted to introduce a more capable
architecture pattern than what most apps start out with in a small team, but
received some pushback from teammates and was looking for help in countering
their arguments.&lt;/p&gt;
&lt;p&gt;The thread for the most part focused on the technical benefits of the proposed
pattern, such as testability, separation of concerns, modularity, etc. These are
all valid trade-offs to consider: we’ve done the same at Lyft when figuring out
what architectures would suit us best. This is also not unique to architectures,
any big technology that influences the overall direction of the codebase:
SwiftUI vs. UIKit, RxSwift vs. Combine vs. async/await, etc.&lt;/p&gt;
&lt;p&gt;But over time I’ve realized that even with a technically “perfect” solution (a
real oxymoron!), there is an entirely different yet equally important factor to
consider: who are the people using it—now and in the future? The success of a
particular technology is highly dependent on the answer to that question, and it
plays a huge role at different stages of the development of that technology.&lt;/p&gt;
&lt;p&gt;In the thread I mentioned above there was very little focus on the human aspect
of the proposal, so I wanted to list a number of things that I personally ask
myself and our teams before considering moving forward:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How much is the onboarding/ramp-up cost?&lt;/strong&gt; While I generally think short-term
pain is worth long-term gain, onboarding is often a continuous cost. New people
join, you might have interns, non-mobile engineers wanting to make quick
contributions, etc. If those people first need a lot of time to ramp up, it’s
worth wondering if the benefits are worth it, or how to reduce that burden. For
example, while we currently aren't using any SwiftUI at Lyft, we have a layer of
&lt;a href="http://www.scottberrevoets.com/2021/10/14/ios-architecture-at-lyft/#declarative-ui"&gt;sugar syntax on top of UIKit&lt;/a&gt; that enables us to use SwiftUI-like syntax anyway.
This makes it easier for both new people that join and already know SwiftUI and
for everybody to move over to SwiftUI if/when we're ready for that.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How easy is it to undo?&lt;/strong&gt; Or: what is the cost of mistakes? If things don’t
pan out the way we want them to, how easily could we switch to something else?
The more difficult switching back is, the higher the commitment level and the
more we need to be sure it’s worth it. This applies both to the internals of the
framework and how the code that uses the framework is structured.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Is it easy to do the right thing?&lt;/strong&gt; This one is straightforward: if it’s easy
to do the right thing it’s more likely people will do the right thing and
achieve the architecture’s potential more. Conversely, if it’s easy to do the
wrong thing, the benefits aren’t realized as much. Especially considering my
previous point, if it’s hard to undo bad usage it’s maybe worth going back to
the drawing board.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How much support is available?&lt;/strong&gt; Popular technologies have a lot of online
material available for support in the form of Stack Overflow questions and
answers, blog posts, videos, open source code + discussions on GitHub, etc. A
home-built solution means this knowledge only lives in-house which increases the
bus factor. The same is true for very opinionated third-party libraries like
RxSwift or The Composable Architecture. I’m a fan of both, but without fully
understanding how they’re implemented you’re at the mercy of the developers and
contributors of these libraries for years to come.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How much institutional knowledge does it require?&lt;/strong&gt; Good architectures hide
domain complexity for its consumers, and incur the complexity internally. To
some extent that’s fine, but if the internals become so complicated that few
people know how it works there is again a high bus factor. It can absolutely be
worth putting some complexity/boilerplate burden onto feature owners to avoid
making complex abstractions that are hard to change in the future once it’s used
everywhere and the original developers have left.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How much effort does it take to see 100% adoption?&lt;/strong&gt; Depending on the size of
the existing code base, it could take a long time to get 100% adoption. That can
be OK if this is the codebase’s first serious architecture, but if it’s version
5 and some parts of the codebase still use version 1 through 3, it’s probably
worth removing those first and reducing &lt;a href="http://mikehadlow.blogspot.com/2014/12/the-lava-layer-anti-pattern.html"&gt;lava layers&lt;/a&gt;. Even if the change
from version 10 to 11 is small and easy, the fragmentation of the codebase
inhibits developer productivity. The quicker the migration the better, and if
the codebase can safely be migrated through automation that’s the best case
outcome.&lt;/p&gt;
&lt;p&gt;But the most important one of all: &lt;strong&gt;do people actually like the architecture?&lt;/strong&gt;
No one likes working in a codebase where everything is a hassle, the underlying
concepts never seem to make sense, abstractions are leaky, and you seem to
always have to do work for it instead of it working for you. Those codebases
diminish the team’s motivation levels and will affect many of the other points
from above.&lt;/p&gt;
&lt;p&gt;On the flip side, if people like the proposed patterns, they will put in a lot
more work in to use them correctly, try harder to do the right thing, are
willing to help others, etc. If not, forcing patterns people don’t like could
lead to developer unhappiness and attrition. We have more than a few examples of
this at Lyft, where a slightly inferior technical solution is overall much more
beneficial because the pattern is a bit simpler to use than the alternative.&lt;/p&gt;
&lt;p&gt;Going back to why I started writing this in the first place: in my opinion the
question “what counterarguments can I use” is not a great first question to ask
when it comes to convincing people your solution is the best one out there.
Understanding &lt;em&gt;why&lt;/em&gt; people are resistant is key. Sure, some people just don’t
like change, but papering over any of the concerns above with a technically
superior solution is a recipe for a bunch of barely-adopted technologies in a
codebase that’s often worse off than if nothing had been done in the first
place.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Third-party libraries are no party at all</title><link href="https://scottberrevoets.com/2022/07/15/third-party-libraries-are-no-party-at-all/" rel="alternate"/><published>2022-07-15T00:00:00-07:00</published><updated>2022-07-15T00:00:00-07:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2022-07-15:/2022/07/15/third-party-libraries-are-no-party-at-all/</id><summary type="html">&lt;p&gt;&lt;em&gt;What better way to end the week than with a hot take?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In my 8 years at Lyft, product managers or engineers have often wanted to add
third-party libraries to one of our apps. Sometimes it’s necessary to integrate
with a specific vendor (like PayPal), sometimes it’s to …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;What better way to end the week than with a hot take?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In my 8 years at Lyft, product managers or engineers have often wanted to add
third-party libraries to one of our apps. Sometimes it’s necessary to integrate
with a specific vendor (like PayPal), sometimes it’s to avoid having to build
something complicated, and sometimes it’s simply to not reinvent the wheel.&lt;/p&gt;
&lt;p&gt;While these are generally reasonable considerations, the risks and associated
costs of using a third-party library are often overlooked or misunderstood. In
some cases the risk is worth it, but to be able to determine that you first need
to be able to define that risk accurately. To make that risk assessment more
transparent and consistent, we defined a process of things we look at to
determine how much risk we're incurring by integrating it and shipping it in one
or more production apps.&lt;/p&gt;
&lt;h2 id="risks"&gt;Risks&lt;/h2&gt;
&lt;p&gt;Most larger organizations, including ours, have some form of code review as part
of their development practices. For those teams, adding a third-party library is
equivalent to adding a bunch of unreviewed code written by someone who doesn't
work on the team, subverting the standards upheld during code review and
shipping code of unknown quality. This introduces risk in how the app runs,
long-term development of the app, and, for larger teams, overall business risk.&lt;/p&gt;
&lt;h3 id="runtime-risks"&gt;Runtime risks&lt;/h3&gt;
&lt;p&gt;Library code generally has the same level of access to system resources as
general app code, but they don't necessarily apply the best practices the
team put in place for managing these resources. This means they have access
to the disk, network, memory, CPU, etc. without any restrictions or
limitations, so they can (over)write files to disk, be memory or CPU hogs
with unoptimized code, cause dead locks or main thread delays, download (and
upload!) tons of data, etc. Worse, they can cause crashes or even &lt;a href="https://www.theverge.com/2020/5/7/21250689/facebook-sdk-bug-ios-app-crash-apple-spotify-venmo-tiktok-tinder"&gt;crash
loops&lt;/a&gt;. &lt;a href="https://github.com/facebook/facebook-ios-sdk/issues/1427"&gt;Twice&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Many of these situations aren't discovered until the app is already available to
customers, at which point fixing it requires creating a new build and going
through the review process which is often time intensive and costly. The risk
can be somewhat mitigated by invoking the library behind a feature flag, but
that isn't a silver bullet either (see below).&lt;/p&gt;
&lt;h3 id="development-risks"&gt;Development risks&lt;/h3&gt;
&lt;p&gt;To quote a coworker: "every line of code is a liability", and this is even more
true for code you didn't write yourself. Libraries could be slow in adopting new
technologies or APIs holding the codebase back, or too fast causing a deployment
target that's too high. When Apple and Google introduce new OS versions each
year, they often require developers update their code based on changes in their
SDKs, and library developers have to follow suit. This requires coordinated
efforts, alignment in priorities, and the ability to get the work done in a
timely manner.&lt;/p&gt;
&lt;p&gt;As the mobile platforms are ever-changing this becomes a continuous, ongoing
risk, compounded by the problem that teams and organizations aren't static
either. When a library that was integrated by a team that no longer exists needs
to be updated, it takes a long time to figure out who should do so. It has
proven extremely rare and extremely difficult to remove a library once it's
there, so we treat it as a long-term maintenance cost.&lt;/p&gt;
&lt;h3 id="business-risks"&gt;Business risks&lt;/h3&gt;
&lt;p&gt;As I mentioned above, modern OSes make no distinction between app code and
library code, so in addition to system resources they also have access to user
information. As app developers we're responsible for using that information
properly, and any libraries are part of that responsibility.&lt;/p&gt;
&lt;p&gt;If the user grants location access to the Lyft app, any third-party library
automatically gets access too. They could then upload that data to their own
servers, competitors' servers, or who knows where else. This is even more
problematic when a library needs a &lt;em&gt;new&lt;/em&gt; permission we didn't already have.&lt;/p&gt;
&lt;p&gt;Similarly, a system is as secure as its weakest link but if you include
unreviewed, unknown code you have no idea how secure it really is. Your
well-designed secure coding practices could all be undone by one misbehaving
library. The same goes for any policies Apple and Google put in place like "you
are not allowed to fingerprint the user".&lt;/p&gt;
&lt;h2 id="mitigating-the-risk"&gt;Mitigating the risk&lt;/h2&gt;
&lt;p&gt;When evaluating a library for production usage, we ask a few questions to
understand the need for the library in the first place.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Can we build this functionality in-house?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In some cases we were able to simply copy/paste the parts of a library we really
needed. In more complex scenarios, where a library talked to a custom backend we
reverse-engineered that API and built a mini-SDK ourselves (again, only the
parts we needed). This is the preferred option 90% of the time, but isn't always
feasible when integrating with very specific vendors or requirements.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How many customers benefit from this library?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In one scenario, we were considering adding a very risky library (according to
the criteria below) intended for a tiny subset of users while still exposing all
of our users to the library. We ran the risk of something going wrong for all
our customers in all our markets for a small group of customers we thought would
benefit from it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What transitive dependencies does this library have?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We'll want to evaluate the criteria below for all dependencies of the library as
well.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What are the exit criteria?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If integration is successful, is there a path to moving it in-house? If it isn't
successful, is there a path to removal?&lt;/p&gt;
&lt;h2 id="evaluation-criteria"&gt;Evaluation criteria&lt;/h2&gt;
&lt;p&gt;If at this point the team still wants to integrate the library, we ask them to
“score” the library according to a standard set of criteria. The list below is
not comprehensive but should give a good indication of the things we look at.&lt;/p&gt;
&lt;h3 id="blocking-criteria"&gt;Blocking criteria&lt;/h3&gt;
&lt;p&gt;These criteria will prevent us from including the library altogether, either
technically or by company policy, and need to be resolved before we can move
forward:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Too high deployment target/target SDKs&lt;/strong&gt;. We support major OSes going back
  at least 4 years so third-party libraries have to support at least that many
  too.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Improper/missing LICENSE&lt;/strong&gt;. We bundle license files with the apps to make
  sure we can legally use the code and attribute it to the license holders.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No conflicting transitive dependencies&lt;/strong&gt;. A library can't have a transitive
  dependency we already include but at a different version.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Does not display its own UI&lt;/strong&gt;. We put great care in making our products look
  as uniform as possible, custom UIs are a detriment to that.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Does not use private APIs&lt;/strong&gt;. We are not willing to risk app rejection over
  the use of private APIs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="major-concerns"&gt;Major concerns&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Closed source&lt;/strong&gt;. Access to the source means we can pick and choose which
  parts of a library we want to include and how to bundle that source with the
  rest of the app. A closed source, binary distribution is much more difficult
  for us to integrate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Builds with warnings&lt;/strong&gt;. We have "warnings as errors" enabled, and a library
  with build warnings is a decent indication of the overall quality of the
  library.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Poor documentation&lt;/strong&gt;. We look for high quality inline docstrings, external
  "how to use" documentation, and meaningful change logs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Binary size impact&lt;/strong&gt;. How big is this library? Some libraries provide a lot
  of functionality of which we only need a fraction. Especially without source
  access it's often an all-or-nothing situation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;External network traffic&lt;/strong&gt;. A library that communicates with upstream
  servers/endpoints we have no control over could take the entire app down when
  the server is down, bad data is sent back, etc. This also has the same privacy
  implications I mentioned above.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Technical support&lt;/strong&gt;. When things don't work as they should we need to be
  able to report/escalate issues and get them fixed in a reasonable amount of
  time. Open source projects are often run by volunteers and usually can't
  commit to timelines, but at least we could make changes ourselves. This is
  impossible without source access.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inability to disable&lt;/strong&gt;. While most libraries specifically require us to
  initialize it, some are more "proactive" in their instantiation and will
  perform work themselves without us ever calling into it. This means when the
  library causes issues we have no option to turn it off through feature flags
  or other mechanisms.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We assign point values to all these (and a few others) criteria and ask
engineers to tally those up for the library they want to include. While low
scores aren't hard-rejected by default, we often ask for more justification to
move forward.&lt;/p&gt;
&lt;h2 id="final-notes"&gt;Final notes&lt;/h2&gt;
&lt;p&gt;While this process may seem very strict and the potential risk hypothetical in
many cases, we have actual, real examples of every scenario I described in this
blog post. Having the evaluations written down and publicly available also helps
in conveying relative risk to people unfamiliar with how mobile platforms works
and demonstrating we're not arbitrarily evaluating the risks.&lt;/p&gt;
&lt;p&gt;Also, I don't want to claim every third-party library is inherently bad. We
actually use quite a few at Lyft: RxSwift and RxJava, Bugsnag's SDK, Google
Maps, Tensorflow, and a few smaller ones for very specific use cases. But all of
these are either well-vetted, or we've decided the risk is worth the benefit
while actually having a clear idea of what those risks and benefits really are.&lt;/p&gt;
&lt;p&gt;Lastly, as a developer pro-tip: always create your own abstractions on top of
the library's APIs and never call their APIs directly. This makes it much easier
to swap (or remove) underlying libraries in the future, again mitigating some
risk associated with long-term development.&lt;/p&gt;
&lt;div class="admonition update"&gt;
&lt;p class="admonition-title"&gt;Update (February 2026)&lt;/p&gt;
&lt;p&gt;The industry has changed a ton over the years but this topic is still very
much relevant. Many vendors have decided to write their own SDKs which are
difficult to avoid, and I don't think writing an in-house SDK for a
third-party vendor really caught on. The growth of Apple's platforms
(hardware capabilities, OSes, Swift and Xcode versions, etc.) and associated
complexity make some of these SDKs also inherently more complex and buggier.&lt;/p&gt;
&lt;p&gt;Fortunately, LLMs have shifted the buy vs build decision more in the
direction of build, and Apple's investment in modern/capable APIs and
privacy manifests made it easier to opt for them than introducing another
dependency. More tooling is available to monitor dependencies and keeping
them up to date.&lt;/p&gt;
&lt;p&gt;I don't think companies have become more thoughtful about the risks they
take on with third-party dependencies, but platform, tooling, and workflow
improvements at least make it a little bit easier to choose which vendor (be
it first, second, or third-party) suits them best.&lt;/p&gt;
&lt;/div&gt;</content><category term="blog"/></entry><entry><title>iOS Architecture at Lyft</title><link href="https://scottberrevoets.com/2021/10/14/ios-architecture-at-lyft/" rel="alternate"/><published>2021-10-14T00:00:00-07:00</published><updated>2021-10-14T00:00:00-07:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2021-10-14:/2021/10/14/ios-architecture-at-lyft/</id><summary type="html">&lt;p&gt;June 30, 2014 was my first day at Lyft as the first iOS hire on the ~3 person
team. The app was written in Objective-C, and the architecture was a 5000-line
nested switch statement.&lt;/p&gt;
&lt;p&gt;Since then, the team has grown to about 70 people and the codebase to 1.5M …&lt;/p&gt;</summary><content type="html">&lt;p&gt;June 30, 2014 was my first day at Lyft as the first iOS hire on the ~3 person
team. The app was written in Objective-C, and the architecture was a 5000-line
nested switch statement.&lt;/p&gt;
&lt;p&gt;Since then, the team has grown to about 70 people and the codebase to 1.5M lines
of code. This required some major changes to how we architect our code, and
since it had been a while since we've given an update like this, now seems as
good a time as any.&lt;/p&gt;
&lt;h2 id="requirements"&gt;Requirements&lt;/h2&gt;
&lt;p&gt;The effort to overhaul and modernize the architecture began around mid-2017.
We started to reach the limits of the patterns we established in the 2015
rewrite of the app, and it was clear the codebase and the team would continue to
grow and probably more rapidly than it had in the past.&lt;/p&gt;
&lt;p&gt;The primary problems that the lack of a more mature architecture presented and
that we wanted to solve were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Isolation&lt;/strong&gt;: Features were heavily intertwined, which made it difficult to
  safely make changes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Testability&lt;/strong&gt;: We were still mostly following MVC with view controllers being
  the main source of business logic which made it difficult to test that logic&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;State management&lt;/strong&gt;: The navigation in most of our app relied on local +
  server state, which grew with the number of features. How and when state
  changed then became too difficult to manage.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There was not going to be one solution that would solve all of this inherently,
but over the course of a few years we developed a number of processes and
technical solutions to reduce these problems.&lt;/p&gt;
&lt;h2 id="modules"&gt;Modules&lt;/h2&gt;
&lt;p&gt;First, to provide better feature separation, we introduced modules. Every
feature had its own module, with its own test suite, that could be developed in
isolation from other modules. This forced us to think more about public APIs and
hiding implementation details behind them. Compile times improved, and it
required much less collaboration with other teams to make changes.&lt;/p&gt;
&lt;p&gt;We also introduced an ownership model that ensured each module has at least one
team that's responsible for that module's tech debt, documentation, etc.&lt;/p&gt;
&lt;h3 id="module-types"&gt;Module types&lt;/h3&gt;
&lt;p&gt;After fully modularizing the app and having 700 modules worth of code, we took
this a step further and introduced a number of &lt;em&gt;module types&lt;/em&gt; that each module
would follow.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UI&lt;/code&gt; modules only contain UI elements (views, view controllers)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Flow&lt;/code&gt; modules contain &lt;a href="#flows"&gt;routing infrastructure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Service&lt;/code&gt; modules contain code to interact with endpoints related to the
  feature's functionality&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Logic&lt;/code&gt; modules contain pure business logic, data transformations, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Breaking modules down this way enabled us to implement dependency validators: we
can validate that certain modules can't depend on others. For example, a logic
module can't depend on a &lt;code&gt;UI&lt;/code&gt; module, and a &lt;code&gt;Service&lt;/code&gt; module can't import UIKit.&lt;/p&gt;
&lt;p&gt;This module structure also prevents complicated circular dependencies,
e.g. a &lt;code&gt;Coupons&lt;/code&gt; module depending on &lt;code&gt;Payments&lt;/code&gt; and vice versa. Instead, the
&lt;code&gt;Payments&lt;/code&gt; module can now import &lt;code&gt;CouponsUI&lt;/code&gt; without needing to import the full
&lt;code&gt;Coupons&lt;/code&gt; feature. It's led to micromodules in some areas, but we've generally
been able to provide good tooling to make this easier to deal with.&lt;/p&gt;
&lt;p&gt;All in all we now have almost 2000 modules total for all Lyft apps.&lt;/p&gt;
&lt;h2 id="dependency-injection"&gt;Dependency Injection&lt;/h2&gt;
&lt;p&gt;Module types solved many of our dependency tree problems at the module level,
but we also needed something more scalable than singletons at the code level.&lt;/p&gt;
&lt;p&gt;For that we've built a lightweight dependency injection framework which we
detailed in a &lt;a href="https://www.youtube.com/watch?v=dA9rGQRwHGs"&gt;SLUG talk&lt;/a&gt;. It resembles a service locator pattern, with a
basic dictionary mapping protocols to instantiations:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;getNetworkCommunicator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;NetworkCommunicating&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NetworkCommunicating&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;NetworkCommunicator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The implementation of &lt;code&gt;bind()&lt;/code&gt; doesn't immediately return &lt;code&gt;NetworkCommunicator&lt;/code&gt;,
but requires the object be mocked if we're in a testing environment:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;productionInstantiators&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;ObjectIdentifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[:]&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;mockedInstantiators&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;ObjectIdentifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[:]&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="kd"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;instantiator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;identifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;ObjectIdentifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;NSClassFromString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;XCTestCase&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;productionInstantiators&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="n"&gt;instantiator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;mockedInstantiators&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In tests, the mock is required or the test will crash:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kr"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NetworkingTests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;XCTestCase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;communicator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MockNetworkCommunicator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;testNetworkCommunications&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NetworkCommunicating&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;communicator&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This brings two benefits:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It forces developers to mock objects in tests, avoiding production side
   effects like making network requests&lt;/li&gt;
&lt;li&gt;It provided a gradual adoption path rather than updating the entire app at
   once through some more advanced system&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Although this framework has some of the same problems as other &lt;a href="https://en.wikipedia.org/wiki/Service_locator_pattern"&gt;Service
Locator&lt;/a&gt; implementations, it works well enough for us and the limitations are
generally acceptable.&lt;/p&gt;
&lt;h2 id="flows"&gt;Flows&lt;/h2&gt;
&lt;p&gt;Flows, inspired by Square's &lt;a href="https://square.github.io/workflow/"&gt;Workflow&lt;/a&gt;, are the backbone of all Lyft apps.
Flows define the navigation rules around a number of related screens the user
can navigate to. The term &lt;code&gt;flow&lt;/code&gt; was already common in everyday communications
("after finishing the in-ride flow we present the user with the payments flow")
so this terminology mapped nicely to familiar terminology.&lt;/p&gt;
&lt;p&gt;Flows rely on state-driven routers that can either show a screen, or route to
other routers that driven by different state. This makes them easy to compose,
which promoted the goal of feature isolation.&lt;/p&gt;
&lt;p&gt;At the core of flows lies the &lt;code&gt;Routable&lt;/code&gt; protocol:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;protocol&lt;/span&gt; &lt;span class="nc"&gt;Routable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;viewController&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;UIViewController&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It just has to be able to produce a view controller. The (simplified) router
part of a flow is implemented like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kr"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;routable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Routable&lt;/span&gt;&lt;span class="p"&gt;?)]&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;addRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;routable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Routable&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;escaping&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;routable&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Routable&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="bp"&gt;first&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In other words: it takes a bunch of rules where if the condition is true
(accepting the flow's state as input) it provides a &lt;code&gt;Routable&lt;/code&gt;. Each flow defines
its own possible routes and matches those to a &lt;code&gt;Routable&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;OnboardingState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;phoneNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;verificationCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OnboardingFlow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;router&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OnboardingState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OnboardingState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addRoute&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;phoneNumber&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;EnterPhoneNumberViewController&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addRoute&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verificationCode&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;VerifyPhoneViewController&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addRoute&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;EnterEmailViewController&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

        &lt;span class="c1"&gt;// If all login details are provided, return  `nil` to indicate this flow has&lt;/span&gt;
        &lt;span class="c1"&gt;// no (other) Routable to provide and should be exited&lt;/span&gt;
        &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addRoute&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;currentRoutable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Routable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We're then composing flows by adding &lt;code&gt;Routable&lt;/code&gt; conformance to each flow and
have it provide a view controller, adding its current &lt;code&gt;Routable&lt;/code&gt;s view
controller as a child:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="nc"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Routable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;rootViewController&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;UIViewController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;parent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;UIViewController&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currentRoutable&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;viewController&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;parent&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now a flow can also route to another flow by adding an entry to its router:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addRoute&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;needsOnboarding&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;OnboardingFlow&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This pattern could let you build entire trees of flows:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Simplified flow diagram" src="/images/flows.png"&gt;&lt;/p&gt;
&lt;p&gt;When we first conceptualized flows we imagined having a tree of about 20 flows
total; today we have more than 80. Flows have become the "unit of development"
of our apps: developers no longer need to care about the full application or a
single module, but can build an ad-hoc app with just the flow they're working
on.&lt;/p&gt;
&lt;h2 id="plugins"&gt;Plugins&lt;/h2&gt;
&lt;p&gt;Although flows simplify state management and navigation, the logic of the
individual screens within a flow could still be very intertwined. To mitigate
that problem, we've introduced plugins. Plugins allow for attaching
functionality to a flow without the flow even knowing that the plugin exists.&lt;/p&gt;
&lt;p&gt;For example, to add more screens to the OnboardingFlow from above, we can expose
a method on it that would call into its router:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="nc"&gt;OnboardingFlow&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;addRoutingPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;routable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Routable&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt;
        &lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;escaping&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OnboardingState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addRoute&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;routable&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Since this method is public, any plugin that imports it can add a new screen. The
flow doesn't know anything about this plugin, so the entire dependency tree is
inverted with plugins. Instead of a flow depending on all the functionalities of
all of its plugins, it provides a simple interface that lets plugins extend this
functionality in isolation by having them depend on the flow.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Simplified plugin setup" src="/images/plugins.png"&gt;&lt;/p&gt;
&lt;p&gt;Since all Lyft apps operate on a tree of flows, the overall dependency graph
changes from a tree shape to a "bubble" shape:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Bubble dependency graph" src="/images/graph.png"&gt;&lt;/p&gt;
&lt;p&gt;This setup provides feature isolation at the compiler level which makes it much
harder to accidentally intertwine features. Each plugin also has its own feature
flag, making it very easy to disable a feature if necessary.&lt;/p&gt;
&lt;p&gt;In addition to routing plugins, we also provide interfaces to add additional
views to any view controller, deep link plugins to deep link to any arbitrary
part of the app, list plugins to build lists with custom content, and a few
others very unique to Lyft's use cases.&lt;/p&gt;
&lt;h2 id="unidirectional-data-flow"&gt;Unidirectional Data Flow&lt;/h2&gt;
&lt;p&gt;More recently we introduced a redux-like unidirectional data flow (UDF) for
screens and views within flows. Flows were optimized for state management within
a collection of screens, the UDF brings the same benefits we saw there to
individual screens.&lt;/p&gt;
&lt;p&gt;A typical redux implementation has state flowing into the UI and actions that
modify state coming out of the UI. Influenced by &lt;a href="https://github.com/pointfreeco/swift-composable-architecture"&gt;The Composable
Architecture&lt;/a&gt;, our implementation of redux actions also includes executing
side effects to interact with the environment (network, disk, notifications,
etc.).&lt;/p&gt;
&lt;h2 id="declarative-ui"&gt;Declarative UI&lt;/h2&gt;
&lt;p&gt;In 2018, we began building out our &lt;a href="https://design.lyft.com/building-a-design-system-library-3a1f0d09088f"&gt;Design System&lt;/a&gt;. At the time, it was a
layer on top of UIKit, often with a slightly modernized API, that would provide
UI elements with common defaults like fonts, colors, icons, dimensions, etc.&lt;/p&gt;
&lt;p&gt;When Apple introduced SwiftUI in mid-2019, it required a deployment target of
iOS 13. At the time, we still supported iOS 10 and even today we still support
iOS 12 so we still can't use it.&lt;/p&gt;
&lt;p&gt;However, we did write an internal library called &lt;code&gt;DeclarativeUI&lt;/code&gt;, which provides
the same declarative APIs that SwiftUI brings but leveraging the Design System
we had already built. Even better, we've built &lt;code&gt;binding&lt;/code&gt; conveniences into both
DeclarativeUI and our UDF &lt;code&gt;Store&lt;/code&gt; types to make them work together seamlessly: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="nc"&gt;DeclarativeUI&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="nc"&gt;Unidirectional&lt;/span&gt;

&lt;span class="kr"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;QuestionView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DeclarativeUI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;viewStore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;QuestionState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="kd"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;QuestionState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;viewStore&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DeclarativeUI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;VStackView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;three&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;HeaderView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;viewStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;textStyle&lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="n"&gt;titleF1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;textAlignment&lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="n"&gt;center&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lineLimit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accessibility&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;postScreenChanged&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;viewStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;VStackView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;two&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;choice&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
                &lt;span class="n"&gt;TwoChoiceButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;onEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;touchUpInside&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;viewStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="n"&gt;choiseSelected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="bp"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;viewStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currentState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usesButtonToIncrementQuestion&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;NextQuestionButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="bp"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="putting-it-all-together"&gt;Putting it all together&lt;/h2&gt;
&lt;p&gt;All these technologies combined make for a completely different developer
experience now than five years ago. Doing the right thing is easy, doing the
wrong thing is difficult. Features are isolated from each other, and even
feature components are separated from each other in different modules.&lt;/p&gt;
&lt;p&gt;Testing was never easier: unit tests for modules with pure business logic,
snapshot tests for UI modules, and for integration tests it takes little effort
to sping up a standalone app with just the flow you're interested in.&lt;/p&gt;
&lt;p&gt;State is easy to track with debug conveniences built into the architectures,
building UI is more enjoyable than it was with plain UIKit, and adding a feature
from 1 app into another is often just a matter of attaching the plugin to a
second flow without detangling it from all other features on that screen.&lt;/p&gt;
&lt;p&gt;It's amazing to look back at where the codebase started some 6 years ago, and
where it is now. Who knows where it will be in another 6 years!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: If you're interested in hearing more, I also talked about many of these
technologies on the &lt;a href="https://podcasts.apple.com/us/podcast/mobile-architecture-pt-1-with-scott-berrevoets/id1453587931?i=1000512549072"&gt;Lyft Mobile Podcast&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Re-binding self: the debugger's break(ing) point</title><link href="https://scottberrevoets.com/2018/08/08/re-binding-self-the-debuggers-breaking-point/" rel="alternate"/><published>2018-08-08T00:00:00-07:00</published><updated>2018-08-08T00:00:00-07:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2018-08-08:/2018/08/08/re-binding-self-the-debuggers-breaking-point/</id><summary type="html">&lt;p&gt;&lt;strong&gt;Update 07-29-2019: The bug described below is fixed in Xcode 11 so this blog
post has become irrelevant. I'm leaving it up for historical purposes.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For the Objective-C veterans in the audience, the strong-self-weak-self dance is
a practice mastered early on and one that is used very frequently. There are …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Update 07-29-2019: The bug described below is fixed in Xcode 11 so this blog
post has become irrelevant. I'm leaving it up for historical purposes.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For the Objective-C veterans in the audience, the strong-self-weak-self dance is
a practice mastered early on and one that is used very frequently. There are a
lot of different incantations, but the most basic one goes something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;__weak&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;weakSelf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;dispatch_group_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dispatch_get_main_queue&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;weakSelf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then, if you needed a strong reference to &lt;code&gt;self&lt;/code&gt; again inside the block, you'd
change it to this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;__weak&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;weakSelf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;dispatch_group_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dispatch_get_main_queue&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weakSelf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;strongSelf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;weakSelf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;strongSelf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;someOtherObject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;doSomethingWith&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;strongSelf&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Fortunately, this was much easier on day 1 of Swift when using the &lt;code&gt;[weak self]&lt;/code&gt;
directive:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;weak&lt;/span&gt; &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;strongSelf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;strongSelf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;someOtherObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;strongSelf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;self&lt;/code&gt; is now &lt;code&gt;weak&lt;/code&gt; inside the closure, making it an optional. Unwrapping it
into &lt;code&gt;strongSelf&lt;/code&gt; makes it a non-optional while still avoiding a retain cycle.
It doesn't feel very Swifty, but it's not terrible.&lt;/p&gt;
&lt;p&gt;More recently, it's become known that Swift supports re-binding &lt;code&gt;self&lt;/code&gt; if you
wrap it in backticks. That makes for an arguably much nicer syntax:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;weak&lt;/span&gt; &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="p"&gt;`&lt;/span&gt;&lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;`&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;self&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;someOtherObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This was long considered, and &lt;a href="https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160118/007425.html"&gt;confirmed&lt;/a&gt; to be, a hack that worked due to a bug in the compiler, but
since it worked and there weren't plans to remove it, people (including us at
Lyft) started treating it as a feature.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;However, there is one big caveat&lt;/strong&gt;: the debugger is entirely hosed for
anything you do in that closure. Ever seen an error like this in your Xcode
console?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;warning&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EXPR&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;:&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;warning&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;initialization&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;variable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;$__lldb_error_result&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;was&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;never&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;used&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;consider&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;replacing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;assignment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;_&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;removing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;$__lldb_error_result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__lldb_tmp_error&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;~~~~^~~~~~~~~~~~~~~~~~~~&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That's because &lt;code&gt;self&lt;/code&gt; was re-bound. This is easy to reproduce: create a new
Xcode project and add the following snippet to &lt;code&gt;viewDidLoad()&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;weak&lt;/span&gt; &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="p"&gt;`&lt;/span&gt;&lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;`&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;self&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;
    &lt;span class="bp"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// set a breakpoint here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;When the breakpoint hits, execute &lt;code&gt;(lldb) po description&lt;/code&gt; and you'll see the
error from above. Note that you're not even &lt;em&gt;using&lt;/em&gt; &lt;code&gt;self&lt;/code&gt; - merely re-binding
it makes the debugger entirely useless inside that scope.&lt;/p&gt;
&lt;p&gt;People with way more knowledge of LLDB than I do can explain this in more detail
(and &lt;a href="https://bugs.swift.org/browse/SR-6156"&gt;have&lt;/a&gt;), but the gist is that the
debugger doesn't like &lt;code&gt;self&lt;/code&gt;'s type changing. At the beginning of the closure
scope, the debugging context assumes that &lt;code&gt;self&lt;/code&gt;'s type is &lt;code&gt;Optional&lt;/code&gt;, but it is
then re-bound to a non-optional, which the debugger doesn't know how to handle.
It's actually pretty surprising the compiler supports changing a variable's type
at all.&lt;/p&gt;
&lt;p&gt;Because of this problem, at Lyft we have decided to eliminate this pattern
entirely in our codebases, and instead re-bind &lt;code&gt;self&lt;/code&gt; to a variable named
&lt;code&gt;this&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you do continue to use this pattern, note that in a
&lt;a href="https://forums.swift.org/t/the-future-of-weak-self-rebinding/10846"&gt;discussion&lt;/a&gt;
on the Swift forums many people agreed that re-binding &lt;code&gt;self&lt;/code&gt; should be
supported by the language without the need for backticks. The &lt;a href="https://github.com/apple/swift/pull/15306"&gt;pull
request&lt;/a&gt; was merged shortly after and
with the release of Swift 4.2 in the fall, you'll be able to use &lt;code&gt;guard let self
= self else { return }&lt;/code&gt; (at the cost of losing debugging capabilities!)&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Using Interface Builder at Lyft</title><link href="https://scottberrevoets.com/2017/03/06/using-interface-builder-at-lyft/" rel="alternate"/><published>2017-03-06T00:00:00-08:00</published><updated>2017-03-06T00:00:00-08:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2017-03-06:/2017/03/06/using-interface-builder-at-lyft/</id><summary type="html">&lt;p&gt;Last week people realized that Xcode 8.3 by default uses storyboards in new
projects without a checkbox to turn this off. This of course sparked the
Interface Builder vs. programmatic UI discussion again, so I wanted to give some
insight in our experience using Interface Builder in building the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Last week people realized that Xcode 8.3 by default uses storyboards in new
projects without a checkbox to turn this off. This of course sparked the
Interface Builder vs. programmatic UI discussion again, so I wanted to give some
insight in our experience using Interface Builder in building the Lyft app. This
is not intended as hard "you should also use Interface Builder" advice, but
rather to show that IB &lt;em&gt;can&lt;/em&gt; work at a larger scale.&lt;/p&gt;
&lt;p&gt;First, some stats about the Lyft app:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;40 storyboards&lt;/li&gt;
&lt;li&gt;100 XIB files&lt;/li&gt;
&lt;li&gt;150 view controllers&lt;/li&gt;
&lt;li&gt;20 people on the iOS team with occasional outside contributors&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With the
&lt;a href="https://www.skilled.io/u/keithsmiley/tales-of-a-rewrite-at-lyft"&gt;rewrite&lt;/a&gt; of
our app we moved to using IB for about 95% of our UI.&lt;/p&gt;
&lt;p&gt;The #1 complaint about using Interface Builder for a project with more than 1
developer is that it's impossible to resolve merge conflicts. &lt;strong&gt;We never have
this problem.&lt;/strong&gt; Everybody on the team can attest that they have never run into
major conflicts they couldn't reasonably resolve.&lt;/p&gt;
&lt;p&gt;With that concern out of the way, what about some of the other common criticisms
Interface Builder regularly gets?&lt;/p&gt;
&lt;h2 id="improving-the-workflow"&gt;Improving the workflow&lt;/h2&gt;
&lt;p&gt;Out of the box, IB has a number of shortcomings that could make working with it
more painful than it needs to be. For example, referencing IB objects from code
still can only be done with string identifiers. There is also no easy way to
embed custom views (designed in IB) in other custom views.&lt;/p&gt;
&lt;p&gt;Over time we have improved the workflow for our developers to mitigate some of
these shortcomings, either by writing some tools or by writing a little bit of
code that can be used project-wide.&lt;/p&gt;
&lt;h3 id="storyboarder-script"&gt;storyboarder script&lt;/h3&gt;
&lt;p&gt;To solve the issue of stringly-typed view controller identifiers, we wrote a
script that, just before compiling the app, generates a struct with static
properties that exposes all view controllers from the app in a strongly-typed
manner. This means that now we can instantiate a view controller in code like
this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;viewController&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Onboarding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SignUp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instantiate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Not only is &lt;code&gt;viewController&lt;/code&gt; now guaranteed to be there at runtime (if something
is wrong in the setup of IB the code won't even compile), but it's also
recognized as a &lt;code&gt;SignUpViewController&lt;/code&gt; and not a generic &lt;code&gt;UIViewController&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="strongly-typed-segues"&gt;Strongly-typed segues&lt;/h3&gt;
&lt;p&gt;All our view controllers have a base view controller named &lt;code&gt;ViewController&lt;/code&gt;.
This base controller implements &lt;code&gt;prepare(for:sender:)&lt;/code&gt; like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;open&lt;/span&gt; &lt;span class="kr"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;segue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;UIStoryboardSegue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;identifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;segue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identifier&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;segueName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;firstLetterUpperString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;selector&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;prepareFor&lt;/span&gt;&lt;span class="si"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;segueName&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s"&gt;:sender:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;responds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;segue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;destination&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This means that a view controller that has a segue to
&lt;code&gt;TermsOfServiceViewController&lt;/code&gt; can now do this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kr"&gt;@objc&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;prepareForTermsOfService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="n"&gt;viewController&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TermsOfServiceViewController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;viewController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;onTermsAccepted&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;weak&lt;/span&gt; &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;proceed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We no longer have to implement &lt;code&gt;prepareForSegue&lt;/code&gt; and then &lt;code&gt;switch&lt;/code&gt; on the
segue's identifier or destination controller, but we can implement a separate
method for every segue from this view controller instead which makes the code
much more readable.&lt;/p&gt;
&lt;h3 id="nibview"&gt;&lt;code&gt;NibView&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;We wrote a &lt;code&gt;NibView&lt;/code&gt; class to make it more convenient to embed custom views in
other views from IB. We marked this class with &lt;code&gt;@IBDesignable&lt;/code&gt; so that it knows
to render itself in IB. All we have to do is drag out a regular &lt;code&gt;UIView&lt;/code&gt; from
the object library and change its class. If there is a XIB with the same name as
the class, &lt;code&gt;NibView&lt;/code&gt; will automatically instantiate it and render it in the
canvas at design time and on screen at runtime.&lt;/p&gt;
&lt;p&gt;Every standalone view we design in IB (which effectively means every view in our
app) inherits from &lt;code&gt;NibView&lt;/code&gt; so we can have an "unlimited" number of nested
views show up and see the final result.&lt;/p&gt;
&lt;h3 id="basic-ibdesignables"&gt;Basic &lt;code&gt;@IBDesignable&lt;/code&gt;s&lt;/h3&gt;
&lt;p&gt;Since a lot of our views have corner radii and borders, we have created this
&lt;code&gt;UIView&lt;/code&gt; extension:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="bp"&gt;UIView&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kr"&gt;@IBInspectable&lt;/span&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;cornerRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CGFloat&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kr"&gt;get&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cornerRadius&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kr"&gt;set&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cornerRadius&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newValue&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kr"&gt;@IBInspectable&lt;/span&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;borderWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CGFloat&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kr"&gt;get&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;borderWidth&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kr"&gt;set&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;borderWidth&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newValue&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kr"&gt;@IBInspectable&lt;/span&gt; &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;borderColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;UIColor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kr"&gt;get&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;UIColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cgColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;borderColor&lt;/span&gt;&lt;span class="p"&gt;!)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kr"&gt;set&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;borderColor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cgColor&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This lets us easily set these properties on &lt;em&gt;any&lt;/em&gt; view (including the ones from
UIKit) from Interface Builder.&lt;/p&gt;
&lt;h3 id="linter"&gt;Linter&lt;/h3&gt;
&lt;p&gt;We wrote a linter to make sure views are not misplaced, have
accessibility labels, trait variations are disabled (since we only officially
support portrait mode on iPhone), etc.&lt;/p&gt;
&lt;h3 id="ibunfuck"&gt;ibunfuck&lt;/h3&gt;
&lt;p&gt;A &lt;a href="https://forums.developer.apple.com/thread/8116"&gt;bug&lt;/a&gt; impacting developers
that use Interface Builder on both Retina and non-Retina screens (which at Lyft
is every developer) has caused us enough grief to write
&lt;a href="https://github.com/Reflejo/ib-unfuck-git"&gt;&lt;code&gt;ibunfuck&lt;/code&gt;&lt;/a&gt; - a tool to remove
unwanted changes from IB files.&lt;/p&gt;
&lt;h3 id="color-palette"&gt;Color palette&lt;/h3&gt;
&lt;p&gt;We created a custom color palette with the commonly used colors in our app so
it's easy to select these colors when building a new UI. The color names in the
palette follow the same names designers use when they give us new designs, so
it's easy to refer to and use without having to copy RGB or hex values.&lt;/p&gt;
&lt;h2 id="our-approach"&gt;Our approach&lt;/h2&gt;
&lt;p&gt;In addition to these tools and project-level improvements, we have a number of
"rules" around our use of IB to keep things sane:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Do as little as possible in code, do as much as possible in IB. That means
  fonts, colors, images, and other properties should not be set in code if they
  don't change at runtime.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;@IBInspectable&lt;/code&gt; on custom views whenever possible.&lt;/li&gt;
&lt;li&gt;Create as much Auto Layout constraints in IB as possible. Sometimes this means
  being clever with how we create constraints so that at runtime we need very
  little code to manipulate a layout.&lt;/li&gt;
&lt;li&gt;If we do need to create constraints in code, we don't use the
  &lt;code&gt;NSLayoutConstraint&lt;/code&gt; (or &lt;code&gt;NSLayoutAnchor&lt;/code&gt;) methods directly. Instead we use
  &lt;a href="https://github.com/SnapKit/SnapKit"&gt;SnapKit&lt;/a&gt;'s convenience methods.&lt;/li&gt;
&lt;li&gt;We still manually push and present view controllers where possible. Even
  though we have created some niceties around using segues, it's still easy to
  accidentally remove one and not find out about it until runtime.&lt;/li&gt;
&lt;li&gt;We tend to create a new storyboard when we recognize a common theme in related
  view controllers. Just as with code, we don't want bloated storyboards with
  all kinds of unrelated things in there. Most of our storyboards have fewer
  than 10 scenes.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://scottberrevoets.com/2016/03/21/outlets-strong-or-weak/"&gt;Our outlets are defined
  carefully.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of course, even with these improvements everything is not peaches and cream.
There are definitely still problems. New versions of Xcode often change the XML
representation which leads to a noisy diff. Some properties can simply not be
set in IB meaning we're forced to break our "do everything in IB" rule.
Interface Builder has bugs we can't always work around.&lt;/p&gt;
&lt;p&gt;However, with our improved infrastructure and the points from above, we are
happy with how IB works for us. We don't have to write tons of Auto Layout code
(which would be incredibly painful due to the nature of our UIs), get a visual
representation of how a view looks without having to run the app after every
minor change, and maybe one day we can get our designers make changes to our UI
without developers' help.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Keeping the Lyft iOS App Accessible</title><link href="https://scottberrevoets.com/2017/01/18/keeping-the-lyft-ios-app-accessible/" rel="alternate"/><published>2017-01-18T00:00:00-08:00</published><updated>2017-01-18T00:00:00-08:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2017-01-18:/2017/01/18/keeping-the-lyft-ios-app-accessible/</id><summary type="html">&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;This article was written by me but originally published on the &lt;a href="https://eng.lyft.com/keeping-lyft-accessible-53155f0098b9"&gt;Lyft
Engineering Blog&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;At Lyft we’re in a unique position: every day we work on an app that affects
people in the real, physical world — not just the digital one. This has an
especially large impact on …&lt;/p&gt;</summary><content type="html">&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Note&lt;/p&gt;
&lt;p&gt;This article was written by me but originally published on the &lt;a href="https://eng.lyft.com/keeping-lyft-accessible-53155f0098b9"&gt;Lyft
Engineering Blog&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;At Lyft we’re in a unique position: every day we work on an app that affects
people in the real, physical world — not just the digital one. This has an
especially large impact on visually impaired users, because our app is
easier to use than the real world alternatives — navigating a train station
or queueing up at a taxi stand. Lyft aims to make a product that is
accessible and easy to use for all of our users.&lt;/p&gt;
&lt;h2 id="accessibility-at-lyft"&gt;Accessibility at Lyft&lt;/h2&gt;
&lt;p&gt;If an app uses Apple’s standard controls and UI elements, it will likely already
work well with VoiceOver; however, Lyft’s UI deviates enough from standard
controls such that we don’t get that benefit out of the box. Another
complication is that many portions of the app are controlled by the server,
including a lot of the text for buttons and labels. Making these UI elements
compatible with VoiceOver often requires tweaking them manually, even if we use
native controls.&lt;/p&gt;
&lt;p&gt;Since we try to keep all of our UI-related code in Interface Builder, and
VoiceOver is just another form of UI, we have written a simple &lt;code&gt;UIView&lt;/code&gt;
extension that allows us to enable accessibility on most of our UI directly from
Interface Builder. In this extension, we add 2 properties to &lt;code&gt;UIView&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;accessibilitySources&lt;/code&gt;: An &lt;code&gt;IBOutletCollection&lt;/code&gt; of &lt;code&gt;UIView&lt;/code&gt;s&lt;/li&gt;
&lt;li&gt;&lt;code&gt;accessibilityFormat&lt;/code&gt;: a simple string that represents the format of the
  accessibility label, which is subsequently passed to &lt;code&gt;String(format:)&lt;/code&gt;. Every
  occurrence of &lt;code&gt;%@&lt;/code&gt; in this string will be replaced with the next element of
  the &lt;code&gt;accessibilitySources&lt;/code&gt;, and &lt;code&gt;[self]&lt;/code&gt; will automatically be replaced with
  the current view’s accessibilityLabel.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We can ctrl + drag UI elements into the &lt;code&gt;accessibilitySources&lt;/code&gt;, and by marking
&lt;code&gt;accessibilityFormat&lt;/code&gt; with &lt;code&gt;@IBInspectable&lt;/code&gt; we can specify the string and the
sources all from Interface Builder, keeping the code clean and to the point.&lt;/p&gt;
&lt;p&gt;For example, we have an accessibility format on the UILabel that displays the
selected ride mode:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Setting the accessibility format" src="/images/AccessibilityFormat.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;[self]&lt;/code&gt; is replaced by the accessibility label, which for &lt;code&gt;UILabel&lt;/code&gt;s is the
text of the label itself. &lt;code&gt;%@&lt;/code&gt; is replaced by an accessibility source, which we
can set up like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Setting the accessibility sources" src="/images/AccessibilitySource.png"&gt;&lt;/p&gt;
&lt;p&gt;By disabling the accessibility label for the subtitle, the top label’s
accessibility label will read “Selected ride mode: Line. Carpool, 2 people
maximum.” without touching any code at all.&lt;/p&gt;
&lt;h2 id="changing-the-process"&gt;Changing the process&lt;/h2&gt;
&lt;p&gt;As we ramped up our VoiceOver efforts, we wanted to assess whether the changes
we made had a meaningful impact to end-users. As developers, we’re too familiar
with our own apps to make an honest call about their usability, and when it
comes to visual impairment, even a standard usability review could brush over
things that would be challenging to a blind or a visually impaired user.&lt;/p&gt;
&lt;p&gt;This is why we’re working with a dedicated accessibility expert, himself a
native VoiceOver user, to constantly validate our work. All the feedback we’ve
been getting from VoiceOver users have further motivated our investment in
accessibility. We have optimized VoiceOver in the main flows of our app, and we
run weekly regression tests to ensure consistency and stability. Our VoiceOver
user also works directly with QA engineers, to let them know what he is looking
for and what is missing.&lt;/p&gt;
&lt;p&gt;Over the last few months, we have made many improvements in various parts of our
app, but also in how seriously we take VoiceOver-related bugs: we block new
releases if VoiceOver users experience bugs when going through a ride. All new
features are expected to be optimized for VoiceOver from the start, and we are
working hard to optimize existing features as well.&lt;/p&gt;
&lt;p&gt;We think of accessibility as part of the user interface, just like labels,
buttons, and text fields. By having our developers implement support for
VoiceOver as part of the initial feature, our visually impaired users will be
able to use these features as easily as our regular users.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Silencing NSLog</title><link href="https://scottberrevoets.com/2016/08/01/silencing-nslog/" rel="alternate"/><published>2016-08-01T00:00:00-07:00</published><updated>2016-08-01T00:00:00-07:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2016-08-01:/2016/08/01/silencing-nslog/</id><summary type="html">&lt;p&gt;When your app has a lot of third-party dependencies, what often happens is that
those libraries log a bunch of things to the Xcode console to help their own
debugging. Unfortunately, a lot of these logs are useful only to the developers
of the library, but not the developers of …&lt;/p&gt;</summary><content type="html">&lt;p&gt;When your app has a lot of third-party dependencies, what often happens is that
those libraries log a bunch of things to the Xcode console to help their own
debugging. Unfortunately, a lot of these logs are useful only to the developers
of the library, but not the developers of apps that integrate the library. For
example, they log things like &lt;code&gt;&amp;lt;SomeLibrary&amp;gt; (version 1.2.3) initialized&lt;/code&gt;, or
&lt;code&gt;&amp;lt;SomeLibrary&amp;gt; started &amp;lt;primary functionality&amp;gt;&lt;/code&gt;, sometimes with a long list of
parameters or input sources that are irrelevant to you.&lt;/p&gt;
&lt;p&gt;Finding your own log statements in a jungle of other logs can then be
very difficult and adds to the frustration of not being able to work the debugger
as you would like to.&lt;/p&gt;
&lt;p&gt;If a library is open source you can suggest a change by removing the log or
otherwise make it less obtrusive. However, if your change gets accepted at all,
that doesn't solve the immediate problem of being able to debug your own code
using the console.&lt;/p&gt;
&lt;p&gt;Meet &lt;code&gt;_NSSetLogCStringFunction()&lt;/code&gt;. This C function has been around in Foundation
for a long time, and while there is some
&lt;a href="https://support.apple.com/kb/TA45403?locale=en_US"&gt;documentation&lt;/a&gt; on it, it's
still a private method. However, that doesn't mean you can't use it in debug
mode when your logs are the most valuable!&lt;/p&gt;
&lt;p&gt;In short, this function lets you set a function pointer that can log C strings,
which &lt;code&gt;NSLog&lt;/code&gt; then uses instead of the normal implementation. You can do this
in two ways.&lt;/p&gt;
&lt;p&gt;The first one is by adding this to your Objective-C bridging header:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#if DEBUG&lt;/span&gt;
&lt;span class="k"&gt;extern&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_NSSetLogCStringFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;BOOL&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="cp"&gt;#endif&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and then use it like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;disableNSLog&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="cp"&gt;#if&lt;/span&gt; &lt;span class="cp"&gt;DEBUG&lt;/span&gt;
    &lt;span class="n"&gt;_NSSetLogCStringFunction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="c1"&gt;// no actual logging, message just gets lost&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="cp"&gt;#endif&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you want to stick to pure Swift, you can do so by adding this to your code
somewhere:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;_silgen_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_NSSetLogCStringFunction&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;setNSLogFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="n"&gt;convention&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;UnsafePointer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Int8&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="nb"&gt;UInt32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ObjCBool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and then use it like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;disableNSLog&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="cp"&gt;#if&lt;/span&gt; &lt;span class="cp"&gt;DEBUG&lt;/span&gt;
    &lt;span class="n"&gt;setNSLogFunction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="c1"&gt;// no actual logging, message just gets lost&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="cp"&gt;#endif&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Obviously, you can do anything you want inside the closure, including writing to
a file, annotating the message with a date/time, passing it to your custom
logging library, etc.&lt;/p&gt;
&lt;p&gt;One downside of this is that Apple's frameworks use &lt;code&gt;NSLog&lt;/code&gt; extensively as well,
so in the above case of completely disabling logging, helpful messages get lost
as well. You won't be able to use &lt;code&gt;NSLog&lt;/code&gt; yourself either anymore, so I suggest
you use &lt;code&gt;print()&lt;/code&gt; or a custom logging framework that's not &lt;code&gt;NSLog&lt;/code&gt; based.&lt;/p&gt;
&lt;p&gt;If you're not afraid of doing (more) horrible things in your codebase, you can
avoid losing Apple frameworks' messages by parsing the stack trace and looking
at the framework that called this function and see if it's something you want to
let through:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;disableNSLog&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="cp"&gt;#if&lt;/span&gt; &lt;span class="cp"&gt;DEBUG&lt;/span&gt;
    &lt;span class="n"&gt;_NSSetLogCStringFunction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="c1"&gt;// message is of type UnsafePointer&amp;lt;Int8&amp;gt; so first see if we can get a&lt;/span&gt;
        &lt;span class="c1"&gt;// normal String from that. Safety first!&lt;/span&gt;

        &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromCString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;callStack&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;NSThread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callStackSymbols&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;sourceString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;callStack&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;separatorSet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;NSCharacterSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;charactersInString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;quot; -[]+?.,&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;stackFrame&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sourceString&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;componentsSeparatedByCharactersInSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;separatorSet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;frameworkName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stackFrame&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;frameworkName&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;UIKit&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;frameworkName&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Foundation&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;MyCustomLogger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="cp"&gt;#endif&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This discards all logs, except if they're coming from &lt;code&gt;UIKit&lt;/code&gt; or &lt;code&gt;Foundation&lt;/code&gt;.
The stack trace parsing is by no means safe (its format could change, for
example), but since it's wrapped in &lt;code&gt;#if DEBUG&lt;/code&gt; directives it won't mess with
anything in the App Store build.&lt;/p&gt;
&lt;p&gt;Note that static libraries are part of your main app's target, which means you
have to filter out logs from your own target to hide those.&lt;/p&gt;
&lt;p&gt;You could even go a bit farther and check the message for keywords you like or
don't like and make a decision on whether you want to log or not. Keep in mind,
though, that any work you do here needs to be fast as you don't always know just
how much is being logged.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Outlets: strong! or weak?</title><link href="https://scottberrevoets.com/2016/03/21/outlets-strong-or-weak/" rel="alternate"/><published>2016-03-21T00:00:00-07:00</published><updated>2016-03-21T00:00:00-07:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2016-03-21:/2016/03/21/outlets-strong-or-weak/</id><summary type="html">&lt;p&gt;There are a lot of styles out there when it comes to using Interface Builder
outlets in Swift. Even Apple's documentation and sample code isn't always
consistent. The most common one, the one Apple uses in its sample code, follows
this pattern:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@IBOutlet private weak var someLabel: UILabel!&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Let's break …&lt;/p&gt;</summary><content type="html">&lt;p&gt;There are a lot of styles out there when it comes to using Interface Builder
outlets in Swift. Even Apple's documentation and sample code isn't always
consistent. The most common one, the one Apple uses in its sample code, follows
this pattern:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@IBOutlet private weak var someLabel: UILabel!&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Let's break this down by keyword:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@IBOutlet&lt;/code&gt; makes Interface Builder recognize the outlet&lt;/li&gt;
&lt;li&gt;&lt;code&gt;private&lt;/code&gt; ensures the outlet isn't accessed outside the current class&lt;/li&gt;
&lt;li&gt;&lt;code&gt;weak&lt;/code&gt; is used because in most situations the owner of the outlet isn't the same as the owner of the view. For example, a view controller doesn't own &lt;code&gt;someLabel&lt;/code&gt; - the view controller's &lt;code&gt;view&lt;/code&gt; does.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;var&lt;/code&gt; because outlets are, by definition, set &lt;em&gt;after&lt;/em&gt; initialization&lt;/li&gt;
&lt;li&gt;&lt;code&gt;someLabel: UILabel&lt;/code&gt; is just an example, this applies to outlets of any kind&lt;/li&gt;
&lt;li&gt;&lt;code&gt;!&lt;/code&gt;, or the implicitly unwrapped optional, is convenient because you don't have to litter your code with &lt;code&gt;?&lt;/code&gt; and &lt;code&gt;if let&lt;/code&gt;s (or just &lt;code&gt;!&lt;/code&gt;) everywhere&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While at first this seems like a solid approach, at Lyft we quickly realized we
weren't fans of this one-size-fits-all way of defining outlets. Instead, the
behavior and consequences of the different elements should define the outlet's
exact syntax, just like any other variable.&lt;/p&gt;
&lt;p&gt;For example, if there is a code path that removes an outlet from its superview,
or the outlet is (intentionally) not hooked up in the storyboard, it needs to be
an optional because the outlet is not guaranteed to be there when it's accessed.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@IBOutlet private var someLabel: UILabel?&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;If there is no code path that re-adds the outlet to the view hierarchy, it would
also be good to make it &lt;code&gt;weak&lt;/code&gt; to not hold on to it unnecessarily when it gets
removed:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@IBOutlet private weak var someLabel: UILabel?&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This ensures that if the label is removed from the superview, it's not being
kept in memory by the strong reference in the view controller. In the most
common case, where there is an outlet that will always be there, a strong,
implicitly unwrapped optional is appropriate:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@IBOutlet private var someLabel: UILabel!&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The outlet isn't &lt;code&gt;weak&lt;/code&gt; in case the code ever changes so that there &lt;em&gt;is&lt;/em&gt; a code
path that removes the view from the view hierarchy but you forget to update the
optionality of the property. The object will stay in memory and using it won't
crash your app.&lt;/p&gt;
&lt;p&gt;These examples all follow 3 simple rules:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;!&lt;/code&gt; needs a guarantee that the view exists, so always use &lt;code&gt;strong&lt;/code&gt; to provide
   that guarantee&lt;/li&gt;
&lt;li&gt;If it's possible the view isn't part of the view hierarchy, use &lt;code&gt;?&lt;/code&gt; and
   appropriate optional-handling (optional binding/chaining) for safety&lt;/li&gt;
&lt;li&gt;If you don't need a view anymore after removing it from the view hierarchy,
   use &lt;code&gt;weak&lt;/code&gt; so it gets removed from memory.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Applying these three rules means you properly use the optional semantics. After
all, using &lt;code&gt;!&lt;/code&gt; for a view that may not exist is no different than defining any
other property as an implicitly unwrapped optional that may not exist.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>@objc class prefixes fixed in Xcode 7 beta 4</title><link href="https://scottberrevoets.com/2015/07/23/objc-class-prefixes-fixed-in-xcode-7-beta-4/" rel="alternate"/><published>2015-07-23T00:00:00-07:00</published><updated>2015-07-23T00:00:00-07:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2015-07-23:/2015/07/23/objc-class-prefixes-fixed-in-xcode-7-beta-4/</id><summary type="html">&lt;p&gt;Back in December I &lt;a href="/2014/12/15/at-objc-creates-a-wrong-class-name-in-objective-c/"&gt;wrote&lt;/a&gt; about what I thought was a bug in the Swift compiler that would expose the wrong class name for a Swift class in Objective-C. I then later &lt;a href="https://devforums.apple.com/message/1072175#1072175"&gt;found out&lt;/a&gt; everything worked as intended and I had just misunderstood what &lt;code&gt;@objc()&lt;/code&gt; exactly did. Apparently it was …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Back in December I &lt;a href="/2014/12/15/at-objc-creates-a-wrong-class-name-in-objective-c/"&gt;wrote&lt;/a&gt; about what I thought was a bug in the Swift compiler that would expose the wrong class name for a Swift class in Objective-C. I then later &lt;a href="https://devforums.apple.com/message/1072175#1072175"&gt;found out&lt;/a&gt; everything worked as intended and I had just misunderstood what &lt;code&gt;@objc()&lt;/code&gt; exactly did. Apparently it was never supposed to modify the class name at compile time, but only at runtime.&lt;/p&gt;
&lt;p&gt;I'm sure changing the class name just at runtime has its uses, but in my opinion, this would be most helpful if it also affected the compile time name of Objective-C classes. It allows you to namespace your classes in Objective-C using your three-letter prefix, without needing that prefix in Swift because you could namespace by way of modules.&lt;/p&gt;
&lt;p&gt;And fortunately, in Xcode 7 beta 4, they have actually modified the &lt;code&gt;@objc()&lt;/code&gt; notation so that it &lt;em&gt;does&lt;/em&gt; do this. For example, if I have a Swift module that I want others to be able to use in their Objective-C codebase, I could write a class like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kr"&gt;@objc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SDCMyClass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="bp"&gt;MyClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;NSObject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And in &lt;code&gt;MyProject-Swift.h&lt;/code&gt; the Objective-C class is defined as:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;SWIFT_CLASS_NAMED&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;MyClass&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;@interface&lt;/span&gt; &lt;span class="nc"&gt;SDCMyClass&lt;/span&gt; : &lt;span class="bp"&gt;NSObject&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nonnull&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;instancetype&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OBJC_DESIGNATED_INITIALIZER&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In Swift I can simply use the class as &lt;code&gt;MyClass&lt;/code&gt;, but in Objective-C its name is &lt;code&gt;SDCMyClass&lt;/code&gt;, which ensures it doesn't collide with other classes named &lt;code&gt;MyClass&lt;/code&gt;. Needless to say, I'm very happy they changed this behavior, it makes much more sense now.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>The power of UIStackView</title><link href="https://scottberrevoets.com/2015/06/13/the-power-of-uistackview/" rel="alternate"/><published>2015-06-13T00:00:00-07:00</published><updated>2015-06-13T00:00:00-07:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2015-06-13:/2015/06/13/the-power-of-uistackview/</id><summary type="html">&lt;p&gt;I was in the audience for "Implementing UI Designs in Interface Builder" at WWDC, where Apple touted &lt;code&gt;UIStackView&lt;/code&gt; as a new, better way to lay out your views that in a lot of cases didn't require Auto Layout.&lt;/p&gt;
&lt;p&gt;While the presentation looked good, I wasn't quite sure this solved a …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I was in the audience for "Implementing UI Designs in Interface Builder" at WWDC, where Apple touted &lt;code&gt;UIStackView&lt;/code&gt; as a new, better way to lay out your views that in a lot of cases didn't require Auto Layout.&lt;/p&gt;
&lt;p&gt;While the presentation looked good, I wasn't quite sure this solved a real problem, as the required constraints weren't too complicated to build in the first pace. However, after I found some time to play around with &lt;code&gt;UIStackView&lt;/code&gt;, I'm convinced. This view is very, very powerful and can make building common UIs a lot simpler. Take, for example, a number grid:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[1] [2] [3]&lt;/span&gt;
&lt;span class="k"&gt;[4] [5] [6]&lt;/span&gt;
&lt;span class="k"&gt;[7] [8] [9]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;[0]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It would take a while to build this kind of UI in Auto Layout. You would have to create constraints between individual labels, make the labels the same size everywhere, make the spacing between all the labels equal, and avoid hardcoding any numbers to keep the whole thing adaptive. This can be a tedious process in Interface Builder, but imagine you're building this in code. It would take forever, especially after debugging your UI.&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;UIStackView&lt;/code&gt;, doing all of this takes about 30 seconds. After dragging out all the labels, you just need to embed every row in its own stack view. Set the distribution (in code that's &lt;code&gt;UIStackViewDistribution&lt;/code&gt;) for these stack views to "fill equally" (&lt;code&gt;.FillEqually&lt;/code&gt;), then embed all four stack views in their own stack view.&lt;/p&gt;
&lt;p&gt;&lt;img alt="UIStackView example" src="/images/UIStackView.png"&gt;&lt;/p&gt;
&lt;p&gt;The top-level, vertical stack view should get top/leading/trailing/bottom space to its superview (or the layout guides), the 4 horizontal stack views an "equal width" constraint to their superview. Finally, center the text of the individual labels (you can select all labels at once and do this with 1 action). Done - that's it.&lt;/p&gt;
&lt;p&gt;But it gets better. Say you want to conditionally show or hide the &lt;code&gt;0&lt;/code&gt; label at the bottom. I don't know why you would want that, but just go with it 😛&lt;/p&gt;
&lt;p&gt;To do this, all you have to is &lt;strong&gt;toggle the&lt;/strong&gt; &lt;code&gt;hidden&lt;/code&gt; &lt;strong&gt;property of that label&lt;/strong&gt;. The containing stack view will automatically reposition its subviews. If you put that line in an animation block, it will reposition its subviews in an animated fashion.&lt;/p&gt;
&lt;p&gt;That last part is a very welcome feature for developers that work with a lot of dynamic views. Instead of creating redundant constraints with lower priorities, removing views from their view hierarchies, and re-adding the view and all its constraints if the new views needs to be re-shown, you can simply toggle &lt;code&gt;hidden&lt;/code&gt; instead.&lt;/p&gt;
&lt;p&gt;As more people get their hands on &lt;code&gt;UIStackView&lt;/code&gt; I'm sure it will show off more of its powers, but needless to say I'm sold. Too bad I can't use it for a while...&lt;/p&gt;</content><category term="blog"/></entry><entry><title>@objc creates a wrong class name in Objective-C</title><link href="https://scottberrevoets.com/2014/12/15/objc-creates-a-wrong-class-name-in-objective-c/" rel="alternate"/><published>2014-12-15T00:00:00-08:00</published><updated>2014-12-15T00:00:00-08:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2014-12-15:/2014/12/15/objc-creates-a-wrong-class-name-in-objective-c/</id><summary type="html">&lt;p&gt;A few months ago, I decided I'd get started porting &lt;a href="https://github.com/sberrevoets/SDCAlertView"&gt;SDCAlertView&lt;/a&gt; to Swift, but I was only a few minutes in until I ran into a problem I had no idea how to solve: I couldn't get my class names right in both Swift and Objective-C. Even though there are …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A few months ago, I decided I'd get started porting &lt;a href="https://github.com/sberrevoets/SDCAlertView"&gt;SDCAlertView&lt;/a&gt; to Swift, but I was only a few minutes in until I ran into a problem I had no idea how to solve: I couldn't get my class names right in both Swift and Objective-C. Even though there are copious amounts of documentation covering the interoperability of Swift and Objective-C, somehow I couldn't get it to work and I brushed it off as me being stupid.&lt;/p&gt;
&lt;p&gt;Tonight I tried again, and after some more research it turned out that the behavior I saw was actually a bug in the compiler! I'm surprised that the latest Xcode version is 6.1.1 and that apparently not enough people have run into this problem for Apple to make it a priority.&lt;/p&gt;
&lt;p&gt;The bug can easily be reproduced by creating an Objective-C project in Xcode, and then adding the following Swift class:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="nc"&gt;UIKit&lt;/span&gt;

&lt;span class="kr"&gt;@objc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SDCMyLabel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyLabel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;UILabel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You would expect that, once you import &lt;code&gt;MyProject-Swift.h&lt;/code&gt; in your Objective-C class, you could instantiate a class named &lt;code&gt;SDCMyLabel&lt;/code&gt;. However, instead, you can only instantiate a class named &lt;code&gt;MyLabel&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I found this very recent &lt;a href="https://stackoverflow.com/questions/26132823/name-of-swift-class-when-exposed-to-objective-c-code-does-not-respect-the-objc"&gt;Stack Overflow&lt;/a&gt; question which pretty much asks the same thing. "milos" gives a great answer by explaining the observed behavior in Xcode 6.0.1, and the expected behavior according to the WWDC session &lt;a href="https://developer.apple.com/videos/wwdc/2014/"&gt;video&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After reading the answer and watching the relevant parts of the vide, I slightly changed my previously-drawn conclusion from "I'm stupid" to "Xcode's stupid" and filed &lt;a href="rdar://19261044"&gt;rdar://19261044&lt;/a&gt;. If you're running into the same problem, please duplicate and cross your fingers the engineers at the fruit company will fix it soon.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Dev-only iPhones</title><link href="https://scottberrevoets.com/2014/10/20/dev-only-iphones/" rel="alternate"/><published>2014-10-20T00:00:00-07:00</published><updated>2014-10-20T00:00:00-07:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2014-10-20:/2014/10/20/dev-only-iphones/</id><summary type="html">&lt;p&gt;With today's release of iOS 8.1, most app developers are looking at 4
significantly different iOS versions they will be supporting: iOS 7.0, iOS 7.1,
iOS 8.0, and iOS 8.1. Factoring in the number of devices (iPhone 4S, iPhone 5,
iPhone 5s, iPhone 6, and …&lt;/p&gt;</summary><content type="html">&lt;p&gt;With today's release of iOS 8.1, most app developers are looking at 4
significantly different iOS versions they will be supporting: iOS 7.0, iOS 7.1,
iOS 8.0, and iOS 8.1. Factoring in the number of devices (iPhone 4S, iPhone 5,
iPhone 5s, iPhone 6, and iPhone 6 Plus) that run these OS versions, there is a
grand total of &lt;strong&gt;16&lt;/strong&gt; different configurations. That's without even counting the
iPhone 5c, which is internally pretty much the same as the 5, or the
&lt;a href="https://www.macrumors.com/2014/09/29/ios-8-major-updates/"&gt;rumored&lt;/a&gt; iOS 8.2 and
iOS 8.3 updates.&lt;/p&gt;
&lt;p&gt;Assuming an average off-contract price of $400 for these models, that means
you're looking at $6,400 for 1 set of iPhones used for development. Creating a
universal app? Your total will now exceed $10,000.&lt;/p&gt;
&lt;p&gt;So now you have these 16 phones that will in most cases be used for testing
maybe a handful of apps, that have chips and software functionality in them that
will be used very little (if at all), and will most of the time just sit in some
desk drawer with a dead battery. I could think of better ways to spend money.&lt;/p&gt;
&lt;p&gt;This problem could have a very simple solution: dev-only iPhones. Remove or
disable the cellular chip (you can simulate cellular networks using the Network
Link Conditioner), give it an 8GB storage chip, remove apps such as Weather,
Stocks, Calculator, Clock, etc., disable the App Store for all but the
developer's own apps, allow any supported iOS version to be installed using
Xcode, and sell at-cost, but only to developers in the iOS Developer Program.&lt;/p&gt;
&lt;p&gt;By disabling cellular and dumbing the OS down to only include parts that
developers can interact with, it becomes pretty useless for people who are just
trying to look for a sweet deal on an iPhone/iPod touch. The win for developers
is that they only need 1 of each device, but they're also
&lt;a href="https://www.macrumors.com/2014/09/23/iphone-6-parts-cost-samsung-a8/"&gt;cheaper&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You could even take it a step further and set memory, CPU and screen size limits
(meaning only use 4" of the available 5.5"), allowing you to simulate an iPhone
5s on an iPhone 6 Plus. Now developers only need to buy the iPhone 6 Plus to be
mostly covered, though I'd still recommend also getting an iPhone 5 or iPhone 5c
for 32-bit devices.&lt;/p&gt;
&lt;p&gt;I may be way off course here due to technical implications or limitations that
I'm not aware of. There are also still cases in which you'd need an actual
device on the actual software as consumers use it. Developers don't always need
to test against &lt;strong&gt;every&lt;/strong&gt; iOS version on &lt;strong&gt;every&lt;/strong&gt; supported iOS device. But for
developers who do and don't want to spend the money on all these different
devices, a dev-only iPhone may be exactly what they need.&lt;/p&gt;
&lt;div class="admonition update"&gt;
&lt;p class="admonition-title"&gt;Update (February 2026)&lt;/p&gt;
&lt;p&gt;This was never really realistic for a variety of reasons, but I still think
the cost of entry for iOS development is unnecessarily high. The simulator
is still pretty good and has gotten much better over the years, but entire
classes of apps can't be simulated properly. The number of software updates,
device matrix, and hardware/software capabilities make dev-only iPhones also
not really scalable.&lt;/p&gt;
&lt;p&gt;The Apple enthusiasts still get new phones regularly enough that used phones
have become cheap enough to cover a bit more of the testing matrix too.&lt;/p&gt;
&lt;/div&gt;</content><category term="blog"/></entry><entry><title>UIMenuController and the responder chain</title><link href="https://scottberrevoets.com/2014/09/15/uimenucontroller-and-the-responder-chain/" rel="alternate"/><published>2014-09-15T00:00:00-07:00</published><updated>2014-09-15T00:00:00-07:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2014-09-15:/2014/09/15/uimenucontroller-and-the-responder-chain/</id><summary type="html">&lt;p&gt;The responder chain is a very important paradigm in the world of iOS development, and not terribly hard to understand. Dozens of articles have been written about it, and with some examples, the concept of finding a responder to a certain event by traversing a chain of potential responders is …&lt;/p&gt;</summary><content type="html">&lt;p&gt;The responder chain is a very important paradigm in the world of iOS development, and not terribly hard to understand. Dozens of articles have been written about it, and with some examples, the concept of finding a responder to a certain event by traversing a chain of potential responders is fairly straight-forward.&lt;/p&gt;
&lt;p&gt;But today it completely threw me off. The goal was very simple and has been done many times before: showing a label whose text can be copied to the clipboard. Having never worked with &lt;code&gt;UIMenuController&lt;/code&gt;, I fired up Google and of course found an excellent &lt;a href="https://nshipster.com/uimenucontroller/"&gt;NSHipster&lt;/a&gt; article near the top.&lt;/p&gt;
&lt;p&gt;I did as the article said. I created my &lt;code&gt;UILabel&lt;/code&gt; subclass (only accepting &lt;code&gt;copy:&lt;/code&gt; as an action it can perform), added my gesture recognizer, and configured and showed the &lt;code&gt;UIMenuController&lt;/code&gt;. It worked, but instead of only showing the Copy menu item, it also showed the Select All item.&lt;/p&gt;
&lt;p&gt;Well, that's weird. I implemented &lt;code&gt;canPerformAction:withSender:&lt;/code&gt; like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;BOOL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;canPerformAction:&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;SEL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nv"&gt;action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;withSender:&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nv"&gt;sender&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;@selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;copy&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I made sure that this method really only returned &lt;code&gt;YES&lt;/code&gt; for the Copy item, proceeded to explicitly return &lt;code&gt;NO&lt;/code&gt; for &lt;code&gt;selectAll:&lt;/code&gt; and, when that failed as well, even changed the implementation of this method to just return &lt;code&gt;NO&lt;/code&gt; for &lt;strong&gt;any&lt;/strong&gt; item. But Select All doesn't take &lt;code&gt;NO&lt;/code&gt; for an answer, and it stubbornly showed up anyway! I was baffled and could not get rid of this Select All item.&lt;/p&gt;
&lt;p&gt;I did some research, and learned that &lt;code&gt;UIMenuController&lt;/code&gt; uses the responder chain to figure out what items to show. Furthermore, the documentation for &lt;code&gt;canPerformAction:withSender:&lt;/code&gt; read:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This default implementation of this method returns YES if the responder class implements the requested action and calls the next responder if it does not.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That would explain the problem, if it weren't for the fact that Select All was the only other item that showed. Cut, Paste, Select, etc. did not. What was so special about Select All that no matter what I did, it always decided to make an unwanted appearance?&lt;/p&gt;
&lt;p&gt;Finally I saw it. One of the last methods of the view controller that created this label had a method named, you guessed it, &lt;code&gt;selectAll:&lt;/code&gt;. I didn't immediately connect the dots, but at least renaming that method to &lt;code&gt;selectAllContacts:&lt;/code&gt; (which was a better name for that method anyway) got rid of Select All.&lt;/p&gt;
&lt;p&gt;But how? I returned &lt;code&gt;NO&lt;/code&gt; from my label's &lt;code&gt;canPerformAction:withSender:&lt;/code&gt; for all actions except &lt;code&gt;copy:&lt;/code&gt;. This means that for all other actions (see the informal &lt;code&gt;UIResponderStandardEditActions&lt;/code&gt;), the next responder in the chain would be called. And (if necessary) the next, and the next.&lt;/p&gt;
&lt;p&gt;At some point, my view controller, which participates in the responder chain, became the first responder and returned &lt;code&gt;YES&lt;/code&gt; for the &lt;code&gt;selectAll:&lt;/code&gt; action, simply because the method was implemented and part of &lt;code&gt;UIResponderStandardEditActions&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So if implementing a method in that protocol is all that's needed to show the corresponding menu item, that would mean that implementing &lt;code&gt;canPerformAction:withSender:&lt;/code&gt; in my &lt;code&gt;UILabel&lt;/code&gt; subclass was unnecessary. I removed the method altogether and sure enough, the Copy menu item was still there. Problem solved.&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Xcode &amp; Objective-C Oddities</title><link href="https://scottberrevoets.com/2014/08/10/xcode-objective-c-oddities/" rel="alternate"/><published>2014-08-10T00:00:00-07:00</published><updated>2014-08-10T00:00:00-07:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2014-08-10:/2014/08/10/xcode-objective-c-oddities/</id><summary type="html">&lt;p&gt;Any developer that has worked with Xcode to write a little more than just "Hello, World" knows that Xcode and Objective-C have their quirks. Chances are you have heard of &lt;a href="https://twitter.com/TextFromXcode"&gt;@TextFromXcode&lt;/a&gt;, the Twitter handle that portrays Xcode as a high school bully in fake text conversations like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Xcode Bully" src="https://cl.ly/image/0I2P191S0Q2k/20140227.PNG"&gt;&lt;/p&gt;
&lt;p&gt;But it's …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Any developer that has worked with Xcode to write a little more than just "Hello, World" knows that Xcode and Objective-C have their quirks. Chances are you have heard of &lt;a href="https://twitter.com/TextFromXcode"&gt;@TextFromXcode&lt;/a&gt;, the Twitter handle that portrays Xcode as a high school bully in fake text conversations like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Xcode Bully" src="https://cl.ly/image/0I2P191S0Q2k/20140227.PNG"&gt;&lt;/p&gt;
&lt;p&gt;But it's not always Xcode that makes Apple platform developers sometimes frown and sigh. Sometimes it's the developers themselves, sometimes it's the documentation, and sometimes a hidden gem from within Apple frameworks makes an appearance. I've compiled a short list of things I've encountered. Because it's Sunday and I didn't have anything better to do.&lt;/p&gt;
&lt;h2 id="uigobblergesturerecognizer"&gt;&lt;code&gt;UIGobblerGestureRecognizer&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Say what? A "&lt;a href="https://github.com/nst/iOS-Runtime-Headers/blob/master/Frameworks/UIKit.framework/UIGobblerGestureRecognizer.h"&gt;&lt;code&gt;gobbler&lt;/code&gt;&lt;/a&gt;" recognizer? Does it recognize when the user imitates a turkey? Well, maybe, but according to &lt;a href="https://twitter.com/ortwingentz/status/225508227234791424"&gt;BJ Homer&lt;/a&gt;, it is used to avoid recognition of a gesture while animations are in progress. Ah of course! Now the name "gobbler" makes perfect sense! 😶&lt;/p&gt;
&lt;h2 id="uitapandahalfrecognizer"&gt;&lt;code&gt;UITapAndAHalfRecognizer&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Speaking of gesture recognizers, there's another remarkable one that lives in the private section of UIKit: &lt;code&gt;UITapAndAHalfRecognizer&lt;/code&gt;. This is a private subclass of &lt;code&gt;UIGestureRecognizer&lt;/code&gt; that records "a tap and a half".&lt;/p&gt;
&lt;p&gt;So what does that mean? Does it detect whether the user goes in for a second tap, but never actually touches the screen? Or does the user touch the screen ever so slightly that the second tap can hardly be considered a complete tap? Nope! This recognizer fires when a second tap stays on the screen. Not sure for what functionality Apple needs this, but it's been around since at least iOS 4, so it probably has a purpose.&lt;/p&gt;
&lt;h2 id="naming-is-hard"&gt;Naming is hard&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;There are only two hard things in Computer Science: cache invalidation and naming things.&lt;br&gt;
- Phil Karlton&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Naming &lt;strong&gt;is&lt;/strong&gt; hard, but Objective-C developers always say that verbosity is one of Objective-C's strengths because its naming conventions are self-documenting. Normally I'd agree, but in this case, I think Apple may want to consider a different name.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.apple.com/library/prerelease/ios/releasenotes/General/iOS80APIDiffs/frameworks/HomeKit.html"&gt;&lt;code&gt;HMCharacteristicValueLockMechanismLastKnownActionUnsecuredUsingPhysicalMovementExterior&lt;/code&gt;&lt;/a&gt; is a brand new constant in iOS 8 and has got to be one of the longest constants that's available in iOS. Try saying it without taking a breath in the middle. &lt;/p&gt;
&lt;p&gt;I found it in the iOS 8 API diffs, but it did make me wonder. What is the longest Objective-C method available in Cocoa Touch? Well, a quick Google search led to a blog post by &lt;a href="https://initwithstyle.net/2013/10/in-search-of-the-longest-method"&gt;Stuart Sharpe&lt;/a&gt;, who used the Objective-C runtime to generate a list of methods in the iOS 7 SDK.&lt;/p&gt;
&lt;p&gt;Turns out, the longest method in the iOS 7 SDK is&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;CUIShapeEffectStack&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;shapeEffectSingleBlurFrom&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                               &lt;/span&gt;&lt;span class="nl"&gt;withInteriorFill&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                                         &lt;/span&gt;&lt;span class="nl"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                                       &lt;/span&gt;&lt;span class="nl"&gt;blurSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="nl"&gt;innerGlowRed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                                 &lt;/span&gt;&lt;span class="nl"&gt;innerGlowGreen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                                  &lt;/span&gt;&lt;span class="nl"&gt;innerGlowBlue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                               &lt;/span&gt;&lt;span class="nl"&gt;innerGlowOpacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                                 &lt;/span&gt;&lt;span class="nl"&gt;innerShadowRed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                               &lt;/span&gt;&lt;span class="nl"&gt;innerShadowGreen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                                &lt;/span&gt;&lt;span class="nl"&gt;innerShadowBlue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                             &lt;/span&gt;&lt;span class="nl"&gt;innerShadowOpacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                                   &lt;/span&gt;&lt;span class="nl"&gt;outerGlowRed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                                 &lt;/span&gt;&lt;span class="nl"&gt;outerGlowGreen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                                  &lt;/span&gt;&lt;span class="nl"&gt;outerGlowBlue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                               &lt;/span&gt;&lt;span class="nl"&gt;outerGlowOpacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                                 &lt;/span&gt;&lt;span class="nl"&gt;outerShadowRed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                               &lt;/span&gt;&lt;span class="nl"&gt;outerShadowGreen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                                &lt;/span&gt;&lt;span class="nl"&gt;outerShadowBlue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                             &lt;/span&gt;&lt;span class="nl"&gt;outerShadowOpacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                            &lt;/span&gt;&lt;span class="nl"&gt;hasInsideShadowBlur&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;                           &lt;/span&gt;&lt;span class="nl"&gt;hasOutsideShadowBlur&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Unless iOS 8 introduces a longer method name, this one takes the cake with 22 arguments and 352 characters. If you have Xcode configured to line-break at 80 characters (yes, some people still do that), this method signature alone, without any arguments, takes up 5 lines.&lt;/p&gt;
&lt;p&gt;Another beauty of a method lives in Core Animation and its method signature is &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;CAMediaTimingFunction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;functionWithControlPoints&lt;/span&gt;&lt;span class="o"&gt;::::&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;::::&lt;/code&gt;? Is that even valid syntax? Yes, and you call the method like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;CAMediaTimingFunction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;functionWithControlPoints&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;.50&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Was an array really too much to ask...?&lt;/p&gt;
&lt;h2 id="nsdate-nildate"&gt;&lt;code&gt;[NSDate nilDate]&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;This one has made the rounds throughout the developer community before, but I'm adding it here in case you haven't seen it yet. Try running the following code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="bp"&gt;NSCalendar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;calendar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;NSCalendar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;currentCalendar&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;calendar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;components&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;NSCalendarUnitYear&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fromDate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;toDate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;NSDate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In the console log, you'll find the following output:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;*** -[__NSCFCalendar components:fromDate:toDate:options:]: fromDate cannot be nil&lt;br&gt;
I mean really, what do you think that operation is supposed to mean with a nil fromDate?&lt;br&gt;
An exception has been avoided for now.&lt;br&gt;
A few of these errors are going to be reported with this complaint, then further violations will simply silently do whatever random thing results from the nil.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Either a grumpy Apple developer woke up at the wrong side of the bed or has a great sense of humor. In any case, I wouldn't mind if more sarcastic warnings started popping up in Xcode's console.&lt;/p&gt;
&lt;h2 id="defying-the-uncertainty-principle-in-ios"&gt;Defying the Uncertainty Principle in iOS&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[I]n 1927, Werner Heisenberg stated that the more precisely the position of some particle is determined, the less precisely its momentum can be known, and vice versa.&lt;br&gt;
&lt;a href="https://en.wikipedia.org/wiki/Uncertainty_principle"&gt;Wikipedia&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If that's true, and it's fairly safe to assume that it is, I'd avoid using &lt;code&gt;-[CLLocation speed]&lt;/code&gt; for apps like radar detectors. I wonder if we're ever going to get a &lt;code&gt;-[CLSpeed location]&lt;/code&gt; method, which has a more accurate speed and a less accurate location.&lt;/p&gt;
&lt;h2 id="xcode-at-it-again"&gt;Xcode at it again&lt;/h2&gt;
&lt;p&gt;Lastly, the following is a screenshot I personally took at work that completely baffled me.&lt;/p&gt;
&lt;p&gt;&lt;img src="/images/XcodeScreenshot.png" style="width: 700px; margin: 0 auto; display: block;"&gt;&lt;/p&gt;
&lt;p&gt;This would not go away, even after cleaning the build folder and rebuilding. In the end I resolved the problem by running&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;stash
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;reset&lt;span class="w"&gt; &lt;/span&gt;--hard&lt;span class="w"&gt; &lt;/span&gt;HEAD
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then, after a clean build and &lt;code&gt;git stash pop&lt;/code&gt; the errors finally went away...&lt;/p&gt;
&lt;p&gt;That's just a few things I've found while working with Xcode and Objective-C over the years. If you have more, please share!&lt;/p&gt;</content><category term="blog"/></entry><entry><title>Objective-C prefixes: a thing of the past?</title><link href="https://scottberrevoets.com/2014/07/25/objective-c-prefixes-a-thing-of-the-past/" rel="alternate"/><published>2014-07-25T00:00:00-07:00</published><updated>2014-07-25T00:00:00-07:00</updated><author><name>Scott Berrevoets</name></author><id>tag:scottberrevoets.com,2014-07-25:/2014/07/25/objective-c-prefixes-a-thing-of-the-past/</id><summary type="html">&lt;p&gt;This past week, there was, again, a lot to do about Objective-C and prefixing. To most iOS developers, the story sounds familiar: one camp is strongly in favor, another camps is strongly against, and a third camp couldn't really care less.&lt;/p&gt;
&lt;p&gt;Unless I'm unaware of any other platforms where the …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This past week, there was, again, a lot to do about Objective-C and prefixing. To most iOS developers, the story sounds familiar: one camp is strongly in favor, another camps is strongly against, and a third camp couldn't really care less.&lt;/p&gt;
&lt;p&gt;Unless I'm unaware of any other platforms where the discussion was also a hot topic, this time I was at least a little responsible for instigating a debate that will never see a clear winner, with the following tweet:&lt;/p&gt;
&lt;blockquote class="twitter-tweet" lang="en" align="center"&gt;&lt;p&gt;&lt;a href="https://twitter.com/bobmccune"&gt;@bobmccune&lt;/a&gt; &lt;a href="https://twitter.com/invalidname"&gt;@invalidname&lt;/a&gt; Also no option to enter class prefix anymore, even for Objective-C projects&lt;/p&gt;&amp;mdash; Scott Berrevoets (@ScottBerrevoets) &lt;a href="https://twitter.com/ScottBerrevoets/statuses/492012762940588033"&gt;July 23, 2014&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src="//platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;

&lt;p&gt;Let's first clear up that Swift doesn't &lt;strong&gt;need&lt;/strong&gt; to have class prefixes, because it has built-in support for namespacing through modules. From what I read, earlier Xcode 6/Swift betas did not have module support, but at least we know it's coming before 1.0 hits.&lt;/p&gt;
&lt;p&gt;However, this doesn't solve any of the problems Objective-C has, and so it was surprising to see this field was removed when creating a new Objective-C project. You can still set this prefix per project &lt;strong&gt;after&lt;/strong&gt; you create it, but since most Xcode templates create source files for you, those files will be created without a prefix.&lt;/p&gt;
&lt;p&gt;I noticed the lack of this option, as well as the lack of a file template for Objective-C categories, back in beta 2 and immediately filed two bugs. The missing file template is as of this writing still open, but the class prefix got closed a few days later. Normally, radars aren't known for their wealth of information related to the issue the radar was filed for, but Apple Engineering made an exception this time.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;After much deliberation, engineering has removed this feature.&lt;/p&gt;
&lt;p&gt;This was removed intentionally. We are no longer encouraging the use of prefixes for app code, and for frameworks, you can create a prefix in the Project Inspector after creating the project.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Okay, so no more prefixes for app code. Didn't see that coming. Does Apple now encourage developers to start making extensive use of frameworks?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;No. If you want to use a prefix, you can set one in the project inspector. Especially for frameworks, this is easy since the template creates no source files that should be prefixed. If you really want to prefix the classes in your main app binary, you might need to rename the few that are created initially, but the recommendation is generally that frameworks should use prefixes (in Obj-C, not in Swift) to avoid namespace issues, and apps generally don't need to.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The key part here is in the last few words: "don't need to".&lt;/p&gt;
&lt;p&gt;Third-party libraries, especially frameworks that don't ship with the source code, need a prefix, because the chances of naming collisions with other code or, even worse, other libraries is significant. Users of the libraries can't (and even if they can, are likely not willing to) change the name of your classes because they collide with some other library's code.&lt;/p&gt;
&lt;p&gt;"App code" on the other hand, is very unique to one particular app. It contains the UI and some of the business logic that puts your app together. Most view controllers, for example, are app code. App code is not likely to be reused, because it's very specific in what it does and is therefore unlikely to collide with other classes (assuming you name your classes well). If you think "well, this functionality may be reused in some other project", it's a great candidate for a framework!&lt;/p&gt;
&lt;p&gt;I was always a big fan of the class prefixes, and while it was sometimes hard to come up with what prefix to use, I've come to actually &lt;strong&gt;like&lt;/strong&gt; how they look, too. Classes that don't have a prefix feel like they're missing something. However, the guideline of "prefix frameworks, not app code" does make sense, so I will start using that from now on as well.&lt;/p&gt;</content><category term="blog"/></entry></feed>