<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="http://jaimzuber.com/feed.xml" rel="self" type="application/atom+xml" /><link href="http://jaimzuber.com/" rel="alternate" type="text/html" /><updated>2026-04-08T20:03:30+00:00</updated><id>http://jaimzuber.com/feed.xml</id><title type="html">Jaim Zuber</title><subtitle>Apple platforms engineering and leadership. Available now.</subtitle><entry><title type="html">Voice AI: Speech-to-Text on Apple Silicon — Talk Notes</title><link href="http://jaimzuber.com/talks/voice-ai/voice-ai-talk-notes.html" rel="alternate" type="text/html" title="Voice AI: Speech-to-Text on Apple Silicon — Talk Notes" /><published>2026-04-06T06:00:00+00:00</published><updated>2026-04-06T06:00:00+00:00</updated><id>http://jaimzuber.com/talks/voice-ai/voice-ai-talk-notes</id><content type="html" xml:base="http://jaimzuber.com/talks/voice-ai/voice-ai-talk-notes.html"><![CDATA[<p>Reference links for my voice AI talk at <a href="https://www.meetup.com/minneapolis-ai-engineer-meetup/events/313816977/">AI Engineer April 2026 Meetup</a> in Minneapolis.</p>

<hr />

<h2 id="cloud-stt-providers">Cloud STT Providers</h2>

<p>Start here. Get an API key and have something working in ten minutes.</p>

<ul>
  <li><a href="https://deepgram.com">Deepgram</a> — WebSocket streaming, VAD and diarization included</li>
  <li><a href="https://www.assemblyai.com">AssemblyAI</a> — strong diarization and summarization features</li>
  <li><a href="https://platform.openai.com/docs/guides/audio">OpenAI Audio API</a> — Whisper endpoint plus TTS in one place</li>
</ul>

<hr />

<h2 id="open-source-models">Open Source Models</h2>

<ul>
  <li><a href="https://github.com/openai/whisper">Whisper</a> — OpenAI’s open source model; the benchmark everything else is measured against</li>
  <li><a href="https://huggingface.co/collections/nvidia/parakeet">Parakeet</a> — NVIDIA’s streaming-capable ASR model collection; CC-BY-4.0</li>
  <li><a href="https://github.com/moonshine-ai/moonshine">Moonshine</a> — small, fast model designed for edge devices; runs on ONNX; <a href="https://huggingface.co/UsefulSensors/moonshine">weights on Hugging Face</a></li>
</ul>

<hr />

<h2 id="on-device--apple-silicon">On-Device / Apple Silicon</h2>

<ul>
  <li><a href="https://github.com/ggerganov/whisper.cpp">Whisper.cpp</a> — C++ port of Whisper; runs on CPU, no GPU required</li>
  <li><a href="https://github.com/argmaxinc/WhisperKit">WhisperKit</a> — Whisper optimized for Apple Silicon via Core ML; open source</li>
  <li><a href="https://github.com/FluidInference/FluidAudio">FluidAudio</a> — open source Swift SDK; runs Parakeet on the Apple Neural Engine; includes VAD and diarization; iOS 17+ / macOS 14+</li>
  <li><a href="https://www.argmaxinc.com/">Argmax</a> — commercial SDK for Mac and iOS; Parakeet and Whisper on Core ML; WebSocket API mirrors Deepgram so you can swap cloud for on-device in one line of code</li>
</ul>

<hr />

<h2 id="text-to-speech">Text-to-Speech</h2>

<p>Not the focus of this talk, but most real apps use both directions.</p>

<ul>
  <li><a href="https://elevenlabs.io">ElevenLabs</a> — high-quality cloud TTS</li>
  <li><a href="https://platform.openai.com/docs/guides/audio">OpenAI Audio API</a> — TTS and STT in one endpoint <em>(see above)</em></li>
</ul>

<hr />

<h2 id="voice-interface-products">Voice Interface Products</h2>

<ul>
  <li><a href="https://wisprflow.ai/">Wispr Flow</a> — voice dictation for Mac/Windows/iOS; works in any text field; requires accessibility access</li>
  <li><a href="https://heywillow.io/">Willow</a> — open source, self-hosted voice assistant; <a href="https://github.com/HeyWillow/willow">GitHub</a></li>
</ul>

<hr />

<h2 id="concepts">Concepts</h2>

<ul>
  <li><a href="https://developer.apple.com/documentation/coreml">Core ML</a> — Apple’s on-device ML framework; routes inference to CPU, GPU, or ANE depending on model and device</li>
  <li><a href="https://github.com/snakers4/silero-vad">Silero VAD</a> — widely used open source Voice Activity Detection model; runs on ONNX</li>
  <li><a href="https://github.com/pyannote/pyannote-audio">pyannote.audio</a> — the standard open source library for speaker diarization</li>
  <li><a href="https://onnx.ai">ONNX</a> — open standard for ML model interoperability across runtimes</li>
</ul>

<hr />

<h2 id="managed-inference--hosting">Managed Inference / Hosting</h2>

<ul>
  <li><a href="https://www.baseten.co">Baseten</a> — host specialized models on GPU infrastructure; more control than cloud APIs, less ops than self-hosting</li>
</ul>

<hr />

<h2 id="products-referenced">Products Referenced</h2>

<ul>
  <li><a href="https://www.notion.com/product/ai-meeting-notes">Notion AI Meeting Notes</a> — shipped in 2025; combines transcription with LLM summarization</li>
  <li><a href="https://www.anthropic.com/claude">Anthropic Claude</a> — added device-level voice control in 2026</li>
</ul>]]></content><author><name></name></author><category term="talks" /><category term="voice-ai" /><summary type="html"><![CDATA[Reference links for my voice AI talk at AI Engineer April 2026 Meetup in Minneapolis.]]></summary></entry><entry><title type="html">Keep Minnebar Weird</title><link href="http://jaimzuber.com/keep-minnebar-weird.html" rel="alternate" type="text/html" title="Keep Minnebar Weird" /><published>2026-03-19T14:00:00+00:00</published><updated>2026-03-19T14:00:00+00:00</updated><id>http://jaimzuber.com/keep-minnebar-weird</id><content type="html" xml:base="http://jaimzuber.com/keep-minnebar-weird.html"><![CDATA[<p>This is adapted from <a href="https://sharpfivesoftware.com/2013/03/31/how-to-win-at-minnebar/">a post I wrote in 2013</a>. Still applies. The next <a href="https://minnestar.org/minnebar/">Minnebar</a> is May 2nd at Best Buy Headquarters.</p>

<hr />

<p>Minnebar is the Twin Cities annual Tech Industry BarCamp. It was, and continues to be, a great event but has started to lose some of the elements that made it so great in the first place. In this post I’ll explain ideas that can help Minnebar keep its edge.</p>

<h3 id="what-should-a-barcamp-be-like">What should a BarCamp be like</h3>

<p>My first experience at Minnebar was in 2010. I remember speakers inviting their audience for input, great discussion developing from the crowd and conversations with other participants spilling into the halls between sessions. What I loved about Minnebar is it exploits some of the universal truisms I’ve found with great tech conferences.</p>

<p><strong>The brainpower in the seats is greater than the brainpower on stage.</strong></p>

<p><strong>The conversations in the hallway between sessions (the hallway track) are more valuable than the sessions themselves.</strong></p>

<p>Minnebar gets smart people conversing about things they’re passionate about. This is the best environment to learn from.</p>

<h3 id="so-whats-the-problem-exactly">So what’s the problem, exactly?</h3>

<p>Minnebar has gotten big. The organizers do a tremendous job putting it all together. The problem is we’re fighting human psychology. The grass roots effort needed to bring out the best in group sessions have a harder time scaling to this many people. It’s easy to generate discussion with 10 people in the room. But many of the sessions at Minnebar last year had over 50 participants. This makes it much easier to fall into the standard “presenter talks and the audience listens” paradigm. There’s nothing wrong with this, but it goes against the goal of BarCamps.</p>

<h3 id="so-what-can-we-do-to-help">So what can we do to help?</h3>

<ul>
  <li>Invite other experts to your session. Encourage them to share their approaches to problems you’re facing (or think you have a solution to).</li>
  <li>Ask your audience questions. This encourages the kind of discussions that carry out into the halls. You don’t need to have the answer for everything you talk about, or solve everything right there.</li>
  <li>Encourage questions from the crowd. Few things facilitate discussion like a good question, but big crowds discourage them. The only thing worse than feeling dumb is feeling dumb in front of 50 people. If the speaker doesn’t make it OK to ask a question, they won’t get asked.</li>
</ul>

<h3 id="so-are-you-taking-your-own-advice">So are you taking your own advice?</h3>

<p>Yep. I’ll be submitting something soon. If you lead a session, please don’t talk at people for 40 minutes. Ask questions. Leave space. The best part of Minnebar has always been what happens when you do.</p>

<p>If you attend my session, please ask questions and share your own experience. I hope we’ll be talking in the halls far into the next session.</p>

<h3 id="how-else-can-you-keep-minnebar-weird">How else can you Keep Minnebar Weird?</h3>
<p>Talk about the nerdy stuff you geek out about and think no-one else cares about. Those are always my favorite talks. The niche obsessions make the best sessions.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[This is adapted from a post I wrote in 2013. Still applies. The next Minnebar is May 2nd at Best Buy Headquarters.]]></summary></entry><entry><title type="html">AI-assisted wins and anti-patterns</title><link href="http://jaimzuber.com/ai/ai-assisted-wins-and-anti-patterns.html" rel="alternate" type="text/html" title="AI-assisted wins and anti-patterns" /><published>2026-03-06T14:00:00+00:00</published><updated>2026-03-06T14:00:00+00:00</updated><id>http://jaimzuber.com/ai/ai-assisted-wins-and-anti-patterns</id><content type="html" xml:base="http://jaimzuber.com/ai/ai-assisted-wins-and-anti-patterns.html"><![CDATA[<p>I’ve been doing AI-assisted development for a while now. Here’s a list of things that are going well. And anti-patterns to avoid. From my conversations with other devs, many are seeing the same things.</p>

<p>There’s a lot of noise out there. The hype merchants promise AI will replace every developer by Tuesday. The skeptics insist nothing has changed. Both are wrong.</p>

<p>What’s actually happening is more interesting. Some things are easier now. Some are the same as they’ve always been. And new failure modes have emerged that need more attention.</p>

<p><strong>TL;DR</strong></p>

<ul>
  <li>AI meaningfully reduces the cost of context switching (good news for anyone who attends too many meetings)</li>
  <li>Your weaknesses become average. Your strengths get amplified</li>
  <li>You can stay productive later in the day</li>
  <li>Documentation and in-depth PR descriptions are basically free</li>
  <li>Shipping code you don’t understand will catch up with you</li>
  <li>Skill atrophy is real. Keep your skills sharp</li>
  <li>If nobody deeply understands the codebase, you have a serious problem</li>
</ul>

<hr />

<h2 id="ai-assisted-wins">AI-assisted wins</h2>

<p>Here are some things that are reliably providing value in organizations.</p>

<h3 id="reduced-cost-of-context-switches">Reduced cost of context switches</h3>

<p>The maker vs. manager schedule problem is real. Was real before AI. Still is. But the drag has lessened. I used to dread coming back from a two-hour meeting block.</p>

<p>Sit down at the keyboard.</p>

<p>Stare at the code.</p>

<p>Spend 20 minutes trying to remember what my previous self was doing. By the time I was back in the flow, it was time for the next meeting.</p>

<p>Now I can ask Claude for a quick recap of what we were working on. <em>“Hey, remind me what we were in the middle of.”</em> Back in business in seconds instead of minutes.</p>

<p><strong>Takeaway for TLMs and Engineering Managers</strong>: This is a bigger deal than it sounds. Context switching has always been the silent killer of manager productivity. You can now do meaningful development work between meetings.</p>

<h3 id="expertise-less-of-a-bottleneck">Expertise less of a bottleneck</h3>

<p>Weaknesses become average. I’ve shipped code with technologies I didn’t know well. Tech even a mid-level dev could out-perform me on.</p>

<p>Before AI, that was risky. With AI assistance I could lean on my engineering spidey-sense to evaluate whether an approach was sound while Claude handled the details. The judgment was mine. The solution was collaborative. The result was something I wouldn’t have been able to do otherwise.</p>

<p><strong>Takeaways</strong>:</p>

<ul>
  <li>This is a productivity win specifically for senior+ engineers. You already know what good looks like. Now you can build it in languages and frameworks that aren’t your native tongue. Junior devs though? Double-edged sword. More on that below.</li>
  <li>Teams don’t have to be as rigid about having every skill set on hand.</li>
</ul>

<h3 id="tlms-and-engineering-managers-can-ship-code">TLMs and Engineering Managers can ship code</h3>

<p>Managers are expected to be hands-on now. The bar keeps rising. “Technically credible leadership” used to mean translating your team’s technical challenges to upper management speak. Now it means actually shipping code.</p>

<p>Carving out deep-work blocks has always been a challenge. Constant interruptions. Slack messages. The inevitable “quick question” that takes 45 minutes.</p>

<p>AI lowers the re-entry cost. Let a prompt run while you’re in a status meeting. Check the results after. Iterate. You’re not fully in the zone, but you’re making progress. For most managers, that’s a win.</p>

<h3 id="enhanced-productivity-time">Enhanced productivity time</h3>

<p>I used to be fried by 4 PM. Not every day. But the ones where I actually got into deep flow and knocked out something real. By late afternoon I had nothing left. Starting a new task? Nah.</p>

<p>Now I can hand something off to Claude and keep making progress. Low cognitive lift. Sometimes I catch a second wind and keep rolling. Sometimes I just review the output and call it a day. <strong>Either way, I’m still productive at 4:30.</strong></p>

<p>The mental overhead of <em>starting</em> a task has dropped dramatically. That’s not nothing. That’s an extra productive hour or two on days that previously would’ve ended with a blank stare at the screen.</p>

<h3 id="documentation-is-easier">Documentation is easier</h3>

<p>Summarization might be LLM’s single most valuable capability.</p>

<p>PR descriptions and wiki pages can be done in seconds. Most of it is good enough to ship as-is. The rest needs a quick pass to remove the slop and hallucinations.</p>

<p><strong>Takeaways</strong>:</p>

<ul>
  <li>PR descriptions now actually describe what changed. Revolutionary, I know.</li>
  <li>Wikis that used to never get written are getting written.</li>
  <li>Review it. Delete the slop. Ship the rest.</li>
</ul>

<p>AI makes some things easier. It also introduces new ways to fail.</p>

<hr />

<h2 id="anti-patterns">Anti-Patterns</h2>

<p>Here are some things that are harming your team.</p>

<h3 id="shipping-code-you-dont-understand">Shipping code you don’t understand</h3>

<p>The AI maximalists will tell you this is fine. Just wait for the next model to come out. We’re one more agent away from total enlightenment!</p>

<p>At some point everything falls apart. When it does, you need to understand the system well enough to fix it. If your codebase was built by an LLM you were barely watching, good luck with that.</p>

<p>Think you can tell when you’re past the point of no return? That’s not how it works. You don’t know you’re over your skis until it’s far too late. And you’ve been dumping in more slop since that point.</p>

<p>A lot of engineering leaders have made their mark by introducing technical debt and “failing up” before the bill is due. Before AI, you might have had a couple years leeway. Now it could be weeks.</p>

<p><strong>Takeaways:</strong> The time to system failure is faster. Engineering rigor still matters. Weak leaders will be exposed earlier.</p>

<h3 id="skill-atrophy">Skill atrophy</h3>

<p>When I was a Director I was deeply involved with the technical decisions. And I still shipped code. But my hands-on skills drifted.</p>

<p>AI is now causing this with ICs who are shipping code daily. If Claude is doing most of the thinking, you’re not using the mental models that make you a strong engineer. You’re a reviewer. Reviewers don’t develop the same depth as builders.</p>

<p><strong>Takeaway</strong>: your team is getting worse every day.</p>

<h3 id="superficial-knowledge">Superficial knowledge</h3>

<p>When you review Claude’s code, you have the same relationship to that code as any reviewer has to code they didn’t write. You understand the surface. You might catch obvious issues. But you don’t have the deep understanding of someone who built it from scratch.</p>

<p>At scale, this is scary.</p>

<p>On the other hand, if no one understands the codebase: you’ve technically removed the bus/lottery problem. There’s no one important enough to worry about leaving. Still, not a solid plan.</p>

<p><strong>Takeaways</strong>:</p>

<ul>
  <li>Junior developers aren’t getting the same reps that built senior developers. That gap is being created right now.</li>
  <li>Senior+ devs: make sure you actually understand your systems. Not just the architecture diagram. The code.</li>
</ul>

<hr />

<h2 id="success-patterns">Success patterns</h2>

<p>Here are ways we can fight the anti-patterns being introduced.</p>

<h3 id="create-time-to-review-your-own-code">Create time to review your own code</h3>

<p>Schedule self-review time. Block it out on your calendar for times when you have the mental bandwidth to dig deep. If you can’t explain what a piece of code does, you’re not done with it.</p>

<p>AI writes code faster than you can review it.</p>

<p>That’s a recipe for disaster.</p>

<p><strong>Schedule dedicated time to actually read the code you’re shipping.</strong> Before creating a PR and passing that mess on to your teammates.</p>

<p>This is the boring advice nobody wants to hear in the middle of a hype cycle. The engineers who skip this step are the ones who will be untangling mysteries six months from now wondering why nothing works. Even in the near-term, your team will thank you.</p>

<h3 id="keep-your-skills-sharp">Keep your skills sharp</h3>

<p>Fight skill atrophy. Be intentional about keeping your skills sharp.</p>

<p>Spend a morning writing code without AI. Work through a hard problem yourself. It’s like going to the gym. Skip it for a week, no big deal. Skip it for six months and everyone will notice.</p>

<p>This is career self-preservation too. Hiring loops still require the same hands-on code challenges. And they’ve ramped up the difficulty. Don’t expect the same muscle-memory to be there if layoffs hit and you’re back in an interview loop.</p>

<hr />

<h2 id="conclusion">Conclusion</h2>

<p>The teams that succeed won’t be the ones who prompt the fastest. They’ll be the ones who still understand what they ship.</p>

<p>We’ll see how this plays out over the next couple years.</p>]]></content><author><name></name></author><category term="ai" /><summary type="html"><![CDATA[I’ve been doing AI-assisted development for a while now. Here’s a list of things that are going well. And anti-patterns to avoid. From my conversations with other devs, many are seeing the same things.]]></summary></entry><entry><title type="html">There are no vibe engineers</title><link href="http://jaimzuber.com/ai/there-are-no-vibe-engineers.html" rel="alternate" type="text/html" title="There are no vibe engineers" /><published>2025-12-01T14:21:00+00:00</published><updated>2025-12-01T14:21:00+00:00</updated><id>http://jaimzuber.com/ai/there-are-no-vibe-engineers</id><content type="html" xml:base="http://jaimzuber.com/ai/there-are-no-vibe-engineers.html"><![CDATA[<p>AI-assisted development is here to stay. Engineering leaders are scrambling to adapt. Despite the hype, many things remain the same. We’ve been through much of this before!</p>

<p>TL;DR</p>

<ul>
  <li>Remember lessons learned building global engineering organizations</li>
  <li>Know the limits of what the technology can do</li>
  <li>Focus on core engineering practices. They are more important than ever</li>
</ul>

<h2 id="everything-has-changed-for-ics">Everything has changed for ICs.</h2>
<p>The day-to-day workflow for developers has changed. Even if devs aren’t using AI to generate code. It can create documention, explain new technologies, and aid in exploring new code.</p>

<h2 id="the-fundamental-laws-of-software-development-havent">The fundamental laws of software development haven’t</h2>
<p>The automobile completely changed how we transport ourselves. But it didn’t change the laws of physics. We still need roads and fuel. Cars still break down. Software is in a similar state.</p>

<p>A lot of snake oil salesmen are promising the world to keep their supply of VC (and sovereign wealth fund) money coming in. Use their predictions with a grain of salt.</p>

<h2 id="writing-code-has-never-been-the-hard-part">Writing code has never been the hard part</h2>
<p>We’ve been able to generate low cost code for a generation. There are armies of devs available (offshore and onshore) who are happy to copy and paste their way to something close to what you asked for. This wasn’t an easy win then, and still isn’t now.</p>

<p>AI generated code is similar. It shows lots of promise, but ultimately can’t produce something that can be released.</p>

<h2 id="context-and-communication-are-still-hard">Context and communication are still hard</h2>
<p>AI generated projects will fail in the same way offshore projects did.</p>

<h3 id="context-is-king">Context is king</h3>
<p>We discovered this the hard way. Offshore projects looked great for the first 80% of the work. Then progress stalled. Many projects needed to be scrapped altogether. Lack of context was a primary reason for these failures: the offshore team didn’t understand enough of the product to make smart decisions. It took a lot of late-night meeting and documentation effort to give the offshore teams the context they needed to build shippable software.</p>

<p>Today, there are lots of skilled and productive offshore teams. But they require ongoing investment to keep everyone on the same page.</p>

<p>Context is still hard. Your $20/month subscription is a toy. Only good for simple tasks. Even plugging in your API key to unlock complex prompts has it’s limits. Your LLM can only handle so much context before it starts spitting out nonsense. Someone has to make sure the LLMs has the right amount of context. <em>Context Engineering is a crucial skill</em>.</p>

<h2 id="long-term-thinking-matters">Long term thinking matters</h2>
<p>LLMs have no idea where your company is heading. And have no stake in your success.</p>

<p>Offshore consulting companies only cared about delivering the project.
We learned to build relationships and even open remote offices. This gave the folks doing the work same long term incentives that our local employees had.</p>

<p>Don’t expect long term thinking from ai-generated code. Humans still need to own the roadmap and architecture.</p>

<h2 id="slow-feedback-kills-projects">Slow feedback kills projects</h2>
<p>Slow feedback loops caused delays that drained the expected benefit of lower cost development.</p>

<p>This is a clear win for AI-assisted development. Feedback time is reduced to seconds.</p>

<h2 id="ai-generated-code-is-mid">AI generated code is mid</h2>
<p>LLMs do not know how to reason. They predict. They guess at what we want them to do based on the code used to train them. Does the training code match your expected quality and security? Probably not. Beware the codebase filled with average, unremarkable code.</p>

<h2 id="engineering-practices-are-more-important-than-ever">Engineering practices are more important than ever</h2>
<p>Tech debt will bite you sooner. A lot of teams could slide for months or years before seeing the problems. AI generated code can show the cracks in your design in days.</p>

<p>Test coverage is critical for making sure your software remains in a working state. Finding breaking changes quickly is a super-power for your team. Even if the code is being written by AI.</p>

<p>Linters and static analyers become even more effective. AI can check it’s own work to deliver code to your desired spec. Without humans even reminding it to.</p>

<h2 id="takeaways">Takeaways</h2>

<h3 id="you-have-the-tools-to-navigate-this-world">You have the tools to navigate this world</h3>
<p>The principles that worked in the past are still valid.</p>

<h3 id="you-still-need-strong-engineers-and-leaders">You still need strong engineers (and leaders)</h3>
<p>Humans need to be in the loop. At every stage.</p>

<h3 id="automate-everything">Automate everything</h3>
<p>Automations have always been force multiplyers. Now even more so.</p>

<h3 id="double-down-on-fast-feedback">Double down on Fast Feedback</h3>
<p>Build that prototype. Try things out!</p>

<h2 id="what-do-you-think">What do you think?</h2>
<p>The above seemed controversial when I started jotting them down in fall 2025. Deep in the initial hype cycle. But I’m seeing more and more devs coming to the same conclusions. 2026 will be a fun ride.</p>

<p>I’ll dig deeper into these topics in future posts. Stay tuned!</p>]]></content><author><name></name></author><category term="ai" /><summary type="html"><![CDATA[AI-assisted development is here to stay. Engineering leaders are scrambling to adapt. Despite the hype, many things remain the same. We’ve been through much of this before!]]></summary></entry><entry><title type="html">How to hire me (in 2026)</title><link href="http://jaimzuber.com/how-to-hire-me-2026.html" rel="alternate" type="text/html" title="How to hire me (in 2026)" /><published>2025-11-07T15:44:00+00:00</published><updated>2025-11-07T15:44:00+00:00</updated><id>http://jaimzuber.com/how-to-hire-me-2026</id><content type="html" xml:base="http://jaimzuber.com/how-to-hire-me-2026.html"><![CDATA[<p>I spent 2025 helping <a href="https://notion.com">Notion</a> with native macOS functionality.</p>

<p>Some highlights</p>

<ul>
  <li>Implemented meeting detection logic for AI Meeting Notes</li>
  <li>Built native macOS and Windows installers for their suite of apps</li>
  <li>macOS expert for their Desktop Infra team</li>
</ul>

<p><strong>I’m currently available for new work. Full-time or consulting.</strong></p>

<h2 id="what-is-next">What is next?</h2>

<h3 id="job-titles">Job titles</h3>
<p>Similar to <a href="/how-to-hire-me-2024">How To Hire Me (in 2024)</a>, both IC and Leadership roles are a good fit. I’ve been doing a lot more hands-on development lately. Here are some suitable job titles</p>

<ul>
  <li>Staff/Principal iOS/macOS Engineer</li>
  <li>Engineering Manager</li>
  <li>Director of Engineering</li>
  <li>CTO (for startups or smaller companies)</li>
</ul>

<h2 id="what-is-new">What is new?</h2>

<p>Here are more details on what I was up to in 2025. And what I plan to leverage in 2026.</p>

<h3 id="improving-ai-meeting-notes-performance-speech-to-text-whisper-on-device-asr">Improving AI Meeting Notes Performance (Speech-to-Text, Whisper, On-Device ASR)</h3>
<p>Meeting Note apps are everywhere now. With many use cases still untapped. Want to improve audio capture, transcription quality, or performance? I’ll provide the roadmap. On-device models are ready for real-world use too. Need a privacy-focused solution or want to reduce cloud inference costs? I got you. Native or Electron.</p>

<p><a href="/ai-meeting-notes">More details here…</a></p>

<h3 id="building-ai-powered-native-desktop-apps-macos-windows-electron">Building AI-Powered Native Desktop Apps (macOS, Windows, Electron)</h3>
<p>Desktop was the trend of 2025. Gone are the days of developing with an IDE, Slack, and a bunch of browser tabs. More and more companies are incorporating native desktop OS features to their web platforms. Despite my native snobbery, I must admit Electron has driven a big part of that shift. Desktop hardware acceleration and on-device AI create real product differentiation opportunities.</p>

<p>That trend will continue. And there is a large opportunity to use desktop hardware acceleration to drive AI functionality.</p>

<h3 id="upskilled-in-web">Upskilled in web</h3>
<p>After 15 years of focusing on Apple platforms, I wanted to broaden my skill set with more web and back-end knowledge. I had led web teams as a Director, but didn’t have a good grasp of the day-to-day life of a web dev.</p>

<p>This year I got the chance! I wrote a bunch of TypeScript and shipped React code used by millions. And worked with a top Web Infra team.</p>

<h3 id="back-to-my-audio-roots">Back to my audio roots</h3>
<p>It was fun to get back to my roots with some Core Audio work. And I even wrote some low-level Windows code which was even farther back in my history. In 2025, I wrote more Objective-C and C++ than Swift. This would be a first in 10+ years.</p>

<h2 id="where-do-i-sign-up">Where do I sign up?</h2>

<p>Does any of the above fit what you’re looking for? Hit me up at <a href="mailto:jaim@sharpfivesoftware.com">jaim@sharpfivesoftware.com</a>!</p>]]></content><author><name></name></author><summary type="html"><![CDATA[I spent 2025 helping Notion with native macOS functionality.]]></summary></entry><entry><title type="html">Run Actor Run</title><link href="http://jaimzuber.com/swift-concurrency/run-actor-run" rel="alternate" type="text/html" title="Run Actor Run" /><published>2024-10-09T14:00:00+00:00</published><updated>2024-10-09T14:00:00+00:00</updated><id>http://jaimzuber.com/swift-concurrency/run-actor-run</id><content type="html" xml:base="http://jaimzuber.com/swift-concurrency/run-actor-run"><![CDATA[<p>There is a technique that is useful <sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> when converting apps to Swift Concurrency.</p>

<p><strong>Fire off work to another actor and wait for it to complete</strong></p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">someNonisolatedFunc</span><span class="p">()</span> <span class="k">async</span> <span class="p">{</span>
   <span class="k">await</span> <span class="kt">MainActor</span><span class="o">.</span><span class="n">run</span> <span class="p">{</span>
      <span class="c1">// do something on the MainActor </span>
   <span class="p">}</span>

   <span class="nf">doSomethingElse</span><span class="p">()</span>
<span class="p">}</span></code></pre></figure>

<p>It is provided for MainActor, but isn’t available on actors you create yourself.</p>

<p>Not fair!</p>

<p>I dug around and found a way to make this available on custom actors.</p>

<h2 id="extension">Extension</h2>

<p>This is an extension that will allow a Global Actor to initiate a run command similar to MainActor. I took the signature from the MainActor definition itself.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">extension</span> <span class="kt">CustomActor</span> <span class="p">{</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">func</span> <span class="n">run</span><span class="o">&lt;</span><span class="kt">T</span><span class="o">&gt;</span><span class="p">(</span><span class="nv">resultType</span><span class="p">:</span> <span class="kt">T</span><span class="o">.</span><span class="k">Type</span> <span class="o">=</span> <span class="kt">T</span><span class="o">.</span><span class="k">self</span><span class="p">,</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">@CustomActor</span> <span class="kd">@Sendable</span> <span class="p">()</span> <span class="k">throws</span> <span class="o">-&gt;</span> <span class="kt">T</span><span class="p">)</span> <span class="k">async</span> <span class="k">rethrows</span> <span class="o">-&gt;</span> <span class="kt">T</span> <span class="k">where</span> <span class="kt">T</span> <span class="p">:</span> <span class="kt">Sendable</span> <span class="p">{</span>
        <span class="k">try</span> <span class="k">await</span> <span class="nf">body</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>This allows us to use the run func in the same way MainActor does.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">someFunction</span><span class="p">()</span> <span class="k">async</span> <span class="p">{</span>
   <span class="k">await</span> <span class="kt">CustomActor</span><span class="o">.</span><span class="n">run</span> <span class="p">{</span>
      <span class="c1">// do something on my custom actor</span>
   <span class="p">}</span>

   <span class="nf">doSomethingElse</span><span class="p">()</span>
<span class="p">}</span></code></pre></figure>

<p>This shouldn’t be heavily used. Although it can be tempting, there is almost always a better way to get similar behavior. But I’ve seen this solve a number of issues devs have faced in legacy code.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>This is frequently a bad idea. But can be appropriate in some cases. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="swift-concurrency" /><summary type="html"><![CDATA[There is a technique that is useful 1 when converting apps to Swift Concurrency. This is frequently a bad idea. But can be appropriate in some cases. &#8617;]]></summary></entry><entry><title type="html">Avoid Massive Actors with Empty Global Actors</title><link href="http://jaimzuber.com/swift-concurrency/empty-global-actors.html" rel="alternate" type="text/html" title="Avoid Massive Actors with Empty Global Actors" /><published>2024-10-02T14:00:00+00:00</published><updated>2024-10-02T14:00:00+00:00</updated><id>http://jaimzuber.com/swift-concurrency/empty-global-actors</id><content type="html" xml:base="http://jaimzuber.com/swift-concurrency/empty-global-actors.html"><![CDATA[<p>The most common way to think about actors is similar to structs and classes. They are a type with properties or methods defined.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">actor</span> <span class="kt">FileSystemAccess</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="nf">readFromFile</span><span class="p">(</span><span class="nv">urlString</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">FileManager</span><span class="o">.</span><span class="k">default</span><span class="o">.</span><span class="nf">contents</span><span class="p">(</span><span class="nv">atPath</span><span class="p">:</span> <span class="n">urlString</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>This is fine. But this can lead to issues as the codebase grows and logic changes. This article will show a case where an actor with no properties or methods defined (an Empty Global Actor) is a valid option. This can help us remove Massive Actors from our code.</p>

<p style="text-align:center;"><img src="../assets/posts/empty-global-actors/massive-actors-are-coming.jpg" alt="Game of Thrones meme saying Massive Actors are Coming" width="50%" /></p>

<h2 id="background">Background</h2>

<p>In my work transitioning apps to Swift Concurrency I’ve wanted to use functionality similar to MainActor, but with a custom actor.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">@CustomActor</span>
<span class="kd">class</span> <span class="kt">SomeClass</span> <span class="p">{}</span></code></pre></figure>

<p>or</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">SomeIsolatedType</span> <span class="p">{</span>
   <span class="kd">@CustomActor</span>
   <span class="kd">func</span> <span class="nf">doWorkOnOtherActor</span><span class="p">()</span>
<span class="p">}</span></code></pre></figure>

<p>This has led me to create Global Actors with no custom functionality. This isn’t how most of us are thinking about actors, but it allows us to do some powerful things.</p>

<ol>
  <li>Avoid dumping too much logic into an Actor. The removes the threat of Massive Actors. And leaves us more options as the codebase evolves.</li>
  <li>Separate the logic in our code from how it is run. This is a powerful technique I’ve used for years to allow code I work with to scale.</li>
</ol>

<p>Let’s talk about an example of where we could use an empty Global Actor.</p>

<h2 id="use-case---restrict-access-to-file-system">Use Case - Restrict Access to File System</h2>
<p>File access is a classic example of code that is not thread safe. The runtime will gladly modify the same file from multiple threads. This can lead hard to reproduce bugs. Actors in Swift Concurrency provide a way to enforce safe file system access at compile time.</p>

<h3 id="possible-approach-1---add-methods-to-the-filesystemactor">Possible Approach 1 - add methods to the FileSystemActor</h3>
<p>One approach for solving this with Swift Concurrency is to add the functionality to an actor. This matches how most of us think about creating actors. And how Apple shows actors being used in their documentation.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">actor</span> <span class="kt">FileSystemAccess</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="nf">readFromFile</span><span class="p">(</span><span class="nv">urlString</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">FileManager</span><span class="o">.</span><span class="k">default</span><span class="o">.</span><span class="nf">contents</span><span class="p">(</span><span class="nv">atPath</span><span class="p">:</span> <span class="n">urlString</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>This will work ok for basic functionality. But has the downside of forcing all file system functionality in to exist in the same type. Even methods that are unrelated and should be broken out into new types.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">actor</span> <span class="kt">FileSystemAccess</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="nf">readFromFile</span><span class="p">(</span><span class="nv">urlString</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">FileManager</span><span class="o">.</span><span class="k">default</span><span class="o">.</span><span class="nf">contents</span><span class="p">(</span><span class="nv">atPath</span><span class="p">:</span> <span class="n">urlString</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="kd">func</span> <span class="nf">unrelatedFileWrite</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// ... </span>
    <span class="p">}</span>

    <span class="kd">func</span> <span class="nf">thisShouldntBeInTheSameType</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// ... </span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<h4 id="downside---massive-actors">Downside - Massive Actors</h4>
<p><strong>This can lead us to the Massive Actor problem.</strong></p>

<h3 id="possible-approach-2---bring-in-the-extensions">Possible Approach 2 - bring in the extensions</h3>

<p>A tempting alternative is to break up large types by using extensions</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">extension</span> <span class="kt">FileSystemAccess</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="nf">unrelatedFileWrite</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// ...</span>
    <span class="p">}</span>

    <span class="kd">func</span> <span class="nf">thisShouldntBeInTheSameType</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// ...</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p><strong>This doesn’t solve anything.</strong> It may split up the functionality into new source files. But the types are still bigger than needed. And the approach has the same downsides as the Massive Actor. Devs do this hoping smaller files make code more readable. But usually it makes things more confusing because you have to look to a new file to understand the behavior the type is responsible for.</p>

<p>Neither of these are good solutions for complex functionality. But may work ok for simple use cases.</p>

<h2 id="solution---use-an-global-actor">Solution - use an Global Actor</h2>

<p>This allows you to annotate classes, properties and methods with a custom actor.</p>

<p>This requires a Global Actor to be defined.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">@globalActor</span> <span class="kd">actor</span> <span class="kt">FileSystemActor</span> <span class="p">{</span>
    <span class="kd">static</span> <span class="k">let</span> <span class="nv">shared</span> <span class="o">=</span> <span class="kt">FileSystemActor</span><span class="p">()</span>
<span class="p">}</span></code></pre></figure>

<p>This allows us to annotate our classes and structs with a custom actor</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">@FileSystemActor</span>
<span class="kd">class</span> <span class="kt">SomeClass</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">fileSystem</span> <span class="o">=</span> <span class="kt">FileSystemAccess</span><span class="p">()</span>

    <span class="kd">func</span> <span class="nf">readFromFile</span><span class="p">(</span><span class="nv">urlString</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="k">async</span> <span class="p">{</span>
        <span class="k">await</span> <span class="n">fileSystem</span><span class="o">.</span><span class="nf">readFromFile</span><span class="p">(</span><span class="nv">urlString</span><span class="p">:</span> <span class="n">urlString</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>This also works for properties</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">SomeClass</span> <span class="p">{</span>
   <span class="kd">@FileSystemActor</span>
    <span class="k">let</span> <span class="nv">fileSystem</span> <span class="o">=</span> <span class="kt">FileSystemAccess</span><span class="p">()</span>
<span class="p">}</span></code></pre></figure>

<p>and methods</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">SomeClass</span> <span class="p">{</span>
   <span class="kd">@FileSystemActor</span>
   <span class="kd">func</span> <span class="nf">accessSomething</span><span class="p">()</span> <span class="p">{</span>

   <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>For our use case, the actor doesn’t need any functionality defined at all. It’s an Empty Global Actor.</p>

<h3 id="benefits-of-using-empty-global-actors">Benefits of using Empty Global Actors</h3>

<ol>
  <li>
    <p>It separates the logic from how it is run. A win!</p>
  </li>
  <li>
    <p>They allow us to isolate functions in legacy code. We know you didn’t mean to have file access in your ViewController, but now you have a workaround until you can make a more proper fix.</p>
  </li>
</ol>

<h4 id="aspect-oriented-programming">Aspect oriented programming?</h4>
<p>Astute observers have been noting that property wrappers in Swift allow us to do things described in <a href="https://medium.com/the-swift-cooperative/aspect-oriented-programming-in-swift-f2366350c527">Aspect Oriented Programming</a>. Splitting out functionality into a specific Actor is a valid way to keep different concerns separate in our code.</p>

<p>In my work I commonly push back on any solutions that introduce more global variables into existence. But this provides enough benefits to justify it.</p>

<h3 id="what-do-you-think">What do you think?</h3>

<p>Would this work for code you’re working with? What are some other use cases for Empty Global Actors?</p>]]></content><author><name></name></author><category term="swift-concurrency" /><summary type="html"><![CDATA[The most common way to think about actors is similar to structs and classes. They are a type with properties or methods defined.]]></summary></entry><entry><title type="html">How to shoot yourself in the foot with Swift Concurrency - Too Many Tasks</title><link href="http://jaimzuber.com/swift-concurrency/how-to-shoot-yourself-in-the-foot.html" rel="alternate" type="text/html" title="How to shoot yourself in the foot with Swift Concurrency - Too Many Tasks" /><published>2024-08-23T14:00:00+00:00</published><updated>2024-08-23T14:00:00+00:00</updated><id>http://jaimzuber.com/swift-concurrency/how-to-shoot-yourself-in-the-foot</id><content type="html" xml:base="http://jaimzuber.com/swift-concurrency/how-to-shoot-yourself-in-the-foot.html"><![CDATA[<p>Adopting async/await and Swift Concurrency in a large codebase can cause subtle bugs to creep in your code. Here are some things to watch out for.</p>

<h2 id="overview">Overview</h2>
<p>Apple is strongly encouraging us to use more async methods in our code. But most of us are working with codebases built long before this paradigm was introduced. This means most of us will be refactoring our existing codebases as part of async migration.</p>

<p>Note: I’m expecting most of this work to occur using Swift 5 language mode. This is fine for now. But we should still be making sure we are preparing ourselves for Swift 6.</p>

<h2 id="async-problems">Async Problems</h2>

<p>Changes in apis are causing us to rethink how we structure our apps. As async functionality is introduced into our code, we need to consider how the rest of our codebase needs to adapt.</p>

<h2 id="our-task-call-an-async-method-from-a-synchronous-context">Our Task? Call an async method from a synchronous context</h2>
<p>You want to call an async method but it’s called from a synchronous context. In this example we’ve extracted file system access and isolated it. This is a valid use of actor isolation, but it will require any usage of it from outside that actor to access it asynchronously.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">actor</span> <span class="kt">FileSystemAccess</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="nf">readFromFile</span><span class="p">(</span><span class="nv">urlString</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">FileManager</span><span class="o">.</span><span class="k">default</span><span class="o">.</span><span class="nf">contents</span><span class="p">(</span><span class="nv">atPath</span><span class="p">:</span> <span class="n">urlString</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<h3 id="simplest-solution-make-the-containing-method-async">Simplest Solution: Make the containing method async</h3>
<p>The simplest solution is to make the calling method async also.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">SomeNonIsolatedClass</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">fileSystem</span> <span class="o">=</span> <span class="kt">FileSystemAccess</span><span class="p">()</span>

    <span class="kd">func</span> <span class="nf">readFromFile</span><span class="p">(</span><span class="nv">urlString</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="k">async</span> <span class="p">{</span>
        <span class="k">await</span> <span class="n">fileSystem</span><span class="o">.</span><span class="nf">readFromFile</span><span class="p">(</span><span class="nv">urlString</span><span class="p">:</span> <span class="n">urlString</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>But that isn’t always the right call. For a couple reasons</p>

<h4 id="can-induce-downstream-changes">Can induce downstream changes</h4>
<p>Changing the method signature may require a bunch of downstream changes. You may not have the time to handle them. Async migration can be an invasive process. It’s wise to keep the blast radius of our changes small. This helps any bugs introduced to be detected quicker.</p>

<h4 id="you-may-not-own-the-code-that-uses-it">You may not own the code that uses it</h4>
<p>You may not be able to change the method signature because you don’t own the code that relies on it.</p>

<h3 id="another-solution-isolate-the-containing-class">Another Solution: Isolate the containing class</h3>
<p>Another option is to annotate the containing class with the same actor keyword. This allows methods from FileSystemAccess to accessed synchronously.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">@FileSystemActor</span>
<span class="kd">class</span> <span class="kt">SomeNonIsolatedClass</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">fileSystem</span> <span class="o">=</span> <span class="kt">FileSystemAccess</span><span class="p">()</span>

    <span class="kd">func</span> <span class="nf">readFromFile</span><span class="p">(</span><span class="nv">urlString</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="k">async</span> <span class="p">{</span>
        <span class="k">await</span> <span class="n">fileSystem</span><span class="o">.</span><span class="nf">readFromFile</span><span class="p">(</span><span class="nv">urlString</span><span class="p">:</span> <span class="n">urlString</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>Also a simple fix <sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>. But that can introduce constraints to the rest of the class you may not want.</p>

<h4 id="forces-other-code-the-be-isolated-to-the-same-actor">Forces other code the be isolated to the same actor</h4>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">   <span class="kd">func</span> <span class="nf">nowIHaveToRunOnTheFileSystemActor</span><span class="p">()</span> <span class="p">{</span>
      <span class="c1">// something that doesn't interact with the file system and shouldn't be run by the FileSystemActor	</span>
   <span class="p">}</span></code></pre></figure>

<p><em>What to do?</em></p>

<p>Asking your favorite LLM will give you advice like this:</p>

<p>To call an asynchronous method from synchronous code, you need to use Task to create a new asynchronous context.</p>

<h3 id="put-a-task-on-it">Put a Task on it</h3>

<p>This is simple enough, and will work in many cases.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">    <span class="kd">func</span> <span class="nf">readFromFile</span><span class="p">(</span><span class="nv">urlString</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">Task</span> <span class="p">{</span>
            <span class="k">await</span> <span class="n">fileSystem</span><span class="o">.</span><span class="nf">readFromFile</span><span class="p">(</span><span class="nv">urlString</span><span class="p">:</span> <span class="n">urlString</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
 </code></pre></figure>

<p>Done?</p>

<p>Well, not quite. This will compile in Swift 5 mode, but won’t compile in Swift 6. You’d have to declare the original class as @unchecked: Sendable.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">SomeNonIsolatedClass</span><span class="p">:</span> <span class="kd">@unchecked</span> <span class="kt">Sendable</span> <span class="p">{</span></code></pre></figure>

<p>Or do something more proper to make the type actually sendable.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">SomeNonIsolatedStruct</span> <span class="p">{</span>
    <span class="kd">@FileSystemActor</span>
    <span class="k">let</span> <span class="nv">fileSystem</span> <span class="o">=</span> <span class="kt">FileSystemAccess</span><span class="p">()</span>

    <span class="kd">func</span> <span class="nf">readFromFile</span><span class="p">(</span><span class="nv">urlString</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">Task</span> <span class="p">{</span>
            <span class="k">await</span> <span class="n">fileSystem</span><span class="o">.</span><span class="nf">readFromFile</span><span class="p">(</span><span class="nv">urlString</span><span class="p">:</span> <span class="n">urlString</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<h2 id="task-problems">Task Problems</h2>

<p>We were able to create an asynchronous context in a synchronous method.</p>

<p><strong>For better for worse, creating a Task to run asynchronous code will be a tempting choice as we transition our code.</strong></p>

<p><strong>Be careful.</strong></p>

<p>While this can work for many cases, there are problems lurking.</p>

<h3 id="early-exit">Early exit</h3>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">    <span class="kd">func</span> <span class="nf">handleSomeOperation</span><span class="p">(</span><span class="nv">urlString</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
        <span class="nf">readFromFile</span><span class="p">(</span><span class="nv">urlString</span><span class="p">:</span> <span class="n">urlString</span><span class="p">)</span>

        <span class="c1">// We've introduced a race condition :(</span>
        <span class="nf">codeThatExpectsTheLastMethodToHaveBeenCompleted</span><span class="p">()</span>
    <span class="p">}</span></code></pre></figure>

<p>The code inside the Task block won’t be executed until after the method completes. This means the method didn’t complete what it was asked to do. While this is common in legacy codebases, it’s not clear to whoever calls the method.</p>

<p>Even if code like this continues to work, it’s easy to introduce a breaking change without it being detected.</p>

<h3 id="unknown-order-of-execution">Unknown order of execution</h3>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">    <span class="kd">func</span> <span class="nf">performTheThing</span><span class="p">()</span> <span class="p">{</span>
        <span class="kt">Task</span> <span class="p">{</span>
            <span class="k">await</span> <span class="nf">doThisFirst</span><span class="p">()</span>
        <span class="p">}</span>

        <span class="c1">// other synchronous functionality</span>

        <span class="kt">Task</span> <span class="p">{</span>
            <span class="k">await</span> <span class="nf">doThisNext</span><span class="p">()</span>
        <span class="p">}</span>
    <span class="p">}</span></code></pre></figure>

<p>Despite appearing like the doThisFirst() will occur before doThisNext(), this isn’t guaranteed. Each Task will be run after the containing method exits. But the order is not guaranteed. Similar to the previous example, even if the code works now. It’s easy to introduce a breaking change without it being detected.</p>

<h3 id="multiple-tasks-can-introduce-performance-problems">Multiple tasks can introduce performance problems</h3>
<p>There is a performance cost when introducing async operations. This is often overlooked. Devs often jump through a lot of async hoops to improve performance and end up making things worse.</p>

<h3 id="swallows-exceptions">Swallows exceptions</h3>
<p>An exception thrown in a new Task context will not report its exceptions to the calling function. The method will have returned before the exception is thrown. This can cause problems to be undetected.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift">    <span class="kd">func</span> <span class="nf">beginProcessFile</span><span class="p">()</span> <span class="k">throws</span> <span class="p">{</span>
        <span class="kt">Task</span> <span class="p">{</span>
            <span class="c1">// errors thrown here will not be caught</span>
            <span class="k">try</span> <span class="k">await</span> <span class="nf">processFile</span><span class="p">()</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="kd">func</span> <span class="nf">processFile</span><span class="p">()</span> <span class="k">async</span> <span class="k">throws</span> <span class="p">{</span>
        <span class="k">throw</span> <span class="kt">TypedError</span><span class="o">.</span><span class="n">badthings</span>
    <span class="p">}</span></code></pre></figure>

<h2 id="better-approaches-when-initiating-a-task">Better approaches when initiating a Task</h2>

<h3 id="less-tasks">Less Tasks</h3>
<p>Less is more. Do your best to get the most out of each Task created. This reduces the blast area of your code prone to future bugs.</p>

<h3 id="response-to-user-input">Response to user input</h3>
<p>This is a good time to create an async context. We already expect whatever we initiate to occur in the near future.</p>

<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">class</span> <span class="kt">ViewModel</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="nf">userPressedSearchButton</span><span class="p">()</span> <span class="p">{</span>
        <span class="kt">Task</span> <span class="p">{</span>
            <span class="c1">// ...</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>In short, use less tasks and keep them close to the view layer.</p>

<h2 id="nothing-new-under-the-sun">Nothing new under the sun</h2>
<p>We had the same type of issues using GDC. But async is forcing us to think about threading in a lot more areas.</p>

<p>Make sure you and your team understand what to look out for when refactoring large projects for async migration.</p>

<hr />

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Annotating the class like this would require FileSystemActor to be a Global Actor, which is not shown. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="swift-concurrency" /><summary type="html"><![CDATA[Adopting async/await and Swift Concurrency in a large codebase can cause subtle bugs to creep in your code. Here are some things to watch out for.]]></summary></entry><entry><title type="html">Swift 6: When the concurrency hits the fan</title><link href="http://jaimzuber.com/swift-concurrency/when-the-concurrency-hits-the-fan.html" rel="alternate" type="text/html" title="Swift 6: When the concurrency hits the fan" /><published>2024-08-12T14:00:00+00:00</published><updated>2024-08-12T14:00:00+00:00</updated><id>http://jaimzuber.com/swift-concurrency/when-the-concurrency-hits-the-fan</id><content type="html" xml:base="http://jaimzuber.com/swift-concurrency/when-the-concurrency-hits-the-fan.html"><![CDATA[<p>Here are notes from my talk Swift 6: When the concurrency hits the fan. Initially presented at <a href="https://www.meetup.com/twin-cities-iphone-developers/events/302397094">Twin Cities iPhone Developers</a> on August 14, 2023.</p>

<p><a href="https://docs.google.com/presentation/d/1EG1uVgamCqdESzoQC1KT906lv4uvLQHIaEJj3f3OzMA/edit?usp=sharing">Slides Here (Google Drive)</a></p>

<h2 id="notes-from-demo">Notes from Demo</h2>

<h3 id="swift-6-features-demoed">Swift 6 Features demoed</h3>

<p><a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0411-isolated-default-values.md">SE-0411 Isolated Default Values</a></p>

<p><a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0414-region-based-isolation.md">SE-0414 Region Based Isolation</a></p>

<h2 id="how-to-learn-more">How to learn more</h2>

<h3 id="wwdc">WWDC</h3>
<ul>
  <li>
    <p><a href="https://developer.apple.com/videos/play/wwdc2024/10169">Migrate your app to Swift 6 (WWDC 2024)</a>. <a href="https://wwdcnotes.com/documentation/wwdcnotes/wwdc24-10169-migrate-your-app-to-swift-6">WWDCNotes</a><br />
This provides practical advise for converting an app mentioned in previous WWDC’s.</p>
  </li>
  <li>
    <p><a href="https://developer.apple.com/videos/play/wwdc2021/10133">Protect mutable state with Swift Actors (WWDC 2021)</a></p>
  </li>
  <li>
    <p><a href="https://developer.apple.com/videos/play/wwdc2021/10132">Meet async/await in Swift (WWDC 2021)</a></p>
  </li>
</ul>

<h2 id="talks">Talks</h2>

<p><a href="https://rlziii.com/">Richard Zarth</a> presented <a href="https://rlziii.com/presentations/SwiftConcurrencyAreWeThereYet.pdf">Swift Concurrency: Are We There Yet</a>
 at <a href="https://omt-conf.com">One More Thing Conf</a> 2024.<br />
It is not online yet. But the notes are available.</p>

<h3 id="web">Web</h3>

<p><a href="https://www.hackingwithswift.com/quick-start/concurrency/whats-the-difference-between-actors-classes-and-structs">What’s the difference between actors, classes and structs (Paul Hudson)</a></p>

<p><a href="https://jacobbartlett.substack.com/p/the-meme-that-gave-me-imposter-syndrome">The Meme that gave me imposter Syndrome</a><br />
A good primer on the new type attributes like @Sendable and @MainActor</p>

<p><a href="https://developer.apple.com/documentation/xcode-release-notes/xcode-16-release-notes">Xcode 16 Release Notes</a><br />
Keep up to date on what is being fixed and shipped.</p>

<p><a href="https://www.emergetools.com/blog/posts/swift-async-await-the-full-toolkit">Async await in Swift: The Full Toolkit</a><br />
A big list of the concepts you’ll want to know. By the folks at <a href="https://www.emergetools.com">Emerge Tools</a>.</p>

<p><a href="https://forums.swift.org/tag/concurrency">Swift Forums</a> have a lot of discussion on the topic.</p>

<h4 id="critiques">Critiques</h4>

<p><a href="Swift 6">https://kean.blog/post/swift-6</a>.<br />
The author laments the amount of work required to get our codebases ready for this new world.</p>

<h4 id="matt-massicotte">Matt Massicotte</h4>
<p>Matt Massicotte earns his own section for writing so much on the topic.</p>

<ul>
  <li><a href="https://www.massicotte.org/concurrency-swift-6-se-401">A list of new features coming in Swift 6</a>
These provide a link to the actual Swift proposals, along with explanations and commentary. There are currently 12 posts in this series, they are all linked from each other.</li>
  <li><a href="https://www.massicotte.org/non-sendable">Non-Sendable types are cool too you know</a>
I tend to use “Thread Safe” as a short hand for understanding Sendable. But they are not quite the same thing. This post does a good job of explaining the difference.</li>
</ul>

<h3 id="mastodon">Mastodon</h3>

<p>Folks are discussing Structured Concurrency on Mastodon!</p>

<p><a href="https://mastodon.social/@mattiem">Matt Massicotte</a></p>

<p><a href="https://mastodon.social/@cocoaphony">Rob Napier</a> describes <a href="https://mastodon.social/@cocoaphony/112907066280093885">some of the good parts</a></p>

<h2 id="credits">Credits</h2>

<p>Thanks to the MN Cocoaheads Slack for sharing links they’ve found useful.</p>

<p>… more to come!</p>]]></content><author><name></name></author><category term="swift-concurrency" /><summary type="html"><![CDATA[Here are notes from my talk Swift 6: When the concurrency hits the fan. Initially presented at Twin Cities iPhone Developers on August 14, 2023.]]></summary></entry><entry><title type="html">The rise of mobile development</title><link href="http://jaimzuber.com/the-rise-of-mobile-development.html" rel="alternate" type="text/html" title="The rise of mobile development" /><published>2024-07-26T12:00:00+00:00</published><updated>2024-07-26T12:00:00+00:00</updated><id>http://jaimzuber.com/the-rise-of-mobile-development</id><content type="html" xml:base="http://jaimzuber.com/the-rise-of-mobile-development.html"><![CDATA[<p><strong>It’s never been easier to develop a mobile app. There are more people developing for mobile platforms than ever.</strong></p>

<p>Recently Donn Felker <a href="https://www.donnfelker.com/the-decline-of-mobile-development/">wrote an article</a> about the decline of mobile development.</p>

<p>I know Donn<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> and respect his opinion. I’ve also lived the headaches he mentions. But I disagree with the conclusions. I’m sticking with mobile development. And frequently advise anyone with the mobile skillset to also.</p>

<p>Let’s talk about some of the points from the article.</p>

<h2 id="people-are-leaving-mobile-development">People are leaving mobile development.</h2>

<p>I’m not aware of any evidence for this. The number of mobile developers and app downloads continues to <a href="https://www.statista.com/statistics/271644/worldwide-free-and-paid-mobile-app-store-downloads/">increase</a>. Competition for mobile jobs is higher than it’s ever been. This was true even before the layoff trend.</p>

<p>To be fair, we’re near the saturation point. Mobile isn’t growing like past years. But we’re far from the point of decline.</p>

<h2 id="its-hard">It’s Hard</h2>

<p>Digging in further, most of the critiques seem to be with large scale development versus the mobile platforms themselves. <em>Mobile development has never been easier</em>.</p>

<p>SwiftUI, Jetpack Compose, Flutter and React Native all allow you to crank out functionality at a rapid pace. It has never been easier to release a mobile app.</p>

<p>SwiftUI is much easier compared the outlets, delegates, and selectors I had to learn to develop for iOS in the early 2010’s.</p>

<p>True, the things that get you out of the gate don’t continue to scale as things become more complex. But that is true is no matter if you’re developing with mobile or web technologies like Rails.</p>

<p>Let’s talk about large apps…</p>

<h2 id="its-complex">It’s Complex</h2>

<p>True. Development for large mobile apps brings a whole new set of challenges. Many devs long to leave their massive app teams for the ability to iterate quickly with small scale development. But web has the same headaches. Most professionally developed web apps already include the following</p>

<ul>
  <li>CSS Frameworks</li>
  <li>Front End libraries (e.g. React, Angular)</li>
  <li>State Management (e.g. Flux)</li>
  <li>Micro Frontends</li>
</ul>

<p>All built on a complex pile of Javascript dependencies.</p>

<p>Developing a large app is hard. On both mobile or web.</p>

<p>Do we need <strong>all</strong> of this complexity, no.</p>

<p>Would it easier if we developed large apps in the same way we developed smaller apps 10 years ago, also no.</p>

<p>“What it comes down to is that most software engineers love to over complicate things”</p>

<p>Definitely true. And regrettably some of these overcomplicated things make it to standard industry practices.</p>

<p>The ability to see simple solutions comes after years of over complicating things. I went through the over complicating phase, like Donn and every other top dev I know.</p>

<p>In hindsight, could we have found a better path? Yes!</p>

<h2 id="walled-garden-problems">Walled garden problems</h2>

<p>Sure. I’m not going to dispute anything here.</p>

<h2 id="is-native-mobile-still-a-viable-career-path">Is native mobile still a viable career path</h2>

<p>Yes. I still advise people to get into native mobile development as a solid career path. Sure, mobile is not the cool kid on the block anymore. The hype has faded. But it’s still one of the hardest skillsets to hire for. Mobile developers are still in demand. And the number of companies that are building large scale mobile apps is increasing.</p>

<p>Native mobile development skills will translate into the future. New devices like Watches and AR/VR headsets are getting smaller. Mobile development sets you up well for this continuing trend.</p>

<h2 id="is-mobile-all-sunshine-and-rainbows">Is mobile all sunshine and rainbows?</h2>

<p>No. Many companies are reducing native mobile spend for the alleged cost savings of web or cross platform development. There are other downsides too.</p>

<h3 id="small-market">Small market</h3>
<p>Mobile is a niche in software development. This means there are less jobs available compared to web frameworks like React etc. Mobile is usually a small portion of a companies development budget and mindset.</p>

<h3 id="limited-scope">Limited scope</h3>
<p>While mobile development is hard, the truly hard problems in large scale software are backend. If you want to show impact in a way your bosses can see, backend development provides more opportunity. Example: we saved $2M in cloud costs by refactoring this workflow. That type of impact is harder to quantify with mobile.</p>

<h2 id="conclusion">Conclusion</h2>
<p>Like I said, I respect Donn and his opinion.</p>

<p>We’re coming from different angles. I started my career writing embedded and Windows apps. I was never a web native developer. Similarly, he went into Android while I went in to iOS. Google tends to promote new dev patterns, while Apple lets its devs create those problems on their own.</p>

<p>For those who want to leave mobile development, I wish you the best. Every skill you learn makes you a better developer. I encourage everyone to try new things and cross pollination is good for everyone.</p>

<p>But I hope you come back. There are more large mobile apps than ever before. <strong>We need more devs who know how to keep things simple.</strong></p>

<hr />

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Donn and I have hung out in multiple cities. But, oddly, not in the Twin Cities where we both lived and did .NET development in the late 2010’s. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><summary type="html"><![CDATA[It’s never been easier to develop a mobile app. There are more people developing for mobile platforms than ever.]]></summary></entry></feed>