<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Cedric's Blog]]></title><description><![CDATA[An iOS Engineer and Back-End (Vapor) Enthusiast focusing on systems architecture.
I'm passionate about Clean Code and Architecture ⚙]]></description><link>https://blog.cedricbahirwe.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1639848689307/x5g8Lu7y-.png</url><title>Cedric&apos;s Blog</title><link>https://blog.cedricbahirwe.com</link></image><generator>RSS for Node</generator><lastBuildDate>Fri, 24 Apr 2026 20:10:13 GMT</lastBuildDate><atom:link href="https://blog.cedricbahirwe.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Dear Self,]]></title><description><![CDATA[Another year, another chapter — not defined by how much changed around you, but by how much evolved within you.
You’ve learned that people aren’t always who they say they are, and things aren’t always what they seem. That discernment isn’t cynicism —...]]></description><link>https://blog.cedricbahirwe.com/dear-self</link><guid isPermaLink="true">https://blog.cedricbahirwe.com/dear-self</guid><category><![CDATA[Life lessons]]></category><category><![CDATA[wisdom]]></category><category><![CDATA[Self Improvement ]]></category><category><![CDATA[self-improvement ]]></category><category><![CDATA[lessons learned]]></category><category><![CDATA[reflection]]></category><dc:creator><![CDATA[Cédric Bahirwe]]></dc:creator><pubDate>Sun, 19 Oct 2025 22:00:00 GMT</pubDate><content:encoded><![CDATA[<h3 id="heading-another-year-another-chapter-not-defined-by-how-much-changed-around-you-but-by-how-much-evolved-within-you">Another year, another chapter — not defined by how much changed around you, but by how much evolved <em>within</em> you.</h3>
<p>You’ve learned that people aren’t always who they say they are, and things aren’t always what they seem. That discernment isn’t cynicism — it’s wisdom. You’ve learned to question, to pause before judging, and to choose understanding over assumption.</p>
<p>This year wasn’t about chasing noise; it was about building quietly — in work, in spirit, and in heart. You made progress in silence, refined your discipline, and learned to separate emotion from logic when it mattered most. You reminded yourself that to escape the cage, you must die while you’re alive — letting go of what limits you, even when it’s familiar.</p>
<p>You also rediscovered love — not the word, but the act. In unity, in sharing, in seeing the best in others even when they couldn’t see it in themselves.</p>
<p>You stood by your principles, even when it meant standing alone. You learned that peace often costs more than pride — but it’s worth every ounce.</p>
<p>So here’s to the next one:<br />To dreaming bigger and executing sharper.<br />To loving louder and living lighter.<br />To being guided not by appearances, but by truth.</p>
<p>May your dreams not come true — may things you could never dream of happen to you.</p>
<p>— <strong>Cédric</strong></p>
]]></content:encoded></item><item><title><![CDATA[How Dependency Injection Saved My Sauce]]></title><description><![CDATA[I was staring at my sauce like it had personally offended me. It was bland, a little watery, and definitely not the masterpiece I had envisioned. In that moment of culinary despair, I realized my sauce had a problem eerily familiar to any programmer:...]]></description><link>https://blog.cedricbahirwe.com/how-dependency-injection-saved-my-sauce</link><guid isPermaLink="true">https://blog.cedricbahirwe.com/how-dependency-injection-saved-my-sauce</guid><category><![CDATA[dependency injection]]></category><category><![CDATA[kitchen]]></category><category><![CDATA[cooking]]></category><category><![CDATA[humour]]></category><category><![CDATA[funny]]></category><category><![CDATA[AI]]></category><category><![CDATA[#ai-tools]]></category><dc:creator><![CDATA[Cédric Bahirwe]]></dc:creator><pubDate>Tue, 07 Oct 2025 17:30:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1759850242424/de362199-dee5-4581-bb48-c3ccefb18b97.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I was staring at my sauce like it had personally offended me. It was bland, a little watery, and definitely not the masterpiece I had envisioned. In that moment of culinary despair, I realized my sauce had a problem eerily familiar to any programmer: <strong>missing dependencies</strong>. A dash of salt here, a splash of vinegar there—each ingredient was like a module in my code. Add it too early, and it crashes the flavor; too late, and the whole thing collapses. But just like in software, a well-timed <strong>dependency injection</strong> saved the day—and my dinner.</p>
<p>The first step was identifying the culprit. Was it the garlic that hadn’t properly initialized? Or maybe the tomatoes were failing their integration test with the spices. I realized I needed a <strong>singleton ingredient</strong>—something reliable that could unify the flavor without causing side effects. Enter the butter. A carefully measured dollop, injected at just the right moment, and suddenly the sauce wasn’t just edible—it was stable, cohesive, and running smoothly on my taste buds.</p>
<p>Of course, like any good programmer-chef, I documented the process. Next time, I’d know exactly when to inject each dependency: the salt before compilation (aka simmering), the vinegar after unit testing (tasting), and the herbs during deployment (plating). And just like debugging a tricky app, there’s a strange satisfaction in watching something once broken transform into perfection, thanks to a well-placed intervention.</p>
<p>In the end, my sauce was more than just dinner—it was a reminder that whether in the kitchen or in code, timing and dependencies matter. A missing ingredient, like a missing module, can cause chaos. But with a little attention, a dash of creativity, and a well-timed injection, even the messiest situations can turn into something delicious. And if nothing else, I learned that <strong>sometimes programming concepts taste a lot better when butter is involved</strong>.</p>
<p><em>“This article was written by Cédric Bahirwe with editorial assistance from AI.”</em></p>
]]></content:encoded></item><item><title><![CDATA[How a 500KB Image Can Take 24MB in Memory—And What You Can Do About It]]></title><description><![CDATA[📷
Photo by Behnam Norouzi


Introduction
When working on a recent client project, I encountered a serious performance issue: the app was consuming excessive memory, leading to slow performance, crashes, and issues with lists rendering large images. ...]]></description><link>https://blog.cedricbahirwe.com/how-a-500kb-image-can-take-24mb-in-memoryand-what-you-can-do-about-it</link><guid isPermaLink="true">https://blog.cedricbahirwe.com/how-a-500kb-image-can-take-24mb-in-memoryand-what-you-can-do-about-it</guid><category><![CDATA[iOS]]></category><category><![CDATA[memory-management]]></category><category><![CDATA[Performance Optimization]]></category><category><![CDATA[Swift]]></category><category><![CDATA[Mobile Development]]></category><category><![CDATA[image optimization ]]></category><category><![CDATA[UIkit]]></category><dc:creator><![CDATA[Cédric Bahirwe]]></dc:creator><pubDate>Thu, 13 Mar 2025 22:00:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1741698710414/cf355dfb-4fb1-4925-b57e-3d71ef412d90.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div data-node-type="callout">
<div data-node-type="callout-emoji">📷</div>
<div data-node-type="callout-text">Photo by <a target="_self" href="https://unsplash.com/@behy_studio?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Behnam Norouzi</a></div>
</div>

<h2 id="heading-introduction">Introduction</h2>
<p>When working on a recent client project, I encountered a serious performance issue: the app was consuming excessive memory, leading to slow performance, crashes, and issues with lists rendering large images. Profiling the app revealed that image handling was a major culprit, especially with <strong>high-resolution</strong> <strong>assets</strong> stored in the project and fetched from the server. Through debugging, I discovered a <strong>shocking</strong> crucial detail: an image's in-memory representation can be <strong>significantly larger</strong> than its file size on disk.</p>
<p>In this article, I'll break down why this happens and share practical strategies on how you can optimize image memory usage in iOS apps.</p>
<h2 id="heading-why-do-images-take-up-so-much-memory">Why Do Images Take Up So Much Memory?</h2>
<p>The key to understanding image memory usage lies in how images are stored on disk versus how they are processed in memory.</p>
<h3 id="heading-compressed-image-on-disk-vs-uncompressed-image-in-memory"><strong>Compressed Image (On Disk) vs. Uncompressed Image (In Memory)</strong></h3>
<p>Images on disk are often stored in <strong>compressed formats</strong> like JPEG or PNG, which significantly reduce file size. However, when an image is loaded into memory, it needs to be <strong>decompressed</strong> into a raw format that the system can render efficiently.</p>
<p>For example, let’s consider a 500KB JPEG image with a resolution of <strong>3000x2000 pixels</strong>:</p>
<ol>
<li><p><strong>JPEG file size (on disk)</strong>: 500KB (compressed)</p>
</li>
<li><p><strong>Decompressed size (in memory)</strong>:</p>
<ul>
<li><p>Each pixel typically requires <strong>4 bytes</strong> (RGBA: Red, Green, Blue, Alpha)</p>
</li>
<li><p>Total pixels: <strong>3000 x 2000 = 6,000,000 pixels</strong></p>
</li>
<li><p>Memory required: <strong>6,000,000 x 4 bytes = 24MB</strong></p>
</li>
</ul>
</li>
</ol>
<p>If the image has a higher resolution or uses more color data, this can easily double, leading to <strong>48MB or more</strong> of memory usage per image!</p>
<p>Now, imagine rendering multiple high-resolution images in a list, the memory usage quickly adds up, causing performance issues, lag, and even crashes on devices with limited RAM.</p>
<h2 id="heading-debugging-the-issue">Debugging the Issue</h2>
<h3 id="heading-how-i-discovered-the-problem"><strong>How I Discovered the Problem</strong></h3>
<p>During development, I noticed that:</p>
<ul>
<li><p>The app <strong>became slower</strong> when loading images from the API.</p>
</li>
<li><p><strong>Scrolling lists with images lagged</strong> or caused crashes.</p>
</li>
<li><p>Xcode <strong>Instruments showed high memory usage and warnings</strong>.</p>
</li>
</ul>
<p>To isolate the issue, I tried:</p>
<ul>
<li><p>Removing all resource-intensive work (but the issue persisted).</p>
</li>
<li><p><strong>Disabling media loading completely</strong>, and suddenly, the app became much faster.</p>
</li>
</ul>
<p>This confirmed that images were the primary cause of excessive memory consumption.</p>
<h2 id="heading-optimizing-image-memory-usage">Optimizing Image Memory Usage</h2>
<p>To solve this issue, I applied several best practices that significantly reduced memory usage and improved performance.</p>
<h3 id="heading-1-downsample-large-images-before-displaying"><strong>1. Downsample Large Images Before Displaying</strong></h3>
<p>Instead of loading full-resolution images into memory, downsample them to a smaller size that matches the display size. This ensured that images use only as much memory as needed, significantly reducing their footprint while maintaining visual quality.</p>
<h3 id="heading-2-cache-images-to-avoid-re-loading"><strong>2. Cache Images to Avoid Re-Loading</strong></h3>
<p>Repeatedly decoding and rendering images is expensive. Using an image caching solution prevents redundant memory usage by storing images in memory or disk after they are first loaded, reducing the need for repeated decoding. Using <a target="_blank" href="https://developer.apple.com/documentation/foundation/nscache"><code>NSCache</code></a> in my case</p>
<h3 id="heading-3-use-efficient-rendering-techniques"><strong>3. Use Efficient Rendering Techniques</strong></h3>
<p>Avoid keeping multiple large images in memory at the same time. Instead, I used lazy loading and remove images that are no longer visible. Additionally, I ensured that image views reuse existing memory rather than creating new instances repeatedly.</p>
<h3 id="heading-4-monitor-and-profile-memory-usage"><strong>4. Monitor and Profile Memory Usage</strong></h3>
<p>Using Xcode’s <strong>Instruments</strong> (especially the <strong>Memory Profiler</strong> and <strong>Leaks tool</strong>) to analyze how the app handles images. This helped identify excessive memory consumption and potential leaks, allowing me to fine-tune the app performance.</p>
<h2 id="heading-key-takeaways-from-this-experience">Key Takeaways from This Experience</h2>
<p>Here are some key takeaways I learned from debugging, researching, and understanding these issues:</p>
<ul>
<li><p><strong>In-memory images are much larger than their file size on disk</strong> due to decompression.</p>
</li>
<li><p><strong>Profiling tools like Xcode Instruments</strong> is a friend, and can help identify excessive memory usage.</p>
</li>
<li><p><strong>Optimizing image handling</strong> with downsampling, caching, and efficient formats significantly improves performance.</p>
</li>
</ul>
<p>By applying these strategies, I was able to reduce memory usage and improve responsiveness in a client’s app. If you're facing similar issues, these techniques could help ensure a smoother experience for users.</p>
<h2 id="heading-further-reading-amp-references">Further Reading &amp; References</h2>
<p>If you want to dive deeper into optimizing memory usage in iOS, check out these resources:</p>
<ul>
<li><p><a target="_blank" href="https://developer.apple.com/documentation/xcode/making-changes-to-reduce-memory-use">Apple Developer Documentation on Reducing Memory Usage</a></p>
</li>
<li><p><a target="_blank" href="https://developer.apple.com/videos/play/wwdc2018/416/">WWDC 2018: iOS Memory Deep Dive</a></p>
</li>
<li><p><a target="_blank" href="https://www.avanderlee.com/swiftui/memory-consumption-loading-uiimage-from-disk/">SwiftLee: Memory Consumption When Loading UIImage</a></p>
</li>
<li><p><a target="_blank" href="https://medium.com/@zippicoder/downsampling-images-for-better-memory-consumption-and-uicollectionview-performance-35e0b4526425">Medium: Downsampling images for better memory consumption and UICollectionView performance</a></p>
</li>
<li><p><a target="_blank" href="https://www.reddit.com/r/SwiftUI/comments/1fpiwa8/how_to_optimize_memory_usage_when_loading_images/">Short Reddit Discussion: How to optimize memory usage when loading images</a></p>
</li>
</ul>
<h3 id="heading-whats-next"><strong>What’s Next?</strong></h3>
<p>If you found this article helpful, feel free to share your thoughts! Have you faced similar memory issues in your apps? Let’s discuss in the comments! 🚀</p>
<p><em>For more insights into my work and interests, feel free to connect with me on</em> <a target="_blank" href="https://www.linkedin.com/in/cedricbahirwe/"><strong><em>LinkedIn</em></strong>,</a> <a target="_blank" href="https://twitter.com/cedricbahirwe">Twitter</a> <em>or explore my projects on</em> <a target="_blank" href="https://github.com/cedricbahirwe"><strong><em>GitHub</em></strong></a>.</p>
]]></content:encoded></item><item><title><![CDATA[Inside CodeXtreme 2025:
A Mentor and Judge’s Perspective on Innovation and Impact]]></title><description><![CDATA[As an iOS engineer with a passion for leveraging technology to address real-world challenges, I had the privilege of serving as a mentor and judge at CodeXtreme 2025. This year's hackathon, themed "Build Things People Need: High-Value, High-Impact Te...]]></description><link>https://blog.cedricbahirwe.com/inside-codextreme-2025-a-mentor-and-judges-perspective-on-innovation-and-impact</link><guid isPermaLink="true">https://blog.cedricbahirwe.com/inside-codextreme-2025-a-mentor-and-judges-perspective-on-innovation-and-impact</guid><category><![CDATA[codextreme]]></category><category><![CDATA[kigali]]></category><category><![CDATA[hackathon]]></category><category><![CDATA[innovation]]></category><category><![CDATA[Students]]></category><dc:creator><![CDATA[Cédric Bahirwe]]></dc:creator><pubDate>Fri, 07 Mar 2025 22:26:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1741252565392/8ca320af-d9c1-49f7-a01f-786b0e6b3167.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As an iOS engineer with a passion for leveraging technology to address real-world challenges, I had the privilege of serving as a mentor and judge at CodeXtreme 2025. This year's hackathon, themed "<strong>Build Things People Need: High-Value, High-Impact Tech Solutions to Shape Communities</strong>" brought together over 250 young tech enthusiasts from various Rwandan universities(<a target="_blank" href="https://www.newtimes.co.rw/article/24413/news/featured/featured-codextreme-hackathon-kicks-off-250-young-tech-enthusiasts-tackle-community-challenges">NEWTIMES.CO.RW</a>)</p>
<p><strong>Fostering Innovation Under Time Constraints</strong></p>
<p>Witnessing participants develop functional solutions within a limited timeframe was truly inspiring. The hackathon's structure encouraged rapid ideation and execution, pushing teams to think critically and collaborate effectively. As a mentor, I guided teams in refining their ideas, ensuring alignment with community needs, and navigating technical challenges. This mentorship was crucial in helping participants transform concepts into viable prototypes.</p>
<p><strong>Evaluating for Real-World Impact</strong></p>
<p>In my role as a judge, I assessed projects based on innovation, technical execution, functionality, theme alignment, and documentation. (<a target="_blank" href="https://codextreme25.devpost.com/">CodeXtreme 2025</a>)<br />The emphasis was on creating solutions that not only showcased technical prowess but also addressed pressing community issues. Projects ranged from AI-driven healthcare applications to blockchain-based financial tools, all aiming to make a tangible difference in society.</p>
<p><strong>Celebrating Collaborative Problem-Solving</strong></p>
<p><a target="_blank" href="http://CODEXTREME25.DEVPOST.COM">CodeXtreme 2025</a> highlighted the power of collaboration and mentorship in driving technological innovation. The event provided a platform for participants to learn from industry experts, exchange ideas, and develop solutions with the potential for significant community impact. As both a mentor and judge, I was honored to contribute to this environment of learning and growth.</p>
<p><strong>Looking Ahead</strong></p>
<p>The creativity and dedication demonstrated at CodeXtreme 2025 reaffirm my belief in the potential of technology to address societal challenges. I look forward to seeing these young innovators continue to develop their projects and drive positive change within their communities.</p>
<p>I extend my heartfelt gratitude to <a target="_blank" href="https://www.linkedin.com/in/cedric-murairi/">Cédric Murairi</a>, Founder of <strong>CodeXtreme</strong>, and the entire <a target="_blank" href="https://www.linkedin.com/company/codextreme/">CodeXtreme team</a> for their unwavering support and commitment. Their efforts have been crucial in creating an environment where young talents can thrive and make a meaningful impact.</p>
<p>I am excited to witness how these projects evolve and contribute to solving real-world problems.</p>
<p><em>For more insights into my work and interests, feel free to connect with me on</em> <a target="_blank" href="https://www.linkedin.com/in/cedricbahirwe/"><em>LinkedIn</em></a><em>, and</em> <a target="_blank" href="https://twitter.com/cedricbahirwe"><em>Twitter</em></a> <em>or explore my projects on</em> <a target="_blank" href="https://github.com/cedricbahirwe"><em>GitHub</em></a><em>.</em></p>
<p><em>Just a note: The content of this article comes from my own experiences as a mentor and judge at CodeXtreme 2025.</em></p>
]]></content:encoded></item><item><title><![CDATA[How do static analysis tools suffer from false negatives and false positives?]]></title><description><![CDATA[Introduction
Static analysis tools, also known as linting tools such as ES Lint (for JavaScript) or SwiftLint (for Swift), are software programs that analyze source code for potential issues, such as bugs, security vulnerabilities, and coding standar...]]></description><link>https://blog.cedricbahirwe.com/how-do-static-analysis-tools-suffer-from-false-negatives-and-false-positives</link><guid isPermaLink="true">https://blog.cedricbahirwe.com/how-do-static-analysis-tools-suffer-from-false-negatives-and-false-positives</guid><category><![CDATA[Linter]]></category><category><![CDATA[static analysis]]></category><category><![CDATA[static code analysis]]></category><category><![CDATA[Python]]></category><category><![CDATA[Swift]]></category><dc:creator><![CDATA[Cédric Bahirwe]]></dc:creator><pubDate>Mon, 13 Feb 2023 21:10:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1676322500822/9300410e-4740-4102-b6af-1ab0233e79d4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-introduction">Introduction</h3>
<p>Static analysis tools, also known as linting tools such as <a target="_blank" href="https://eslint.org/">ES Lint</a> (for JavaScript) or <a target="_blank" href="https://realm.github.io/SwiftLint/">SwiftLint</a> (for Swift), are software programs that analyze source code for potential issues, such as bugs, security vulnerabilities, and coding standards violations, without actually executing the code. While these tools can be very effective in finding and fixing problems in code, they are not perfect and can suffer from both <strong>false negatives</strong> (i.e. missed issues) and <strong>false positives</strong> (i.e. reported issues that are not actually problems).</p>
<p><strong>False negatives</strong> can occur when a static analysis tool fails to detect an issue that is present in the code. This can be due to limitations in the tool's ability to accurately analyze the code, or because the tool does not have enough information to detect the issue.</p>
<p><strong>False positives</strong> can occur when a static analysis tool reports an issue that is not actually a problem. This can happen because the tool is not able to accurately distinguish between valid and invalid code patterns, or because the tool is not able to properly interpret the intended behaviour of the code.</p>
<h3 id="heading-example-in-python">Example (in Python)</h3>
<p>Here's a simple example in <a target="_blank" href="https://www.python.org/">Python</a> to demonstrate the concept of false negatives and false positives in static analysis tools.<br />Consider the following code:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">divide</span>(<span class="hljs-params">a, b</span>):</span>
    <span class="hljs-keyword">return</span> a / b

print(divide(<span class="hljs-number">10</span>, <span class="hljs-number">2</span>))
print(divide(<span class="hljs-number">10</span>, <span class="hljs-number">0</span>))
</code></pre>
<p>A static analysis tool might run on this code and report no issues because it cannot detect that the second call to <code>divide</code> will raise a <code>ZeroDivisionError</code> exception in <code>Python</code> or a runtime error in <code>Swift</code> . This is an example of a false negative because the issue of dividing by zero is present in the code, but the static analysis tool fails to detect it.</p>
<p>On the other hand, if the static analysis tool reports an issue with the following code:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">divide</span>(<span class="hljs-params">a, b</span>):</span>
    <span class="hljs-keyword">if</span> b == <span class="hljs-number">0</span>:
        <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">"Cannot divide by zero"</span>)
    <span class="hljs-keyword">return</span> a / b

print(divide(<span class="hljs-number">10</span>, <span class="hljs-number">2</span>))
print(divide(<span class="hljs-number">10</span>, <span class="hljs-number">0</span>))
</code></pre>
<p>because it may not recognize that raising an error in response to a divide by zero is the intended behaviour. This would be an example of a false positive, as the code is correct, but the static analysis tool reports a problem where none exists.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>To reduce the number of <strong>false negatives</strong> and <strong>false positives</strong>, it's important to choose a static analysis tool that is well-suited to the programming language and application being analyzed and to configure the tool appropriately for your specific needs.<br />Additionally, it's important to regularly review the results of the static analysis and carefully evaluate each reported issue to determine whether it is a true problem or a false positive.</p>
<p>I hope you learned something new from this post, more to come but if you have a question or a suggestion leave a comment or find me on <a target="_blank" href="https://twitter.com/cedricbahirwe"><strong><em>Twitter</em></strong></a></p>
]]></content:encoded></item><item><title><![CDATA[Building Music Recognition with ShazamKit and AVFoundation]]></title><description><![CDATA[ShazamKit is a recent Apple Framework announced during WWDC 2021, that brings audio matching capabilities within your app. You can make any prerecorded audio recognizable by building your own custom catalogue using audio from podcasts, videos, and mo...]]></description><link>https://blog.cedricbahirwe.com/audio-recognition-with-shazamkit-and-avfoundation</link><guid isPermaLink="true">https://blog.cedricbahirwe.com/audio-recognition-with-shazamkit-and-avfoundation</guid><category><![CDATA[shazamkit]]></category><category><![CDATA[Swift]]></category><category><![CDATA[iOS]]></category><category><![CDATA[SwiftUI]]></category><category><![CDATA[UIkit]]></category><dc:creator><![CDATA[Cédric Bahirwe]]></dc:creator><pubDate>Fri, 04 Nov 2022 14:22:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1667571328902/ui1zgBn5h.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>ShazamKit</strong> is a recent Apple Framework announced during WWDC 2021, that brings <strong>audio matching</strong> capabilities within your app. You can make any prerecorded audio recognizable by building your own custom catalogue using audio from podcasts, videos, and more or match music to the millions of songs in Shazam’s vast catalogue.</p>
<p>Today, we are going to build a simple music-matching recognizer. The idea is to build a component that is independent of the UI framework being used (SwiftUI or UIKit).</p>
<p>We will create a <code>Swift</code> class named creatively <code>ShazamRecognizer</code> that will have some simple tasks to perform: </p>
<ol>
<li>Create the properties that are going to help us in building our class</li>
<li>Request Permission to record audio using the <code>AVFoundation</code> framework</li>
<li>Start Recording and Send the record to <code>ShazamKit</code> for recognition matching</li>
<li>Handle the response from ShazamKit (<strong>Success</strong> when a match was found or <strong>Error</strong> when no match was found)</li>
<li>Display our result in a UI (e.g: SwiftUI or UIKit)</li>
</ol>
<h2 id="heading-create-the-properties-that-are-going-to-help-us-in-building-our-class">Create the properties that are going to help us in building our class</h2>
<pre><code>final <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ShazamRecognizer</span>: <span class="hljs-title">NSObject</span>, <span class="hljs-title">ObservableObject</span> </span>{
    <span class="hljs-comment">// 1. Audio Engine</span>
    private <span class="hljs-keyword">let</span> audioEngine = AVAudioEngine()

    <span class="hljs-comment">// 2. Shazam Engine</span>
    private <span class="hljs-keyword">let</span> shazamSession = SHSession()

    <span class="hljs-comment">// 3. UI state purpose</span>
    @Published private(set) <span class="hljs-keyword">var</span> isRecording = <span class="hljs-literal">false</span>

    <span class="hljs-comment">// 4. Success Case</span>
    @Published private(set) <span class="hljs-keyword">var</span> matchedTrack: ShazamTrack?

    <span class="hljs-comment">// 5. Failure Case</span>
    @Published <span class="hljs-keyword">var</span> error: ErrorAlert? = nil
}
</code></pre><p>In the above declarations, we have:</p>
<ol>
<li>We create the <code>audioEngine</code> which is used to <code>start</code> and <code>stop</code> the recording.</li>
<li>We create the <code>shazamSession</code> which is used to perform the <strong>matching</strong> process.</li>
<li>We use <code>isRecording</code> to track whether or not there is an ongoing recording operation. This value can be used for example to show different UI for each state.</li>
<li>We create a variable of custom type (<a target="_blank" href="https://github.com/cedricbahirwe/iOS-Concepts/blob/master/P01-Shazam/P01-Shazam/Model/ShazamTrack.swift"><code>ShazamTrack</code></a>) to store our result in case of success (when a match was found).</li>
<li>In case of failure, we store the error in the <code>error</code> variable of type <a target="_blank" href="https://github.com/cedricbahirwe/iOS-Concepts/blob/master/P01-Shazam/P01-Shazam/Model/ErrorAlert.swift"><code>ErrorAlert</code></a> which can be used to display an Alert or a UI, etc.</li>
</ol>
<h2 id="heading-request-permission-to-record-audio-using-the-avfoundation-framework">Request Permission to record audio using the <code>AVFoundation</code> framework</h2>
<p>In our class, we proceed by adding the <code>listenToMusic()</code> function.</p>
<pre><code>func listenToMusic() {
    <span class="hljs-comment">// 1.</span>
    <span class="hljs-keyword">let</span> audioSession = AVAudioSession.sharedInstance()
    <span class="hljs-comment">// 2. </span>
    audioSession.requestRecordPermission { status <span class="hljs-keyword">in</span>
        <span class="hljs-keyword">if</span> status {
            <span class="hljs-comment">// 3.</span>
            self.recordAudio()
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-comment">// 4. </span>
            self.error = ErrorAlert(<span class="hljs-string">"Please Allow Microphone Access !!!"</span>)
        }
    }
}
</code></pre><p>In our <code>listenToMusic()</code> function:</p>
<ol>
<li>We use an <code>audioSession</code> to communicate to the operating system the general nature of our app’s audio without detailing the specific behaviour or required interactions with the audio hardware.</li>
<li>Using the <code>audioSession</code>, We request user's permission to record audio. At this point, we have to add a new property (<code>NSMicrophoneUsageDescription</code>) in our <code>Info.plist</code>, with a message that tells the user why the app is requesting access to the device’s microphone otherwise our app might crash at runtime.</li>
<li>In case the user gives permission we start the recording operation in our <code>recordAudio()</code> function that we are going to build in the next section.</li>
<li>In case the user has denied permission, we simply store the error in our <code>error</code> variable.</li>
</ol>
<blockquote>
<p><a target="_blank" href="https://developer.apple.com/documentation/avfaudio/avaudiosession">AVAudioSession</a>: An audio session acts as an intermediary between your app and the operating system — and, in turn, the underlying audio hardware. </p>
</blockquote>
<h2 id="heading-start-recording-and-send-the-record-to-shazamkit-for-recognition">Start Recording and Send the record to ShazamKit for recognition</h2>
<p>Here is what our <code>recordAudio()</code> function looks like! Hmmm!!!, Quite a function~</p>
<pre><code>private func recordAudio() {
    <span class="hljs-comment">// 1. If the `audioEngine` is running, stop it and return</span>
    <span class="hljs-keyword">if</span> audioEngine.isRunning {
        self.stopAudioRecording()
        <span class="hljs-keyword">return</span>
    }

    <span class="hljs-comment">// 2. Create a inputNode to listen to</span>
    <span class="hljs-keyword">let</span> inputNode = audioEngine.inputNode

    <span class="hljs-comment">// 3. Create the format to use for our inputNode </span>
    <span class="hljs-comment">/// We are using .zero as a bus for this example</span>
    <span class="hljs-keyword">let</span> format = inputNode.outputFormat(forBus: .zero)

    <span class="hljs-comment">// 4. Removes the tap if already installed on the node</span>
    inputNode.removeTap(onBus: .zero)

    <span class="hljs-comment">// 5. Install an audio tap on the bus using our inputNode</span>
    <span class="hljs-comment">// Record, monitor, and observe the output of the node.</span>
    <span class="hljs-comment">// This will listen to music continuously</span>
    inputNode.installTap(onBus: .zero,
                         <span class="hljs-attr">bufferSize</span>: <span class="hljs-number">1024</span>,
                         <span class="hljs-attr">format</span>: format)
    { buffer, time <span class="hljs-keyword">in</span>
        <span class="hljs-comment">// 6. Start Shazam Matching Operation,</span>
        <span class="hljs-comment">// Converts the audio in the buffer to a signature and </span>
        <span class="hljs-comment">// searches the reference signatures in the session catalog.</span>
        self.shazamSession.matchStreamingBuffer(buffer, <span class="hljs-attr">at</span>: time)
    }

    <span class="hljs-comment">// 7. Prepare the audio engine to start</span>
    audioEngine.prepare()

    <span class="hljs-keyword">do</span> {
        <span class="hljs-comment">// 8. Start the audio engine</span>
        <span class="hljs-keyword">try</span> audioEngine.start()

        DispatchQueue.main.async {
            <span class="hljs-comment">// 9. Set the recording state to true</span>
            self.recording = <span class="hljs-literal">true</span>
        }
    } <span class="hljs-keyword">catch</span> {
        <span class="hljs-comment">// 10. Handle any error that may occur</span>
        self.error = ErrorAlert(error.localizedDescription)
    }
}
</code></pre><p>After going, through the above function, the question to be asked is, how do we handle the response that the <code>shazamSession</code> will return?</p>
<p>No worries, that's the topic for our next exciting section.</p>
<p>In case, you are wondering what the <code>stopAudioRecording()</code> function mentioned above looks like, here you go:</p>
<pre><code>private func stopAudioRecording() {
    audioEngine.stop()
    isRecording = <span class="hljs-literal">false</span>
}
</code></pre><h2 id="heading-handle-the-response-from-shazamkit">Handle the response from ShazamKit</h2>
<p>First, we need to tell the <code>shazamSession</code>, where to delegate its result!</p>
<p>As you can see below, we are using our <code>ShazamRecognizer</code> class as the delegate for the session so that we can be informed when there is a successful result or a failure. </p>
<pre><code>override init() {
    <span class="hljs-built_in">super</span>.init()
    <span class="hljs-comment">// Sets delegate to be ShazamRecognizer class</span>
    shazamSession.delegate = self
}
</code></pre><p>By doing the above, we are obliged to conform to the <code>SHSessionDelegate</code> protocol, and implement its delegate methods. So We extend our class and add the following:</p>
<pre><code>extension ShazamRecognizer: SHSessionDelegate {
    func session(_ session: SHSession, didFind match: SHMatch) {
        DispatchQueue.main.async {
            <span class="hljs-comment">// 1.</span>
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> firstItem = match.mediaItems.first {
               <span class="hljs-comment">// 1.</span>
                self.matchedTrack = ShazamTrack(firstItem)
                <span class="hljs-comment">// 2.</span>
                self.stopAudioRecording()
            }
        }
    }

    func session(_ session: SHSession, didNotFindMatchFor signature: SHSignature, <span class="hljs-attr">error</span>: <span class="hljs-built_in">Error</span>?) {
        DispatchQueue.main.async {
            <span class="hljs-comment">// 3.</span>
            self.error = ErrorAlert(error?.localizedDescription ?? <span class="hljs-string">"No Match found!"</span>)
            <span class="hljs-comment">// 4. Stop Audio recording</span>
            self.stopAudioRecording()
        }
    }
}
</code></pre><p>Our first delegate method is <code>func session(_ session: SHSession, didFind match: SHMatch)</code>, It is called when a match was found. Here, we:</p>
<ol>
<li>Get the first item in the match's <code>mediaItems</code>(An array of the media items in the catalog that match the query signature, in order of the quality of the match); We then convert the <code>firstItem</code> of type <a target="_blank" href="https://developer.apple.com/documentation/shazamkit/shmatchedmediaitem"><code>SHMatchedMediaItem</code></a> into our own custom model <a target="_blank" href="https://github.com/cedricbahirwe/iOS-Concepts/blob/master/P01-Shazam/P01-Shazam/Model/ShazamTrack.swift"><code>ShazamTrack</code></a></li>
<li>We stop the audio recording by calling our <code>stopAudioRecording()</code> which stops our <code>audioEngine</code>.</li>
</ol>
<p>Our second delegate method is <code>func session(_ session: SHSession, didNotFindMatchFor signature: SHSignature, error: Error?)</code>, It is is called when no match was found. Here, we:</p>
<ol>
<li>Send the error message to our <code>error</code> variable.</li>
<li>We stop the audio recording by calling our <code>stopAudioRecording()</code> which stops our <code>audioEngine</code>.</li>
</ol>
<p>At this point, We are pretty much done with our audio recognition system!👏🏻💪🏼.
We are ready to use it in our application, no matter the UI framework.</p>
<p>For this example, I've used <code>SwiftUI</code> for a quick prototype, but you can use <code>UIKit</code> as well without any particular effort.</p>
<blockquote>
<p>You  can  find the <a target="_blank" href="https://github.com/cedricbahirwe/iOS-Concepts/tree/master/P01-Shazam">full demo project</a></p>
</blockquote>
<h2 id="heading-conclusion">Conclusion</h2>
<p><code>ShazamKit</code> framework has a lot to offer, but in this article, we have just scratched the tip of the iceberg, I hope you have learned something today :)</p>
]]></content:encoded></item><item><title><![CDATA[How to fetch remote data using Combine Framework in SwiftUI]]></title><description><![CDATA[During WWDC 2019, Apple introduces Combine  which provides a declarative Swift API for processing values over time. 
These values can represent many kinds of asynchronous events. Combine declares publishers to expose values that can change over time,...]]></description><link>https://blog.cedricbahirwe.com/fetch-remote-data-using-combine</link><guid isPermaLink="true">https://blog.cedricbahirwe.com/fetch-remote-data-using-combine</guid><category><![CDATA[Swift]]></category><category><![CDATA[iOS]]></category><category><![CDATA[Combine]]></category><category><![CDATA[SwiftUI]]></category><dc:creator><![CDATA[Cédric Bahirwe]]></dc:creator><pubDate>Fri, 02 Sep 2022 20:42:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1662150993664/U8meBHnJ0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>During WWDC 2019, Apple introduces <code>Combine</code>  which provides a declarative Swift API for processing values over time. 
These values can represent many kinds of asynchronous events. <code>Combine</code> declares publishers to expose values that can change over time, and subscribers to receive those values from the publishers.</p>
<p>Today, we will focus on how we can utilize <code>combine</code>'s publishers and subscribers system to fetch remote data from a remote data source and populate the data to our UI.</p>
<p>We'll keep it simple and stupid!; I chose the <a target="_blank" href="https://random-data-api.com/">Random Data API</a>, and we'll be using their <a target="_blank" href="https://random-data-api.com/documentation">Users Endpoint</a> which provides some mock information for a random user.</p>
<h3 id="heading-step1-create-the-data-models-for-our-api">Step1: Create the data models for our API</h3>
<p>We are going to need the <code>RemoteUser</code> and the <code>Address</code> models</p>
<pre><code class="lang-swift"><span class="hljs-comment">// MARK: - RemoteUser</span>
<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">RemoteUser</span>: <span class="hljs-title">Identifiable</span>, <span class="hljs-title">Codable</span> </span>{
    <span class="hljs-keyword">let</span> id: <span class="hljs-type">Int</span>
    <span class="hljs-keyword">let</span> firstName, lastName: <span class="hljs-type">String</span>
    <span class="hljs-keyword">let</span> username, email: <span class="hljs-type">String</span>
    <span class="hljs-keyword">let</span> phoneNumber: <span class="hljs-type">String</span>
    <span class="hljs-keyword">let</span> address: <span class="hljs-type">Address</span>

    <span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">CodingKeys</span>: <span class="hljs-title">String</span>, <span class="hljs-title">CodingKey</span> </span>{
        <span class="hljs-keyword">case</span> id
        <span class="hljs-keyword">case</span> firstName = <span class="hljs-string">"first_name"</span>
        <span class="hljs-keyword">case</span> lastName = <span class="hljs-string">"last_name"</span>
        <span class="hljs-keyword">case</span> username, email
        <span class="hljs-keyword">case</span> phoneNumber = <span class="hljs-string">"phone_number"</span>
        <span class="hljs-keyword">case</span> address
    }
}

<span class="hljs-comment">// MARK: - Address</span>
<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Address</span>: <span class="hljs-title">Codable</span> </span>{
    <span class="hljs-keyword">let</span> city: <span class="hljs-type">String</span>
    <span class="hljs-keyword">let</span> state: <span class="hljs-type">String</span>
    <span class="hljs-keyword">let</span> country: <span class="hljs-type">String</span>

    <span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">CodingKeys</span>: <span class="hljs-title">String</span>, <span class="hljs-title">CodingKey</span> </span>{
        <span class="hljs-keyword">case</span> city
        <span class="hljs-keyword">case</span> state, country
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">formatted</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">String</span> {
       <span class="hljs-keyword">return</span> <span class="hljs-string">"\(city), \(state), \(country)"</span>
    }
}
</code></pre>
<h3 id="heading-step2-introduce-a-fetch-function">Step2: Introduce a <code>fetch</code> function</h3>
<p>This function does the heavy lifting for us, taking an input of type <code>URLRequest</code> as a parameter and returning an output of type <code>AnyPublisher&lt;T, Error&gt;</code></p>
<pre><code class="lang-swift"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">perform</span>&lt;T: Decodable&gt;<span class="hljs-params">(<span class="hljs-number">_</span> request: URLRequest)</span></span> -&gt; <span class="hljs-type">AnyPublisher</span>&lt;<span class="hljs-type">T</span>, <span class="hljs-type">Error</span>&gt; {
    <span class="hljs-keyword">return</span> <span class="hljs-type">URLSession</span>.shared
        .dataTaskPublisher(<span class="hljs-keyword">for</span>: request) <span class="hljs-comment">// Returns a publisher that wraps a URL session data task for a given URL request.</span>
        .<span class="hljs-built_in">map</span> { $<span class="hljs-number">0</span>.data }
        .decode(type: <span class="hljs-type">T</span>.<span class="hljs-keyword">self</span>, decoder: <span class="hljs-type">JSONDecoder</span>())
        .receive(on: <span class="hljs-type">DispatchQueue</span>.main) <span class="hljs-comment">// Receive elements on the Main Queue</span>
        .eraseToAnyPublisher()
}
</code></pre>
<h3 id="heading-step3-build-our-simple-ui-and-run">Step3: Build our simple UI and Run</h3>
<p>Basically, in our <code>ContenView</code>, we will use a simple <code>List</code> where we shall be showing our user information represented by a <code>UserRowView</code></p>
<pre><code class="lang-swift"><span class="hljs-keyword">import</span> Combine
<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">ContentView</span>: <span class="hljs-title">View</span> </span>{
    @<span class="hljs-type">State</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> users = [<span class="hljs-type">RemoteUser</span>]() <span class="hljs-comment">// Store our users</span>
    @<span class="hljs-type">State</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> isFetchingData = <span class="hljs-literal">false</span> <span class="hljs-comment">// Manage the fetching spinner</span>
    @<span class="hljs-type">State</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> cancellables = <span class="hljs-type">Set</span>&lt;<span class="hljs-type">AnyCancellable</span>&gt;()

    <span class="hljs-keyword">var</span> body: some <span class="hljs-type">View</span> {
        <span class="hljs-type">NavigationView</span> {
            <span class="hljs-type">List</span> {
                <span class="hljs-type">ForEach</span>(users) { user  <span class="hljs-keyword">in</span>
                    <span class="hljs-type">UserRowView</span>(user)
                }

                <span class="hljs-keyword">if</span> isFetchingData {
                    <span class="hljs-type">ProgressView</span>()
                        .frame(maxWidth: .infinity)
                }
            }
            .navigationTitle(<span class="hljs-string">"\(users.count) Active Users"</span>)
            .onAppear(perform: fetchSomeData) <span class="hljs-comment">// fetch data when view appears</span>
        }
    }

    <span class="hljs-comment">// Some raw URLRequest for testing purpose </span>
    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">getURLRequest</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">URLRequest</span> {
        <span class="hljs-keyword">var</span> request = <span class="hljs-type">URLRequest</span>(url: <span class="hljs-type">URL</span>(string: <span class="hljs-string">"https://random-data-api.com/api/v2/users"</span>)!)
        request.httpMethod = <span class="hljs-string">"GET"</span>
        <span class="hljs-keyword">return</span> request
    }

    <span class="hljs-comment">// Perform network call</span>
    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fetchSomeData</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">guard</span> users.<span class="hljs-built_in">count</span> &lt; <span class="hljs-number">50</span> <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> }
        <span class="hljs-keyword">let</span> result: <span class="hljs-type">AnyPublisher</span>&lt;<span class="hljs-type">RemoteUser</span>, <span class="hljs-type">Error</span>&gt; = perform(getURLRequest())
        isFetchingData = <span class="hljs-literal">true</span>

        result.sink { <span class="hljs-number">_</span> <span class="hljs-keyword">in</span>
        } receiveValue: { item <span class="hljs-keyword">in</span>
            <span class="hljs-built_in">print</span>(item)
            <span class="hljs-keyword">self</span>.users.append(item)
            isFetchingData = <span class="hljs-literal">false</span>
            fetchSomeData() <span class="hljs-comment">// Fetch new data when request has completed</span>
        }
        .store(<span class="hljs-keyword">in</span>: &amp;cancellables) <span class="hljs-comment">// Stores a type-erasing cancellable instance.</span>
    }
}
</code></pre>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">UserRowView</span>: <span class="hljs-title">View</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">let</span> user: <span class="hljs-type">RemoteUser</span>
    <span class="hljs-keyword">init</span>(<span class="hljs-number">_</span> user: <span class="hljs-type">RemoteUser</span>) {
        <span class="hljs-keyword">self</span>.user = user
    }
    <span class="hljs-keyword">var</span> body: some <span class="hljs-type">View</span> {
        <span class="hljs-type">VStack</span>(alignment: .leading, spacing: <span class="hljs-number">10</span>) {
            <span class="hljs-type">Text</span>(<span class="hljs-string">"Name: \(user.firstName) \(user.lastName)"</span>)
                .fontWeight(.bold)
            <span class="hljs-type">Text</span>(<span class="hljs-string">"Username: \(Text("</span>@\(user.username)<span class="hljs-string">").foregroundColor(.accentColor))"</span>)
                .fontWeight(.semibold)
            <span class="hljs-type">Text</span>(<span class="hljs-string">"Tél: \(user.phoneNumber)"</span>)
                .fontWeight(.medium)
            <span class="hljs-type">Text</span>(<span class="hljs-string">"Address: \(user.address.formatted())"</span>)
                .italic()
                .foregroundColor(.secondary)
        }
        .padding(<span class="hljs-number">8</span>)
        .frame(maxWidth: .infinity, alignment: .leading)
        .cornerRadius(<span class="hljs-number">10</span>)
    }
}
</code></pre>
<p>You will observe that on every successful request, another request will be made until the number of users reaches <code>50</code> as specified in the code.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662150713242/ZMA7O5ktb.png" alt="Article Screenshot" /></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p><code>Combine</code> framework has a lot to offer, but in this article, we have just scratched the tip of the iceberg, I hope you have learned something today :)</p>
]]></content:encoded></item></channel></rss>