{"id":36450,"date":"2022-07-07T15:23:17","date_gmt":"2022-07-07T19:23:17","guid":{"rendered":"https:\/\/mjtsai.com\/blog\/?p=36450"},"modified":"2022-07-08T16:37:07","modified_gmt":"2022-07-08T20:37:07","slug":"model-view-controller-store-reinventing-mvc-for-swiftui-with-boutique","status":"publish","type":"post","link":"https:\/\/mjtsai.com\/blog\/2022\/07\/07\/model-view-controller-store-reinventing-mvc-for-swiftui-with-boutique\/","title":{"rendered":"Model View Controller Store: Reinventing MVC for SwiftUI With Boutique"},"content":{"rendered":"<p><a href=\"https:\/\/build.ms\/2022\/06\/22\/model-view-controller-store\/\">Joe Fabisevich<\/a> (<a href=\"https:\/\/twitter.com\/mergesort\/status\/1539644834394374145\">tweet<\/a>):<\/p>\n<blockquote cite=\"https:\/\/build.ms\/2022\/06\/22\/model-view-controller-store\/\">\n<p>Apple has never provided a blessed architecture for SwiftUI, and many developers have spent thousands of hours filling the gaps with their own ideas. A familiar approach for many developers is to take the <a href=\"https:\/\/www.objc.io\/issues\/13-architecture\/mvvm\/\">MVVM<\/a> pattern many people adopted in their UIKit\/AppKit apps and translate it to the needs of SwiftUI. That can work well enough, but you begin to see some cracks in the architecture when you need to manage <a href=\"https:\/\/developer.apple.com\/documentation\/swiftui\/state-and-data-flow\">state and data flow<\/a> due how heavily SwiftUI leans on having a single source of truth. Others have taken the path of integrating powerful libraries such as <a href=\"https:\/\/github.com\/pointfreeco\/swift-composable-architecture\">The Composable Architecture<\/a> which provide you with the tools to reason about your entire application. TCA takes inspiration from <a href=\"https:\/\/redux.js.org\">redux<\/a>, more specifically <a href=\"https:\/\/guide.elm-lang.org\/architecture\/\">The Elm Architecture<\/a>, two patterns that are rather incredible in how they allow you to define your entire application as a tree of state. But TCA&rsquo;s great power comes with <del>great responsibility<\/del> a very high learning curve, which can make it difficult to learn personally. TCA&rsquo;s goals are much more of a fit for solving problems with a very high level of complexity, which may not be necessary for every app.<\/p>\n<p>[&#8230;]<\/p>\n<p>You can think the Store as the storage for your model objects, in SwiftUI this would be your <a href=\"https:\/\/www.raywenderlich.com\/11781349-understanding-data-flow-in-swiftui\">single source of truth<\/a>. If you model your data correctly then your user interface will always do what you expect it to do. That relationship between data and user interface is why Views having a single source of truth is so important.<\/p>\n<p>I&rsquo;ve built a batteries-included Store that comes with everything you&rsquo;ll need out of the box called <a href=\"https:\/\/www.github.com\/mergesort\/Boutique\">Boutique<\/a> to be the foundation for that data. <a href=\"https:\/\/www.github.com\/mergesort\/Boutique\">Boutique<\/a> does <em>no behind the scenes magic<\/em> and doesn&rsquo;t resort to shenanigans like runtime hacking to achieve a great developer experience.<\/p>\n<\/blockquote>\n\n<p><a href=\"https:\/\/github.com\/mergesort\/Bodega\">Bodega<\/a>:<\/p>\n<blockquote cite=\"https:\/\/github.com\/mergesort\/Bodega\"><p>Bodega is a straightforward actor-based library for writing files to disk with an incredibly simple API. Bodega is fully usable and useful on its own, but it&rsquo;s also the foundation of <a href=\"https:\/\/github.com\/mergesort\/Boutique\">Boutique<\/a>.<\/p><p>[&#8230;]<\/p><p>Bodega provides two kinds of storage for you, <code>DiskStorage<\/code> and <code>ObjectStorage<\/code>. <code>DiskStorage<\/code> is for writing <code>Data<\/code> to disk, and <code>ObjectStorage<\/code> builds upon <code>DiskStorage<\/code> allowing you to write any <code>Codable<\/code> object to disk using a very similar API.<\/p><p>Both <code>DiskStorage<\/code> and <code>ObjectStorage<\/code> are implemented as actors which means they take care of properly synchronizing disk reads and writes.<\/p><\/blockquote>\n\n<p><a href=\"https:\/\/github.com\/mergesort\/Boutique\">Boutique<\/a>:<\/p>\n<blockquote cite=\"https:\/\/github.com\/mergesort\/Boutique\">\n<p>Boutique is a simple but powerful persistence library, and more. With its dual-layered memory + disk caching architecture Boutique provides a way to build apps that update in real time with full offline storage in only a few lines of code using an incredibly simple API.<\/p>\n<p>[&#8230;]<\/p>\n<p>Storing images or other binary data in Boutique is technically supported but not recommended. The reason is that storing images in Boutique&rsquo;s can balloon up the in-memory store, and your app&rsquo;s memory as a result. For similar reasons as it&rsquo;s not recommended to store images or binary blobs in a database, it&rsquo;s not recommended to store images or binary blobs in Boutique.<\/p>\n<p>[&#8230;]<\/p>\n<p>The <code>Store<\/code> is a simple way to gain the benefits of offline storage and realtime updates, but by using the <code>@Stored<\/code> property wrapper we can cache any property in-memory and on disk with just one line of code.<\/p>\n<\/blockquote>\n\n<p>Previously:<\/p>\n<ul>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2022\/07\/07\/blackbird-a-reference-architecture-for-local-first-connected-mobile-apps\/\">Blackbird: a Reference Architecture for Local-First Connected Mobile Apps<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2018\/05\/15\/app-architecture-ios-application-design-patterns-in-swift\/\">App Architecture: iOS Application Design Patterns in Swift<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2016\/05\/24\/mvc-n-isolating-network-calls-from-view-controllers\/\">MVC-N: Isolating Network Calls From View Controllers<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2016\/04\/03\/protocol-oriented-mvvm\/\">Protocol-Oriented MVVM<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2015\/12\/20\/mvvm-is-not-very-good\/\">MVVM Is Not Very Good<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2015\/12\/12\/humble-object-pattern-in-swift\/\">Humble Object Pattern in Swift<\/a><\/li>\n<li><a href=\"https:\/\/mjtsai.com\/blog\/2015\/10\/22\/mvvm-in-swift\/\">MVVM in Swift<\/a><\/li>\n<\/ul>","protected":false},"excerpt":{"rendered":"<p>Joe Fabisevich (tweet): Apple has never provided a blessed architecture for SwiftUI, and many developers have spent thousands of hours filling the gaps with their own ideas. A familiar approach for many developers is to take the MVVM pattern many people adopted in their UIKit\/AppKit apps and translate it to the needs of SwiftUI. That [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"apple_news_api_created_at":"2022-07-07T19:23:25Z","apple_news_api_id":"69d44ab8-22d9-490e-8ad1-c843a8f3f236","apple_news_api_modified_at":"2022-07-08T20:37:10Z","apple_news_api_revision":"AAAAAAAAAAAAAAAAAAAAAQ==","apple_news_api_share_url":"https:\/\/apple.news\/AadRKuCLZSQ6K0chDqPPyNg","apple_news_coverimage":0,"apple_news_coverimage_caption":"","apple_news_is_hidden":false,"apple_news_is_paid":false,"apple_news_is_preview":false,"apple_news_is_sponsored":false,"apple_news_maturity_rating":"","apple_news_metadata":"\"\"","apple_news_pullquote":"","apple_news_pullquote_position":"","apple_news_slug":"","apple_news_sections":"\"\"","apple_news_suppress_video_url":false,"apple_news_use_image_component":false,"footnotes":""},"categories":[4],"tags":[27,2078,30,2077,374,74,71,1774,2200,901,1812],"class_list":["post-36450","post","type-post","status-publish","format-standard","hentry","category-programming-category","tag-craft","tag-ios-15","tag-mac","tag-macos-12","tag-mvc","tag-opensource","tag-programming","tag-swift-codable","tag-swift-concurrency","tag-swift-programming-language","tag-swiftui"],"apple_news_notices":[],"_links":{"self":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/36450","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/comments?post=36450"}],"version-history":[{"count":3,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/36450\/revisions"}],"predecessor-version":[{"id":36472,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/posts\/36450\/revisions\/36472"}],"wp:attachment":[{"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/media?parent=36450"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/categories?post=36450"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mjtsai.com\/blog\/wp-json\/wp\/v2\/tags?post=36450"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}