<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.2.2">Jekyll</generator><link href="https://movieos.org/feed.xml" rel="self" type="application/atom+xml" /><link href="https://movieos.org/" rel="alternate" type="text/html" /><updated>2025-08-09T17:02:11-05:00</updated><id>https://movieos.org/feed.xml</id><title type="html">Tom Insam</title><subtitle>Words by Tom Insam</subtitle><author><name>tominsam</name></author><entry><title type="html">Easy watching of @Observable object properties from Swift</title><link href="https://movieos.org/2024/11/easy-watching-of-observable-object-properties/" rel="alternate" type="text/html" title="Easy watching of @Observable object properties from Swift" /><published>2024-11-18T09:40:00-06:00</published><updated>2024-11-18T09:40:00-06:00</updated><id>https://movieos.org/2024/11/easy-watching-of-observable-object-properties</id><content type="html" xml:base="https://movieos.org/2024/11/easy-watching-of-observable-object-properties/"><![CDATA[<p>I’ve been experimenting migrating <a href="/code/flame/">Flame</a> from <a href="https://developer.apple.com/documentation/swiftui/migrating-from-the-observable-object-protocol-to-the-observable-macro">ObservableObject to Observable</a>.</p>

<p>It’s very simple to have SwiftUI watch these obejcts for changes, but because of the legacy navigation I have in the app, I rely on the ability to watch the object for changes from normal Swift code. Previously I was subscribing to the Combine publishers for this, but now that I’ve moved to the new macro, I’ve been missing this simple functionality.</p>

<p>Here’s a simple extension that makes this easier:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">func</span> <span class="n">observeObject</span><span class="o">&lt;</span><span class="kt">T</span><span class="p">:</span> <span class="kt">Observable</span> <span class="o">&amp;</span> <span class="kt">AnyObject</span><span class="p">,</span> <span class="kt">S</span><span class="o">&gt;</span><span class="p">(</span>
    <span class="n">_</span> <span class="nv">object</span><span class="p">:</span> <span class="kt">T</span><span class="p">,</span>
    <span class="nv">keypath</span><span class="p">:</span> <span class="kt">KeyPath</span><span class="o">&lt;</span><span class="kt">T</span><span class="p">,</span> <span class="kt">S</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="nv">onChange</span><span class="p">:</span> <span class="kd">@escaping</span> <span class="p">(</span><span class="kt">S</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Void</span>
<span class="p">)</span> <span class="p">{</span>
    <span class="n">withObservationTracking</span> <span class="p">{</span>
        <span class="n">_</span> <span class="o">=</span> <span class="n">object</span><span class="p">[</span><span class="nv">keyPath</span><span class="p">:</span> <span class="n">keypath</span><span class="p">]</span>
    <span class="p">}</span> <span class="nv">onChange</span><span class="p">:</span> <span class="p">{</span> <span class="p">[</span><span class="k">weak</span> <span class="n">object</span><span class="p">]</span> <span class="k">in</span>
        <span class="k">guard</span> <span class="k">let</span> <span class="nv">object</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
        <span class="kt">Task</span> <span class="p">{</span> <span class="kd">@MainActor</span> <span class="k">in</span>
            <span class="nf">onChange</span><span class="p">(</span><span class="n">object</span><span class="p">[</span><span class="nv">keyPath</span><span class="p">:</span> <span class="n">keypath</span><span class="p">])</span>
            <span class="nf">observeObject</span><span class="p">(</span><span class="n">object</span><span class="p">,</span> <span class="nv">keypath</span><span class="p">:</span> <span class="n">keypath</span><span class="p">,</span> <span class="nv">onChange</span><span class="p">:</span> <span class="n">onChange</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And here’s how you use it:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">observeObject</span><span class="p">(</span><span class="n">viewModel</span><span class="p">,</span> <span class="nv">keypath</span><span class="p">:</span> <span class="p">\</span><span class="o">.</span><span class="n">selection</span><span class="p">)</span> <span class="p">{</span> <span class="p">[</span><span class="k">weak</span> <span class="k">self</span><span class="p">]</span> <span class="n">selection</span> <span class="k">in</span>
    <span class="k">self</span><span class="p">?</span><span class="o">.</span><span class="nf">valueDidChange</span><span class="p">(</span><span class="n">selection</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The Flame codebase change is <a href="https://github.com/tominsam/flametouch/commit/7252f44d965649b8611fb0c677e7b48894a8c635">here</a>.</p>]]></content><author><name>tominsam</name></author><category term="swift" /><category term="observable" /><summary type="html"><![CDATA[I’ve been experimenting migrating Flame from ObservableObject to Observable.]]></summary></entry><entry><title type="html">Apple Photos</title><link href="https://movieos.org/2022/12/apple-photos/" rel="alternate" type="text/html" title="Apple Photos" /><published>2022-12-08T00:00:00-06:00</published><updated>2022-12-08T00:00:00-06:00</updated><id>https://movieos.org/2022/12/apple-photos</id><content type="html" xml:base="https://movieos.org/2022/12/apple-photos/"><![CDATA[<p>Photos: “Here’s a favorites album! You can put the photos you like the most in it! There are other albums, and you can share the whole library with your family, but not your albums. Apart from the favorites album, you <em>have</em> to share that one.”</p>

<p>Watch: “You can sync any album of photos you want to your watch! But just one.”</p>

<p>Lock screen: “You can have a rotating collection of photos on your lock screen now! What’s that? Albums? Favorites? What’s an album? Nah, I’ll pick the photos for you, but I have magic, so I know you’ll love them. I don’t care what you marked as favorite. And no, if you actually like one of them, I won’t show you where it is in your library, that would be too easy. No, you can’t just use an album.”</p>

<p>iPad lock screen: “Hahaha no.”</p>

<p>Watch: “Magically chosen photos that you’ll love? Interesting! No, you can’t use those. Just albums.”</p>

<p>Photos: “No, why would you want to see the specially curated list of magical photos the lock screen thinks you’d love? That’s absurd. Here’s a set of unrelated Memories that I know you’ll enjoy.”</p>

<p>Lock screen: “Don’t be silly, you can’t put <em>Memories</em> on the lock screen, why would you want that? My selection is better.”</p>

<p>Watch: “Sure you can have Memories on the watch! No you can’t pick which one.”</p>

<p>Third party API: “No of course you can’t let users pick photos from any of these curated lists, that’s silly.”</p>]]></content><author><name>tominsam</name></author><category term="photos" /><category term="ios" /><category term="complaining" /><summary type="html"><![CDATA[Photos: “Here’s a favorites album! You can put the photos you like the most in it! There are other albums, and you can share the whole library with your family, but not your albums. Apart from the favorites album, you have to share that one.”]]></summary></entry><entry><title type="html">Data Models in iPhone apps</title><link href="https://movieos.org/2022/06/data-models/" rel="alternate" type="text/html" title="Data Models in iPhone apps" /><published>2022-06-20T00:00:00-05:00</published><updated>2022-06-20T00:00:00-05:00</updated><id>https://movieos.org/2022/06/data-models</id><content type="html" xml:base="https://movieos.org/2022/06/data-models/"><![CDATA[<p>[This is a lightly-edited flow-of-consciousness thing taken from a slack conversation]</p>

<p>Is it worth trying to write complicated Codable implementations in Swift to map your network model to the local business model you prefer? It’s a lot of complicated code, but maybe having a single representation of your data is worth the trade-off.</p>

<p>I hate doing it, but no. I always end up with a network model and a database model and a “used in the app” model and mapping methods between them.</p>

<p>I hate it because it’s irritating, because the models are always <em>almost</em> the same as each other and it grates maintaining the conversions which are always boring 40 line functions of <code class="language-plaintext highlighter-rouge">self.foo = other.foo</code> over and over. (At least now we have swift you don’t get weird bugs where some of your mappings forget to instantiate all the properties.) It feels like it should be possible to build a magical perfect object that you can decode from the wire and then put directly into the database, then pull it out and power the UI from it.</p>

<p>But Codable is an excellent demonstration of why you can’t really do that. Your wire protocol isn’t quite what you want, so to have a wire object that’s also your business object you need to write a ton of Codable conformance code. (You can’t write just a little bit - Codable auto-conformance is all or nothing.) In fact, you need to write so much code that it would be <em>easier</em> and <em>more maintainable</em> to just write a simple-as-possible wire-format Codable container and a mapping function. For all intents and purposes the Codable conformance <em>is a wire format object</em>, you’re just writing it in an inconvenient syntax.</p>

<p>Your database representation needs to be much flatter than the business object - your model is a deep structure but you don’t want 300 tables and 600 joins so you need to flatten out the deep object to a single table - you’re doing the same thing with custom SQL statements to serialize and un-serialize your rich object, whereas a flat object and a mapping could write to the table without custom code, and would be easier to read.</p>

<p>And that’s even before getting to things like “it would be super convenient if my view data models were immutable, so I can use swift UI / redux” but core data models are not only mutable, but some other thread can mutate them for you with no indication that things changed unless you’re explicitly observing for that sort of thing.</p>

<p>More complication - there are actually 2 (sometimes 3) network representations, because you sometimes need to send objects back to the server. The create call (normally) won’t have, say, an object ID or a created date, but those properties are non-optional on your network model for decoded objects, so your create call object is different from the get call. And your update call probably wants <em>everything</em> to be optional so you can update only one property at once.</p>

<p>So no, your life will be easier if you just write multiple specialist representations of your objects for different contexts, keep them all as simple as possible, and write mappings between them.</p>]]></content><author><name>tominsam</name></author><category term="ios" /><category term="swift" /><category term="programming" /><summary type="html"><![CDATA[[This is a lightly-edited flow-of-consciousness thing taken from a slack conversation]]]></summary></entry></feed>