thegoodalright.dev2023-02-03T00:00:00Zhttps://thegoodalright.dev/feed/ios.xmlEgormikhnevich.egor@gmail.comExploring Default Argument Values2023-02-03T00:00:00Zhttps://thegoodalright.dev/posts/exploring-default-arguments/<p>It's 2023 and I'm not sure that comparing Objective-C with Swift is still relevant, but I'll go with it anyway. Unlike Objective-C, Swift is blessed with a handy feature of default argument values. The feature is quite a life improvement and it feels awkward when a programming language does't support it.</p>
<p></p><div class="table-of-contents"><ul><li><a href="https://thegoodalright.dev/posts/exploring-default-arguments/#optional-default-values">Optional Default Values </a></li><li><a href="https://thegoodalright.dev/posts/exploring-default-arguments/#structured-default-arguments">Structured Default Arguments </a></li><li><a href="https://thegoodalright.dev/posts/exploring-default-arguments/#warnings-to-the-rescue">Warnings to the rescue </a></li><li><a href="https://thegoodalright.dev/posts/exploring-default-arguments/#unconclusion">Unconclusion </a></li></ul></div><p></p>
<p>Nevertheless, there are times when default argument values can get in the way. Alas, in a rather mischievous manner. Changing a default argument value is a subtle enterprise. Especially so in libraries and frameworks, e.g. when you aren't the library's interface consumer and can't be sure how it's used.</p>
<p>There are multiple occasions when changing default argument values can bite you. The first occasion is function overloads. Swift supports similar function signatures, but with tweaked arguments or a return value:</p>
<pre class="language-swift"><code class="language-swift"><span class="token keyword">func</span> <span class="token function-definition function">render</span><span class="token punctuation">(</span><span class="token omit keyword">_</span> content<span class="token punctuation">:</span> <span class="token class-name">String</span><span class="token punctuation">,</span> newlineStyle<span class="token punctuation">:</span> <span class="token class-name">LineEndingStyle</span> <span class="token operator">=</span> <span class="token punctuation">.</span>linux<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token class-name">Markdown</span> <span class="token punctuation">{</span>
<span class="token comment">// ...</span>
<span class="token punctuation">}</span>
<span class="token keyword">func</span> <span class="token function-definition function">render</span><span class="token punctuation">(</span><span class="token omit keyword">_</span> content<span class="token punctuation">:</span> <span class="token class-name">String</span><span class="token punctuation">,</span> newlineStyle<span class="token punctuation">:</span> <span class="token class-name">LineEndingStyle</span> <span class="token operator">=</span> <span class="token punctuation">.</span>linux<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token constant">HTML</span> <span class="token punctuation">{</span>
<span class="token comment">// ...</span>
<span class="token punctuation">}</span></code></pre>
<p>The second occasion is wrapping a function with default arguments, but leaving a way to pass the argument down to the function:</p>
<pre class="language-swift"><code class="language-swift"><span class="token keyword">func</span> <span class="token function-definition function">prettify</span><span class="token punctuation">(</span><span class="token omit keyword">_</span> content<span class="token punctuation">:</span> <span class="token class-name">String</span><span class="token punctuation">,</span> newlineStyle<span class="token punctuation">:</span> <span class="token class-name">LineEndingStyle</span> <span class="token operator">=</span> <span class="token punctuation">.</span>linux<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token class-name">Markdown</span> <span class="token punctuation">{</span>
<span class="token comment">// ...</span>
<span class="token function">render</span><span class="token punctuation">(</span>prettyContent<span class="token punctuation">,</span> newlineStyle<span class="token punctuation">:</span> newlineStyle<span class="token punctuation">)</span>
<span class="token punctuation">}</span></code></pre>
<p>In both cases it seems reasonable to provide a default value for the <code>newlineStyle</code> argument. What's more, the default value is visible in autocompletion. In other words, it works great... unless we need to change the default value.</p>
<p>As you might have noticed, we would need to change the default value in multiple places. Some can be colocated in the same source file, whole others might reside in neighbor files or in different projects. And it might happen that one such place could be overlooked.</p>
<h2 id="optional-default-values" tabindex="-1">Optional Default Values <a class="header-anchor" href="https://thegoodalright.dev/posts/exploring-default-arguments/#optional-default-values" aria-hidden="true">#</a></h2>
<p>Well, that's awkward, but what can be done about it? I would argue that optional nil default arguments might be a solution we are after:</p>
<pre class="language-swift"><code class="language-swift"><span class="token comment">// Instead of this:</span>
<span class="token keyword">func</span> <span class="token function-definition function">render</span><span class="token punctuation">(</span><span class="token omit keyword">_</span> content<span class="token punctuation">:</span> <span class="token class-name">String</span><span class="token punctuation">,</span> newlineStyle<span class="token punctuation">:</span> <span class="token class-name">LineEndingStyle</span> <span class="token operator">=</span> <span class="token punctuation">.</span>linux<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token constant">HTML</span> <span class="token punctuation">{</span>
<span class="token comment">// ...</span>
<span class="token punctuation">}</span>
<span class="token comment">// One would do this:</span>
<span class="token keyword">func</span> <span class="token function-definition function">render</span><span class="token punctuation">(</span><span class="token omit keyword">_</span> content<span class="token punctuation">:</span> <span class="token class-name">String</span><span class="token punctuation">,</span> newlineStyle<span class="token punctuation">:</span> <span class="token class-name">LineEndingStyle</span><span class="token operator">?</span> <span class="token operator">=</span> <span class="token nil constant">nil</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token class-name">Markdown</span> <span class="token punctuation">{</span>
<span class="token keyword">let</span> newlineStyle <span class="token operator">=</span> newlineStyle <span class="token operator">??</span> <span class="token punctuation">.</span>linux
<span class="token comment">// ...</span>
<span class="token punctuation">}</span></code></pre>
<p>There are certainly downsides. For example, the default value won't longer show up in code completion and probably would need a mention in a function's documentation. Oof, that sounds like another can of worms and I agree.</p>
<p>But at least it removes the burden of tracking down changed default values from code consumers. So, technically it adds a bit more longevity to consumers' code.</p>
<p>One more thing that this approach doesn't tackle is... we might forget to pass the default argument down from a wrapper function to an actual function. E.g. forget to pass the <code>prettify's newlineStyle</code> argument to the <code>render</code> function. That's especially true when there are more than one such arguments.</p>
<h2 id="structured-default-arguments" tabindex="-1">Structured Default Arguments <a class="header-anchor" href="https://thegoodalright.dev/posts/exploring-default-arguments/#structured-default-arguments" aria-hidden="true">#</a></h2>
<p>So, is there anything we can do to help with trickling down default arguments? One way to mitigate it is by composing arguments in a struct. <code>UIButton.Configuration</code> is a good example of the said technique. Returning to our samples this might look like this:</p>
<pre class="language-swift"><code class="language-swift"><span class="token comment">// Instead of this:</span>
<span class="token keyword">func</span> <span class="token function-definition function">render</span><span class="token punctuation">(</span><span class="token omit keyword">_</span> content<span class="token punctuation">:</span> <span class="token class-name">String</span><span class="token punctuation">,</span> newlineStyle<span class="token punctuation">:</span> <span class="token class-name">LineEndingStyle</span> <span class="token operator">=</span> <span class="token punctuation">.</span>linux<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token constant">HTML</span> <span class="token punctuation">{</span>
<span class="token comment">// ...</span>
<span class="token punctuation">}</span>
<span class="token comment">// One would do this:</span>
<span class="token keyword">struct</span> <span class="token class-name">Configuration</span> <span class="token punctuation">{</span>
<span class="token keyword">var</span> newlineStyle<span class="token punctuation">:</span> <span class="token class-name">LineEndingStyle</span> <span class="token operator">=</span> <span class="token punctuation">.</span>newlineStyle
<span class="token punctuation">}</span>
<span class="token keyword">func</span> <span class="token function-definition function">render</span><span class="token punctuation">(</span><span class="token omit keyword">_</span> content<span class="token punctuation">:</span> <span class="token class-name">String</span><span class="token punctuation">,</span> configuration<span class="token punctuation">:</span> <span class="token class-name">Configuration</span> <span class="token operator">=</span> <span class="token punctuation">.</span><span class="token keyword">init</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token class-name">Markdown</span> <span class="token punctuation">{</span>
<span class="token comment">// ...</span>
<span class="token punctuation">}</span></code></pre>
<p>Even tho it stands as <a href="https://mastodon.social/@marcoarment/109761392536752965">not the most revered UIKit's API</a>, but it does the job. On the other hand, there's so much more to the struct technique that it unavoidably feels cumbersome for tiny interfaces. What's more, it still doesn't solve the trickling down arguments problem.</p>
<h2 id="warnings-to-the-rescue" tabindex="-1">Warnings to the rescue <a class="header-anchor" href="https://thegoodalright.dev/posts/exploring-default-arguments/#warnings-to-the-rescue" aria-hidden="true">#</a></h2>
<p>This section is somewhat dragged by the ears and doesn't strictly relate to the default arguments discussion. Nevertheless, I would argue that it's a good tool to have in any codebase.</p>
<p>I'm speaking about unused function arguments warning. I can't recall how many times I was saved by this warning. Unfortunately, it doesn't work out-of-box in Swift. There are some discussions around adding it to Swift <a href="https://forums.swift.org/t/add-warnings-for-unused-function-arguments-by-default/47271/24">Add warnings for unused function arguments by default - Evolution / Discussion - Swift Forums</a>, but it feels like it won't happen.</p>
<p>For now we can use tools like <a href="https://github.com/peripheryapp/periphery">peripheryapp/periphery: A tool to identify unused code in Swift projects</a> to help us track down unused arguments. It might sound somewhat as an overkill just for that warning and I agree.</p>
<p>But my favorite is <a href="https://github.com/nicklockwood/SwiftFormat">nicklockwood/SwiftFormat: A command-line tool and Xcode Extension for formatting Swift code</a>. SwiftFormatter has a linter mode, that would warn about unused variables instead of replacing them with underscores. Just be certain to <code>--disable unusedArguments</code> in automatic formatting. This way unused arguments would stay in the code and the linter mode would have a chance to surface them</p>
<h2 id="unconclusion" tabindex="-1">Unconclusion <a class="header-anchor" href="https://thegoodalright.dev/posts/exploring-default-arguments/#unconclusion" aria-hidden="true">#</a></h2>
<p>I can't say any the mentioned method is particularly elegant. Personally, the one with optional default values has the most favorable set of tradeoffs. Tho, it's subjective and not universally applicable.</p>
<p>On the other hand, I find it fascinating how such tiny things can impact code longevity. I wonder how many steps language designers need to foresee to come up with a well rounded solution?</p>
<p>Anyway, please let me know if there's have a better option on the table.</p>
<p>You can make anything, till next time :)</p>
Combine Rate Limiting2021-06-17T00:00:00Zhttps://thegoodalright.dev/posts/combine-rate-limiting/<p>Combine, Apple's take on the reactive programming paradigm. Despite that <code>AsyncSequence</code> <a href="https://forums.swift.org/t/swift-concurrency-reactive-programming/49547">is a new kid in town reactive town</a>, it's safe to say that Combine is here to stay. Ah, I already can see how the said is aging like milk π₯²</p>
<p>Let me suggest a quick stroll through Combine's avenues. Behind well-polished facades, right to the shady custom publishers' back alleys. To places which decent youtuber-traveler would visit straightway.</p>
<p>Though, you might as well check out the end implementation <a href="https://github.com/elfenlaid/rate-limiter">elfenlaid/rate-limiter</a></p>
<p></p><div class="table-of-contents"><ul><li><a href="https://thegoodalright.dev/posts/combine-rate-limiting/#under-pressure">Under Pressure </a></li><li><a href="https://thegoodalright.dev/posts/combine-rate-limiting/#behind-the-facades">Behind the Facades </a></li><li><a href="https://thegoodalright.dev/posts/combine-rate-limiting/#to-the-limit">To the limit </a></li><li><a href="https://thegoodalright.dev/posts/combine-rate-limiting/#the-implementation">The Implementation </a></li><li><a href="https://thegoodalright.dev/posts/combine-rate-limiting/#here-be-dragons">Here be Dragons </a></li><li><a href="https://thegoodalright.dev/posts/combine-rate-limiting/#conclusion">Conclusion </a></li></ul></div><p></p>
<h2 id="under-pressure" tabindex="-1">Under Pressure <a class="header-anchor" href="https://thegoodalright.dev/posts/combine-rate-limiting/#under-pressure" aria-hidden="true">#</a></h2>
<p><a href="https://en.wikipedia.org/wiki/Reactive_programming">Reactive programming</a> is one of those well-brewed paradigms that outgrew initial hype and became classics. Heck, <a href="http://reactivex.io/">Rx</a> specification will celebrate its 10th birthday this year.</p>
<p>The Apple platform is not an exception here. There're tons and tons of reactive and reactive-like frameworks available for it. Here are the honorable mentions: <a href="https://github.com/ReactiveCocoa/ReactiveCocoa">ReactiveCocoa</a>/<a href="https://github.com/ReactiveCocoa/ReactiveSwift">ReactiveSwift</a> has been here since 2012, <a href="https://github.com/ReactiveCocoa/ReactiveSwift">ReactiveSwift</a>'s initial release dates to 2015.</p>
<p>But in 2019, Apple comes off with <a href="https://developer.apple.com/documentation/combine">Combine</a>. It will take another 20 years and a retired Apple veteran to spin a tale of Combine's origins and why 2019 was the year.</p>
<p>For now, let's distract ourselves by scrutinizing the framework. By mere coincidence, Combine looks suspiciously Rx-like. The good ol' <em>asynchronous</em> <em>stream of values over time</em> concept. Though, with one, and significant, distinction. Combine included the <strong>backpressure</strong> mechanism.</p>
<p>The backpressure concept coordinates a <em>consumer's demand</em> (values processing) and a <em>producer's supply</em> (values generating). Thus, you can't send more events than consumers can, well, consume.</p>
<p>From the UI standpoint, backpressure doesn't seem like a groundbreaking concept. UI is rarely demand-bounded and generally concerned only about the latest values. However, your best friends - throttle and debounce, are here to carry the weight when it comes to that.</p>
<p>And indeed, all shipped Combine consumers (<code>sink</code> and <code>assign</code>) are rolling with unlimited demand. That's somewhat ironic and also makes backpressure a bit underrepresented citizen.</p>
<p>But don't forget that Combine is a general framework. Aside from User Interface, there are a lot of cases where you'll find backpressure useful. In system design, backpressure is a well-known and honored concept. Take for example <a href="https://twitter.com/mononcqc/">Fred Hebert</a>'s classics <a href="https://ferd.ca/queues-don-t-fix-overload.html">Queues Don't Fix Overload</a> and <a href="https://ferd.ca/handling-overload.html">Handling Overload</a>. Frameworks like <a href="https://hexdocs.pm/gen_stage/GenStage.html">GenStage</a> are purely demand-driven.</p>
<p>Though, the before-mentioned examples are mostly from the backend side of things. But backpressure is not only a safeguard from overflow but also introduces the point of concurrency and parallelization. Imagine an intelligent Combine's <code>map</code> that would parallelize workload if given 20 or more items (like <code>AsyncSequence</code> π).</p>
<h2 id="behind-the-facades" tabindex="-1">Behind the Facades <a class="header-anchor" href="https://thegoodalright.dev/posts/combine-rate-limiting/#behind-the-facades" aria-hidden="true">#</a></h2>
<p>So, Combine not only helps to build message pipelines. Combine goes even further and orchestrates the data flow itself, twirls a pipeline's valves, keeps the system safe from overflows.</p>
<p>If the orchestration process sounds like an intimidating chore, well, it is. It's much more pleasant to interact with a well-polished facade and never dig to the pipeline's internals for sure:</p>
<ul>
<li>First of all, there are so many moving parts: <code>Publisher, Publishers, Subscriber, Subscription, Cancellable, Scheduler</code>.</li>
<li>Secondly, Combine is (surprise-surprise) a closed-source project. Only a handful of people know its internals. The rest are left to speculations. For example, we still don't know how to build <a href="https://twitter.com/a_grebenyuk/status/1388880562228809730">thread-safe Publishers</a> π</li>
</ul>
<p>It would be a minefield walk if not for <a href="https://github.com/OpenCombine/OpenCombine">OpenCombine</a> and <a href="https://github.com/CombineCommunity/CombineExt">CombineExt</a>. Don't get me wrong, it still is a minefield walk, with explosion leftovers, body parts scattered here and there, smokes from the production builds. But this time, a smiling sergeant is marching ahead of you. Which is nothing but relieving, I guess.</p>
<h2 id="to-the-limit" tabindex="-1">To the limit <a class="header-anchor" href="https://thegoodalright.dev/posts/combine-rate-limiting/#to-the-limit" aria-hidden="true">#</a></h2>
<p>Okay-okay, back to the business. Let's actually build the rate limiter :) By the way, what the heck is a rate limiter, and where can it be used? Say that you are ingesting large portions of data. It might be for the sake of data scrapping or a part of <a href="https://en.wikipedia.org/wiki/Extract,_transform,_load">Extract, transform, load</a> process. Whatever it is, you certainly want to keep your appetites in check and don't accidentally DDoS your data sources.</p>
<p>That's why most public APIs are often rate-limited. So, rate limiting is an omnipresent system design pattern that keeps clients' traffic at bay. By the way, let's check out some real-world limit examples:</p>
<blockquote>
<p>The API is limited to 5 requests per second per base<br />
β <a href="https://airtable.com/api/meta">Airtable API</a></p>
</blockquote>
<p>and</p>
<blockquote>
<p>You can make up to 5,000 requests per hour.<br />
β <a href="https://docs.github.com/en/rest/reference/rate-limit">Github API</a></p>
</blockquote>
<p>How about expressing the limits in Combine's pipeline terms. We are dealing with a request count per a given time unit metric. Let's plan the rate limiter interface accordingly:</p>
<pre class="language-swift"><code class="language-swift"><span class="token comment">//Airtable's constraint</span>
publisher<span class="token punctuation">.</span><span class="token function">rateLimited</span><span class="token punctuation">(</span>by<span class="token punctuation">:</span> <span class="token number">5</span><span class="token punctuation">,</span> per<span class="token punctuation">:</span> <span class="token punctuation">.</span><span class="token function">second</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token comment">// Github's constraint</span>
publisher<span class="token punctuation">.</span><span class="token function">rateLimited</span><span class="token punctuation">(</span>by<span class="token punctuation">:</span> <span class="token number">5000</span><span class="token punctuation">,</span> per<span class="token punctuation">:</span> <span class="token punctuation">.</span><span class="token function">hour</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token comment">// Oh, let's drop a `scheduler` to count the passing time</span>
publisher<span class="token punctuation">.</span><span class="token function">rateLimited</span><span class="token punctuation">(</span>by<span class="token punctuation">:</span> <span class="token number">100</span><span class="token punctuation">,</span> per<span class="token punctuation">:</span> <span class="token punctuation">.</span><span class="token function">second</span><span class="token punctuation">(</span><span class="token number">30</span><span class="token punctuation">)</span><span class="token punctuation">,</span> scheduler<span class="token punctuation">:</span> <span class="token punctuation">.</span>main<span class="token punctuation">)</span></code></pre>
<p>By the way, limits won't be limits if they never hit. So the next step of designing the rate limiter's interface is to know how to handle the limit hit. The plan is to queue the client's demand while the rate limit is on cooldown:</p>
<pre class="language-swift"><code class="language-swift">publisher<span class="token punctuation">.</span><span class="token function">rateLimited</span><span class="token punctuation">(</span>by<span class="token punctuation">:</span> <span class="token number">5000</span><span class="token punctuation">,</span> per<span class="token punctuation">:</span> <span class="token punctuation">.</span><span class="token function">hour</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> queue<span class="token punctuation">:</span> <span class="token punctuation">.</span>fifo<span class="token punctuation">)</span></code></pre>
<p>A sound solution, though, something is not quite right here. I think that the <code>.fifo</code> is an acceptable strategy for most cases. But at the same time, it's a too opinionated solution. Perhaps <code>.filo</code> is a more appropriate strategy, what about timeout, oh, and don't forget queue priorities... π₯² It's a tough choice to make, so let's work around it by providing the library's first extension point!</p>
<p>One can summarize a rate limiter as middleware between <code>Upstream</code> and <code>Downstream</code> that controls the pipeline's throughput. And we're looking for means to abstract the control behavior. And what can abstract concepts better than protocols? The end protocol might look like this:</p>
<pre class="language-swift"><code class="language-swift"><span class="token keyword">protocol</span> <span class="token class-name">ThroughputStrategy</span> <span class="token punctuation">{</span>
<span class="token keyword">typealias</span> <span class="token class-name">Action</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token class-name">Void</span>
<span class="token keyword">func</span> <span class="token function-definition function">requestThroughput</span><span class="token punctuation">(</span><span class="token omit keyword">_</span> action<span class="token punctuation">:</span> <span class="token attribute atrule">@escaping</span> <span class="token class-name">Action</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span></code></pre>
<p>I've decided to implicitly constraint the throughput rate by one value per action. I.e., <code>Downstream</code> can't demand 10 values in one go. It certainly reduces the interface's generality but drastically simplifies its implementation. As a side note, <a href="https://github.com/CombineCommunity/CombineExt">CombineExt</a> has a superb example of a <a href="https://github.com/CombineCommunity/CombineExt/blob/main/Sources/Common/DemandBuffer.swift">demand buffer handling</a> that can help us in the future.</p>
<p>Let's take the new protocol for a spin:</p>
<pre class="language-swift"><code class="language-swift"><span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">QueueStrategy</span><span class="token punctuation">:</span> <span class="token class-name">ThroughputStrategy</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span>
<span class="token keyword">let</span> strategy <span class="token operator">=</span> <span class="token class-name">QueueStrategy</span><span class="token punctuation">(</span>rate<span class="token punctuation">:</span> <span class="token number">2</span><span class="token punctuation">,</span> interval<span class="token punctuation">:</span> <span class="token punctuation">.</span><span class="token function">seconds</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> scheduler<span class="token punctuation">:</span> scheduler<span class="token punctuation">)</span>
publisher<span class="token punctuation">.</span><span class="token function">rateLimited</span><span class="token punctuation">(</span>by<span class="token punctuation">:</span> strategy<span class="token punctuation">)</span></code></pre>
<p>Explicitly defined strategies are also sharable. The previous interface <code>.rateLimited(by:_ per:_ scheduler:_)</code> served as a single API's entry point. That might do for a globally constrained API (like <a href="https://airtable.com/api/meta">Airtable API</a>). But for complex APIs with different sets of constraints, the single API entry point won't do. Check out the complete limits set of limits for the Github API π€―</p>
<pre class="language-swift"><code class="language-swift"><span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">GithubClient</span> <span class="token punctuation">{</span>
<span class="token keyword">private</span> <span class="token keyword">lazy</span> <span class="token keyword">var</span> unauthenticatedLimiter <span class="token operator">=</span> <span class="token class-name">QueueThroughputStrategy</span><span class="token punctuation">(</span>
rate<span class="token punctuation">:</span> <span class="token number">60</span><span class="token punctuation">,</span> interval<span class="token punctuation">:</span> <span class="token punctuation">.</span><span class="token function">minute</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> scheduler<span class="token punctuation">:</span> scheduler
<span class="token punctuation">)</span>
<span class="token keyword">private</span> <span class="token keyword">lazy</span> <span class="token keyword">var</span> authenticatedLimiter <span class="token operator">=</span> <span class="token class-name">QueueThroughputStrategy</span><span class="token punctuation">(</span>
rate<span class="token punctuation">:</span> <span class="token number">5000</span><span class="token punctuation">,</span> interval<span class="token punctuation">:</span> <span class="token punctuation">.</span><span class="token function">hour</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> scheduler<span class="token punctuation">:</span> scheduler
<span class="token punctuation">)</span>
<span class="token keyword">private</span> <span class="token keyword">lazy</span> <span class="token keyword">var</span> tokenApiLimiter <span class="token operator">=</span> <span class="token class-name">QueueThroughputStrategy</span><span class="token punctuation">(</span>
rate<span class="token punctuation">:</span> <span class="token number">1000</span><span class="token punctuation">,</span> interval<span class="token punctuation">:</span> <span class="token punctuation">.</span><span class="token function">hour</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> scheduler<span class="token punctuation">:</span> scheduler
<span class="token punctuation">)</span>
<span class="token keyword">private</span> <span class="token keyword">lazy</span> <span class="token keyword">var</span> searchAuthenticatedLimiter <span class="token operator">=</span> <span class="token class-name">QueueThroughputStrategy</span><span class="token punctuation">(</span>
rate<span class="token punctuation">:</span> <span class="token number">30</span><span class="token punctuation">,</span> interval<span class="token punctuation">:</span> <span class="token punctuation">.</span><span class="token function">minute</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> scheduler<span class="token punctuation">:</span> scheduler
<span class="token punctuation">)</span>
<span class="token keyword">private</span> <span class="token keyword">lazy</span> <span class="token keyword">var</span> searchUnauthenticatedLimiter <span class="token operator">=</span> <span class="token class-name">QueueThroughputStrategy</span><span class="token punctuation">(</span>
rate<span class="token punctuation">:</span> <span class="token number">10</span><span class="token punctuation">,</span> interval<span class="token punctuation">:</span> <span class="token punctuation">.</span><span class="token function">minute</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> scheduler<span class="token punctuation">:</span> scheduler
<span class="token punctuation">)</span>
<span class="token comment">// ...</span>
<span class="token punctuation">}</span></code></pre>
<h2 id="the-implementation" tabindex="-1">The Implementation <a class="header-anchor" href="https://thegoodalright.dev/posts/combine-rate-limiting/#the-implementation" aria-hidden="true">#</a></h2>
<p>I'll spare you from the implementation details as you might check them <a href="https://github.com/elfenlaid/rate-limiter">here</a>. Though, I should brag that at last I've used <a href="https://github.com/apple/swift-collections">Swift Collections</a>'s <code>Deque</code>! Also big shot-out to <a href="https://www.pointfree.co/">Point Free</a> with <a href="https://github.com/pointfreeco/combine-schedulers#testscheduler">combine-schedulers</a> package. Life would be much more difficult without the <code>TestScheduler</code> (yet another missed standard Combine class π):</p>
<pre class="language-swift"><code class="language-swift"><span class="token punctuation">(</span><span class="token number">1</span><span class="token operator">...</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">.</span>publisher
<span class="token punctuation">.</span><span class="token function">rateLimited</span><span class="token punctuation">(</span>by<span class="token punctuation">:</span> strategy<span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">sink</span><span class="token punctuation">(</span>receiveValue<span class="token punctuation">:</span> <span class="token punctuation">{</span> values<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token short-argument">$0</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">store</span><span class="token punctuation">(</span><span class="token keyword">in</span><span class="token punctuation">:</span> <span class="token operator">&</span>cancellables<span class="token punctuation">)</span>
<span class="token punctuation">(</span><span class="token number">1</span><span class="token operator">...</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">.</span>publisher
<span class="token punctuation">.</span><span class="token function">rateLimited</span><span class="token punctuation">(</span>by<span class="token punctuation">:</span> strategy<span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">sink</span><span class="token punctuation">(</span>receiveValue<span class="token punctuation">:</span> <span class="token punctuation">{</span> values<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span><span class="token short-argument">$0</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">store</span><span class="token punctuation">(</span><span class="token keyword">in</span><span class="token punctuation">:</span> <span class="token operator">&</span>cancellables<span class="token punctuation">)</span>
scheduler<span class="token punctuation">.</span><span class="token function">advance</span><span class="token punctuation">(</span>by<span class="token punctuation">:</span> <span class="token punctuation">.</span><span class="token function">seconds</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token comment">// Rate limiter starts immediately, and throughputs events up to the initial capacity:</span>
<span class="token function">print</span><span class="token punctuation">(</span>values<span class="token punctuation">)</span> <span class="token comment">// [1,1]</span>
<span class="token comment">// Now the limiter waits for its interval to pass...</span>
scheduler<span class="token punctuation">.</span><span class="token function">advance</span><span class="token punctuation">(</span>by<span class="token punctuation">:</span> <span class="token punctuation">.</span><span class="token function">seconds</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token comment">// ... to get another round of values</span>
<span class="token function">print</span><span class="token punctuation">(</span>values<span class="token punctuation">)</span> <span class="token comment">// [1,1, 2,2]</span></code></pre>
<p>Also, times it's hard to wrap your head around custom publishers due to hairy implementation details. Sincerely speaking, I failed to come up with a better custom publisher's guide than "follow the example and see what happens." <a href="https://github.com/CombineCommunity/CombineExt/blob/main/Sources/Operators/Create.swift"><code>AnyPublisher.create</code></a> is a decent reference source.</p>
<h2 id="here-be-dragons" tabindex="-1">Here be Dragons <a class="header-anchor" href="https://thegoodalright.dev/posts/combine-rate-limiting/#here-be-dragons" aria-hidden="true">#</a></h2>
<p>A word of warning, though. The current implementation is quite naive. It doesn't account for Upstream's shenanigans. For example, the rate limiter assumes that an <code>Upstream</code> issues only one request per demand unit. But it might be not true, say a network retry gets in a way, or DNS resolution took way too long and throttled the issued requests, or the <code>Upstream</code> might issue multiple requests by design.</p>
<p>Whatever it is, make sure that you've planned the limit hit scenario. Never mind the pattern (circuit breaker, delayed retry, etc.), make sure that it's there and, if possible, in the <code>Downstream</code>.</p>
<p>It's also hard to say whether the implementation is thread-safe. I've used it in my personal projects without noticeable issues. Though it might not be the case for you.</p>
<h2 id="conclusion" tabindex="-1">Conclusion <a class="header-anchor" href="https://thegoodalright.dev/posts/combine-rate-limiting/#conclusion" aria-hidden="true">#</a></h2>
<p>That concludes the rate limiter showcase. As you can see, Combine's backpressure mechanics is quite a peculiar thing. It's a pity that most of the built-in publishers sorta ignore it and start with <code>unlimited</code> demand right off the bat.</p>
<p>Overall, if you think that Rx is an excellent reactive interface specification, you would also like Combine. But, unfortunately, it's not exactly my case. Don't get me wrong, Combine is a decent framework and certainly does its job. But, alas, it's a so PITA to work with. Not only due to custom publishers state, but even simple operations like retry can be <a href="https://twitter.com/DonnyWals/status/1389900253638406146">deceptively tricky to implement</a>.</p>
<p>It's ridiculous how many pain points a single decent official custom publisher example can help with. But we are not there yet. To be honest, WWDC 2021 wasn't a great year for Combine. High hopes for WWDC 2022, I guess π€</p>
<p>You can make anything, till next time :)</p>
Swift String Showcase: Privacy Logs2021-05-21T00:00:00Zhttps://thegoodalright.dev/posts/string-interpolation-showcase/<p>Here I'm (a year late to the party π₯²) explaining what Swift String Interpolations is all about and showcasing a custom string interpolation type.</p>
<p>Here's the <a href="https://gist.github.com/elfenlaid/568168ea4b2068aee2305752443a366f">full implementation</a> and bits of context to follow along.</p>
<p></p><div class="table-of-contents"><ul><li><a href="https://thegoodalright.dev/posts/string-interpolation-showcase/#introduction-to-string-interpolation">Introduction to String Interpolation </a></li><li><a href="https://thegoodalright.dev/posts/string-interpolation-showcase/#extend-string-interpolation">Extend String Interpolation </a></li><li><a href="https://thegoodalright.dev/posts/string-interpolation-showcase/#custom-string-interpolation-type">Custom String Interpolation Type </a></li><li><a href="https://thegoodalright.dev/posts/string-interpolation-showcase/#logger-nuances">Logger Nuances </a></li><li><a href="https://thegoodalright.dev/posts/string-interpolation-showcase/#conclusion">Conclusion </a></li></ul></div><p></p>
<h2 id="introduction-to-string-interpolation" tabindex="-1">Introduction to String Interpolation <a class="header-anchor" href="https://thegoodalright.dev/posts/string-interpolation-showcase/#introduction-to-string-interpolation" aria-hidden="true">#</a></h2>
<p>According to <a href="https://en.wikipedia.org/wiki/Rich_Hickey">Rich Hickey's vibes</a>, we're up to look for interpolation's definition. Without further ado:</p>
<blockquote>
<ol>
<li>the insertion of something of a different nature into something else
<ul>
<li>"the interpolation of songs into the piece"</li>
</ul>
</li>
<li>a remark interjected in a conversation
<ul>
<li>"as the evening progressed their interpolations became more ridiculous"</li>
</ul>
</li>
</ol>
</blockquote>
<p><code>A remark interjected in a conversation</code> π§? Voila, a thing to interpolate in the everyday dandy dictionary.</p>
<p>Anyway, talking about the definitions, we're going to concern ourselves with the first one. Most modern programming languages support string interpolation in some way:</p>
<ul>
<li>Rust <code>println!("The {species}'s name is {name}.");</code></li>
<li>JS <code>console.log(`The ${species}'s name is {name}.`);</code></li>
<li>Elixir <code>IO.puts("The #{species}'s name is #{name}.")</code></li>
<li>Swift <code>print("The \(species)'s name is \(name).")</code></li>
<li>You have the idea...</li>
</ul>
<p>Nothing particularly new here, right? Except here is Swift 5's custom string interpolations <a href="https://github.com/apple/swift-evolution/blob/master/proposals/0228-fix-expressiblebystringinterpolation.md">SE-0228</a> proposal. In case you've missed it completely, it gives control over the default string interpolation. Here are the proposed potential uses:</p>
<pre class="language-swift"><code class="language-swift"><span class="token comment">// Use printf-style format strings:</span>
<span class="token string-literal"><span class="token string">"The price is $</span><span class="token interpolation-punctuation punctuation">\(</span><span class="token interpolation">cost<span class="token punctuation">,</span> format<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"%.2f"</span></span></span><span class="token interpolation-punctuation punctuation">)</span><span class="token string">"</span></span>
<span class="token comment">// Use UTS #35 number formats:</span>
<span class="token string-literal"><span class="token string">"The price is </span><span class="token interpolation-punctuation punctuation">\(</span><span class="token interpolation">cost<span class="token punctuation">,</span> format<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"Β€###,##0.00"</span></span></span><span class="token interpolation-punctuation punctuation">)</span><span class="token string">"</span></span>
<span class="token comment">// Use Foundation.NumberFormatter, or a new type-safe native formatter:</span>
<span class="token string-literal"><span class="token string">"The price is </span><span class="token interpolation-punctuation punctuation">\(</span><span class="token interpolation">cost<span class="token punctuation">,</span> format<span class="token punctuation">:</span> moneyFormatter</span><span class="token interpolation-punctuation punctuation">)</span><span class="token string">"</span></span>
<span class="token comment">// Mimic String.init(_:radix:uppercase:)</span>
<span class="token string-literal"><span class="token string">"The checksum is 0x</span><span class="token interpolation-punctuation punctuation">\(</span><span class="token interpolation">checksum<span class="token punctuation">,</span> radix<span class="token punctuation">:</span> <span class="token number">16</span></span><span class="token interpolation-punctuation punctuation">)</span><span class="token string">"</span></span></code></pre>
<p>What I did a year ago - I've checked said uses, which are totally rad, thought, "yeah, that's cool," and was off with it. <strong>But</strong> I've entirely missed the way of said proposal to land - custom interpolation types.</p>
<p>Don't be me, read to the end, or review or revise the <a href="https://github.com/apple/swift-evolution/blob/master/proposals/0228-fix-expressiblebystringinterpolation.md">proposal</a>, or <a href="https://nshipster.com/expressiblebystringinterpolation/">some</a> <a href="https://www.avanderlee.com/swift/string-interpolation/">other</a> explanation.</p>
<h2 id="extend-string-interpolation" tabindex="-1">Extend String Interpolation <a class="header-anchor" href="https://thegoodalright.dev/posts/string-interpolation-showcase/#extend-string-interpolation" aria-hidden="true">#</a></h2>
<p>Before we talk about <a href="https://thegoodalright.dev/posts/string-interpolation-showcase/#custom-string-interpolation-type">custom interpolation types</a>, I want to mention that types are not the only available extension point. Most of the time, it's much quicker to drop a <code>String.StringInterpolation</code> extension.</p>
<p>Let's have a look at what it takes to implement the purposed use cases:</p>
<pre class="language-swift"><code class="language-swift"><span class="token keyword">let</span> πΊπΈ <span class="token operator">=</span> <span class="token class-name">Locale</span><span class="token punctuation">(</span>identifier<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"en_US"</span></span><span class="token punctuation">)</span>
<span class="token keyword">extension</span> <span class="token class-name">String</span><span class="token punctuation">.</span><span class="token class-name">StringInterpolation</span> <span class="token punctuation">{</span>
<span class="token comment">// Use printf-style format strings:</span>
<span class="token keyword">mutating</span> <span class="token keyword">func</span> <span class="token function-definition function">appendInterpolation</span><span class="token punctuation">(</span><span class="token omit keyword">_</span> value<span class="token punctuation">:</span> <span class="token class-name">Double</span><span class="token punctuation">,</span> format<span class="token punctuation">:</span> <span class="token class-name">String</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">appendInterpolation</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">(</span>format<span class="token punctuation">:</span> format<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token comment">// Use UTS #35 number formats: http://www.unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns</span>
<span class="token keyword">mutating</span> <span class="token keyword">func</span> <span class="token function-definition function">appendInterpolation</span><span class="token punctuation">(</span><span class="token omit keyword">_</span> value<span class="token punctuation">:</span> <span class="token class-name">NSNumber</span><span class="token punctuation">,</span> format<span class="token punctuation">:</span> <span class="token class-name">String</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">let</span> formatter <span class="token operator">=</span> <span class="token class-name">NumberFormatter</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
formatter<span class="token punctuation">.</span>locale <span class="token operator">=</span> πΊπΈ
formatter<span class="token punctuation">.</span>format <span class="token operator">=</span> format <span class="token comment">// .format @available(macOS 10.0, *)</span>
<span class="token function">appendInterpolation</span><span class="token punctuation">(</span>formatter<span class="token punctuation">.</span><span class="token function">string</span><span class="token punctuation">(</span>from<span class="token punctuation">:</span> value<span class="token punctuation">)</span><span class="token operator">!</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token comment">// Use Foundation.NumberFormatter, or a new type-safe native formatter:</span>
<span class="token keyword">mutating</span> <span class="token keyword">func</span> <span class="token function-definition function">appendInterpolation</span><span class="token punctuation">(</span><span class="token omit keyword">_</span> value<span class="token punctuation">:</span> <span class="token class-name">Double</span><span class="token punctuation">,</span> format<span class="token punctuation">:</span> <span class="token class-name">NumberFormatter</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">appendInterpolation</span><span class="token punctuation">(</span>format<span class="token punctuation">.</span><span class="token function">string</span><span class="token punctuation">(</span>from<span class="token punctuation">:</span> value <span class="token keyword">as</span> <span class="token class-name">NSNumber</span><span class="token punctuation">)</span><span class="token operator">!</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token comment">// Mimic String.init(_:radix:uppercase:)</span>
<span class="token keyword">mutating</span> <span class="token keyword">func</span> <span class="token function-definition function">appendInterpolation</span><span class="token operator"><</span><span class="token class-name">Value</span><span class="token punctuation">:</span> <span class="token class-name">BinaryInteger</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token omit keyword">_</span> value<span class="token punctuation">:</span> <span class="token class-name">Value</span><span class="token punctuation">,</span> radix<span class="token punctuation">:</span> <span class="token class-name">Int</span><span class="token punctuation">,</span> uppercase<span class="token punctuation">:</span> <span class="token class-name">Bool</span> <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">appendInterpolation</span><span class="token punctuation">(</span><span class="token class-name">String</span><span class="token punctuation">(</span>value<span class="token punctuation">,</span> radix<span class="token punctuation">:</span> radix<span class="token punctuation">,</span> uppercase<span class="token punctuation">:</span> uppercase<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">let</span> moneyFormatter <span class="token operator">=</span> <span class="token class-name">NumberFormatter</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
moneyFormatter<span class="token punctuation">.</span>locale <span class="token operator">=</span> πΊπΈ
moneyFormatter<span class="token punctuation">.</span>numberStyle <span class="token operator">=</span> <span class="token punctuation">.</span>currency
moneyFormatter<span class="token punctuation">.</span>maximumFractionDigits <span class="token operator">=</span> <span class="token number">2</span>
<span class="token keyword">let</span> cost <span class="token operator">=</span> <span class="token number">12.8</span>
<span class="token string-literal"><span class="token string">"The price is $</span><span class="token interpolation-punctuation punctuation">\(</span><span class="token interpolation">cost<span class="token punctuation">,</span> format<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"%.2f"</span></span></span><span class="token interpolation-punctuation punctuation">)</span><span class="token string">"</span></span> <span class="token comment">// The price is $12.80</span>
<span class="token string-literal"><span class="token string">"The price is </span><span class="token interpolation-punctuation punctuation">\(</span><span class="token interpolation">cost <span class="token keyword">as</span> <span class="token class-name">NSNumber</span><span class="token punctuation">,</span> format<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"Β€###,##0.00"</span></span></span><span class="token interpolation-punctuation punctuation">)</span><span class="token string">"</span></span> <span class="token comment">// The price is $12.80</span>
<span class="token string-literal"><span class="token string">"The price is </span><span class="token interpolation-punctuation punctuation">\(</span><span class="token interpolation">cost<span class="token punctuation">,</span> format<span class="token punctuation">:</span> moneyFormatter</span><span class="token interpolation-punctuation punctuation">)</span><span class="token string">"</span></span> <span class="token comment">// The price is $12.80</span>
<span class="token keyword">let</span> checksum <span class="token operator">=</span> <span class="token number">123123123</span>
<span class="token string-literal"><span class="token string">"The checksum is 0x</span><span class="token interpolation-punctuation punctuation">\(</span><span class="token interpolation">checksum<span class="token punctuation">,</span> radix<span class="token punctuation">:</span> <span class="token number">16</span></span><span class="token interpolation-punctuation punctuation">)</span><span class="token string">"</span></span> <span class="token comment">// The checksum is 0x756b5b3</span></code></pre>
<p>Well, it wasn't that complicated or horrible (aside from <a href="https://nshipster.com/formatter/#locale-awareness">emoji locales</a>). Oh, you can find more examples of <code>StringInterpolation</code> extensions <a href="https://www.avanderlee.com/swift/string-interpolation/">here</a>.</p>
<p>If you follow along with the implementation, you may notice that things kind of become messy with every override of <code>appendInterpolation</code>. We were forced to juggle with types (<code>NSNumber/Double</code>) and compromise on APIs, far from optimal. That's where custom string interpolation types come into play.</p>
<h2 id="custom-string-interpolation-type" tabindex="-1">Custom String Interpolation Type <a class="header-anchor" href="https://thegoodalright.dev/posts/string-interpolation-showcase/#custom-string-interpolation-type" aria-hidden="true">#</a></h2>
<p>Aside from reducing String type clutter, custom string interpolation types rein how and what is out to be interpolated. But to think about it, where can it be useful?</p>
<p>Well... How about logs? Logging is a pain point on iOS (and we're clearly not there <a href="https://steipete.com/posts/logging-in-swift/">yet</a>). The post's idea emerged while I was lurking around <a href="https://steipete.com/posts/logging-in-swift/">Peter's post</a> and stumbled upon the next lines:</p>
<pre class="language-swift"><code class="language-swift"><span class="token keyword">let</span> logger <span class="token operator">=</span> <span class="token class-name">Logger</span><span class="token punctuation">(</span>subsystem<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"com.steipete.LoggingTest"</span></span><span class="token punctuation">,</span> category<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"main"</span></span><span class="token punctuation">)</span>
logger<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token string-literal"><span class="token string">"Logging </span><span class="token interpolation-punctuation punctuation">\(</span><span class="token interpolation">obj<span class="token punctuation">.</span>description<span class="token punctuation">,</span> privacy<span class="token punctuation">:</span> <span class="token punctuation">.</span><span class="token keyword">public</span></span><span class="token interpolation-punctuation punctuation">)</span><span class="token string">"</span></span><span class="token punctuation">)</span></code></pre>
<p>Aha <code>(obj.description, privacy: .public)</code>! Clearly, a custom string interpolation is in play. I immediately went off to check its implementation at <a href="https://github.com/apple/swift-log">Apples' Swift Log repo</a>. Alas, this is not a complete <code>os.Logger</code> implementation, and it misses interpolation details.</p>
<p>I guess we have no choice but to try and implement our own logs privacy policy :) Let's define an API surface:</p>
<pre class="language-swift"><code class="language-swift"><span class="token keyword">func</span> <span class="token function-definition function">log</span><span class="token punctuation">(</span><span class="token omit keyword">_</span> message<span class="token punctuation">:</span> <span class="token class-name">Message</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">print</span><span class="token punctuation">(</span>message<span class="token punctuation">)</span> <span class="token punctuation">}</span>
<span class="token function">log</span><span class="token punctuation">(</span><span class="token string-literal"><span class="token string">"Hello, </span><span class="token interpolation-punctuation punctuation">\(</span><span class="token interpolation"><span class="token string-literal"><span class="token string">"World!"</span></span><span class="token punctuation">,</span> privacy<span class="token punctuation">:</span> <span class="token punctuation">.</span><span class="token keyword">public</span></span><span class="token interpolation-punctuation punctuation">)</span><span class="token string">"</span></span><span class="token punctuation">)</span> <span class="token comment">// Hello, world!</span>
<span class="token function">log</span><span class="token punctuation">(</span><span class="token string-literal"><span class="token string">"Hello, </span><span class="token interpolation-punctuation punctuation">\(</span><span class="token interpolation"><span class="token string-literal"><span class="token string">"Secret!"</span></span><span class="token punctuation">,</span> privacy<span class="token punctuation">:</span> <span class="token punctuation">.</span><span class="token keyword">private</span></span><span class="token interpolation-punctuation punctuation">)</span><span class="token string">"</span></span><span class="token punctuation">)</span> <span class="token comment">// Hello, ******!</span>
<span class="token function">log</span><span class="token punctuation">(</span><span class="token string-literal"><span class="token string">"Static strings say whatever they want"</span></span><span class="token punctuation">)</span> <span class="token comment">// Static strings say whatever they want</span></code></pre>
<p>By the way, <code>Logger</code>'s '<code>privacy:</code> is set to <code>.auto</code>. All of that without saying that <a href="https://developer.apple.com/documentation/os/oslogprivacy#"><code>OSLogPrivacy</code></a> is way more intricate than our example implementation:</p>
<pre class="language-swift"><code class="language-swift"><span class="token keyword">struct</span> <span class="token class-name">Message</span> <span class="token punctuation">{</span>
<span class="token keyword">enum</span> <span class="token class-name">Privacy</span> <span class="token punctuation">{</span>
<span class="token keyword">case</span> `<span class="token keyword">public</span>`
<span class="token keyword">case</span> `<span class="token keyword">private</span>`
<span class="token punctuation">}</span>
<span class="token keyword">var</span> value<span class="token punctuation">:</span> <span class="token class-name">String</span>
<span class="token punctuation">}</span></code></pre>
<p>So, we already can log <code>Message</code> instances:</p>
<pre class="language-swift"><code class="language-swift"><span class="token function">log</span><span class="token punctuation">(</span><span class="token class-name">Message</span><span class="token punctuation">(</span>value<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"Hello, World!"</span></span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// Message(value: "Hello, World!")</span></code></pre>
<p>But they aren't looking pretty, are they? Thankfully we are one protocol conformance away from fixing the situation:</p>
<pre class="language-swift"><code class="language-swift"><span class="token keyword">extension</span> <span class="token class-name">Message</span><span class="token punctuation">:</span> <span class="token class-name">CustomStringConvertible</span> <span class="token punctuation">{</span>
<span class="token keyword">var</span> description<span class="token punctuation">:</span> <span class="token class-name">String</span> <span class="token punctuation">{</span> value <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token function">log</span><span class="token punctuation">(</span><span class="token class-name">Message</span><span class="token punctuation">(</span>value<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"Hello, World!"</span></span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// Hello, World!</span></code></pre>
<p>How about dealing with static strings or string literals before tackling interpolations? Plus <code>ExpressibleByStringLiteral</code> conformance is a requirement for <code>ExpressibleByStringInterpolation</code> anyway.</p>
<pre class="language-swift"><code class="language-swift"><span class="token keyword">extension</span> <span class="token class-name">Message</span><span class="token punctuation">:</span> <span class="token class-name">ExpressibleByStringLiteral</span> <span class="token punctuation">{</span>
<span class="token keyword">init</span><span class="token punctuation">(</span>stringLiteral value<span class="token punctuation">:</span> <span class="token class-name">StringLiteralType</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">self</span><span class="token punctuation">.</span><span class="token keyword">init</span><span class="token punctuation">(</span>value<span class="token punctuation">:</span> value<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token function">log</span><span class="token punctuation">(</span><span class="token string-literal"><span class="token string">"Hello, World!"</span></span><span class="token punctuation">)</span> <span class="token comment">// Hello, World!</span></code></pre>
<p>Now we're ready to deal with the whole interpolation thing:</p>
<pre class="language-swift"><code class="language-swift"><span class="token keyword">extension</span> <span class="token class-name">Message</span><span class="token punctuation">:</span> <span class="token class-name">ExpressibleByStringInterpolation</span> <span class="token punctuation">{</span>
<span class="token keyword">init</span><span class="token punctuation">(</span>stringInterpolation<span class="token punctuation">:</span> <span class="token class-name">StringInterpolation</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">self</span><span class="token punctuation">.</span><span class="token keyword">init</span><span class="token punctuation">(</span>value<span class="token punctuation">:</span> stringInterpolation<span class="token punctuation">.</span>string<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">struct</span> <span class="token class-name">StringInterpolation</span><span class="token punctuation">:</span> <span class="token class-name">StringInterpolationProtocol</span> <span class="token punctuation">{</span>
<span class="token keyword">var</span> string <span class="token operator">=</span> <span class="token string-literal"><span class="token string">""</span></span>
<span class="token keyword">init</span><span class="token punctuation">(</span>literalCapacity<span class="token punctuation">:</span> <span class="token class-name">Int</span><span class="token punctuation">,</span> interpolationCount<span class="token punctuation">:</span> <span class="token class-name">Int</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
string<span class="token punctuation">.</span><span class="token function">reserveCapacity</span><span class="token punctuation">(</span>literalCapacity<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">mutating</span> <span class="token keyword">func</span> <span class="token function-definition function">appendLiteral</span><span class="token punctuation">(</span><span class="token omit keyword">_</span> literal<span class="token punctuation">:</span> <span class="token class-name">String</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
string<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>literal<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">mutating</span> <span class="token keyword">func</span> <span class="token function-definition function">appendInterpolation</span><span class="token operator"><</span><span class="token class-name">T</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token omit keyword">_</span> value<span class="token punctuation">:</span> <span class="token class-name">T</span><span class="token punctuation">,</span> privacy<span class="token punctuation">:</span> <span class="token class-name">Privacy</span> <span class="token operator">=</span> <span class="token punctuation">.</span><span class="token keyword">private</span><span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">T</span><span class="token punctuation">:</span> <span class="token class-name">CustomStringConvertible</span> <span class="token punctuation">{</span>
<span class="token keyword">switch</span> privacy <span class="token punctuation">{</span>
<span class="token keyword">case</span> <span class="token punctuation">.</span><span class="token keyword">public</span><span class="token punctuation">:</span>
string<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>value<span class="token punctuation">.</span>description<span class="token punctuation">)</span>
<span class="token keyword">case</span> <span class="token punctuation">.</span><span class="token keyword">private</span><span class="token punctuation">:</span>
string<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span>
<span class="token class-name">Array</span><span class="token punctuation">(</span>repeating<span class="token punctuation">:</span> <span class="token string-literal"><span class="token string">"*"</span></span><span class="token punctuation">,</span> count<span class="token punctuation">:</span> value<span class="token punctuation">.</span>description<span class="token punctuation">.</span>count<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">joined</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre>
<p>The main workhorse of <code>ExpressibleByStringInterpolation</code> is <code>Message.StringInterpolation</code> type and its <code>appendInterpolation</code> implementation. You can check out <a href="https://gist.github.com/elfenlaid/568168ea4b2068aee2305752443a366f">full implementation here</a>.</p>
<h2 id="logger-nuances" tabindex="-1"><code>Logger</code> Nuances <a class="header-anchor" href="https://thegoodalright.dev/posts/string-interpolation-showcase/#logger-nuances" aria-hidden="true">#</a></h2>
<p>As I've mentioned before, an actual <code>Logger</code> implementation is way more intricate. We can get some insights into how things work by checking the <code>os</code> interface:</p>
<pre class="language-swift"><code class="language-swift"><span class="token keyword">public</span> <span class="token keyword">struct</span> <span class="token class-name">Logger</span> <span class="token punctuation">{</span>
<span class="token keyword">public</span> <span class="token keyword">func</span> <span class="token function-definition function">log</span><span class="token punctuation">(</span><span class="token omit keyword">_</span> message<span class="token punctuation">:</span> <span class="token class-name">OSLogMessage</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
<span class="token keyword">public</span> <span class="token keyword">struct</span> <span class="token class-name">OSLogMessage</span> <span class="token punctuation">:</span> <span class="token class-name">ExpressibleByStringInterpolation</span><span class="token punctuation">,</span> <span class="token class-name">ExpressibleByStringLiteral</span> <span class="token punctuation">{</span>
<span class="token keyword">public</span> <span class="token keyword">init</span><span class="token punctuation">(</span>stringInterpolation<span class="token punctuation">:</span> <span class="token class-name">OSLogInterpolation</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span></code></pre>
<p>So, <code>OSLogInterpolation</code> is an analogue to our <code>Message.StringInterpolation</code> type. By digging further, we can see tons of overrides of <code>appendInterpolation</code>:</p>
<pre class="language-swift"><code class="language-swift"><span class="token comment">//..</span>
<span class="token keyword">public</span> <span class="token keyword">mutating</span> <span class="token keyword">func</span> <span class="token function-definition function">appendInterpolation</span><span class="token punctuation">(</span>
<span class="token omit keyword">_</span> argumentString<span class="token punctuation">:</span> <span class="token attribute atrule">@autoclosure</span> <span class="token attribute atrule">@escaping</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token class-name">String</span><span class="token punctuation">,</span>
align<span class="token punctuation">:</span> <span class="token class-name">OSLogStringAlignment</span> <span class="token operator">=</span> <span class="token punctuation">.</span><span class="token keyword">none</span><span class="token punctuation">,</span>
privacy<span class="token punctuation">:</span> <span class="token class-name">OSLogPrivacy</span> <span class="token operator">=</span> <span class="token punctuation">.</span>auto
<span class="token punctuation">)</span>
logger<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span>
<span class="token string-literal"><span class="token string">"Hello, </span><span class="token interpolation-punctuation punctuation">\(</span><span class="token interpolation"><span class="token string-literal"><span class="token string">"World!"</span></span><span class="token punctuation">,</span>
align<span class="token punctuation">:</span> <span class="token class-name">OSLogStringAlignment</span><span class="token punctuation">.</span><span class="token keyword">right</span><span class="token punctuation">(</span>columns<span class="token punctuation">:</span> <span class="token number">20</span><span class="token punctuation">)</span></span><span class="token interpolation-punctuation punctuation">)</span><span class="token string">"</span></span>
<span class="token punctuation">)</span>
<span class="token comment">// > Hello, World!</span>
<span class="token keyword">public</span> <span class="token keyword">func</span> <span class="token function-definition function">appendInterpolation</span><span class="token punctuation">(</span>
<span class="token omit keyword">_</span> number<span class="token punctuation">:</span> <span class="token attribute atrule">@autoclosure</span> <span class="token attribute atrule">@escaping</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token class-name">Double</span><span class="token punctuation">,</span>
format<span class="token punctuation">:</span> <span class="token class-name">OSLogFloatFormatting</span> <span class="token operator">=</span> <span class="token punctuation">.</span>fixed<span class="token punctuation">,</span>
align<span class="token punctuation">:</span> <span class="token class-name">OSLogStringAlignment</span> <span class="token operator">=</span> <span class="token punctuation">.</span><span class="token keyword">none</span><span class="token punctuation">,</span>
privacy<span class="token punctuation">:</span> <span class="token class-name">OSLogPrivacy</span> <span class="token operator">=</span> <span class="token punctuation">.</span>auto
<span class="token punctuation">)</span>
logger<span class="token punctuation">.</span><span class="token function">info</span><span class="token punctuation">(</span><span class="token string-literal"><span class="token string">"</span><span class="token interpolation-punctuation punctuation">\(</span><span class="token interpolation"><span class="token number">123.123</span><span class="token punctuation">,</span> format<span class="token punctuation">:</span> <span class="token punctuation">.</span>exponential</span><span class="token interpolation-punctuation punctuation">)</span><span class="token string">"</span></span><span class="token punctuation">)</span>
<span class="token comment">// > 1.231230e+02</span></code></pre>
<p>First of all, arguments are following <code>@autoclosure @escaping () -> T</code> signature. In that way, some arguments won't be computed when logs are switched off (either overall or reduced by a log level).</p>
<p>Secondly, formatting parameters like <code>.format</code> and <code>.align</code> to tune output meessages. <a href="https://developer.apple.com/documentation/os/osloginterpolation#">The official <code>OSLogInterpolation</code>'s documentation</a> covers more of them.</p>
<p>Thirdly, there are many overrides for concrete types like <code>UInt8/UInt16/UInt32/UInt64/etc.</code>. I presume it speeds up either compilation or method resolution for specified types π€</p>
<h2 id="conclusion" tabindex="-1">Conclusion <a class="header-anchor" href="https://thegoodalright.dev/posts/string-interpolation-showcase/#conclusion" aria-hidden="true">#</a></h2>
<p>As you can see, Swift String Interpolation is a hilarious feature that might be easily overlooked, as it feels so at home with the current implementation.</p>
<p>You may find a much more detailed explanation of <a href="https://nshipster.com/expressiblebystringinterpolation/"><code>ExpressibleByStringInterpolation</code> at NSHipster</a>, which covers yet another <a href="https://nshipster.com/expressiblebystringinterpolation/#implementing-a-custom-string-interpolation-type">custom interpolation type example</a>.</p>
<p>Know more cases of <code>ExpressibleByStringInterpolation</code>? Don't hesitate to hit me up on <a href="https://mastodon.social/@elfenlaid">Mastodon</a>!</p>
<p>You can make anything, till next time :)</p>
CocoaPods Setup ft. Apple Silicon2021-04-14T00:00:00Zhttps://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/<p>The article describes <a href="https://cocoapods.org/">CocoaPods</a> setup for Apple Silicon (<code>arch64</code>) machines. We're going to skim over possible Ruby Environment setups, discuss why and how pin gems versions, and how to resolve common issues.</p>
<p></p><div class="table-of-contents"><ul><li><a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#ruby-management">Ruby Management </a><ul><li><a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#system-ruby">System Ruby </a></li><li><a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#homebrew">Homebrew </a></li><li><a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#rvm">RVM </a></li><li><a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#rbenv">rbenv </a></li><li><a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#asdf">asdf </a></li></ul></li><li><a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#gems-management">Gems Management </a><ul><li><a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#bundler">Bundler </a></li><li><a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#gemset">Gemset </a></li></ul></li><li><a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#pods-management">Pods Management </a><ul><li><a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#pod-install-quirks">Pod Install Quirks </a></li><li><a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#project-setup">Project Setup </a></li></ul></li><li><a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#conclusion-(or-rant)">Conclusion (or Rant) </a></li></ul></div><p></p>
<h2 id="ruby-management" tabindex="-1">Ruby Management <a class="header-anchor" href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#ruby-management" aria-hidden="true">#</a></h2>
<div class="p-4 my-5 bg-blueGray-100 dark:bg-blueGray-700 bg-opacity-100 dark:bg-opacity-30 rounded shadow-sm text-sm text-gray-600 dark:text-gray-400 flex items-center">
<svg class="w-6 h-6 stroke-current text-sky-600 dark:text-sky-300" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
<div class="unprose ml-4">
<p><strong>tl;dr:</strong></p>
<ul>
<li>
<p>If you can roll with old Ruby (<code>~2.6.3</code>) - checkout <a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#system-ruby">System Ruby</a> solution</p>
</li>
<li>
<p>Otherwise, I recommend to use <a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#asdf">asdf</a> or <a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#rbenv">rbenv</a></p>
<p></p></li></ul></div><p></p>
</div>
<p><a href="https://cocoapods.org/">CocoaPods</a> is a Ruby gem or package. Therefore we can't get much done without a proper Ruby setup. We'll kick off the tutorial by discussing our Ruby environment options.</p>
<h3 id="system-ruby" tabindex="-1">System Ruby <a class="header-anchor" href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#system-ruby" aria-hidden="true">#</a></h3>
<div class="p-4 my-5 bg-blueGray-100 dark:bg-blueGray-700 bg-opacity-100 dark:bg-opacity-30 rounded shadow-sm text-sm text-gray-600 dark:text-gray-400 flex items-center">
<svg class="w-6 h-6 stroke-current text-sky-600 dark:text-sky-300" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
<div class="unprose ml-4">
<p><strong>tl;dr:</strong></p>
<ul>
<li>
<p>Set <a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#system-ruby-gem_home">GEM_HOME</a></p>
</li>
<li>
<p>Set <a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/##system-ruby-path">PATH</a></p>
<p></p></li></ul></div><p></p>
</div>
<p>Fortunately for us, macOS has Ruby on board. It's a <code>2.6.3</code> version on Big Sur <code>11.2.3</code> at the time of writing. It's an outdated version and all, but let's be honest here, for most iOS projects, it might be enough π¬</p>
<p>Just imagine for a second, no Ruby environment juggling, and you are ready to roll from the start with a minimal setup. I assure you, it's nothing wrong with using the system Ruby. Especially so with arm-based machines:</p>
<pre class="language-bash"><code class="language-bash">$ <span class="token function">file</span> /usr/bin/ruby
/usr/bin/ruby: Mach-O universal binary with <span class="token number">2</span> architectures: <span class="token punctuation">[</span>x86_64:Mach-O <span class="token number">64</span>-bit executable x86_64<span class="token punctuation">]</span> <span class="token punctuation">[</span>arm64e:Mach-O <span class="token number">64</span>-bit executable arm64e<span class="token punctuation">]</span>
/usr/bin/ruby <span class="token punctuation">(</span>for architecture x86_64<span class="token punctuation">)</span>: Mach-O <span class="token number">64</span>-bit executable x86_64
/usr/bin/ruby <span class="token punctuation">(</span>for architecture arm64e<span class="token punctuation">)</span>: Mach-O <span class="token number">64</span>-bit executable arm64e</code></pre>
<p>See that <code>file</code> output with 2 architectures? It means that the binary can run either natively or under Rosetta (<code>arch -x86_64 ruby</code>) emulation.</p>
<p>Does it matter? Well, sometimes Rosetta fails. For example, launching <code>arch -x86_64</code> scripts under <code>arch -x86_64</code> mode will fail with <code>arch: posix_spawnp: ruby: Bad CPU type in executable</code>. You might think that the example is a bit contrived. Alas, libraries' support for <code>M1</code> is a mess at the time of writing. So you happen to find a strange workaround here and there.</p>
<p>Universal binaries don't matter much in terms of local Ruby setup, but I guess it's fewer things to worry about.</p>
<h4 id="system-ruby-gem_home" tabindex="-1">System Ruby: GEM_HOME <a class="header-anchor" href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#system-ruby-gem_home" aria-hidden="true">#</a></h4>
<p>Probably the main issue you'll encounter with the system Ruby is that <code>gem install</code> requires <code>sudo</code> by default.</p>
<p>But we can easily fix this by providing <code>GEM_HOME</code> environment variable:</p>
<pre class="language-bash"><code class="language-bash"><span class="token comment"># ~/.bash_profile or ~/.zshrc</span>
<span class="token builtin class-name">export</span> <span class="token assign-left variable">GEM_HOME</span><span class="token operator">=</span><span class="token string">"~/.gem/ruby/2.6.3/"</span>
<span class="token comment"># fish shell</span>
<span class="token builtin class-name">set</span> <span class="token parameter variable">-x</span> GEM_HOME <span class="token string">"~/.gem/ruby/2.6.3/"</span></code></pre>
<p>After sourcing the config (or opening a new terminal tab), <code>gem install</code> no longer needs <code>sudo</code>. Try it by installing <a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#bundler">bundler</a> (<code>gem install bundler</code>). You'll probably need it later :)</p>
<h4 id="system-ruby-path" tabindex="-1">System Ruby: PATH <a class="header-anchor" href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#system-ruby-path" aria-hidden="true">#</a></h4>
<p>By the way, you'll also need to add <code>~/.gem/ruby/2.6.3/bin</code> to your <code>$PATH</code>. It's the home of gem executables (<code>bundler</code>, <code>pods</code>, <code>fastlane</code>):</p>
<pre class="language-bash"><code class="language-bash"><span class="token comment"># ~/.bash_profile or ~/.zshrc</span>
<span class="token builtin class-name">export</span> <span class="token assign-left variable"><span class="token environment constant">PATH</span></span><span class="token operator">=</span><span class="token string">"~/.gem/ruby/2.6.3/bin<span class="token variable">${<span class="token environment constant">PATH</span><span class="token operator">:+</span><span class="token operator">:</span>${<span class="token environment constant">PATH</span>}</span>}"</span>
<span class="token comment"># fish shell</span>
<span class="token builtin class-name">set</span> <span class="token parameter variable">-U</span> fish_user_paths <span class="token string">"~/.gem/ruby/2.6.3/bin"</span> <span class="token variable">$fish_user_paths</span></code></pre>
<div class="p-4 my-5 bg-blueGray-100 dark:bg-blueGray-700 bg-opacity-100 dark:bg-opacity-30 rounded shadow-sm text-sm text-gray-600 dark:text-gray-400 flex items-center">
<svg class="w-6 h-6 stroke-current text-sky-600 dark:text-sky-300" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
<div class="unprose ml-4">
<p><a href="https://fishshell.com/">Fishshell</a> users, please checkout <a href="https://gist.github.com/elfenlaid/46f7f908d82bed0dd95e29c5ee618284">path.fish</a> function to simplify <code>$PATH</code> handling.</p>
<p></p></div><br />
</div><p></p>
<h3 id="homebrew" tabindex="-1">Homebrew <a class="header-anchor" href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#homebrew" aria-hidden="true">#</a></h3>
<div class="p-4 my-5 bg-blueGray-100 dark:bg-blueGray-700 bg-opacity-100 dark:bg-opacity-30 rounded shadow-sm text-sm text-gray-600 dark:text-gray-400 flex items-center">
<svg class="w-6 h-6 stroke-current text-sky-600 dark:text-sky-300" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
<div class="unprose ml-4">
<p><strong>tl;dr:</strong></p>
<ul>
<li>
<p>Install <a href="https://brew.sh/">Homebrew</a></p>
</li>
<li>
<p><code>brew install ruby@3</code> and follow the provided instructions</p>
<p></p></li></ul></div><p></p>
</div>
<p>If you don't swap Ruby versions, but need something other than the <a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#system-ruby">system Ruby version</a>, then <a href="https://brew.sh/">Homebrew</a> is here for you.</p>
<p><a href="https://brew.sh/">Homebrew</a> has a <a href="https://formulae.brew.sh/api/formula/ruby.json">collection of precompiled Ruby versions</a>. The group included <code>2.4, 2.5, 2.6, 2.7, 3.0</code> versions at the time of writing. You can specify version via:</p>
<pre class="language-bash"><code class="language-bash">$ brew <span class="token function">install</span> ruby@3.0
If you need to have ruby first <span class="token keyword">in</span> your <span class="token environment constant">PATH</span>, run:
<span class="token builtin class-name">echo</span> <span class="token string">'export PATH="/opt/homebrew/opt/ruby/bin:$PATH"'</span> <span class="token operator">>></span> ~/.zshrc
<span class="token punctuation">..</span>.</code></pre>
<p>I'll leave you in the good hands of <a href="https://brew.sh/">Homebrew</a> for the subsequent setup. All you need to do is to follow the provided instructions.</p>
<p>Mind that you can install as many versions as you need. Though the swapping between them is passable at best. Another downside of <a href="https://brew.sh/">Homebrew</a> is its precompiled versions range. If your version is not on the list, well, <code>brew</code> won't help you here.</p>
<p>Also, specific Ruby versions are drift with time. For example, <code>ruby@3</code> installs <code>3.0.1</code> today, but tomorrow's <code>ruby@3</code> will install <code>3.0.2</code> or something entirely different.</p>
<p>Despite all the downsides and inconveniences, it's a sensible path to take to avert Ruby compilation (some <a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#rvm">RVM</a> hacks use this <a href="https://brew.sh/">Homebrew</a> feature).</p>
<h3 id="rvm" tabindex="-1">RVM <a class="header-anchor" href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#rvm" aria-hidden="true">#</a></h3>
<div class="p-4 my-5 bg-blueGray-100 dark:bg-blueGray-700 bg-opacity-100 dark:bg-opacity-30 rounded shadow-sm text-sm text-gray-600 dark:text-gray-400 flex items-center">
<svg class="w-6 h-6 stroke-current text-sky-600 dark:text-sky-300" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
<div class="unprose ml-4">
<p><strong>tl;dr:</strong></p>
<ul>
<li>
<p>Follow the official <a href="https://rvm.io/">RVM</a> instructions</p>
</li>
<li>
<p>Also, mind I interest you in switching to something <a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#asdf">else</a> π€</p>
<p></p></li></ul></div><p></p>
</div>
<p><a href="https://rvm.io/">RVM</a> stands for Ruby Version Manager and is considered a classical approach to managing the Ruby environment.</p>
<p>There're already tons of guides on installing <a href="https://rvm.io/">RVM</a>. Therefore I leave you here.</p>
<p>As far as Apple Silicon is concerned, you might find the <a href="https://rvm.io/">RVM</a> ride to be a bit bumpy: <a href="https://github.com/rvm/rvm/issues/5047">Unable to install any version of ruby on macOS Big Sur Β· Issue #5047 Β· rvm/rvm</a>. Some workarounds, including installing <a href="https://brew.sh/">Homebrew</a> versions, are specified in the discussion.</p>
<p><a href="https://rvm.io/">RVM</a> is more than capable of providing a decent Ruby environment. Nevertheless, my take is <a href="https://rvm.io/">RVM</a> feels too hacky. It loads in the shell, overrides <code>cd</code>, or requires additional prompt mockery. I often find the <a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#rbenv">rbenv</a> or <a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#asdf">asdf</a> to be a better choice.</p>
<h3 id="rbenv" tabindex="-1">rbenv <a class="header-anchor" href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#rbenv" aria-hidden="true">#</a></h3>
<div class="p-4 my-5 bg-blueGray-100 dark:bg-blueGray-700 bg-opacity-100 dark:bg-opacity-30 rounded shadow-sm text-sm text-gray-600 dark:text-gray-400 flex items-center">
<svg class="w-6 h-6 stroke-current text-sky-600 dark:text-sky-300" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
<div class="unprose ml-4">
<p><strong>tl;dr:</strong></p>
<ul>
<li>
<p><code>brew install rbenv</code> and follow instructions</p>
</li>
<li>
<p><a href="https://github.com/rbenv/rbenv#installation">Custom installation instructions</a></p>
<p></p></li></ul></div><p></p>
</div>
<p><a href="https://github.com/rbenv/rbenv">rbenv</a> is yet Ruby environment manager. Alas, a less popular one. It works via <code>PATH</code> directories prioritization trick (<a href="https://github.com/rbenv/rbenv#how-it-works">How <code>rbenv</code> works</a> it details). So, no side scripts and other shell override shenanigans in the background (<a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#rvm">RVM</a> π)</p>
<p>It also has a dedicated page for <a href="https://github.com/rbenv/rbenv/wiki/Why-rbenv%3F"><code>rbenv</code> vs <code>RVM</code> comparison</a> if you are into it.</p>
<p>The main <code>rbenv</code>'s' drawback for <code>arm64</code> architecture is that some Ruby versions require <a href="https://github.com/rbenv/ruby-build/issues/1691">unconventional installation approach</a>. Nevertheless, <code>rbenv</code> is genuinely good. It was my Ruby environment manager before <a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#asdf">asdf</a>.</p>
<h3 id="asdf" tabindex="-1">asdf <a class="header-anchor" href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#asdf" aria-hidden="true">#</a></h3>
<div class="p-4 my-5 bg-blueGray-100 dark:bg-blueGray-700 bg-opacity-100 dark:bg-opacity-30 rounded shadow-sm text-sm text-gray-600 dark:text-gray-400 flex items-center">
<svg class="w-6 h-6 stroke-current text-sky-600 dark:text-sky-300" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
<div class="unprose ml-4">
<p><strong>tl;dr:</strong></p>
<ul>
<li>
<p>follow the <a href="https://asdf-vm.com/#/core-manage-asdf?id=install">official install instructions</a></p>
</li>
<li>
<p>install Ruby plugin <code>asdf plugin-add ruby https://github.com/asdf-vm/asdf-ruby.git</code></p>
</li>
<li>
<p>install required version <code>asdf install ruby 3.0.0 && asdf ruby global 3.0.0</code></p>
<p></p></li></ul></div><p></p>
</div>
<p><a href="https://github.com/asdf-vm/asdf">asdf</a> is an extendable version manager with support for <a href="https://github.com/asdf-vm/asdf-ruby">Ruby</a>, <a href="https://github.com/asdf-vm/asdf-nodejs">Node.js</a>, <a href="https://github.com/asdf-vm/asdf-elixir">Elixir</a>, <a href="https://github.com/asdf-vm/asdf-erlang">Erlang</a>, and <a href="https://asdf-vm.com/#/plugins-all">more</a>. The principle behind <code>asdf</code> is similar to <code>rbenv</code>, both use <code>PATH</code> directories prioritization.</p>
<p><a href="https://asdf-vm.com/#/core-manage-asdf?id=install">Official documentation</a> is genuinely good. Alas, an installation process might turn a bit cryptic, especially considering <a href="https://github.com/asdf-vm/asdf/issues/785">Common Homebrew issues π» Β· Issue #785</a>. I'm personally using the plain <code>git clone</code> method here (don't forget to subscribe to <a href="https://github.com/asdf-vm/asdf">asdf releases on GitHub</a>)</p>
<p><a href="https://github.com/asdf-vm/asdf-ruby">asdf Ruby plugin</a> aside from managing Ruby environments, also can:</p>
<ul>
<li>Install <a href="https://github.com/asdf-vm/asdf-ruby#default-gems">default gems</a> right after installing a Ruby versions. Presumably, you want <code>bundler</code>, <code>pry</code>, or gems of your choice to be available on each and every installed Ruby version.</li>
<li>Help with <a href="https://github.com/asdf-vm/asdf-ruby#migrating-from-another-ruby-version-manager">migrating from other Ruby version managers</a>. Meaning it supports <code>.ruby-version</code> configuration file</li>
</ul>
<p><a href="https://github.com/asdf-vm/asdf">asdf</a> seems to go well along M1 and builds most Ruby versions just fine. Alas, <a href="https://github.com/asdf-vm/asdf-ruby/issues/210">there are nuances</a></p>
<p>If you got tired of a never-ending stream of language managers, check out <a href="https://github.com/asdf-vm/asdf">asdf</a>. Despite a bit messy setup, it's dope!</p>
<h2 id="gems-management" tabindex="-1">Gems Management <a class="header-anchor" href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#gems-management" aria-hidden="true">#</a></h2>
<div class="p-4 my-5 bg-blueGray-100 dark:bg-blueGray-700 bg-opacity-100 dark:bg-opacity-30 rounded shadow-sm text-sm text-gray-600 dark:text-gray-400 flex items-center">
<svg class="w-6 h-6 stroke-current text-sky-600 dark:text-sky-300" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
<div class="unprose ml-4">
<p><strong>tl;dr:</strong> use <a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#bundler">bundler</a></p>
<p></p></div><br />
</div><p></p>
<p>At this point, I presume you have a working Ruby setup. Check out the <a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#ruby-management">Ruby management</a> chapter if it's not the case.</p>
<p>It's a great temptation to globally install <a href="https://cocoapods.org/">CocoaPods</a>(<code>gem install cocoapods</code>) and jump straight into the project. But hear me out, knowing (version control) the exact gem version we work with is always a good idea.</p>
<p>I'm sure you want to get the same result from running <code>pod install</code> on your machine and on a college's machine or a build server. Even if you an indie developer, there's a notion of time. Your future self will have a different setup. Imagine how happy you'll be after enumerating <a href="https://cocoapods.org/">CocoaPods</a> and Ruby versions for the whole day just to reproduce a particular build. The pinned or at least known version of tooling never harms.</p>
<p>With that out of the way, let's discuss how we can pin gem versions in the Ruby environment.</p>
<h3 id="bundler" tabindex="-1">Bundler <a class="header-anchor" href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#bundler" aria-hidden="true">#</a></h3>
<p><a href="https://bundler.io/">bundler</a> is sort of <a href="https://cocoapods.org/">CocoaPods</a> but for Ruby gems. From the side, <a href="https://bundler.io/">bundler</a> looks like a complete Xzibit thing: installing package manage to manage package manager while managing packages.</p>
<p>Alas, while sounding like insanity, it's a surprisingly reoccurring theme. For example, Python with <code>easy_install</code>, <code>pip</code>, and <code>pipenv</code> or Haskell and its <code>slack</code> + <code>cabal</code> pairing.</p>
<p><a href="https://cocoapods.org/">CocoaPods</a> is heavily inspired by <a href="https://bundler.io/">bundler</a>, and indeed we can draw a lot of parallels between them:</p>
<ul>
<li><code>Gemfile</code> is analog to <code>Podfile</code></li>
<li><code>Gemfile.lock</code> is analog to <code>Podfile.lock</code></li>
</ul>
<p>By the way, <a href="https://cocoapods.org/">CocoaPods</a> themselves <a href="https://github.com/CocoaPods/CocoaPods/blob/master/Gemfile">use bundler</a>. And as you can see, <code>bundler</code> doesn't take much to setup:</p>
<ol>
<li>Create a <code>Gemfile</code> (or use <code>bundle init</code>) and specify required dependencies. A typical iOS project <code>Gemfile</code>:</li>
</ol>
<pre class="language-ruby"><code class="language-ruby"><span class="token comment"># frozen_string_literal: true</span>
source <span class="token string-literal"><span class="token string">'https://rubygems.org'</span></span>
gem <span class="token string-literal"><span class="token string">'cocoapods'</span></span>
gem <span class="token string-literal"><span class="token string">'fastlane'</span></span></code></pre>
<ol start="2">
<li>Install specified dependencies with <code>bundle install</code>. At this stage <code>bundler</code> generates dependency tree in <code>Gemfile.lock</code> file:</li>
</ol>
<pre class="language-ruby"><code class="language-ruby"><span class="token constant">GEM</span>
<span class="token symbol">remote</span><span class="token operator">:</span> https<span class="token operator">:</span><span class="token operator">/</span><span class="token operator">/</span>rubygems<span class="token punctuation">.</span>org<span class="token operator">/</span>
<span class="token symbol">specs</span><span class="token operator">:</span>
CFPropertyList <span class="token punctuation">(</span><span class="token number">3.0</span><span class="token number">.3</span><span class="token punctuation">)</span>
activesupport <span class="token punctuation">(</span><span class="token number">5.2</span><span class="token number">.4</span><span class="token number">.5</span><span class="token punctuation">)</span>
concurrent<span class="token operator">-</span>ruby <span class="token punctuation">(</span><span class="token operator">~</span><span class="token operator">></span> <span class="token number">1.0</span><span class="token punctuation">,</span> <span class="token operator">>=</span> <span class="token number">1.0</span><span class="token number">.2</span><span class="token punctuation">)</span>
i18n <span class="token punctuation">(</span><span class="token operator">>=</span> <span class="token number">0.7</span><span class="token punctuation">,</span> <span class="token operator"><</span> <span class="token number">2</span><span class="token punctuation">)</span>
minitest <span class="token punctuation">(</span><span class="token operator">~</span><span class="token operator">></span> <span class="token number">5.1</span><span class="token punctuation">)</span>
<span class="token operator">...</span></code></pre>
<p>Don't forget to check in <code>Gemfile.lock</code> to the source control or pin the exact gem versions in the <code>Gemfile</code>.</p>
<ol start="3">
<li>Run commands with <code>bundle exec</code> prefix:</li>
</ol>
<pre class="language-bash"><code class="language-bash">$ bundle <span class="token builtin class-name">exec</span> pod <span class="token function">install</span></code></pre>
<p>You might add an alias <code>alias be="bundle exec"</code> to avoid typing <code>bundle exec</code> over and over again:</p>
<pre class="language-bash"><code class="language-bash">$ be pod <span class="token function">install</span></code></pre>
<p>Also, most of the shells have a dedicated <code>bundler</code> plugin with completions:</p>
<ul>
<li><a href="https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/bundler">ohmyzsh/plugins/bundler at master Β· ohmyzsh/ohmyzsh</a></li>
<li><a href="https://github.com/oh-my-fish/plugin-bundler">oh-my-fish/plugin-bundler: Use Ruby's Bundler automatically for some commands.</a></li>
<li><code>brew</code> comes with a <a href="https://formulae.brew.sh/formula/bundler-completion">bundler-completion formulae</a></li>
</ul>
<ol start="4">
<li>(Optional) <code>bundler</code> can pin <a href="https://bundler.io/gemfile_ruby.html">Ruby version</a> as well:</li>
</ol>
<pre class="language-ruby"><code class="language-ruby">ruby <span class="token string-literal"><span class="token string">'~> 2.6.0'</span></span>
<span class="token comment"># or</span>
ruby <span class="token string-literal"><span class="token string">'3.0.1'</span></span></code></pre>
<p>You might consider using this method, but it depends on your Ruby environment manager of choice. Some managers use <code>.ruby-version</code> or <code>.tool-versions</code> file mechanisms.</p>
<h3 id="gemset" tabindex="-1">Gemset <a class="header-anchor" href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#gemset" aria-hidden="true">#</a></h3>
<p>A lesser-known option of handling gem versions is a <code>gemset</code>. Gemset is a snapshot of globally installed gems. Both <a href="https://rvm.io/gemsets">RVM</a> and <a href="https://github.com/jf/rbenv-gemset">rbenv</a> support a gemset-like notion. Alas, <a href="https://github.com/asdf-vm/asdf-ruby/issues/25">asdf Ruby plugin don't and won't have it</a></p>
<p>From the first take, gemsets won't work well in "long-term" projects. Yet, it can be helpful in one-shot scripts or library tryouts. Nevertheless, I wholeheartedly recommend sticking to <a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#bundler">bundler</a>. But if you have a <code>bundler</code> reckoning, I guess gemsets are better than nothing :)</p>
<h2 id="pods-management" tabindex="-1">Pods Management <a class="header-anchor" href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#pods-management" aria-hidden="true">#</a></h2>
<p>Hey, we're getting closer! At this point, I presume you have a working Ruby setup and installed <a href="https://cocoapods.org/">CocoaPods</a> (either via <a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#bundler">bundler</a> or globally). It it's not the case, consider skimming through <a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#ruby-management">Ruby environment management</a> and <a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#gems-management">gems management</a> parts.</p>
<p>Aside from few quirks, there's nothing new to running <a href="https://cocoapods.org/">CocoaPods</a> under Apple Silicon. Alas, <a href="https://github.com/CocoaPods/CocoaPods/issues/10408">CocoaPods doesn't officially support Apple Silicon</a> at the moment of writing. With that said, CocoaPods run perfectly fine under Rosetta and times even natively.</p>
<p>Our end goal is a project setup that runs on both <code>arm64</code> and <code>x86</code> simulators. Such configuration allows a graceful migration without sacrificing simulator performance.</p>
<p>Firstly, we'll see that <code>pod install</code> works as expected. Secondly, we'll build a project and discuss possible build issues.</p>
<h3 id="pod-install-quirks" tabindex="-1">Pod Install Quirks <a class="header-anchor" href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#pod-install-quirks" aria-hidden="true">#</a></h3>
<p>If you are lucky enough, <code>pod install</code> or <code>bundle exec pod install</code> won't cause any issues on your machine. If that's the case, move along to the <a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#project-setup">project setup</a> chapter.</p>
<p>If not, well, here's the most common issue with <a href="https://cocoapods.org/">CocoaPods</a> out there:</p>
<h4 id="ffi-error" tabindex="-1"><code>ffi</code> error <a class="header-anchor" href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#ffi-error" aria-hidden="true">#</a></h4>
<p><code>pod install</code> exists with a <code>ffi</code>-related exception:</p>
<pre class="language-bash"><code class="language-bash">$ bundle <span class="token builtin class-name">exec</span> pod <span class="token function">install</span>
<span class="token punctuation">..</span>.
LoadError - dlsym<span class="token punctuation">(</span>0x7fc182ca67b0, Init_ffi_c<span class="token punctuation">)</span>: symbol not found - /Library/Ruby/Gems/2.6.0/gems/ffi-1.14.2/lib/ffi_c.bundle
<span class="token punctuation">..</span>.</code></pre>
<p>Turns out, <code>ffi</code> is also <a href="https://github.com/libffi/libffi/pull/621">waiting for M1 adoption</a> and probably will get one in <code>3.4</code> version.</p>
<p>Depending on your setup, try the following steps if you're using <a href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#bundler">bundler</a>:</p>
<ul>
<li>Pin <code>ffi</code> in <code>Gemfile</code>: <code>gem 'ffi', '1.14.2'</code></li>
<li>Run <code>bundler install</code> (You'll probably need to delete <code>Gemfile.lock</code> beforehand)</li>
<li>Run <code>pod install</code> under Rosetta: <code>arch -x86_64 bundle exec pod install</code></li>
</ul>
<p>If you have a global CocoaPods pod installed, the steps are mostly the same, but instead, you'll need to pin <code>ffi</code> globally: <code>gem install ffi -v 1.14.2</code></p>
<h3 id="project-setup" tabindex="-1">Project Setup <a class="header-anchor" href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#project-setup" aria-hidden="true">#</a></h3>
<p>At last, it's time to build and run the project! Select iOS simulator target and launch the build process. Take my congratulations π if everything works fine. It was a long way. I wasn't that lucky and faced another bunch of issues.</p>
<p>First of all, if the project was create in Xcode 11 or older, make sure to get rid of <code>VALID_ARCHS</code> build setting:</p>
<ul>
<li><code>VALID_ARCHS</code> is <a href="https://developer.apple.com/documentation/xcode-release-notes/xcode-12-release-notes">no longer a thing in Xcode 12</a>
<ul>
<li>Remove it from build settings (in user-defined variables) and config files</li>
</ul>
</li>
<li><code>VALID_ARCHS</code> setting was replaced by <code>EXCLUDED_ARCHS</code>. Update it accordingly or leave empty</li>
<li>Optionally (but highly recommended) to have <code>ONLY_ACTIVE_ARCH = YES</code> for <code>Debug</code> builds
<ul>
<li>Make sure that it's set to <code>ONLY_ACTIVE_ARCH = NO</code> for <code>Release</code>. Xcode doesn't care about <code>ONLY_ACTIVE_ARCH</code> when assembling the <code>Release</code> for a general device. Yet, let's keep things explicit, shall we?</li>
</ul>
</li>
</ul>
<h4 id="excluded_archs-shenanigans" tabindex="-1"><code>EXCLUDED_ARCHS</code> shenanigans <a class="header-anchor" href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#excluded_archs-shenanigans" aria-hidden="true">#</a></h4>
<p>You've built the project only to face the following error:</p>
<pre class="language-bash"><code class="language-bash">Showing Recent Errors Only
~/Work/quotes/Quotes/Sources/Scenes/Premium/PremiumScene.swift:5:8: Could not <span class="token function">find</span> module <span class="token string">'Analytics'</span> <span class="token keyword">for</span> target <span class="token string">'arm64-apple-ios-simulator'</span><span class="token punctuation">;</span> found: x86_64-apple-ios-simulator, x86_64</code></pre>
<p>Some CocoaPods vendors add a nasty quick-fix to work around <a href="https://developer.apple.com/documentation/xcode-release-notes/xcode-12-release-notes">Xcode 12 VALID_ARCHS</a> deprecation:</p>
<pre class="language-ruby"><code class="language-ruby">Pod<span class="token double-colon punctuation">::</span><span class="token class-name">Spec</span><span class="token punctuation">.</span><span class="token keyword">new</span> <span class="token keyword">do</span> <span class="token operator">|</span>s<span class="token operator">|</span>
<span class="token comment"># ...</span>
s<span class="token punctuation">.</span>user_target_xcconfig <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token string-literal"><span class="token string">'EXCLUDED_ARCHS[sdk=iphonesimulator*]'</span></span> <span class="token operator">=></span> <span class="token string-literal"><span class="token string">'arm64'</span></span> <span class="token punctuation">}</span>
s<span class="token punctuation">.</span>pod_target_xcconfig <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token string-literal"><span class="token string">'EXCLUDED_ARCHS[sdk=iphonesimulator*]'</span></span> <span class="token operator">=></span> <span class="token string-literal"><span class="token string">'arm64'</span></span> <span class="token punctuation">}</span>
<span class="token comment"># ...</span>
<span class="token keyword">end</span></code></pre>
<p>These two lines force Xcode to skip assembling the <code>arm64</code> simulator slice and failing the build.</p>
<div class="p-4 my-5 bg-blueGray-100 dark:bg-blueGray-700 bg-opacity-100 dark:bg-opacity-30 rounded shadow-sm text-sm text-gray-600 dark:text-gray-400 flex items-center">
<svg class="w-6 h-6 stroke-current text-sky-600 dark:text-sky-300" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
<div class="unprose ml-4">
<p>Tweaking <code>user_target_xcconfig</code> in a <code>Podspec</code> is a big NO-NO. Please, consider other methods.</p>
<p></p></div><br />
</div><p></p>
<p>To fix the issues we're going to patch the project and libraries <code>.xcconfig</code> files:</p>
<pre class="language-ruby"><code class="language-ruby">post_install <span class="token keyword">do</span> <span class="token operator">|</span>pi<span class="token operator">|</span>
pi<span class="token punctuation">.</span>target_installation_results<span class="token punctuation">.</span><span class="token keyword">each</span> <span class="token keyword">do</span> <span class="token operator">|</span>result<span class="token operator">|</span>
result<span class="token punctuation">.</span><span class="token keyword">each</span> <span class="token keyword">do</span> <span class="token operator">|</span>name<span class="token punctuation">,</span> installation_result<span class="token operator">|</span>
target <span class="token operator">=</span> installation_result<span class="token punctuation">.</span>target
installation_result<span class="token punctuation">.</span>native_target<span class="token punctuation">.</span>build_configurations<span class="token punctuation">.</span><span class="token keyword">each</span> <span class="token keyword">do</span> <span class="token operator">|</span>config<span class="token operator">|</span>
config_path <span class="token operator">=</span> target<span class="token punctuation">.</span>xcconfig_path<span class="token punctuation">(</span>config<span class="token punctuation">.</span>name<span class="token punctuation">)</span>
<span class="token keyword">next</span> <span class="token keyword">unless</span> config_path<span class="token punctuation">.</span>exist<span class="token operator">?</span>
config_data <span class="token operator">=</span> config_path<span class="token punctuation">.</span>read
config_data<span class="token punctuation">.</span>gsub<span class="token operator">!</span><span class="token punctuation">(</span><span class="token string-literal"><span class="token string">"EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm64"</span></span><span class="token punctuation">,</span> <span class="token string-literal"><span class="token string">""</span></span><span class="token punctuation">)</span>
<span class="token builtin">File</span><span class="token punctuation">.</span>write<span class="token punctuation">(</span>config_path<span class="token punctuation">,</span> config_data<span class="token punctuation">)</span>
<span class="token keyword">end</span>
<span class="token keyword">end</span>
<span class="token keyword">end</span>
<span class="token keyword">end</span></code></pre>
<p>I find it as a rather dirty solution, but that's the reality we live in. Also, keep in mind that tweaking <code>build_settings</code> alone won't cut here.</p>
<h4 id="missing-arm64-simulator-slice" tabindex="-1">Missing <code>arm64</code> Simulator Slice <a class="header-anchor" href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#missing-arm64-simulator-slice" aria-hidden="true">#</a></h4>
<p><code>pod install</code> runs just fine, but the project won't build with the following error:</p>
<pre class="language-bash"><code class="language-bash">ld: <span class="token keyword">in</span> <span class="token punctuation">..</span>/<span class="token punctuation">..</span>/SpotifyiOS.framework/SpotifyiOS<span class="token punctuation">(</span>MPMessagePackReader.o<span class="token punctuation">)</span>, building <span class="token keyword">for</span> iOS Simulator, but linking <span class="token keyword">in</span> object <span class="token function">file</span> built <span class="token keyword">for</span> iOS, <span class="token function">file</span> <span class="token string">'../../SpotifyiOS.framework/SpotifyiOS'</span> <span class="token keyword">for</span> architecture arm64
clang: error: linker <span class="token builtin class-name">command</span> failed with <span class="token builtin class-name">exit</span> code <span class="token number">1</span> <span class="token punctuation">(</span>use <span class="token parameter variable">-v</span> to see invocation<span class="token punctuation">)</span></code></pre>
<p>We've encountered a close-sourced (framework or library) dependency that doesn't have an <code>arm64</code> Simulator slice. Alas, we can't do much here unless <a href="https://github.com/bogo/arm64-to-sim">you're brave enough π</a></p>
<p>The thing is, classic fat Mach-O libraries can't have two slices of <code>arm64</code> architecture. The trick works only with the new [XCFramework](<a href="https://developer.apple.com/documentation/swift_packages/distributing_binary_frameworks_as_swift_packages">Distributing Binary Frameworks as Swift Packages | Apple Developer Documentation</a>). You can find more about XCFrameworks here:</p>
<ul>
<li><a href="https://pspdfkit.com/blog/2020/supporting-xcframeworks/">Supporting XCFrameworks | PSPDFKit</a></li>
<li><a href="https://kean.blog/post/xcframeworks-caveats">XCFrameworks | kean.blog</a></li>
</ul>
<p>The quick fix is to launch Xcode under Rosetta (<code>arch -x86_64 xed .</code>) and notify a third-party vendor.</p>
<h2 id="conclusion-(or-rant)" tabindex="-1">Conclusion (or Rant) <a class="header-anchor" href="https://thegoodalright.dev/posts/cocoapods-ft-apple-silicon/#conclusion-(or-rant)" aria-hidden="true">#</a></h2>
<p>iOS package management is living through its wild west. The advent of new architecture surely doesn't make things easier either. For now, I hope that you found the guide helpful.</p>
<p>Despite all the rough edges, I can't thank the iOS community enough. <a href="https://cocoapods.org/">CocoaPods</a> team delivers the best experience possible and even more. I only wish for Apple to stop pretending CocoaPods doesn't exist in the first place.</p>
<p>It's hard to make any calls but just imagine a graceful Swift Package Manager migration or proactively making CocoaPods support a new Xcode change. How cool would that be, huh? π€</p>
<p>You can make anything, till next time :)</p>