<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title><![CDATA[JP Simard]]></title>
  <link href="https://jpsim.com/atom.xml" rel="self"/>
  <link href="https://jpsim.com/"/>
  <updated>2026-01-02T19:54:14+00:00</updated>
  <id>https://jpsim.com/</id>
  <author>
    <name><![CDATA[JP Simard]]></name>
    <email><![CDATA[jp@jpsim.com]]></email>
  </author>
  <generator uri="http://octopress.org/">Octopress</generator>

  
  <entry>
    <title type="html"><![CDATA[Mobile Development in the Age of AI]]></title>
    <link href="https://jpsim.com/mobile-development-in-the-age-of-ai/"/>
    <updated>2026-01-02T15:00:00+00:00</updated>
    <id>https://jpsim.com/mobile-development-in-the-age-of-ai</id>
    <content type="html"><![CDATA[<p>After working in mobile engineering for about a decade and a half, across
multiple eras of iOS and Android tooling, languages, and architectural
approaches, I&#8217;ve never been more excited about the landscape than I am right
now.</p>

<p>When I started making apps in 2011, it was thrilling to go from an idea to
something you could physically touch and hold in your hand within hours. Today,
that same feeling exists—but the iteration cycles are now an order of magnitude
faster.</p>

<p>The fundamentals haven’t changed. What <em>has</em> changed is the speed at which
intent turns into working software.</p>

<h2>Translation Is (Basically) a Solved Problem</h2>

<p>A lot of where AI applications go wrong today comes from anthropomorphizing
models and ascribing to them a kind of human-style intelligence. As
<a href="https://www.youtube.com/watch?v=lXUZvyajciY">Andrej Karpathy</a> puts it: <em>“We’re summoning ghosts, not
building animals.”</em></p>

<p>But one category of tasks these LLM “ghosts” excel at is translation.</p>

<p>That’s obvious when translating natural language like English to French, but
it’s equally true for:</p>

<ul>
<li>TypeScript to Swift</li>
<li>Jetpack Compose to SwiftUI</li>
<li>Web APIs to platform SDKs</li>
<li>Architectural patterns across platforms</li>
</ul>


<p>See <a href="https://simonwillison.net/2025/Dec/15/porting-justhtml/">this example</a>
where Simon Willison ported JustHTML from Python to JavaScript with Codex CLI
and GPT-5.2 in 4.5 hours.</p>

<p>If you operate in an environment where your web app, iOS app, and Android app
are separate codebases with baseline functional parity, AI fundamentally changes
the economics of one codebase for each platform.</p>

<p>Instead of rewriting features, you’re translating intent.</p>

<p>A web PR becomes a reference implementation. Existing mobile code becomes
executable context. Asking an agent to “take a first pass at implementing this
natively” is now a normal—and productive—workflow, especially for apps with
large CRUD-style surface areas and well-defined business logic.</p>

<h2>Context Beats Code</h2>

<p><a href="https://simonwillison.net/2025/jun/27/context-engineering/">Simon Willison</a> has written about <em>context engineering</em>:
“the art of providing all the context for the task to be plausibly solvable by
the LLM.” The limiting factor in AI-assisted development isn’t the model itself,
but how clearly the problem is framed.</p>

<p>That aligns closely with what actually matters in software engineering:</p>

<ul>
<li>Understanding the domain</li>
<li>Choosing the right abstractions</li>
<li>Designing the architecture</li>
<li>Encoding business rules correctly</li>
<li>Setting up the right conditions for QA and validating core behavior and edge cases</li>
</ul>


<p>These are platform-agnostic, one-time costs. Once you’ve paid them, the knowledge
transfers cleanly across platforms.</p>

<p>This is also where the distinction between <strong>process-driven</strong> and
<strong>outcome-driven</strong> engineers becomes important. As
<a href="https://simonwillison.net/2026/Jan/2/ben-werdmuller/">Ben Werdmuller</a> put it, we’re
likely to see a real split between people who are outcome-driven and excited to
test their work with users faster, and people who are process-driven and derive
meaning primarily from the engineering itself.</p>

<p>LLMs disproportionately benefit outcome-driven engineers—those who know what
“good” looks like and can steer toward it. The tools reward clarity of intent,
not ceremony.</p>

<h2>What Mobile Development Actually Looks Like Now</h2>

<p>In practice, most of the code I’ve written over the past twelve months hasn’t
been typed directly into Xcode or Android Studio.</p>

<p>A common setup today looks like this:</p>

<ul>
<li>A coding agent (e.g. Claude Code, Cursor, etc.) on one side of the screen</li>
<li>Xcode or Android Studio on the other</li>
<li>The bulk of code editing happening in the agent</li>
<li>The IDE acting as a platform-native harness: breakpoints, debugger, previews,
and deeper integrations with simulators and emulators</li>
</ul>


<p>With a modest amount of investment, the need for an IDE as a primary input tool
almost disappears. The more you can hoist your compiler and language server out
of the IDE and into terminal-ready commands, the more you can augment the
capabilities of your coding agent.</p>

<h2>Native Platforms Pair Well With AI, Despite Training Gaps</h2>

<p>A fair concern is that frontier models still have less deep knowledge of
iOS- and Android-specific APIs than they do of web or Python ecosystems.</p>

<p><a href="https://www.cocoawithlove.com/blog/llms-twelve-months-later.html">Matt Gallagher</a> recently wrote about the state of Swift and iOS
knowledge in frontier models at the end of 2025. He notes that <em>“most LLMs are
trained on data that is 2+ years old, and their Swift style often feels older
still.”</em></p>

<p>The conclusion isn’t that models are perfect, but that modern native platforms
provide strong constraints:</p>

<ul>
<li>Strong static typing</li>
<li>Clear compiler diagnostics</li>
<li>Declarative UI frameworks</li>
<li>Deterministic tooling</li>
</ul>


<p>These constraints matter more than raw training data volume. What matters day to
day isn’t whether an LLM can one-shot a perfect solution, but whether it can
iterate quickly with tight feedback. Strong typing in Swift, Kotlin, and
TypeScript helps enormously here.</p>

<p>I don’t know about you, but I don’t typically write perfect code on the first try
either.</p>

<h2>A Brief Note on React Native</h2>

<p>It’s absolutely possible to build excellent mobile apps using React Native or
fully native stacks.</p>

<p>What’s often underestimated is the organizational overhead and depth of
expertise required to do React Native well at scale. Teams that succeed tend to
invest heavily in platform infrastructure, tooling, and internal knowledge.
Shopify is a well-known example of what “doing it right” looks like.</p>

<p>AI shifts this tradeoff. When translation becomes cheap, the value of
lowest-common-denominator abstractions drops. Platform-idiomatic code no longer
implies slower delivery.</p>

<p>Side note: this is a large part of why Ramp’s mobile engineering team is much
leaner than people expect given the breadth of the apps&#8217; capabilities.</p>

<h2>The Centaur Model, Applied</h2>

<p>This way of working maps closely to what Cory Doctorow calls the
<a href="https://pluralistic.net/2025/12/05/pop-that-bubble/#u-washington"><em>Centaur model</em></a>. In automation theory, “a centaur” is a person
assisted by a machine.</p>

<p>Humans and machines work together, each focused on what they do best:</p>

<ul>
<li>Humans handle judgment, architecture, domain understanding, and taste</li>
<li>Machines handle repetition, translation, and acceleration</li>
</ul>


<p>AI doesn’t remove the need for software engineers. It removes the least
interesting parts of the job and sharpens the most important ones.</p>

<h2>Why This Reinforces Native Teams</h2>

<p>With AI-assisted translation:</p>

<ul>
<li>Separate codebases no longer incur the same maintenance costs as they once did</li>
<li>Platform fidelity no longer trades off against velocity</li>
<li>Engineers can build where they’re strongest and port confidently</li>
</ul>


<p>This is exactly how we’re working today—and why Ramp is hiring <a href="https://jobs.ashbyhq.com/ramp/4859cd5e-f2a9-44d7-81f7-8bfc0e62369f">iOS engineers</a>
and <a href="https://jobs.ashbyhq.com/ramp/f564dcf9-9390-4a3f-896f-8047a5086040">Android engineers</a>.</p>

<p>If you care about building platform-idiomatic mobile apps, moving quickly
without sacrificing quality, and spending more time on judgment than
boilerplate, this is a particularly good moment to be doing native mobile
engineering.</p>

<h2>Closing</h2>

<p>The tools changed.<br/>
The job didn’t.</p>

<p>If anything, it got more interesting.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Building an NFC Music Box]]></title>
    <link href="https://jpsim.com/building-an-nfc-music-box/"/>
    <updated>2023-03-31T11:00:00+00:00</updated>
    <id>https://jpsim.com/building-an-nfc-music-box</id>
    <content type="html"><![CDATA[<p>My son and I built an NFC-based music player so he can play the music he
wants, develop his own musical taste but most of all so that he and I
could have something fun to build together.</p>

<p>Kind of like a modern day record player, but infinitely more extensible,
and with each album costing 1/100th the price of a vinyl record.</p>

<p>Here it is in action (<em>music starts 9 seconds into the video, set your
volume to low</em>):</p>

<iframe title="vimeo-player" src="https://player.vimeo.com/video/813573763?h=6b3ebec0bc" width="640" height="360" frameborder="0" allowfullscreen></iframe>


<p><br /></p>

<p>And here&#8217;s how we built it:</p>

<h2>Materials</h2>

<table>
<thead>
<tr>
<th><br /></th>
<th><br /></th>
<th><br /></th>
<th><br /></th>
<th><br /></th>
</tr>
</thead>
<tbody>
<tr>
<td><img src="https://jpsim.com/images/posts/musicbox/echo.jpg" alt="" /></td>
<td><img src="https://jpsim.com/images/posts/musicbox/case.jpg" alt="" /></td>
<td><img src="https://jpsim.com/images/posts/musicbox/pi.jpg" alt="" /></td>
<td><img src="https://jpsim.com/images/posts/musicbox/nfc_module.jpg" alt="" /></td>
<td><img src="https://jpsim.com/images/posts/musicbox/nfc_cards.jpg" alt="" /></td>
</tr>
</tbody>
</table>


<ul>
<li><a href="https://www.amazon.com/dp/B08YT3BWMP">Amazon Echo Dot, $20</a>:
For the price, this is a surprisingly hackable, portable speaker. I would have
prefered something without any microphones at all, but I <em>mostly</em> trust the
hardware mute switch.</li>
<li><a href="https://www.etsy.com/listing/739034156">Case, $20</a>: Beautiful
retro-style case for the Raspberry Pi. The top compartment is made to
house a fan, but this is where I put the NFC module instead. The folks
who make this at <a href="https://twitter.com/theC4Labs">C4 Labs</a> were <a href="https://twitter.com/simjp/status/1259980961665576960">super nice</a>.
We broke a piece when we started building the box and they sent a
replacement part right away.</li>
<li><a href="https://www.raspberrypi.org/products/raspberry-pi-4-model-b/">Raspberry Pi, $35</a>:
I chose to go with a Raspberry Pi because they&#8217;re cheap, have good
compatible NFC modules, lots of case options on Etsy and easy to
develop on.</li>
<li><a href="https://www.amazon.com/dp/B01CSTW0IA">NFC Module, $5</a>: I can&#8217;t
believe this thing is just $5. Works great. Lots of good tutorials on
connecting this to a Raspberry Pi.</li>
<li><a href="https://store.gototags.com/nfc-pvc-card-ntag213/">NFC Cards, $29</a>:
I got 100 cards at $0.29 each.</li>
</ul>


<p>In total, this adds up to $109, but in my case I already had a Raspberry
Pi and an Echo Dot lying around that I could repurpose for this, so it
cost me closer to $55.</p>

<h2>Services</h2>

<p>I&#8217;m already an Apple Music subscriber, so using that as the music source
was the cheapest solution, although this setup would work with any music
service that works with Amazon Echo: Spotify, Amazon Music, TuneIn,
CloudPlayer, Deezer, iHeartRadio.</p>

<p>In the exploration phase for this project, I wanted to buy DRM-free
music, wire a speaker directly to the Raspberry Pi and play it locally.
However, that would have meant that adding music later would be a much
more tedious task. Plus it&#8217;s hard to find DRM-free music these days, and
when you do find what you want it ends up being pricey. Especially
compared to the convenience and cost of today&#8217;s streaming options.</p>

<p>I also explored using HomePods as the speakers (see addendum below).</p>

<p>The biggest downside to the current streaming approach is that there&#8217;s a
5-10 second delay after tapping a card and music starting.</p>

<h2>Assembly</h2>

<table>
<thead>
<tr>
<th><br /></th>
<th><br /></th>
<th><br /></th>
</tr>
</thead>
<tbody>
<tr>
<td><img src="https://jpsim.com/images/posts/musicbox/assembly1.jpg" alt="Assembly #1" /></td>
<td><img src="https://jpsim.com/images/posts/musicbox/assembly2.jpg" alt="Assembly #2" /></td>
<td><img src="https://jpsim.com/images/posts/musicbox/assembly3.jpg" alt="Assembly #3" /></td>
</tr>
<tr>
<td><img src="https://jpsim.com/images/posts/musicbox/assembly4.jpg" alt="Assembly #4" /></td>
<td><img src="https://jpsim.com/images/posts/musicbox/assembly5.jpg" alt="Assembly #5" /></td>
<td><img src="https://jpsim.com/images/posts/musicbox/assembly6.jpg" alt="Assembly #6" /></td>
</tr>
</tbody>
</table>


<p><br /></p>

<p>I assembled this with my son over a few days in May 2020, and then
finished the setup in September 2021. The actual hardware assembly
probably only took a combined total of 3 hours though.</p>

<h2>Software</h2>

<p><em>Disclaimer: This code is rough, it&#8217;s suitable for a toy project. I&#8217;m
sharing it in case it&#8217;s useful for others getting started with a similar,
project. I&#8217;m not claiming this is beautiful quality code.</em></p>

<h3>Echo Dot Remote Control</h3>

<p>I use the <a href="https://github.com/thorsten-gehrig/alexa-remote-control">alexa-remote-control</a> shell script to
control the Echo Dot. When it&#8217;s up and running, it couldn&#8217;t be easier:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='shell'><span class='line'><span></span>$<span class="w"> </span>alexa_remote_control.sh<span class="w"> </span>-e<span class="w"> </span><span class="s2">&quot;playmusic:APPLE_MUSIC:The Lion King&quot;</span>
</span><span class='line'>$<span class="w"> </span>alexa_remote_control.sh<span class="w"> </span>-e<span class="w"> </span>pause
</span><span class='line'>$<span class="w"> </span>alexa_remote_control.sh<span class="w"> </span>-e<span class="w"> </span>play
</span><span class='line'>$<span class="w"> </span>alexa_remote_control.sh<span class="w"> </span>-e<span class="w"> </span>vol:15
</span></code></pre></td></tr></table></div></figure>


<h3>NFC Reader</h3>

<p>I use <a href="https://github.com/pelwell/MFRC522-python">MFRC522-python</a> to
interface with the NFC module.</p>

<p>The Python script that reads cards and plays music looks like this:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
<span class='line-number'>42</span>
<span class='line-number'>43</span>
<span class='line-number'>44</span>
<span class='line-number'>45</span>
<span class='line-number'>46</span>
<span class='line-number'>47</span>
<span class='line-number'>48</span>
<span class='line-number'>49</span>
<span class='line-number'>50</span>
<span class='line-number'>51</span>
<span class='line-number'>52</span>
<span class='line-number'>53</span>
<span class='line-number'>54</span>
<span class='line-number'>55</span>
<span class='line-number'>56</span>
<span class='line-number'>57</span>
<span class='line-number'>58</span>
<span class='line-number'>59</span>
<span class='line-number'>60</span>
<span class='line-number'>61</span>
<span class='line-number'>62</span>
<span class='line-number'>63</span>
<span class='line-number'>64</span>
<span class='line-number'>65</span>
<span class='line-number'>66</span>
<span class='line-number'>67</span>
<span class='line-number'>68</span>
<span class='line-number'>69</span>
<span class='line-number'>70</span>
<span class='line-number'>71</span>
<span class='line-number'>72</span>
<span class='line-number'>73</span>
<span class='line-number'>74</span>
</pre></td><td class='code'><pre><code class='python'><span class='line'><span></span><span class="kn">import</span> <span class="nn">RPi.GPIO</span> <span class="k">as</span> <span class="nn">GPIO</span>
</span><span class='line'><span class="kn">import</span> <span class="nn">MFRC522</span>
</span><span class='line'><span class="kn">import</span> <span class="nn">os</span>
</span><span class='line'><span class="kn">import</span> <span class="nn">signal</span>
</span><span class='line'>
</span><span class='line'><span class="n">continue_reading</span> <span class="o">=</span> <span class="kc">True</span>
</span><span class='line'><span class="n">last_seen_card</span> <span class="o">=</span> <span class="kc">None</span>
</span><span class='line'><span class="n">echo_name</span> <span class="o">=</span> <span class="s2">&quot;MusicBox Echo&quot;</span>
</span><span class='line'><span class="n">current_dir</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">abspath</span><span class="p">(</span><span class="vm">__file__</span><span class="p">))</span>
</span><span class='line'><span class="n">alexa_control_script</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">current_dir</span><span class="p">,</span> <span class="s2">&quot;alexa_remote_control.sh&quot;</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'><span class="k">def</span> <span class="nf">alexa</span><span class="p">(</span><span class="n">command</span><span class="p">):</span>
</span><span class='line'>    <span class="n">os</span><span class="o">.</span><span class="n">system</span><span class="p">(</span>
</span><span class='line'>        <span class="s1">&#39;</span><span class="si">{alexa_control_script}</span><span class="s1"> -d &quot;</span><span class="si">{echo_name}</span><span class="s1">&quot; -e &quot;</span><span class="si">{command}</span><span class="s1">&quot;&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span>
</span><span class='line'>            <span class="n">alexa_control_script</span><span class="o">=</span><span class="n">alexa_control_script</span><span class="p">,</span>
</span><span class='line'>            <span class="n">echo_name</span><span class="o">=</span><span class="n">echo_name</span><span class="p">,</span>
</span><span class='line'>            <span class="n">command</span><span class="o">=</span><span class="n">command</span><span class="p">,</span>
</span><span class='line'>        <span class="p">)</span>
</span><span class='line'>    <span class="p">)</span>
</span><span class='line'>
</span><span class='line'><span class="k">def</span> <span class="nf">pause</span><span class="p">():</span>
</span><span class='line'>    <span class="n">alexa</span><span class="p">(</span><span class="s2">&quot;pause&quot;</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'><span class="k">def</span> <span class="nf">play_music</span><span class="p">(</span><span class="n">query</span><span class="p">):</span>
</span><span class='line'>    <span class="nb">print</span><span class="p">(</span><span class="n">query</span><span class="p">)</span>
</span><span class='line'>    <span class="n">pause</span><span class="p">()</span>
</span><span class='line'>    <span class="n">alexa</span><span class="p">(</span><span class="s2">&quot;playmusic:APPLE_MUSIC:</span><span class="si">{query}</span><span class="s2">&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">query</span><span class="o">=</span><span class="n">query</span><span class="p">))</span>
</span><span class='line'>
</span><span class='line'><span class="k">def</span> <span class="nf">set_volume</span><span class="p">(</span><span class="n">volume</span><span class="p">):</span>
</span><span class='line'>    <span class="n">alexa</span><span class="p">(</span><span class="s2">&quot;vol:</span><span class="si">{volume}</span><span class="s2">&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">volume</span><span class="o">=</span><span class="n">volume</span><span class="p">))</span>
</span><span class='line'>
</span><span class='line'><span class="k">def</span> <span class="nf">end_read</span><span class="p">(</span><span class="n">signal</span><span class="p">,</span> <span class="n">frame</span><span class="p">):</span>
</span><span class='line'>    <span class="k">global</span> <span class="n">continue_reading</span>
</span><span class='line'>    <span class="n">continue_reading</span> <span class="o">=</span> <span class="kc">False</span>
</span><span class='line'>    <span class="n">GPIO</span><span class="o">.</span><span class="n">cleanup</span><span class="p">()</span>
</span><span class='line'>
</span><span class='line'><span class="n">signal</span><span class="o">.</span><span class="n">signal</span><span class="p">(</span><span class="n">signal</span><span class="o">.</span><span class="n">SIGINT</span><span class="p">,</span> <span class="n">end_read</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'><span class="n">MIFAREReader</span> <span class="o">=</span> <span class="n">MFRC522</span><span class="o">.</span><span class="n">MFRC522</span><span class="p">()</span>
</span><span class='line'>
</span><span class='line'><span class="k">while</span> <span class="n">continue_reading</span><span class="p">:</span>
</span><span class='line'>    <span class="c1"># Scan for cards</span>
</span><span class='line'>    <span class="p">(</span><span class="n">status</span><span class="p">,</span> <span class="n">TagType</span><span class="p">)</span> <span class="o">=</span> <span class="n">MIFAREReader</span><span class="o">.</span><span class="n">MFRC522_Request</span><span class="p">(</span><span class="n">MIFAREReader</span><span class="o">.</span><span class="n">PICC_REQIDL</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'>    <span class="c1"># If we have a card, continue</span>
</span><span class='line'>    <span class="k">if</span> <span class="n">status</span> <span class="o">==</span> <span class="n">MIFAREReader</span><span class="o">.</span><span class="n">MI_OK</span><span class="p">:</span>
</span><span class='line'>        <span class="c1"># Get the UID of the card</span>
</span><span class='line'>        <span class="p">(</span><span class="n">status</span><span class="p">,</span> <span class="n">uid</span><span class="p">)</span> <span class="o">=</span> <span class="n">MIFAREReader</span><span class="o">.</span><span class="n">MFRC522_Anticoll</span><span class="p">()</span>
</span><span class='line'>
</span><span class='line'>        <span class="c1"># If we have the UID, continue</span>
</span><span class='line'>        <span class="k">if</span> <span class="n">status</span> <span class="o">==</span> <span class="n">MIFAREReader</span><span class="o">.</span><span class="n">MI_OK</span> <span class="ow">and</span> <span class="n">last_seen_card</span> <span class="o">!=</span> <span class="n">uid</span><span class="p">:</span>
</span><span class='line'>            <span class="n">last_seen_card</span> <span class="o">=</span> <span class="n">uid</span>
</span><span class='line'>
</span><span class='line'>            <span class="c1"># Print UID</span>
</span><span class='line'>            <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;Card UID: </span><span class="si">%s</span><span class="s2">&quot;</span> <span class="o">%</span> <span class="n">uid</span><span class="p">)</span>
</span><span class='line'>            <span class="k">if</span> <span class="n">uid</span> <span class="o">==</span> <span class="p">[</span><span class="mi">136</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">76</span><span class="p">,</span> <span class="mi">240</span><span class="p">,</span> <span class="mi">48</span><span class="p">]:</span>
</span><span class='line'>                <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;PAUSE&quot;</span><span class="p">)</span>
</span><span class='line'>                <span class="n">pause</span><span class="p">()</span>
</span><span class='line'>            <span class="k">elif</span> <span class="n">uid</span> <span class="o">==</span> <span class="p">[</span><span class="mi">136</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">236</span><span class="p">,</span> <span class="mi">236</span><span class="p">,</span> <span class="mi">140</span><span class="p">]:</span>
</span><span class='line'>                <span class="n">set_volume</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>
</span><span class='line'>            <span class="k">elif</span> <span class="n">uid</span> <span class="o">==</span> <span class="p">[</span><span class="mi">136</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">247</span><span class="p">,</span> <span class="mi">42</span><span class="p">,</span> <span class="mi">81</span><span class="p">]:</span>
</span><span class='line'>                <span class="n">set_volume</span><span class="p">(</span><span class="mi">40</span><span class="p">)</span>
</span><span class='line'>            <span class="k">elif</span> <span class="n">uid</span> <span class="o">==</span> <span class="p">[</span><span class="mi">136</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">137</span><span class="p">,</span> <span class="mi">170</span><span class="p">,</span> <span class="mi">175</span><span class="p">]:</span>
</span><span class='line'>                <span class="n">play_music</span><span class="p">(</span><span class="s2">&quot;Star Wars A New Hope Soundtrack&quot;</span><span class="p">)</span>
</span><span class='line'>            <span class="k">elif</span> <span class="n">uid</span> <span class="o">==</span> <span class="p">[</span><span class="mi">136</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">148</span><span class="p">,</span> <span class="mi">191</span><span class="p">,</span> <span class="mi">167</span><span class="p">]:</span>
</span><span class='line'>                <span class="n">play_music</span><span class="p">(</span><span class="s2">&quot;Paco de Lucia&quot;</span><span class="p">)</span>
</span><span class='line'>            <span class="k">elif</span> <span class="n">uid</span> <span class="o">==</span> <span class="p">[</span><span class="mi">136</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">160</span><span class="p">,</span> <span class="mi">224</span><span class="p">,</span> <span class="mi">204</span><span class="p">]:</span>
</span><span class='line'>                <span class="n">play_music</span><span class="p">(</span><span class="s2">&quot;Dexter Gordon&quot;</span><span class="p">)</span>
</span><span class='line'>            <span class="k">elif</span> <span class="n">uid</span> <span class="o">==</span> <span class="p">[</span><span class="mi">136</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">197</span><span class="p">,</span> <span class="mi">238</span><span class="p">,</span> <span class="mi">167</span><span class="p">]:</span>
</span><span class='line'>                <span class="n">play_music</span><span class="p">(</span><span class="s2">&quot;Rainbow Connection&quot;</span><span class="p">)</span>
</span><span class='line'>            <span class="k">elif</span> <span class="n">uid</span> <span class="o">==</span> <span class="p">[</span><span class="mi">136</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">220</span><span class="p">,</span> <span class="mi">82</span><span class="p">]:</span>
</span><span class='line'>                <span class="n">play_music</span><span class="p">(</span><span class="s2">&quot;Genesis&quot;</span><span class="p">)</span>
</span><span class='line'>            <span class="k">elif</span> <span class="n">uid</span> <span class="o">==</span> <span class="p">[</span><span class="mi">136</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">232</span><span class="p">,</span> <span class="mi">57</span><span class="p">,</span> <span class="mi">93</span><span class="p">]:</span>
</span><span class='line'>                <span class="c1"># and so on...</span>
</span></code></pre></td></tr></table></div></figure>


<p>You&#8217;ll notice that there are special cards for pausing, setting a high
volume and setting a low volume. The rest of the cards map to music.</p>

<p>The main thing I&#8217;d like to improve at some point is to avoid hardcoding
a mapping of the card UIDs to music and instead program it using the
companion iPhone app by writing a payload to the card. The iOS side I
know how to do pretty quickly, but I&#8217;d have to spend more time to figure
out how to do it on the MFRC522 reader side of things.</p>

<h3>API Server</h3>

<p>There&#8217;s an API server that can be used to control the music box using
the companion iPhone app.</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
<span class='line-number'>42</span>
<span class='line-number'>43</span>
<span class='line-number'>44</span>
</pre></td><td class='code'><pre><code class='python'><span class='line'><span></span><span class="kn">from</span> <span class="nn">flask</span> <span class="kn">import</span> <span class="n">Flask</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="n">jsonify</span>
</span><span class='line'><span class="kn">import</span> <span class="nn">os</span>
</span><span class='line'>
</span><span class='line'><span class="n">api</span> <span class="o">=</span> <span class="n">Flask</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
</span><span class='line'><span class="n">echo_name</span> <span class="o">=</span> <span class="s2">&quot;MusicBox Echo&quot;</span>
</span><span class='line'><span class="n">current_dir</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">abspath</span><span class="p">(</span><span class="vm">__file__</span><span class="p">))</span>
</span><span class='line'><span class="n">alexa_control_script</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">current_dir</span><span class="p">,</span> <span class="s2">&quot;alexa_remote_control.sh&quot;</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'><span class="k">def</span> <span class="nf">alexa</span><span class="p">(</span><span class="n">command</span><span class="p">):</span>
</span><span class='line'>    <span class="n">os</span><span class="o">.</span><span class="n">system</span><span class="p">(</span>
</span><span class='line'>        <span class="s1">&#39;</span><span class="si">{alexa_control_script}</span><span class="s1"> -d &quot;</span><span class="si">{echo_name}</span><span class="s1">&quot; -e &quot;</span><span class="si">{command}</span><span class="s1">&quot;&#39;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span>
</span><span class='line'>            <span class="n">alexa_control_script</span><span class="o">=</span><span class="n">alexa_control_script</span><span class="p">,</span>
</span><span class='line'>            <span class="n">echo_name</span><span class="o">=</span><span class="n">echo_name</span><span class="p">,</span>
</span><span class='line'>            <span class="n">command</span><span class="o">=</span><span class="n">command</span><span class="p">,</span>
</span><span class='line'>        <span class="p">)</span>
</span><span class='line'>    <span class="p">)</span>
</span><span class='line'>
</span><span class='line'><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/pause&quot;</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s2">&quot;POST&quot;</span><span class="p">])</span>
</span><span class='line'><span class="k">def</span> <span class="nf">pause</span><span class="p">():</span>
</span><span class='line'>    <span class="n">alexa</span><span class="p">(</span><span class="s2">&quot;pause&quot;</span><span class="p">)</span>
</span><span class='line'>    <span class="k">return</span> <span class="s2">&quot;</span><span class="si">{}</span><span class="s2">&quot;</span>
</span><span class='line'>
</span><span class='line'><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/play&quot;</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s2">&quot;POST&quot;</span><span class="p">])</span>
</span><span class='line'><span class="k">def</span> <span class="nf">play</span><span class="p">():</span>
</span><span class='line'>    <span class="k">if</span> <span class="n">request</span><span class="o">.</span><span class="n">json</span><span class="p">:</span>
</span><span class='line'>        <span class="n">play_music</span><span class="p">(</span><span class="n">request</span><span class="o">.</span><span class="n">json</span><span class="p">[</span><span class="s2">&quot;query&quot;</span><span class="p">])</span>
</span><span class='line'>    <span class="k">else</span><span class="p">:</span>
</span><span class='line'>        <span class="n">alexa</span><span class="p">(</span><span class="s2">&quot;play&quot;</span><span class="p">)</span>
</span><span class='line'>    <span class="k">return</span> <span class="s2">&quot;</span><span class="si">{}</span><span class="s2">&quot;</span>
</span><span class='line'>
</span><span class='line'><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/play/&lt;query&gt;&quot;</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s2">&quot;POST&quot;</span><span class="p">])</span>
</span><span class='line'><span class="k">def</span> <span class="nf">play_music</span><span class="p">(</span><span class="n">query</span><span class="p">):</span>
</span><span class='line'>    <span class="nb">print</span><span class="p">(</span><span class="n">query</span><span class="p">)</span>
</span><span class='line'>    <span class="n">pause</span>
</span><span class='line'>    <span class="n">alexa</span><span class="p">(</span><span class="s2">&quot;playmusic:APPLE_MUSIC:</span><span class="si">{query}</span><span class="s2">&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">query</span><span class="o">=</span><span class="n">query</span><span class="p">))</span>
</span><span class='line'>    <span class="k">return</span> <span class="s2">&quot;</span><span class="si">{}</span><span class="s2">&quot;</span>
</span><span class='line'>
</span><span class='line'><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/volume/&lt;int:volume&gt;&quot;</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s2">&quot;POST&quot;</span><span class="p">])</span>
</span><span class='line'><span class="k">def</span> <span class="nf">set_volume</span><span class="p">(</span><span class="n">volume</span><span class="p">):</span>
</span><span class='line'>    <span class="n">alexa</span><span class="p">(</span><span class="s2">&quot;vol:</span><span class="si">{volume}</span><span class="s2">&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">volume</span><span class="o">=</span><span class="n">volume</span><span class="p">))</span>
</span><span class='line'>    <span class="k">return</span> <span class="s2">&quot;</span><span class="si">{}</span><span class="s2">&quot;</span>
</span><span class='line'>
</span><span class='line'><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&quot;__main__&quot;</span><span class="p">:</span>
</span><span class='line'>    <span class="n">api</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">host</span><span class="o">=</span><span class="s2">&quot;0.0.0.0&quot;</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>


<h3>iPhone App</h3>

<p>Of course I made an iPhone app using <a href="https://developer.apple.com/xcode/swiftui/">SwiftUI</a> &amp;
<a href="https://github.com/pointfreeco/swift-composable-architecture">Composable Architecture</a>.</p>

<p>This helps me quickly adjust the volume, play/pause and play specific
music. If my kid falls alseep to music, I can stop it without having to
walk into his room.</p>

<p>Siri intents work too, so I can play/pause music by speaking to Siri
without having to launch the app.</p>

<p><img src="https://jpsim.com/images/posts/musicbox/app.jpg" alt="" /></p>

<h2>Closed Source</h2>

<p>Hopefully this post is helpful to someone interested in building
something similar. I&#8217;ve posted enough code and details to get you
started, but I won&#8217;t be open sourcing the whole project because I&#8217;m just
not willing to field support questions or feature requests. I&#8217;m happy to
answer questions about my experience building this, but I&#8217;m sorry I
can&#8217;t help you figure out why something&#8217;s not working if you go build
something similar.</p>

<hr />

<h2>Addendum: HomePod Attempt</h2>

<p>At one point I wanted to use a stereo pair of HomePods for this project,
but all my attempts to reverse engineer a way to play music on them from
a Raspberry Pi were fruitless. I tried sniffing the network traffic via
Charles Proxy while playing music from the iOS Music app or even the
Shortcuts app and wasn&#8217;t able to crack it. I did end up getting it
working using <a href="https://github.com/owntone/owntone-server">forked-daap</a> but this set the HomePods in a
weird state. I don&#8217;t remember all the details because I gave up on that
approach for two reasons: the first is that I couldn&#8217;t get it to work
well and the second is that I wanted to keep the HomePods in our living
room while the music box was meant to be in my son&#8217;s bedroom.</p>

<hr />
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Evaluating SwiftSyntax for use in SwiftLint]]></title>
    <link href="https://jpsim.com/evaluating-swiftsyntax-for-use-in-swiftlint/"/>
    <updated>2018-11-22T14:50:00+00:00</updated>
    <id>https://jpsim.com/evaluating-swiftsyntax-for-use-in-swiftlint</id>
    <content type="html"><![CDATA[<p><strong>tl;dr; Implementing SwiftLint using SwiftSyntax instead of SourceKitten would make it run over 20x slower 😭</strong></p>

<p><strong>Update:</strong> Since writing this post, I learnt that SwiftSyntax&#8217;s upcoming byte tree deserialization mode will speed this up considerably.
I hope to post a follow-up article on this shortly.</p>

<p>I have for some time been looking forward to reimplementing some of <a href="https://github.com/realm/SwiftLint">SwiftLint</a>&#8217;s simpler syntax-only rules with <a href="https://github.com/apple/swift-syntax">SwiftSyntax</a>. If you&#8217;re not familiar with it, the recent <a href="https://nshipster.com/swiftsyntax/">NSHipster article</a> gives a great overview. My motivation for integrating it into SwiftLint was that it would be nice to use an officially maintained library directly to obtain the syntax tree rather than the open source but community-maintained <a href="https://github.com/jpsim/SourceKitten">SourceKitten</a> library. I was also under the false impression that SwiftSyntax would be significantly faster than SourceKit/SourceKitten.</p>

<p>SourceKitten gets its syntax tree by dynamically loading <a href="https://github.com/apple/swift/tree/master/tools/SourceKit">SourceKit</a> and making cross-process XPC calls to a SourceKit daemon. In a typical uncached lint run, SwiftLint spends a significant amount of time waiting on this syntax tree for each file being linted. Because SwiftSyntax is <a href="https://github.com/apple/swift-syntax#building-swiftsyntax-from-master">code-generated</a> from the same syntax definition files as the Swift compiler, I had (incorrectly) assumed that calculating a Swift file&#8217;s syntax tree using SwiftSyntax was done entirely in-process by the library, which would have lead to significant performance gains by avoiding the cross-process XPC call made by SourceKitten for equivalent functionality.</p>

<p>In reality, SwiftSyntax delegates all parsing &amp; lexing to the <code>swiftc</code> binary, <a href="https://github.com/apple/swift-syntax/blob/0.40200.0/Sources/SwiftSyntax/SwiftSyntax.swift#L100-L101">launching the process</a>, <a href="https://github.com/apple/swift-syntax/blob/0.40200.0/Sources/SwiftSyntax/SwiftSyntax.swift#L102">reading its output from stdout</a> and <a href="https://github.com/apple/swift-syntax/blob/0.40200.0/Sources/SwiftSyntax/SwiftSyntax.swift#L103-L104">deserializing the JSON response</a> into its <code>SourceFileSyntax</code> Swift type. This is repeated for each file being parsed 😱.</p>

<p><strong>Launching a new instance of the Swift compiler for each file parsed is orders of magnitude slower than SourceKitten&#8217;s XPC call to a long-lived SourceKit daemon.</strong></p>

<p>I discovered this after <a href="https://github.com/realm/SwiftLint/pull/2476">reimplementing</a> a very simple SwiftLint rule with a SwiftSyntax-based implementation: <a href="https://github.com/realm/SwiftLint/blob/master/Rules.md#fallthrough">Fallthrough</a>. This opt-in rule is a perfect proof-of-concept for integrating SwiftSyntax into SwiftLint because it literally just finds all occurrences of the <code>fallthrough</code> keyword and reports a violation at that location. I measured the time it took to lint a folder of ~100 Swift files from Lyft&#8217;s iOS codebase with only the <code>fallthrough</code> rule whitelisted.</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='yaml'><span class='line'><span></span><span class="c1"># .swiftlint.yml configuration file</span>
</span><span class='line'><span class="nt">included</span><span class="p">:</span>
</span><span class='line'><span class="w">  </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">path/to/lint/dir</span><span class="w"> </span><span class="c1"># contains ~100 Swift files</span>
</span><span class='line'><span class="nt">whitelist_rules</span><span class="p">:</span>
</span><span class='line'><span class="w">  </span><span class="p p-Indicator">-</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">fallthrough</span>
</span></code></pre></td></tr></table></div></figure>


<p>I compiled both SwiftLint from <code>master</code> and again with this <code>fallthrough-swift-syntax</code> branch with <code>swift build -c release</code> and named the binaries <code>swiftlint-master</code> and <code>swiftlint-swift-syntax</code>. I then benchmarked both binaries using the excellent <a href="https://github.com/sharkdp/hyperfine">hyperfine</a> utility.</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
</pre></td><td class='code'><pre><code class='shell'><span class='line'><span></span>$<span class="w"> </span>hyperfine<span class="w"> </span><span class="s1">&#39;./swiftlint-master lint --quiet --no-cache&#39;</span><span class="w"> </span><span class="s1">&#39;./swiftlint-swift-syntax lint --quiet --no-cache&#39;</span>
</span><span class='line'>Benchmark<span class="w"> </span><span class="c1">#1: ./swiftlint-master lint --quiet --no-cache</span>
</span><span class='line'><span class="w">  </span>Time<span class="w"> </span><span class="o">(</span>mean<span class="w"> </span>±<span class="w"> </span>σ<span class="o">)</span>:<span class="w">     </span><span class="m">231</span>.3<span class="w"> </span>ms<span class="w"> </span>±<span class="w">   </span><span class="m">5</span>.1<span class="w"> </span>ms<span class="w">    </span><span class="o">[</span>User:<span class="w"> </span><span class="m">130</span>.5<span class="w"> </span>ms,<span class="w"> </span>System:<span class="w"> </span><span class="m">29</span>.2<span class="w"> </span>ms<span class="o">]</span>
</span><span class='line'><span class="w">  </span>Range<span class="w"> </span><span class="o">(</span>min<span class="w"> </span>…<span class="w"> </span>max<span class="o">)</span>:<span class="w">   </span><span class="m">224</span>.3<span class="w"> </span>ms<span class="w"> </span>…<span class="w"> </span><span class="m">238</span>.3<span class="w"> </span>ms
</span><span class='line'><span class="w"> </span>
</span><span class='line'>Benchmark<span class="w"> </span><span class="c1">#2: ./swiftlint-swift-syntax lint --quiet --no-cache</span>
</span><span class='line'><span class="w">  </span>Time<span class="w"> </span><span class="o">(</span>mean<span class="w"> </span>±<span class="w"> </span>σ<span class="o">)</span>:<span class="w">      </span><span class="m">5</span>.254<span class="w"> </span>s<span class="w"> </span>±<span class="w">  </span><span class="m">0</span>.149<span class="w"> </span>s<span class="w">    </span><span class="o">[</span>User:<span class="w"> </span><span class="m">20</span>.309<span class="w"> </span>s,<span class="w"> </span>System:<span class="w"> </span><span class="m">23</span>.110<span class="w"> </span>s<span class="o">]</span>
</span><span class='line'><span class="w">  </span>Range<span class="w"> </span><span class="o">(</span>min<span class="w"> </span>…<span class="w"> </span>max<span class="o">)</span>:<span class="w">    </span><span class="m">4</span>.839<span class="w"> </span>s<span class="w"> </span>…<span class="w">  </span><span class="m">5</span>.354<span class="w"> </span>s
</span><span class='line'><span class="w"> </span>
</span><span class='line'>Summary
</span><span class='line'><span class="w">  </span><span class="s1">&#39;./swiftlint-master lint --quiet --no-cache&#39;</span><span class="w"> </span>ran
</span><span class='line'><span class="w">   </span><span class="m">22</span>.71<span class="w"> </span>±<span class="w"> </span><span class="m">0</span>.82<span class="w"> </span><span class="nb">times</span><span class="w"> </span>faster<span class="w"> </span>than<span class="w"> </span><span class="s1">&#39;./swiftlint-swift-syntax lint --quiet --no-cache&#39;</span>
</span></code></pre></td></tr></table></div></figure>


<p><strong>The SwiftSyntax version was 22x slower than the existing SourceKitten version</strong></p>

<p><em>Note that I ran SwiftLint with its caching mechanism and logging disabled to accurately measure the time it took just to perform the lint, rather than the overhead from logging or skipping the lint entirely by just returning cached results. Although logging only added 3ms to 10ms in my tests.</em></p>

<hr />

<p>Ultimately, this means SwiftLint will be keeping its SourceKitten-based implementation for the foreseeable future, unless SwiftSyntax removes its reliance on costly compiler invocations and drastically improves its performance. I really hope the Swift team can somehow find a way to move parsing and lexing into SwiftSyntax itself, making the library much more appealing to use.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Introducing Yams 1.0]]></title>
    <link href="https://jpsim.com/introducing-yams-1-dot-0/"/>
    <updated>2018-05-19T11:40:00+00:00</updated>
    <id>https://jpsim.com/introducing-yams-1-dot-0</id>
    <content type="html"><![CDATA[<p><img src="https://raw.githubusercontent.com/jpsim/Yams/master/yams.jpg" alt="Yams: A Sweet &amp; Swifty YAML Parser" /></p>

<p><a href="https://twitter.com/norio_nomura">Norio Nomura</a> and I (ok, honestly mostly Norio 😅) have been building a
Swift library for encoding &amp; decoding <a href="http://yaml.org">YAML</a> for the last 18 months and
it&#8217;s now stable enough to make a 1.0 release and share with the world.</p>

<p>It&#8217;s called Yams, you can find it on GitHub at <a href="https://github.com/jpsim/Yams">jpsim/Yams</a> and API
docs are located at <a href="https://jpsim.com/Yams">jpsim.com/Yams</a>.</p>

<p>You could say it&#8217;s a Swift binding for <a href="https://github.com/yaml/libyaml">LibYAML</a> but I&#8217;d argue it&#8217;s
much more than that.</p>

<p>I&#8217;m actually very happy with how this library ended up. It plays nicely with
Swift 4&#8217;s <a href="https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types">Codable protocol</a>, meaning that you get fast &amp; type-safe
encoding &amp; decoding that feels right at home in modern Swift.</p>

<p>Here&#8217;s a simple example of encoding &amp; decoding <code>Codable</code> types:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
</pre></td><td class='code'><pre><code class='swift'><span class='line'><span></span><span class="kd">import</span> <span class="nc">Yams</span>
</span><span class='line'>
</span><span class='line'><span class="kd">struct</span> <span class="nc">S</span><span class="p">:</span> <span class="n">Codable</span> <span class="p">{</span>
</span><span class='line'>  <span class="kd">var</span> <span class="nv">p</span><span class="p">:</span> <span class="nb">String</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="kd">let</span> <span class="nv">s</span> <span class="p">=</span> <span class="n">S</span><span class="p">(</span><span class="n">p</span><span class="p">:</span> <span class="s">&quot;test&quot;</span><span class="p">)</span>
</span><span class='line'><span class="kd">let</span> <span class="nv">encoder</span> <span class="p">=</span> <span class="n">YAMLEncoder</span><span class="p">()</span>
</span><span class='line'><span class="kd">let</span> <span class="nv">encodedYAML</span> <span class="p">=</span> <span class="k">try</span> <span class="n">encoder</span><span class="p">.</span><span class="n">encode</span><span class="p">(</span><span class="n">s</span><span class="p">)</span>
</span><span class='line'><span class="n">encodedYAML</span> <span class="p">==</span> <span class="s">&quot;&quot;&quot;</span>
</span><span class='line'><span class="s">  p: test</span>
</span><span class='line'>
</span><span class='line'><span class="s">  &quot;&quot;&quot;</span>
</span><span class='line'><span class="kd">let</span> <span class="nv">decoder</span> <span class="p">=</span> <span class="n">YAMLDecoder</span><span class="p">()</span>
</span><span class='line'><span class="kd">let</span> <span class="nv">decoded</span> <span class="p">=</span> <span class="k">try</span> <span class="n">decoder</span><span class="p">.</span><span class="n">decode</span><span class="p">(</span><span class="n">S</span><span class="p">.</span><span class="kc">self</span><span class="p">,</span> <span class="n">from</span><span class="p">:</span> <span class="n">encodedYAML</span><span class="p">)</span>
</span><span class='line'><span class="n">s</span><span class="p">.</span><span class="n">p</span> <span class="p">==</span> <span class="n">decoded</span><span class="p">.</span><span class="n">p</span>
</span></code></pre></td></tr></table></div></figure>


<p>Alternatively, you can use it to work with Swift scalar &amp; collection types,
which is probably the easiest way to start parsing arbitrary YAML for your
projects. Finally, there&#8217;s a third mode, which is a Yams-native API that best
translates to how LibYAML works.</p>

<p>This library&#8217;s been powering a number of popular projects that use YAML for
configuration, like <a href="https://github.com/realm/SwiftLint">SwiftLint</a>, <a href="https://github.com/SwiftGen/SwiftGen">SwiftGen</a>,
<a href="https://github.com/yonaskolb/XcodeGen">XcodeGen</a> &amp; used in <a href="https://github.com/jpsim/SourceKitten">SourceKitten</a> to parse Swift
Package Manager build manifests. So if you&#8217;ve wanted to add YAML configuration
files to your Swift CLI, or want to interoperate with other tools that process
YAML, I encourage you to give Yams a try.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Building a serverless password-protected photo gallery]]></title>
    <link href="https://jpsim.com/awspics/"/>
    <updated>2017-07-04T06:14:00+00:00</updated>
    <id>https://jpsim.com/awspics</id>
    <content type="html"><![CDATA[<p>Building a serverless photo gallery?
<em>Easy!</em>
Password-protecting that without adding servers?
<em>Surprisingly much more complex.</em></p>

<p><img src="https://github.com/jpsim/AWSPics/raw/master/assets/awspics.gif" alt="" /></p>

<h2>Goals</h2>

<blockquote><p>Host a self-contained, declarative infrastructure, password-protected,
data-driven static photo gallery to share personal pictures with friends and
family, without needing to run, maintain (or pay for) servers.</p></blockquote>

<p>With <a href="https://twitter.com/simjp/status/873604866043543552">the recent addition</a>
to our family, I wanted to set up a place where I could share pictures with
our closest friends and family. Facebook wasn&#8217;t an option because&#8230;
<a href="https://daringfireball.net/2017/06/fuck_facebook">yeah</a>,
<a href="http://www.slate.com/articles/technology/data_mine_1/2013/09/facebook_privacy_and_kids_don_t_post_photos_of_your_kids_online.html">because</a>
<a href="http://www.telegraph.co.uk/women/family/i-dont-put-pictures-of-my-children-on-facebook---and-you-shouldn/">many</a>
<a href="http://www.huffingtonpost.com.au/2016/02/07/can-i-post-photos-of-other-peoples-children_n_9184560.html">reasons</a>
<a href="https://medium.com/matter/beware-your-baby-s-face-is-online-and-on-sale-d33ae8cdaa9d">actually</a>.</p>

<p>Most of my family members are on Apple devices, while most of my friends are in
the Google/Android ecosystem. So for day to day sharing of moments, we post a
few pictures to iCloud photo sharing, and a few others to a WhatsApp group. But
neither of these can really serve as the canonical place where we&#8217;re storing
these photos long-term, because WhatsApp is pretty ephemeral, and the iCloud
photo sharing experience <em>sucks</em> on non-Apple devices.</p>

<h2>Architecture</h2>

<p>Unfortunately there&#8217;s no &#8220;put a password in front of AWS CloudFront&#8221; checkbox.
I wish there were, so I wouldn&#8217;t have had to build this. But there isn&#8217;t, so it
was time to roll up my sleeves and learn a bit about how modern web
infrastructure is built. Or at least my idea of it.</p>

<p><img src="https://github.com/jpsim/AWSPics/raw/master/assets/architecture.png" alt="" /></p>

<p>There are 7 main components:</p>

<ol>
<li><strong>CloudFront with restricted bucket access</strong> to prevent unauthenticated
access to the site or its pictures.</li>
<li><strong>Login lambda function</strong> to validate authentication and sign cookies to
allow access to restricted buckets.</li>
<li><strong>Source S3 bucket</strong> to store original pictures and metadata driving the
site.</li>
<li><strong>Resized S3 bucket</strong> to store resized versions of the original pictures.</li>
<li><strong>Web S3 bucket</strong> to store the static website generated from the data in the
source bucket.</li>
<li><strong>Resize lambda function</strong> to automatically resize images added to the source
S3 bucket and store them in the resized S3 bucket.</li>
<li><strong>Site builder lambda function</strong> to automatically rebuild the static website
when changes are made to the source S3 bucket.</li>
</ol>


<h2>Can it be simplified?</h2>

<p>Of course. There are a few ways I can think of to simplify this, but the
tradeoffs aren&#8217;t worthwhile IMO.</p>

<ol>
<li><strong>Resize images on demand.</strong> Rather than resize all the images when they&#8217;re
first added to the source bucket, the resize lambda could be exposed via a
CloudFront origin. However, since the static site only really uses two
image sizes, and that lambda functions have a significant
<a href="https://serverless.com/blog/keep-your-lambdas-warm/">cold start penalty</a>,
it&#8217;s much more efficient to just precompute the resized images.</li>
<li><strong>Consolidate buckets.</strong>  Rather than separate source, resized &amp; web buckets,
they could just be in a single bucket. However, this would just shift the
complexity a bit since the stack would then need to filter new object
notifications to know which function to invoke. Plus, right now to back up
the valuable content, I just need to periodically mirror the source bucket,
rather than all the derivative data in the resized and web buckets.</li>
</ol>


<h2>Problems?</h2>

<p>I have a few annoyances with this setup as-is.</p>

<p>One is that only the resized bucket triggers the site builder function. That
means that any other object modified in the source bucket, such as the
<code>metadata.yml</code> files that include album comments, don&#8217;t trigger a site build.</p>

<p>Another related problem is that for every new image in the source bucket, two
are created in the resized bucket, each one invoking the site builder function.
Not only that, but if I upload an album with lots of pictures all in one shot,
the site will be rebuilt twice for each picture! 🙀</p>

<p>Unfortunately, S3 buckets can only have a
<a href="https://stackoverflow.com/q/31471178/373262">single notification per event type</a>,
so we can&#8217;t trigger both the resize and site builder functions when new objects
are created on the source bucket.</p>

<p>I think the solution here would involve
<a href="http://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html">publishing S3 events to SNS or SQS</a>
and &#8220;debouncing&#8221; the site builder lambda, but again&#8230; <strong>#complexity</strong>.</p>

<p><img src="https://media.giphy.com/media/mYqaRkXyoGbcY/giphy.gif" alt="" /></p>

<p>I&#8217;ll probably do this eventually, but I&#8217;m in no hurry.</p>

<h2>Code</h2>

<p>I&#8217;ve open sourced the entire AWS stack on GitHub over at
<a href="https://github.com/jpsim/AWSPics">jpsim/AWSPics</a>. I&#8217;m also hosting a demo
site over at <a href="https://awspics.net">awspics.net</a> (use &#8220;username&#8221;/&#8221;password&#8221; as
credentials to check it out).</p>

<p>There&#8217;s a (long) video walkthrough <a href="https://youtu.be/010AGcY4uoE">on YouTube</a>
too, if that&#8217;s useful to follow along.</p>

<h2>Closing Thoughts</h2>

<p>Overall, I really enjoyed stepping outside my comfort zone of native
Swift/ObjC/C++ programming, learning a TON about several AWS services and
ultimately meeting my goal of setting up a private photo gallery.</p>

<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">If you think incremental Xcode builds take a while, try deploying changes to an AWS CloudFormation stack 😅 <a href="https://t.co/yTPsqarT92">pic.twitter.com/yTPsqarT92</a></p>&mdash; JP Simard (@simjp) <a href="https://twitter.com/simjp/status/881640404851884033">July 2, 2017</a></blockquote>


<script async src="https://jpsim.com//platform.twitter.com/widgets.js" charset="utf-8"></script>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Uncovering SourceKit]]></title>
    <link href="https://jpsim.com/uncovering-sourcekit/"/>
    <updated>2014-07-06T16:07:00+00:00</updated>
    <id>https://jpsim.com/uncovering-sourcekit</id>
    <content type="html"><![CDATA[<p>To support a <a href="http://developer.apple.com/swift">fancy new language</a>, nifty <a href="https://developer.apple.com/library/prerelease/ios/recipes/xcode_help-source_editor/ExploringandEvaluatingSwiftCodeinaPlayground/ExploringandEvaluatingSwiftCodeinaPlayground.html">realtime IDE</a> features and impressive <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithObjective-CAPIs.html">cross-language interoperability</a>, Apple had to develop several new underlying tools. Here, we&#8217;ll focus on <em>SourceKit</em>, Xcode&#8217;s under-appreciated sidekick.</p>

<p><img src="https://jpsim.com/images/posts/sidekick.jpg" alt="sidekick" /></p>

<center><sub>SourceKitSidekick temporarily wearing a cape</sub></center>


<p><br/></p>

<h2>What is SourceKit?</h2>

<p>SourceKit is the set of tools that enables most of Swift&#8217;s source code manipulation features: source code parsing, syntax highlighting, typesetting, autocomplete, cross-language header generation, and lots more.</p>

<h2>Architecture</h2>

<p>Xcode traditionally runs its compiler (<a href="http://clang.llvm.org">Clang</a>) <em>in-process</em>, which means that any time the compiler would crash, so would the IDE.</p>

<p><img src="https://jpsim.com/images/posts/house_of_cards.jpg" alt="house of cards" /></p>

<center><sub>Xcode architecture diagram</sub></center>


<p><br/></p>

<p>Exacerbating the problem, Xcode can easily invoke the compiler thousands of times to parse, highlight and typeset source code, all before a user ever hits <em>⌘+B</em>. That&#8217;s because unlike most editors (Vim/Sublime/etc), Xcode doesn&#8217;t use regular expressions to parse source code, but rather Clang&#8217;s powerful (though much more complex) parser/tokenizer.</p>

<p>Thankfully, Swift in Xcode 6 moves away from this architecture<sup>1</sup>, combining all these source code manipulation features into a separate process that communicates with Xcode through <a href="https://developer.apple.com/library/mac/documentation/macosx/conceptual/bpsystemstartup/chapters/CreatingXPCServices.html">XPC</a>: <code>sourcekitd</code>. This XPC daemon is launched whenever Xcode 6 loads any Swift code.</p>

<p><img src="https://jpsim.com/images/posts/sourcekit_terminated.jpg" alt="sourcekit terminated" /></p>

<center><sub>Life would be miserable if Xcode crashed every time this appeared</sub></center>


<p><br/></p>

<h2>How Xcode uses SourceKit</h2>

<p>Since SourceKit is a private and undocumented tool, we need to get a little creative to learn how to use it. By setting the <code>SOURCEKIT_LOGGING</code><sup>2</sup> environment variable, Xcode will log its SourceKit communications to <code>stdout</code>, allowing us to monitor its communications in realtime. This is how many of the commands covered in this article were discovered.</p>

<h2>Unified Symbol Resolution</h2>

<p>SourceKit uses a Clang feature called the USR (Unified Symbol Resolution) as a unique identifier for a source code token (i.e. class, property, method, etc.). This is what allows you to <em>⌘+click</em> any token in Xcode and navigate to its definition. The USR is even more powerful now that it can unify a representation across languages (Swift/ObjC).</p>

<p><img src="https://jpsim.com/images/posts/usr.jpg" alt="usr" /></p>

<center><sub>The USR at work</sub></center>


<p><br/></p>

<p>To print the USR&#8217;s from a Swift file (and their locations), you can run the following command:</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>$ xcrun swift-ide-test -print-usrs -source-filename=Musician.swift
</span><span class='line'>10:7 s:C14swift_ide_test8Musician
</span><span class='line'>14:9 s:vC14swift_ide_test8Musician4nameSS
</span><span class='line'>19:9 s:vC14swift_ide_test8Musician9birthyearSu
</span><span class='line'>33:5 s:FC14swift_ide_test8MusiciancFMS0_FT4nameSS9birthyearSu_S0_
</span><span class='line'>33:10 s:vFC14swift_ide_test8MusiciancFMS0_FT4nameSS9birthyearSu_S0_L_4nameSS
</span><span class='line'>33:24 s:vFC14swift_ide_test8MusiciancFMS0_FT4nameSS9birthyearSu_S0_L_9birthyearSu
</span><span class='line'>34:9 s:vFC14swift_ide_test8MusiciancFMS0_FT4nameSS9birthyearSu_S0_L_4selfS0_
</span><span class='line'>34:21 s:vFC14swift_ide_test8MusiciancFMS0_FT4nameSS9birthyearSu_S0_L_4nameSS
</span><span class='line'>35:9 s:vFC14swift_ide_test8MusiciancFMS0_FT4nameSS9birthyearSu_S0_L_4selfS0_
</span><span class='line'>35:26 s:vFC14swift_ide_test8MusiciancFMS0_FT4nameSS9birthyearSu_S0_L_9birthyearSu</span></code></pre></td></tr></table></div></figure>


<h2>Swift<em>ish</em> header generation</h2>

<p><em>⌘+clicking</em> on a token defined in Objective-C from Swift will cause Xcode to trigger a Swift-like header to be generated. I say Swift-like because this generated file is not valid Swift<sup>3</sup>, but at least displays the Swift syntax equivalent to the Objective-C tokens.</p>

<p><a href="https://jpsim.com/images/posts/generated_swift_header.jpg"><img src="https://jpsim.com/images/posts/generated_swift_header.jpg" alt="Generated Swift Header" /></a></p>

<center><sub>Left: original Objective-C header. Right: SourceKit-generated Swift-ish version.</sub></center>


<p><br/></p>

<h2>Using SourceKit from the command line</h2>

<p><img src="https://jpsim.com/images/posts/sourcekit_playground.jpg" alt="SourceKit Playground" /></p>

<p>There are 3 main command line tools that allow to interact with SourceKit: <code>sourcekitd-test</code>, <code>swift-ide-test</code> and <code>swift</code>.</p>

<p>I compiled a shell script with documentation that runs through many useful commands like syntax highlighting, interface generation, AST parsing, demangling, and more.</p>

<p>The script is available on GitHub as a <a href="https://gist.github.com/jpsim/13971c81445219db1c63#file-sourcekit_playground-sh">gist</a>.</p>

<h2>3rd Party Tools Using SourceKit</h2>

<p>Because SourceKit lives outside of Xcode, it’s possible to leverage it to build anything from a Swift IDE to a documentation generator.</p>

<h3>jazzy<sup>♪♫</sup></h3>

<p><img src="https://jpsim.com/images/posts/jazzy.jpg" alt="jazzy" /></p>

<p><a href="https://github.com/realm/jazzy">jazzy</a> is a command-line utility that generates documentation for your Swift and Objective-C projects. It uses SourceKit to derive Swift syntax from Objective-C defined tokens (i.e. class, property, method, etc.).</p>

<h3>SwiftEdit</h3>

<p><a href="https://github.com/jpsim/SwiftEdit">SwiftEdit</a> is a proof-of-concept editor that supports syntax highlighting for Swift files.</p>

<p><img src="https://jpsim.com/images/posts/SwiftEdit.png" alt="SwiftEdit" /></p>

<h2>SourceKit &amp; You</h2>

<p>We’re just scratching the surface of what’s possible to build with SourceKit. Tools could be made to measure cross-language code coverage, or provide an editor where Objective-C and Swift can be edited simultaneously. Hopefully this article inspires you to build something with SourceKit and improve our tools in the process.</p>

<hr />

<p><em>1: Objective-C in Xcode 6 (Beta 2) doesn&#8217;t use SourceKit at all, keeping Xcode&#8217;s traditional clang-in-process architecture. I expect this to change before Xcode 6 GM.</em></p>

<p><em>2: For SourceKit logging, launch Xcode with <sub><code>export SOURCEKIT_LOGGING=3 &amp;&amp; /Applications/Xcode6-Beta2.app/Contents/MacOS/Xcode</code></sub></em></p>

<p><em>3: Speculation: I expect private Swift modules to expose public interfaces using a similar syntax once the language has <a href="https://github.com/ksm/SwiftInFlux#access-control">access control mechanisms</a>.</em></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[SF Swift Meetup]]></title>
    <link href="https://jpsim.com/sf-swift-meetup/"/>
    <updated>2014-06-27T14:58:00+00:00</updated>
    <id>https://jpsim.com/sf-swift-meetup</id>
    <content type="html"><![CDATA[<p>I recently gave a talk on <a href="https://developer.apple.com/swift">Swift</a> at the <a href="http://www.meetup.com/swift-language">SF/SV Swift Language User Group</a> meetup. We&#8217;ve made the whole thing available online:</p>

<ul>
<li><a href="http://realm.io/news/swift-unchartered-territory-swift-intro-and-internals">Video</a></li>
<li><a href="https://speakerdeck.com/jpsim/swift-uncharted-territory">Slides</a></li>
<li><a href="https://github.com/jpsim/talks">Slides Source</a></li>
</ul>


<p>The first half of the talk covers the basic features and syntax of the language. The rest of the talk touches on some more in-depth discoveries about the runtime, introspection and how the tools work behind the scenes.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Use a VPN!]]></title>
    <link href="https://jpsim.com/use-a-vpn/"/>
    <updated>2014-04-13T12:22:00+00:00</updated>
    <id>https://jpsim.com/use-a-vpn</id>
    <content type="html"><![CDATA[<p>Using a private VPN has never been easier, it&#8217;s foolish not to do it. It takes ~60s and you&#8217;re protected on untrusted WiFi networks.</p>

<p>This article is more public service announcement than tutorial, since it&#8217;s really so easy you have no reason not to do it.</p>

<p>I&#8217;ll be showing you how to set up <a href="https://openvpn.net">OpenVPN</a> on <a href="https://www.digitalocean.com">DigitalOcean</a> using <a href="https://github.com/pearkes/tugboat">tugboat</a>, but any server provider will do.</p>

<h2>1: Spin up an instance</h2>

<p><code>$ tugboat create vpn</code></p>

<h2>2: Install OpenVPN Access Server</h2>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>$ tugboat ssh vpn
</span><span class='line'>$ wget http://swupdate.openvpn.org/as/openvpn-as-2.0.6-Ubuntu13.amd64.deb
</span><span class='line'>$ dpkg -i openvpn-as-2.0.6-Ubuntu13.amd64.deb
</span><span class='line'>$ sudo passwd openvpn</span></code></pre></td></tr></table></div></figure>


<p>The steps are 1) ssh, 2) download 3) install 4) set password. It&#8217;s that easy.</p>

<p>You can find the latest OpenVPN AS package for your system <a href="https://openvpn.net/index.php/access-server/download-openvpn-as-sw.html">here</a>.</p>

<h2>3: Download your client</h2>

<p>In the STDOUT from the <code>dpkg -i</code>, you&#8217;ll see a <code>Client UI</code> URL to connect to from your computer/smartphone to download the client. It&#8217;ll look like this:</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>$ Access Server web UIs are available here:
</span><span class='line'>$ Admin  UI: https://123.456.789.123:943/admin
</span><span class='line'>$ Client UI: https://123.456.789.123:943/</span></code></pre></td></tr></table></div></figure>


<p>Accessing this URL will likely give you a certificate warning in your browser. This is because OpenVPN created a self-signed SSL certificate that your browser won&#8217;t recognize. You can either proceed anyway, or download the auto-generated cert from your machine directly, and then proceed. I wasn&#8217;t able to do this, so if you know how, please comment and I&#8217;ll update this article.</p>

<h2>4: Connect</h2>

<p>Install the client, connect and voilà. You&#8217;re now tunneling all your traffic through your vpn!</p>

<h2>5 (optional): Save the DigitalOcean image</h2>

<p>To save on cost, I only create this VPN server (from a saved image) when I need it and destroy it when I&#8217;m done. This saves on hosting costs <strong>AND</strong> DigitalOcean doesn&#8217;t <em>actually</em> <a href="https://www.digitalocean.com/community/questions/questions-on-snapshot-billing">charge for snapshots</a>!!!</p>

<h2>Final Thoughts</h2>

<p>I&#8217;m writing this article from San Francisco&#8217;s public city WiFi, through my newly minted OpenVPN server, without fear of a malicious San Franciscan sniffing my sensitive data :).</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Moving to SF]]></title>
    <link href="https://jpsim.com/moving-to-sf/"/>
    <updated>2014-03-31T13:59:00+00:00</updated>
    <id>https://jpsim.com/moving-to-sf</id>
    <content type="html"><![CDATA[<p>I&#8217;m excited to share that I&#8217;ll be joining an awesome <a href="http://ycombinator.org">YC</a> company in San Francisco, starting in 2 weeks!</p>

<p>The company has yet to launch, so I can&#8217;t say much about it, but I <em>can</em> tell you that I wouldn&#8217;t have joined it if I didn&#8217;t think it has the potential to change how software is built on a fundamental level.</p>

<p>This decision is actually the result of over 6 weeks of careful job hunting and deliberation, so it wasn&#8217;t done on a whim.</p>

<p>I&#8217;ve had a blast building <a href="http://magneticbear.com">Magnetic Bear Studios</a> over the last 3 years, but it&#8217;s time to move on. The truth is, I&#8217;m a product builder, not a business builder. It&#8217;s what I love. And that&#8217;s what I&#8217;ll be doing at this new job. The other stuff was always incidental to me, but was taking up the majority of my attention.</p>

<p>I look forward to sharing more with you once we launch!</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[JPSThumbnailAnnotation updated for iOS 7]]></title>
    <link href="https://jpsim.com/jpsthumbnailannotation-updated-for-ios-7/"/>
    <updated>2014-03-27T11:55:00+00:00</updated>
    <id>https://jpsim.com/jpsthumbnailannotation-updated-for-ios-7</id>
    <content type="html"><![CDATA[<p>I&#8217;ve just given a fresh coat of iOS7-flavoured paint to my most popular open-source library: JPSThumbnailAnnotation.</p>

<p>Check it out on <a href="https://github.com/jpsim/JPSThumbnailAnnotation">GitHub</a> or just add it to CocoaPods: <code>pod 'JPSThumbnailAnnotation'</code>.</p>

<p><img src="https://github.com/jpsim/JPSThumbnailAnnotation/raw/master/screenshots2.jpg" alt="Screenshots" /></p>

<p>I decided not to maintain the iOS 6 <em>style</em>, but iOS 6 is still supported.</p>

<p>By the way, this is what the old style looked like. It&#8217;s still in git, if you need to find it for some reason.</p>

<p><img src="https://github.com/jpsim/JPSThumbnailAnnotation/raw/master/screenshots.jpg" alt="Old Screenshots" /></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Keyboard Layout Guide]]></title>
    <link href="https://jpsim.com/keyboard-layout-guide/"/>
    <updated>2014-03-26T14:41:00+00:00</updated>
    <id>https://jpsim.com/keyboard-layout-guide</id>
    <content type="html"><![CDATA[<p>I really like iOS 7&#8217;s <code>topLayoutGuide</code> and <code>bottomLayoutGuide</code>. They&#8217;re immensely useful. But in my adventures with Auto Layout, I&#8217;ve often wished that Apple had added a third member to this exclusive group: a sort of <code>keyboardLayoutGuide</code>.</p>

<p>It&#8217;s a bit of a drag to have to set up <code>NSNotification</code> observers just to be able to keep your textfield on the screen when the keyboard barges into view. So I added a <code>keyboardLayoutGuide</code> to <code>UIViewController</code> myself. You can check it out on GitHub here: <a href="https://github.com/jpsim/JPSKeyboardLayoutGuide">jpsim/JPSKeyboardLayoutGuide</a>.</p>

<p>If you&#8217;ve used Apple&#8217;s layout guides before, you&#8217;ll know that they&#8217;re actually just <code>id</code>&#8217;s that conform to the <a href="https://developer.apple.com/library/ios/documentation/uikit/reference/UILayoutSupport_Protocol/Reference/Reference.html"><code>UILayoutSupport</code> Protocol</a>. So I figured that having the guide just be a zero-sized UIView was probably the best way to go. This way, I can easily add it to the view and bind it to the keyboard frame by modifying a constraint&#8217;s constant.</p>

<p>In all honesty, I dislike the inheritance approach I took here; it seems like the easy way out. I&#8217;m hoping that either myself or a contributor will have a stroke of genius and find a composition-based way to do this. Perhaps a starting point would be a category on <code>UIViewController</code> along with an associated object as the <code>keyboardLayoutGuide</code> property&#8230; But then the <code>NSNotification</code>s will be troublesome. If you have an idea, please fork and send a PR or open up an issue on <a href="https://github.com/jpsim/JPSKeyboardLayoutGuide">github</a>!</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Private Pods]]></title>
    <link href="https://jpsim.com/private-pods/"/>
    <updated>2014-03-17T19:12:00+00:00</updated>
    <id>https://jpsim.com/private-pods</id>
    <content type="html"><![CDATA[<p>Keeping a project modular is a lofty software development goal that we all strive for even though it can never be fully attained.</p>

<p>One way to encourage a modular architecture is to use a dependency manager like <a href="http://cocoapods.org">Cocoapods</a>. I&#8217;ve blogged about using Cocoapods <a href="https://jpsim.com/cocoapods-tips-tricks">before</a>, but I didn&#8217;t touch on splitting up portions of your code that wouldn&#8217;t benefit from being open-sourced. Either because they&#8217;re too application-specific, or they&#8217;re proprietary.</p>

<p>For example, say you were building an application backed by a private web API, that needed to persist data to disk. A naïve implementation (<em>read what <strong>most</strong> of us would do, including myself</em>) would combine UI, API and persistence all in one project/workspace. In a fully modular app, UI/API/persistence would all have some degree of separation.</p>

<p>Private pods go a long way to help split up your codebase into logical modules. This becomes especially valuable in medium-large projects with 10k+ lines of code.</p>

<p>There are two main ways to make a private pod: remote and local.</p>

<h3>Remote</h3>

<p>You can either create a Specs-like private repo as indicated in <a href="http://guides.cocoapods.org/making/private-cocoapods.html">this official Cocoapods guide</a> or you can host your <code>.podspec</code> online and reference it directly in your <code>Podfile</code>.</p>

<p>Here&#8217;s how you&#8217;d reference it directly in a <code>Podfile</code>:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span></span><span class="n">pod</span><span class="w"> </span><span class="s1">&#39;PrivatePod&#39;</span><span class="p">,</span><span class="w"> </span><span class="ss">:podspec</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="s1">&#39;https://github.com/jpsim/JPSDisplayLink/raw/master/JPSDisplayLink.podspec&#39;</span>
</span></code></pre></td></tr></table></div></figure>


<h3>Local</h3>

<p>Though I find it unlikely, it might make sense to keep your private pods in the same repo as your project. In which case, you&#8217;d commit all its code in a special folder (i.e. <code>local_pods/POD_NAME</code>) and you&#8217;d specify the podspec path in your <code>Podfile</code>.</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span></span><span class="n">pod</span><span class="w"> </span><span class="s1">&#39;PrivatePod&#39;</span><span class="p">,</span><span class="w"> </span><span class="ss">:path</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="s1">&#39;local_pods/PrivatePod&#39;</span>
</span></code></pre></td></tr></table></div></figure>


<p>In this case, the <code>.podspec</code> would be located at <code>local_pods/PrivatePod/PrivatePod.podspec</code>.</p>

<p><em>Note: I&#8217;ve had inconsistent results writing local podspecs. Specs that worked perfectly in a remote repo didn&#8217;t work at all locally for no apparent reason. Your mileage may vary, I might have made a stupid mistake.</em></p>

<h2>Word of caution</h2>

<p>Splitting up your codebase is not something you should do for its own sake. The additional overhead probably won&#8217;t be worthwhile in smaller projects. But the larger the project, the bigger the gain.</p>

<p>On that note, I hope this article helps you decide how you want to split your monolithic 300k line app. But hey, you could always build static libraries instead (<em>cue laughter</em>).</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[CocoaPods Tips & Tricks]]></title>
    <link href="https://jpsim.com/cocoapods-tips-tricks/"/>
    <updated>2014-03-12T09:45:00+00:00</updated>
    <id>https://jpsim.com/cocoapods-tips-tricks</id>
    <content type="html"><![CDATA[<p><a href="http://cocoapods.org">CocoaPods</a> documentation isn&#8217;t very opiniated, which can cause confusion for newcomers. Here are a few tricks I&#8217;ve picked up after building a few dozen projects with CocoaPods.</p>

<h2>Source Control</h2>

<p>Should you check in <code>Podfile.lock</code>? How about your <code>*.xcworkspace</code> and the <code>Pods</code> directory? Since all of these are automatically generated by running <code>pod install</code>, is it redundant to also include them in your source control?</p>

<p>Some CocoaPods maintainers recommend to check everything in.</p>

<p>My approach is to check in <code>Podfile.lock</code> but not <code>xcworkspace</code> and <code>Pods</code>. Here&#8217;s why:</p>

<h3>Podfile.lock</h3>

<p>Checking in <code>Podfile.lock</code> has the advantage of keeping track of the pod versions last used when working on the project. Also, if you run <code>pod install</code> and some pods have changed versions, you&#8217;ll see the change in your next commit, encouraging you to confirm that nothing broke.</p>

<h3>Workspace</h3>

<p>In most cases, checking in the <code>*.xcworkspace</code> to source control is unnecessary, since <code>pod install</code> will automatically generate it for you. There are rare cases in which you need to customize it, in which case you should check it in.</p>

<h3>Pods Directory</h3>

<p>Checking in the <code>Pods</code> directory is a contested issue. Checking it in allows full control of the pods and will ensure you review every change to your vendored code. Not checking it in will keep your code more decoupled from its dependencies and clean up your commit logs. I don&#8217;t do it because it ends up creating too many commit messages, especially if you&#8217;re using a library which is updated very often, like <a href="https://github.com/AFNetworking/AFNetworking">AFNetworking</a>. Most dependency managers in other languages tend to not check in dependencies as well (i.e. npm &amp; rubygems).</p>

<h3>Finalizing a Project</h3>

<p>If I&#8217;m going to stop working on a project for a while, or I&#8217;m handing it off to a client, I&#8217;ll create a branch with everything checked in. <code>Podfile.lock</code>, <code>*.xcworkspace</code> and <code>Pods</code>. This way, even if pods change over time, or they&#8217;re removed from github, or whatever happens, I&#8217;m sure to have a working version of my project.</p>

<h3>Resources</h3>

<p>No matter what strategy you choose to take for CocoaPods SCM, be aware of the pros/cons of each.</p>

<p>Here are a few resources to help you decide:</p>

<ul>
<li><a href="https://github.com/CocoaPods/pod-template/blob/master/.gitignore">Official CocoaPods <code>.gitignore</code></a></li>
<li><a href="https://github.com/swizzlr/integer">CocoaPods plugin to maintain pod integrity</a></li>
<li><a href="https://twitter.com/simjp/status/443779386865500160">An interesting Twitter conversation about this article :)</a></li>
</ul>


<h2>Modifying Pods</h2>

<p>Before modifying a pod, ask yourself if it&#8217;s necessary. Often, a category or subclass will do.</p>

<p>Pre-CocoaPods, when all vendored code was checked in, it was trivial to make small changes to 3rd party code. It&#8217;s still easy now, but not as obvious. There are many ways to make changes to Pods in your project:</p>

<h3>1. Pull Request</h3>

<p>If your modifications are useful to other users of the 3rd party library, then consider submitting a <a href="https://help.github.com/articles/using-pull-requests">Pull Request</a>. This way, everyone benefits. Hopefully it will be merged quickly.</p>

<h3>2. Fork</h3>

<p>Certain projects are notorious for taking a long time to merge PR&#8217;s (<a href="https://github.com/kif-framework/KIF">cough</a>). In those cases or cases when your PR&#8217;s aren&#8217;t accepted, it can be useful to just point your <code>Podfile</code> to your fork of the project. Add this to your <code>Podfile</code>:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span></span><span class="n">pod</span><span class="w"> </span><span class="s1">&#39;KIF&#39;</span><span class="p">,</span><span class="w"> </span><span class="ss">:git</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="s1">&#39;https://github.com/ksuther/KIF&#39;</span>
</span></code></pre></td></tr></table></div></figure>


<p>The downside to this approach is that you have to keep your fork up to date, which is where the patchfile approach comes in.</p>

<h3>3. Diffs</h3>

<p>If your modifications are small, I&#8217;d encourage you to use patchfiles (<code>*.diff</code>) to apply them to your pods. This will apply your modifications every time you run <code>pod install</code>, with the added benefit of using the latest upstream updates. To see how it works, take a look at this github project: <a href="https://github.com/jpsim/pod-diffs">jpsim/pod-diffs</a>.</p>

<p><em>Sidenote: patchfiles are super neat! Even if you modify code around your modification or the line numbers change, it&#8217;s smart enought to find it again!</em></p>

<h3>4. Check it in</h3>

<p>This is a last resort. If all other methods fail, check in your vendored code into your project outside of CocoaPods. Then, you&#8217;re free to modify it in any way you see fit. But then you need to update it manually when the upstream version changes.</p>

<h2>Continuous Integration &amp; Xcode Bots</h2>

<p>There&#8217;s this misconception that using Xcode 5&#8217;s <a href="https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/xcode_guide-continuous_integration/ConfigureBots/ConfigureBots.html">Bots</a> feature for CI with CocoaPods projects means that you <strong>must</strong> check in your pods. In fact, Xcode Bots have the ability to run scripts prior to building your app. Check out <a href="https://gist.github.com/mtitolo/f5283c54e300d88d9418">this gist</a> for a comprehensive guide to Bots and CocoaPods.</p>

<hr />

<p><em>Thanks to @alloy, @aschndr, @ashfurrow &amp; @swizzlr for their feedback while writing this article</em></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[JPSVolumeButtonHandler]]></title>
    <link href="https://jpsim.com/jpsvolumebuttonhandler/"/>
    <updated>2014-02-19T14:38:00+00:00</updated>
    <id>https://jpsim.com/jpsvolumebuttonhandler</id>
    <content type="html"><![CDATA[<p>The fun times building a <a href="https://github.com/jpsim/JPSVolumeButtonHandler">tiny open source class</a> to use an iOS device&#8217;s hardware volume buttons in 3rd party apps.</p>

<p>This wasn&#8217;t <em>too</em> hard, but the I thought the process merited a short blog post.</p>

<p>I recently made a pixel-for-pixel clone of Apple&#8217;s iOS 7 <code>UIImagePickerController</code> (the camera portion, not the library portion). The full code can be found on <a href="https://github.com/jpsim/JPSImagePickerController">github</a>.</p>

<p>My motivation for doing this was that I was working on a client app that included functionality to take a picture and analyze it to determine whether or not it passed certain quality checks (legibility, resolution, completeness, etc). I wanted to display a message based on the result of this quality check during the &#8220;review&#8221; process of <code>UIImagePickerController</code>.</p>

<p><img src="https://raw.github.com/jpsim/JPSImagePickerController/master/screenshots.png" alt="JPSImagePickerController in action" /></p>

<p>The part I want to discuss is the code that uses the phone&#8217;s hardware volume up button to snap a picture.</p>

<p>There are a few hurdles to jump over to make this seamless:</p>

<ol>
<li>There&#8217;s no official API or notification to observe the audio volume or hardware volume button presses</li>
<li><code>UIImagePickerController</code> doesn&#8217;t pass-through volume button presses to the system audio</li>
<li>There&#8217;s no official way to stop the system audio from being modified on a volume button press</li>
<li>The only way to disable the HUD that appears on volume change is to display an <code>MPVolumeView</code> (it won&#8217;t work if it&#8217;s hidden or if its alpha is zero)</li>
<li>The only way to set the system audio level is to use a deprecated method (i.e. if we want to undo a volume change caused by a volume button press)</li>
<li>The system audio volume won&#8217;t go up if it&#8217;s already set to 1 and won&#8217;t go down if it&#8217;s set to 0. This means that button presses won&#8217;t be registered if audio is at its maximum or its minimum.</li>
</ol>


<p>So I wrote a class called <a href="https://github.com/jpsim/JPSVolumeButtonHandler"><code>JPSVolumeButtonHandler</code></a> that solves the previous problems in the following way:</p>

<ol>
<li>Create an off-screen <code>MPVolumeView</code></li>
<li>If the volume is at 1, set it to <code>0.99999f</code>, if it&#8217;s at 0, set it to <code>0.00001f</code>, otherwise do nothing</li>
<li>KVO observe the <code>outputVolume</code> property of an <code>AVAudioSession</code></li>
<li>Set the <code>AVAudioSession</code>&#8217;s category to <code>AVAudioSessionCategoryAmbient</code> to not duck any other volume</li>
<li>Trigger up/down code blocks when KVO notifies us of a change</li>
<li>Reset the system audio to its initial value when KVO notifies us that it has changed</li>
<li>Discard KVO notifications when the new volume value is the same as our initial value, since that means we reset the volume</li>
</ol>


<p>Hopefully either the <a href="https://github.com/jpsim/JPSImagePickerController">image picker clone</a>, or this <a href="https://github.com/jpsim/JPSVolumeButtonHandler">volume button class</a> ends up being useful to a few people.</p>

<p>Thanks for reading!</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[WWDC needs to <span>radically</span> change]]></title>
    <link href="https://jpsim.com/wwdc-radically-change/"/>
    <updated>2013-04-26T16:53:00+00:00</updated>
    <id>https://jpsim.com/wwdc-radically-change</id>
    <content type="html"><![CDATA[<p>This is my road (so far) to WWDC13. This should serve as a reminder to my future self to avoid WWDC next year.</p>

<p>The effort that went into getting a ticket is far more than I&#8217;m willing to admit&#8230; it&#8217;s JUST a conference, after all! Here&#8217;s a recap:</p>

<h2>Early April</h2>

<ol>
<li>Start researching WWDC history to be well prepared (see last section). I felt this was critical to get a ticket.</li>
<li>Rumors and trends converge towards a June 10-14 schedule, with an April 25th announcement</li>
<li>Sign up to 2 different WWDC announcement notification services (for redundancy)</li>
<li>Book round-trip flights and accommodations early to avoid much higher costs once they skyrocket after the announcement.</li>
</ol>


<h2>April 25</h2>

<ol>
<li>9:55, start repeatedly refreshing WWDC page</li>
<li>10:00:05, see purchase button, enter info</li>
<li>10:00:55, click &#8220;complete order&#8221;</li>
<li>Get error, enter info again</li>
<li>10:01:55, click &#8220;complete order&#8221;</li>
<li>Get redirected to WWDC where it says <a href="http://i97.photobucket.com/albums/l230/Insider2000/SpitCereal.png">&#8220;sold out&#8221;</a></li>
<li>Mope for a few minutes</li>
<li>Acceptance: Oh well, it&#8217;s just a conference. I&#8217;ll take this as a week-long vacation in SF. Go on with the rest of my day.</li>
<li>16:30, receive a phone call from Apple rep saying <strong>&#8220;We see you encountered some errors while purchasing your WWDC tickets this morning. I&#8217;d like to let you know that we have a ticket reserved for you and we&#8217;ll be sending you an email with information on how to complete your purchase in the next twelve hours [sic].&#8221;</strong></li>
<li>Celebrate for a few minutes</li>
</ol>


<h2>April 26</h2>

<ol>
<li>14:27, receive email from Apple with instructions to complete the purchase</li>
<li>16:00, attempt to complete the purchase but run into another set of errors on:

<ul>
<li>Google Chrome</li>
<li>OS X Safari</li>
<li>iOS Safari</li>
<li>Firefox</li>
<li>reset OS X Safari</li>
<li>it works!</li>
</ul>
</li>
<li>Finally complete the order.</li>
</ol>


<p>Note to self&#8230; avoid WWDC next year, people will be resort to writing bots to buy their tickets and the conference will sell out in 0.04s.</p>

<h2>WWDC Stats</h2>

<h2>Conference Dates</h2>

<ul>
<li>2003: June 23 to June 27</li>
<li>2004: June 28 to July 2</li>
<li>2005: June 6 to June 10</li>
<li>2006: August 7 to August 11</li>
<li>2007: June 11 to June 15</li>
<li>2008: June 9 to June 13</li>
<li>2009: June 8 to June 12</li>
<li>2010: June 7 to June 11</li>
<li>2011: June 6 to June 10</li>
<li>2012: June 11 to June 15</li>
</ul>


<h2>Announcement Dates, Times and Sell Out Durations</h2>

<ul>
<li>2010: April 28 (8 days)</li>
<li>2011: March 28 (12hrs: 16x faster than previous year)</li>
<li>2012: April 25 (1h43m: 7x faster than previous year)</li>
<li>2013: April 25, 10:00am (90s: 70x faster than previous year)</li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[JPSThumbnailAnnotation]]></title>
    <link href="https://jpsim.com/jpsthumbnailannotation/"/>
    <updated>2013-04-22T06:43:00+00:00</updated>
    <id>https://jpsim.com/jpsthumbnailannotation</id>
    <content type="html"><![CDATA[<p>I just released a new iOS component called <a href="https://github.com/jpsim/JPSThumbnailAnnotation">JPSThumbnailAnnotation</a> which is a great way to display thumbnails on a map.</p>

<p><img src="https://github.com/jpsim/JPSThumbnailAnnotation/raw/master/screenshots.jpg" alt="JPSThumbnailAnnotation in action" /></p>

<p>This component was actually originally built at <a href="http://mgn.tc">MBS</a> for the ScalaOne iPhone app (now called TypesafeCon). You can find the original source <a href="https://github.com/magneticbear/scalaone_iphone">here</a>.</p>

<p>After seeing <a href="http://samvermette.com">Sam Vermette</a>&#8217;s talk on Saturday at <a href="http://nsnorth.ca">NSNorth</a> on Open Source and his philosophy towards it, I decided to go back and extract this little component. It was built pretty quickly at the time, so it was very tightly coupled to the app itself. So I abstracted it a bit, and chose to release it as an individual component. This should make it easier for other iOS developers to integrate this component into their own map apps.</p>

<p>Here&#8217;s to hoping we find a few things at <a href="http://dashbook.co">Dashbook</a> that we can release in the open in the near future!</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Letting Go &amp; Moving On]]></title>
    <link href="https://jpsim.com/letting-go-and-moving-on/"/>
    <updated>2013-04-15T07:54:00+00:00</updated>
    <id>https://jpsim.com/letting-go-and-moving-on</id>
    <content type="html"><![CDATA[<p>I&#8217;ve been building <a href="http://mgn.tc">Magnetic Bear Studios</a> for the last two years.</p>

<p>From a logo on a napkin and a 200$ contract, to an exponentially growing mobile dev shop. One of my first big lessons running a young company was that <strong><em>it takes on a life of its own!</em></strong> It&#8217;s scary.</p>

<p>You&#8217;re focused 24/7 on finishing feature/contract #89 while dealing with time-sensitive business issues&#8230; and when you finally take a breath to look at where you are, you realize that <strong>the company itself</strong> has been evolving and pivoting. In a sense, it&#8217;s a bit like the saying goes:</p>

<blockquote><p>Focus on what&#8217;s important and the rest will take care of itself.</p></blockquote>

<p>What the saying fails to convey is that the rest may not be done the way <em>you</em> would do it.</p>

<p>Being a technical founder at a small company, I&#8217;ve had my head buried in code for the last few months. When I looked up again to assess where the business was going, I saw that the team&#8217;s interests were moving rapidly away from services towards building a product entirely ourselves.</p>

<p>To be fair, <a href="http://mgn.tc/m2d">the goal from day one was always to evolve to a product company</a>, so that seed was planted a long time ago.</p>

<p>But it was still difficult to let go of the services side. Of the CEO position and its responsibilities. Of the brand and company we had built from the ground up.</p>

<p>Ultimately, I couldn&#8217;t be more enthusiastic and confident about this new chapter. Being CTO of <a href="http://dashbook.co">Dashbook</a>, I&#8217;ll be focusing 100% on tech while relying on 2 extraordinary co-founders to take care of every other aspect of the business.</p>

<p>So the lesson here is that you have to let the company run its natural course. It has a mind of its own&#8230; and you should trust it!</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[On the importance of <span>pointless projects</span>]]></title>
    <link href="https://jpsim.com/pointless-projects/"/>
    <updated>2013-01-24T09:44:00+00:00</updated>
    <id>https://jpsim.com/pointless-projects</id>
    <content type="html"><![CDATA[<p>Yesterday, I <a href="https://github.com/jpsim/LetterpressPlayer">open sourced</a> a Letterpress<sup>[1]</sup> cheating app for iPhone.</p>

<p>I built it because I knew it would imply a bit of computer vision, OCR, basic set comparisons, yet it could probably be done in just a few hours.</p>

<p>Even though it will never make money and the codebase is slightly embarrassing, I feel reenergized after writing it and my mind is clearer.</p>

<p>Given that most of our projects at <a href="http://magneticbear.com">MBS</a> spend a few <em>months</em> in incubation before launching, it&#8217;s incredibly refreshing to take a break. And of course, that&#8217;s nothing compared to projects in more traditional fields of engineering like mech or electrical, which typically take years to release.</p>

<p>So go out there and build something <em>crude</em> but useful.</p>

<p><em>[1] <a href="http://www.atebits.com/letterpress/">Letterpress</a> is an insanely simple yet addicting game for iOS by Loren Brichter (a.k.a. atebits). If that name sounds familiar, it&#8217;s possibly because he&#8217;s also credited with the invention of the Pull-to-refresh concept and built the first version of what&#8217;s now Twitter&#8217;s official iPhone app.</em></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[A more <span>open</span> kind of agency]]></title>
    <link href="https://jpsim.com/a-more-open-kind-of-agency/"/>
    <updated>2013-01-07T14:12:00+00:00</updated>
    <id>https://jpsim.com/a-more-open-kind-of-agency</id>
    <content type="html"><![CDATA[<p>How MBS is leading the way to creating a new type of agency: a fully open one.</p>

<p><em>This article was originally published on <a href="http://magneticbear.com">magneticbear.com</a></em></p>

<p>Since we started <a href="http://magneticbear.com">Magnetic Bear Studios</a> about a year and a half ago, one of our core values was being as open as possible; our history, our process, our margins, our code.</p>

<p>Up to date, we have open sourced 10 internal projects on <a href="https://github.com/magneticbear">github</a>. In 2013, we&#8217;re making open source an integral part of our company process.</p>

<p>To start off this wave of openness, we&#8217;re publishing our main company website at <a href="https://github.com/magneticbear/magneticbear.com">github.com/magneticbear/magneticbear.com</a>. By the way, the site is based on a wonderful open source framework called <a href="https://github.com/bevry/docpad">docpad</a>.</p>

<p>To help in our efforts to support the open-source community, we&#8217;ll be releasing some of our internal, currently closed-source projects, at the rate of one every month or two.</p>

<p>Finally, over the next few weeks, the MBS team will also be writing some blog articles here to document and publish some of the lessons we learn building mobile apps for startups from around the world.</p>

<p>We&#8217;ll let you know when we have more open goodness to share with you!</p>

<p><em>PS: There&#8217;s a lot more to open source than code. We&#8217;ll be making an effort to share everything from our networking stack to our contract lifecycle to some app design freebies.</em></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Goals for <span>2013</span>]]></title>
    <link href="https://jpsim.com/goals-for-2013/"/>
    <updated>2013-01-07T12:03:00+00:00</updated>
    <id>https://jpsim.com/goals-for-2013</id>
    <content type="html"><![CDATA[<p>Writing my goals for 2013 publicly to keep myself accountable.</p>

<h2>Gig monthly</h2>

<h3>What?</h3>

<p>There was a time when I played sax at local restaurants, corporate gigs, heck I even <a href="http://mgn.tc/psgroove">recorded an album</a> with <a href="http://patricksimard.com">my brother</a> that sold pretty decently.</p>

<h3>How?</h3>

<p>I&#8217;ve got a bassist, drummer and guitarist lined up. I&#8217;m approaching several of the bars/restaurants I used to play to set up some gigs.</p>

<h2>Learn Japanese</h2>

<h3>What?</h3>

<p><a href="http://twitter.com/c_contant">Christine</a> and I will be learning Japanese this year. Our goal is to reach survival-in-Japan level by the end of the year.</p>

<h3>How?</h3>

<p>I bought all 3 levels of Rosetta Stone Japanese today. 3x 20 minutes and an hour-long session with Christine once a week.</p>

<h2>Get featured on Hacker News</h2>

<h3>What?</h3>

<p>I&#8217;d certainly like to refine my writing abilities and this is a stretch goal to accomplish that.</p>

<h3>How?</h3>

<p>Blogging here and on the <a href="http://magneticbear.com/labs">MBS Labs</a> website weekly.</p>

<h2>Run a 3:30 marathon</h2>

<h3>What?</h3>

<p>I&#8217;m not in the shape I&#8217;d like to be. I ran a 4:00 marathon last year and I can already run a 1:30 half marathon so this is the next logical step.</p>

<h3>How?</h3>

<p>Following the <a href="http://runkeeper.com/fitness-class/running-marathon/12">RunKeeper training plan</a>. Track my progress on my <a href="http://runkeeper.com/user/jpsim/profile">RunKeeper profile</a>.</p>

<h2>Bike daily</h2>

<h3>What?</h3>

<p>Starting on February 1st (in the snow for an added challenge). This doesn&#8217;t apply to days I have to drive.</p>

<h3>How?</h3>

<p>I&#8217;m buying a bike and doing it!</p>
]]></content>
  </entry>
  
</feed>
