# Swift / Apple Development Chat



## Nycturne

Since a couple of us keep getting sidetracked discussing development in Swift or on Apple platforms, maybe we can do some of it here? A place to post questions, neat learnings, complaints, etc to share with others without sidetracking other threads on it?

Maybe I can start by saying: SwiftUI, you are drunk, go home. When you attach a context menu to a view, if it happens to be a button (NavigationLink in this case), it replicates the button's state at the time instead of rendering the button content. But if I attach the context view to the content, the button can swallow the gesture and prevent the context menu from working unless you are on macOS. Great times. Great times. I suspect I'm going to have to drop down to UIKit to get behavior I like on this one.


----------



## Cmaier

Yikes.

I use SwiftUI (so far) only in discrete views, and particularly in certain views that have tables views in them. Mostly because it’s so much easier to make each table row have a different ad hoc layout in SwiftUI than it is in UIKit.  There are still just too many limitations for me to use SwiftUI for anything more complicated than that.


----------



## Nycturne

Cmaier said:


> Yikes.
> 
> I use SwiftUI (so far) only in discrete views, and particularly in certain views that have tables views in them. Mostly because it’s so much easier to make each table row have a different ad hoc layout in SwiftUI than it is in UIKit.  There are still just too many limitations for me to use SwiftUI for anything more complicated than that.



I've been building an app from scratch in SwiftUI since late last year in my spare time, and it's gotten far enough along that it's time to starting pushing towards a beta/release. So far I've managed to limit how often I drop down to AppKit/UIKit, but there's still some utterly bizarre things that just don't work right in SwiftUI. 

If SwiftUI let you specify a preview view for a context menu, this would take me all of 5 seconds to fix, but it doesn't. So instead I have to drop down to UIKit and expose out the context preview myself.


----------



## MEJHarrison

I like to tinker with SwiftUI.  I usually get hyped up around WWDC, play with it for a month or two and then move on.  I skipped last year because I was into VR at the time.  But this year, I'm expecting my new Mac to be here by then.  That's going to be a blast.  Xcode is just too annoying on my old Mac. It seems like it's 10-15 minutes by the time I get Xcode going, my project loaded, my project building, etc..  I suspect the new M1 will make a world of difference in that regard.  So I plan to dive back in head first come June.

My biggest problem is I need a project, or it's just playing around.  But I have no good ideas.  The last application I wanted I've already turned into a Mac app.  I just need a good idea.  Not so much for sale (though that would be cool).  Just enough to keep me interested and give me a good reason to keep working out the problems.  Otherwise I'll be bored a month later and on to the next interest.


----------



## MEJHarrison

Since we're talking about SwiftUI, does anyone have good resources?  I've been a fan of the stuff from Mark Moeykens from Big Mountain Studio.  But it's 100% encyclopedic, not "let's learn SwiftUI".  Still, if you like a good reference, it's fantastic.  In addition to getting "the book", you get the whole thing as an Xcode project as well.  So you could find that "page" in the project and play with real working code.  I'll attach a screenshot if you're curious.  I have found it to be quite complete and he updates them throughout the year.  The only downside is you're buying a single version of the book.  When he updates the books for all the new stuff this year, it will be a new purchase (with a discount).  All his books are also on sale this week, so if you're interested, it's the perfect time to get something that will be outdated in a month.

What other resources do people use?  Since it's been a couple years, I wouldn't be opposed to something that's more teaching than reference.

Here's a screenshot of the project that goes with the SwiftUI Mastery book.  This is the source code for the actual book.  I tried to get the preview going, but after playing with it for 15 minutes, I finally gave up.  Can't wait for my new Mac.


----------



## Nycturne

I’m a bit all over the place with how I’ve picked up SwiftUI. Partly because I’m too cheap to actually pay for a good resource, and there‘s a lot of “here‘s the basics” and not enough “here’s how to build something substantial with the framework” out there, IMO.

That said, I think the most helpful stuff for me has been the random videos on YouTube where someone replicates some specific bit of UI in SwiftUI. It’s mostly replicating some of the slicker animations Apple does in their apps, but it’s useful for understanding just how to describe some of these more detailed components within SwiftUI. But it probably is best if you have a working understanding first as some channels like kavsoft don’t do any explanation of the code, just demonstration of the code step-by-step.

I’d love a resource that’s aimed at demonstrating how to _scale_ SwiftUI up, looking at good vs bad patterns. At this rate, if I ship this project, my next project might have to be writing up what I’ve learned about SwiftUI and the bad patterns I’ve had to keep fixing to avoid painting myself in a corner. There’s a couple folks who have written up their thoughts, but one boiled down to “I used a massive Observable Object thinking it would work like a Redux Store, invaliding my whole view hierarchy on every state change, and my performance sucked, so I’m trying to avoid Observable Object going forward.“ While I’m over here watching them throw the baby out with the bath water. But getting good performance is tricky. It’s  easy to over-observe state as you try to refactor things to get better composition.

(It probably doesn’t help that I’m a bit of a MVVM noob and have been making mistakes that I then need to clean up on that front too. I’m in the middle of making it easier to compose model objects into a single view model rather than trying to compose multiple view models into a view)


----------



## MEJHarrison

Nycturne said:


> I’m a bit all over the place with how I’ve picked up SwiftUI. Partly because I’m too cheap to actually pay for a good resource, and there‘s a lot of “here‘s the basics” and not enough “here’s how to build something substantial with the framework” out there, IMO.




Apple themselves are the worst at that.  Their stuff is very flashy and designed to perfectly fit SwiftUI.  I don't find their demos to be super great real-world examples.



Nycturne said:


> That said, I think the most helpful stuff for me has been the random videos on YouTube where someone replicates some specific bit of UI in SwiftUI. It’s mostly replicating some of the slicker animations Apple does in their apps, but it’s useful for understanding just how to describe some of these more detailed components within SwiftUI. But it probably is best if you have a working understanding first as some channels like kavsoft don’t do any explanation of the code, just demonstration of the code step-by-step.




Interesting...  I'd not thought of that.  I used to prefer reading, but have gotten more into YouTube this past year.  I might have to check that out.


----------



## Andropov

Heh. I changed jobs in February and my new employer is all-in on SwiftUI for new developements. So for the last couple months I've been working mostly on SwiftUI only, and for production apps. I used to think it was too soon to use SwiftUI on prod, but they got it working nicely. One thing that I think helps immensely is having flexible design requirements. The worst part of SwiftUI is the edge cases where getting it to match the design is hard or impossible. But you can often just design around it. 

A real example comes to mind: an app we're developing had a button on the navigation bar that triggered an action sheet (which is unavailable in SwiftUI, unless you drop down to UIKit) but after talking with the designer, it was agreed that using a pull-down contextual menu was also a good choice. Saved a lot of development effort and IMHO is actually a better choice than the action sheet. Of course, you can't always do this, but not fighting the framework at every step helps a lot.


----------



## Nycturne

Andropov said:


> Heh. I changed jobs in February and my new employer is all-in on SwiftUI for new developements. So for the last couple months I've been working mostly on SwiftUI only, and for production apps. I used to think it was too soon to use SwiftUI on prod, but they got it working nicely. One thing that I think helps immensely is having flexible design requirements. The worst part of SwiftUI is the edge cases where getting it to match the design is hard or impossible. But you can often just design around it.




Generally good advice when you can follow it. I have had to design around SwiftUI mis-behavior, but sometimes designing around the problem is itself a chore.

I think SwiftUI was ready for production with V1, but for specific cases. Each year has covered more of what UIKit/AppKit can do and I think it's maybe 80% of the way there, which is enough for an awful lot of apps. But if you need a AVRoutePickerView, or a WKWebView, or the like, you _have_ to drop down to UIKit or AppKit. Not much choice when you are rubbing up against the edge cases of the framework. 



Andropov said:


> A real example comes to mind: an app we're developing had a button on the navigation bar that triggered an action sheet (which is unavailable in SwiftUI, unless you drop down to UIKit) but after talking with the designer, it was agreed that using a pull-down contextual menu was also a good choice. Saved a lot of development effort and IMHO is actually a better choice than the action sheet. Of course, you can't always do this, but not fighting the framework at every step helps a lot.




Yeah, for that example, I definitely wouldn't fight the framework. My gripes are more that it seems like Apple is rushing to get coverage done and when they add functionality it's not always really usable as-is, like the context menu screenshot above. Because SwiftUI doesn't fully support the functionality, it's a whole ordeal when you hit bugs like what I've run across. Sidebars on iPad, toolbars on the Mac, focus on Apple TV when using complicated layouts... they all are still pretty buggy in one way or another.


----------



## Andropov

Nycturne said:


> Generally good advice when you can follow it. I have had to design around SwiftUI mis-behavior, but sometimes designing around the problem is itself a chore.



It definitely depends on how strict the requirements are. For personal projects, where I can freely change the design or drop entire features, I love using SwiftUI. But I've worked at places where design requirements had to be followed to the pixel. Using SwiftUI there would have been a nightmare.



Nycturne said:


> But if you need a AVRoutePickerView, or a WKWebView, or the like, you _have_ to drop down to UIKit or AppKit. Not much choice when you are rubbing up against the edge cases of the framework.



Out of curiosity, what have you been using on macOS? SwiftUI over AppKit or SwiftUI over UIKit using Catalyst? It's kinda awkward that both are possible right now. 



Nycturne said:


> Yeah, for that example, I definitely wouldn't fight the framework. My gripes are more that it seems like Apple is rushing to get coverage done and when they add functionality it's not always really usable as-is, like the context menu screenshot above. Because SwiftUI doesn't fully support the functionality, it's a whole ordeal when you hit bugs like what I've run across. Sidebars on iPad, toolbars on the Mac, focus on Apple TV when using complicated layouts... they all are still pretty buggy in one way or another.



The focus engine comes to mind here as well. I gave up trying to implement a CommandGroup whose commands affected only the active window on a multi-scene SwiftUI app on macOS (commands are app-wide so you need a reference to the active View or ViewModel... good luck doing that using the SwiftUI focus API). 

I agree that Apple is basically rushing the full API coverage here, but there's no good option here IMHO. I think I'd rather have a buggy API than no API at all, but it's frustrating when you find existing but unusable API, like the context menu bug you found. And if you can't work around the bug, you may end up having to throw away a lot of what you had already done and drop to UIKit.

Thankfully, they're fixing the bugs at a good pace too. I'm sure the buggiest already released APIs will be fixed post-WWDC '22. Last year I saw all the SwiftUI bugs I was seeing in my SwiftUI app (navigation bar related) disappear as soon as I recompiled the app with the Xcode beta. That was beautiful.


----------



## Nycturne

Andropov said:


> It definitely depends on how strict the requirements are. For personal projects, where I can freely change the design or drop entire features, I love using SwiftUI. But I've worked at places where design requirements had to be followed to the pixel. Using SwiftUI there would have been a nightmare.




Yeah, I’m somewhere in the middle. It’s not so much that I have to do things a specific way, but rather I’d have to depart far away from what people expect in order to avoid certain limitations, so I tend to push through and make it work because so much other stuff is faster to do. I’m not doing anything terribly fancy (yet), but there are things Apple does in their own apps that simply cannot be done in SwiftUI yet, while there’s some really fancy stuff that is quite doable in SwiftUI once you are in the right mindset for it.

To get hover effects working in the Mac toolbar for items taller than 32pt, I made some ugly hacky workarounds that convinced SwiftUI to setup the hover area properly. But at least I didn’t have to drop down to AppKit. But it got me my ”fancy” toolbar control similar to Podcasts/Books/Music.

Context menus I either need to drop down and create my own view modifier and result builder to redescribe the context menu in terms of the UIKit version, or build custom selection/editing views. I might actually go the latter since it enables bulk editing as well.



Andropov said:


> Out of curiosity, what have you been using on macOS? SwiftUI over AppKit or SwiftUI over UIKit using Catalyst? It's kinda awkward that both are possible right now.



SwiftUI over AppKit. Using Catalyst gets you a functional navigation stack which I could certainly use, but also comes with its own set of issues where things need to be done specifically for Catalyst because it isn’t exactly the same as UIKit. And there are things that AppKit SwiftUI supports but Catalyst does not.

I built a custom navigation stack for AppKit SwiftUI, but it causes havoc with the toolbar that I need to find a clever answer for. I might pull some ideas from the Apple TV interface I’ve been building to do it and just bypass the behavior entirely in favor of something a little different. I’ll just lose the nice translucent effect of the titlebar when used with a scroll bar to do it…



Andropov said:


> The focus engine comes to mind here as well. I gave up trying to implement a CommandGroup whose commands affected only the active window on a multi-scene SwiftUI app on macOS (commands are app-wide so you need a reference to the active View or ViewModel... good luck doing that using the SwiftUI focus API).




I’m about to bang my head on that particular wall. Let’s see how bad it is.



Andropov said:


> I agree that Apple is basically rushing the full API coverage here, but there's no good option here IMHO. I think I'd rather have a buggy API than no API at all, but it's frustrating when you find existing but unusable API, like the context menu bug you found. And if you can't work around the bug, you may end up having to throw away a lot of what you had already done and drop to UIKit.




I’ve taken the approach of building specific components and making them SwiftUI-like. It’s not too bad but it is tedious hooking up custom result builders for things.



Andropov said:


> Thankfully, they're fixing the bugs at a good pace too. I'm sure the buggiest already released APIs will be fixed post-WWDC '22. Last year I saw all the SwiftUI bugs I was seeing in my SwiftUI app (navigation bar related) disappear as soon as I recompiled the app with the Xcode beta. That was beautiful.




That is the good news for sure. The main reason I’ve gotten as far as I have in pure SwiftUI is because of the fixes in SwiftUI 3.


----------



## Andropov

Nycturne said:


> Yeah, I’m somewhere in the middle. It’s not so much that I have to do things a specific way, but rather I’d have to depart far away from what people expect in order to avoid certain limitations, so I tend to push through and make it work because so much other stuff is faster to do. I’m not doing anything terribly fancy (yet), but there are things Apple does in their own apps that simply cannot be done in SwiftUI yet, while there’s some really fancy stuff that is quite doable in SwiftUI once you are in the right mindset for it.



About the fancy stuff, being able to animate every change you want to so easily is a big plus of SwiftUI. I hope it drives changes on the tools UI/UX designers use so animations become a core part of the design. From what I've seen (Figma files), animations aren't usually specified on the design template (don't know if it's a tool limitation or just that most UI/UX designers are still not used to specifying them, as they used to be costly to implement in code). So it's often left undetermined. Really hope that changes, I believe good (but subtle) animations make an app feel much more polished.



Nycturne said:


> And there are things that AppKit SwiftUI supports but Catalyst does not.



And vice versa  (don't remember the specifics anymore).


----------



## Nycturne

Andropov said:


> The focus engine comes to mind here as well. I gave up trying to implement a CommandGroup whose commands affected only the active window on a multi-scene SwiftUI app on macOS (commands are app-wide so you need a reference to the active View or ViewModel... good luck doing that using the SwiftUI focus API).




Aha, so I think I figured this out. The focus engine _generally_ is aimed at keyboard focus for iOS/Mac, or cursor focus on Apple TV. However, focusedSceneValue() is intended to provide this sort of 'first responder' style state information, and then you can observe this state using @FocusedValue in your commands object. 

I was able to create a contextual command that is enabled when specific views are visible, and it seems to work well on iPad as well, but iPad seems to have some issues triggering update of state properly in cases where it works on Mac. I'll have to read up a bit more since it seems related to how iPad expects commands to be based on navigation state, rather than other app state (like the state of your underlying model).



Andropov said:


> And vice versa  (don't remember the specifics anymore).




Yeah, it's the question: Do you want UIKit-isms or AppKit-isms?


----------



## Andropov

Nycturne said:


> Aha, so I think I figured this out. The focus engine _generally_ is aimed at keyboard focus for iOS/Mac, or cursor focus on Apple TV. However, focusedSceneValue() is intended to provide this sort of 'first responder' style state information, and then you can observe this state using @FocusedValue in your commands object.
> 
> I was able to create a contextual command that is enabled when specific views are visible, and it seems to work well on iPad as well, but iPad seems to have some issues triggering update of state properly in cases where it works on Mac. I'll have to read up a bit more since it seems related to how iPad expects commands to be based on navigation state, rather than other app state (like the state of your underlying model).



I'm doing exactly that, but the FocusedValues property setter for my @FocusedValue object is not called when I click on a different window. It gets called if I press 'tab' (which controls keyboard focus, so I'm guessing that's why it works) or if I create a new scene, but that's about it.
It's particularly frustrating because I mostly did the command things for macOS, where there used to be this very simple concept of the 'keyWindow', literally *the window that receives the keys*. But it got screwed over due to the new iPadOS multitasking model. Now an app can have multiple key windows (one per scene), but there's no 'keyScene' property to find which scene is actually receiving the keys. So I can't reliably retrieve the true keyWindow, even if the windows themselves are clearly calling makeKeyAndVisible reliably because I see the semaphore in the window bar change colors.

If you got it working on Mac but it doesn't always fire on iPad, maybe I'm not getting it to work on macOS because I'm using Catalyst. I'll test my app on iPad to see how badly it works there.


----------



## Nycturne

Andropov said:


> I'm doing exactly that, but the FocusedValues property setter for my @FocusedValue object is not called when I click on a different window. It gets called if I press 'tab' (which controls keyboard focus, so I'm guessing that's why it works) or if I create a new scene, but that's about it.
> It's particularly frustrating because I mostly did the command things for macOS, where there used to be this very simple concept of the 'keyWindow', literally *the window that receives the keys*. But it got screwed over due to the new iPadOS multitasking model. Now an app can have multiple key windows (one per scene), but there's no 'keyScene' property to find which scene is actually receiving the keys. So I can't reliably retrieve the true keyWindow, even if the windows themselves are clearly calling makeKeyAndVisible reliably because I see the semaphore in the window bar change colors.




That sounds about right. I'm able to reproduce this behavior on iPad with my command hierarchy. That, and the fact that disable states don't seem to update without changing the navigation stack pretty much breaks much of the keyboard shortcut behaviors I want to enable on iPad. I also didn't expect iPadOS to hide  menu commands that are disabled, so discovery is a bit of a mess on iPad.

Even better, if I use something like Cmd-Right Arrow, iPad can't show the button that needs to be pressed, but Mac can. Awesome.

Menu Commands on iPad seem pretty borked right now. Especially since iOS seems to be acting like commands _should _be attached to views rather than scenes (similar to toolbars on iOS), but must be attached to the scene. Awesome. (/s)



Andropov said:


> If you got it working on Mac but it doesn't always fire on iPad, maybe I'm not getting it to work on macOS because I'm using Catalyst. I'll test my app on iPad to see how badly it works there.




I think that's the case. I just tried a multi-window scenario on macOS specifically again and the menu disable states were updating when switching the foreground window and doing nothing else. So Window A has a view up that enables the command, Window B doesn't, and the command is enabled when I bring A to the front, and disabled when I bring B to the front.


----------



## Andropov

Nycturne said:


> That sounds about right. I'm able to reproduce this behavior on iPad with my command hierarchy. That, and the fact that disable states don't seem to update without changing the navigation stack pretty much breaks much of the keyboard shortcut behaviors I want to enable on iPad. I also didn't expect iPadOS to hide  menu commands that are disabled, so discovery is a bit of a mess on iPad.
> 
> Even better, if I use something like Cmd-Right Arrow, iPad can't show the button that needs to be pressed, but Mac can. Awesome.
> 
> Menu Commands on iPad seem pretty borked right now. Especially since iOS seems to be acting like commands _should _be attached to views rather than scenes (similar to toolbars on iOS), but must be attached to the scene. Awesome. (/s)



Yep, had the same problem with the .disabled buttons not changing on state change.

There's also no way of overriding the default macOS commands as far as I know. If you try .keyboardShortcut("I"), the shortcut is not even shown in the menu! In fact, I remember that when I first tried this, the entire CommandGroup disappeared from the menu if one of the buttons had a reserved keyboardShortcut. And I haven't found any list of what shortcuts are not allowed. Preview uses _⌘+I_ for toggling the inspector panel, so that's why I tried to use the same shortcut for my app (I try to be consistent with the system behaviour). Nope.

This is a good example of an API that, while nice, needs work to be usable. I bet they'll fix it this year.


----------



## Nycturne

Andropov said:


> There's also no way of overriding the default macOS commands as far as I know. If you try .keyboardShortcut("I"), the shortcut is not even shown in the menu! In fact, I remember that when I first tried this, the entire CommandGroup disappeared from the menu if one of the buttons had a reserved keyboardShortcut. And I haven't found any list of what shortcuts are not allowed. Preview uses _⌘+I_ for toggling the inspector panel, so that's why I tried to use the same shortcut for my app (I try to be consistent with the system behaviour). Nope.




I noticed that collisions are messy for sure. On the AppKit side collisions don’t get resolved in a deterministic way, and so things just break if two menu items declare the same keyboard shortcut. Fun.

That said, Cmd-I seems to work fine for me on both AppKit and UIKit. So not sure what collision you are running into. Seems like it would be the "Toggle Italics" command, but not sure how that would be available by default.


----------



## Andropov

Nycturne said:


> That said, Cmd-I seems to work fine for me on both AppKit and UIKit. So not sure what collision you are running into. Seems like it would be the "Toggle Italics" command, but not sure how that would be available by default.



Oh my god. I just tried it on my iPad an it shows the ⌘+I. If you've tried on AppKit and it works, then it's a Catalyst-only issue. Lol.


----------



## Nycturne

Andropov said:


> Oh my god. I just tried it on my iPad an it shows the ⌘+I. If you've tried on AppKit and it works, then it's a Catalyst-only issue. Lol.



Come to the dark side and tweak things on top of AppKit. We have cake and broken toolbars. 

(I may have eaten the cake)


----------



## Andropov

Nycturne said:


> Come to the dark side and tweak things on top of AppKit. We have cake and broken toolbars.



Do you have tooltips? I want tooltips 

I think it's too late for me to rewrite the app to use Catalyst now anyway. Specially so close to the WWDC.


----------



## DT

I'm considering starting a more general development thread, everything outside of native iOS/Mac, different tech stacks, languages, frameworks, DB design, tools, maybe some light infrastructure/DEVOPS type talk, maybe actual project talk, like who/what/where are working on / past projects, etc.  I don't want to distract from this thread since it seems a lot of dev folks are focused here - and don't want it just to be me yammering on in an empty room


----------



## MEJHarrison

DT said:


> I'm considering starting a more general development thread, everything outside of native iOS/Mac, different tech stacks, languages, frameworks, DB design, tools, maybe some light infrastructure/DEVOPS type talk, maybe actual project talk, like who/what/where are working on / past projects, etc.  I don't want to distract from this thread since it seems a lot of dev folks are focused here - and don't want it just to be me yammering on in an empty room




At work: .NET, AWS
At home: Swift, SwiftUI, Unity (playing with my Quest 2)
Other: TDD & testing in general, good design

Those are my highlights.


----------



## Nycturne

DT said:


> I'm considering starting a more general development thread, everything outside of native iOS/Mac, different tech stacks, languages, frameworks, DB design, tools, maybe some light infrastructure/DEVOPS type talk, maybe actual project talk, like who/what/where are working on / past projects, etc. I don't want to distract from this thread since it seems a lot of dev folks are focused here - and don't want it just to be me yammering on in an empty room




I could always complain about React Native and Java. My C# experience is getting far too rusty these days. 

But I figured since there are folks that came from The Other Place, an Apple-specific thread may (or may not) work. A more general development thread would probably get more traffic anyways.

On another front, I'm getting pretty close to pulling the trigger on getting this through a beta review and start pulling folks into a beta pool. Also getting the website + early marketing material + social media presence ready. I just wish the Mac app wasn't what feels like 2 weeks behind the iPhone/iPad app.


----------



## DT

Nycturne said:


> But I figured since there are folks that came from The Other Place, an Apple-specific thread may (or may not) work. A more general development thread would probably get more traffic anyways.




Whatever you'd like, I just didn't want to pollute your thread with off-topic development chit chat, but if you're OK with the intrusion, that's cool too.


----------



## MEJHarrison

Makes no difference to me.  I'm not bothered with more posts in this thread.


----------



## DT

Side note:  the little G walked in when I was posting above - she was like, "OMG, that's Persona 5!"


----------



## Andropov

MEJHarrison said:


> Other: TDD & testing in general, good design



Learning TDD has been in my to-do list since forever. None of the companies I've worked for had tests as a core part of the development process, and I just didn't bother with my own apps. Weirdly enough, despite having zero tests, some companies relied heavily in architectures whose primary benefit was... ease of testing (I'm thinking VIPER). Even at the cost of massive amounts of boilerplate. For one of the latest projects I worked at, the client enforced a strict VIPER architecture + Protocol Oriented Programming + mandatory use of Publishers (instead of delegation) to 'talk' between architecture layers. I SWEAR 80% of the project was just boilerplate. Adding a single new screen required ~15 new project files. And yet: no tests.

Thankfully, I no longer have to work in that project (yay!). And I've been told we'll soon have a new team lead which has strong skills in TDD, so I may learn soon 

On the architecture front though, sometimes I feel like a lot of people are just drinking the kool-aid. I thought maybe that'd change after I acquired more experience, but my opinion has only changed for the worse. For my own apps, I've been using MVVM since I started with SwiftUI (MVC for my old UIKit apps). And while I've run into problems I have to work on (massive ViewModel), it's far less tedious than prematurely abstracting everything in case ever want to reuse it (but with no intention on doing so).



MEJHarrison said:


> At home: Swift, SwiftUI, Unity (playing with my Quest 2)



Ooh it's been a while since I last touched Unity. I've been participating on a game jam every summer for the last 2 years, but other than that, I haven't done much lately.



Nycturne said:


> I could always complain about React Native



I _do_ always complain about React Native. Tried it once for a couple days, and it was enough to keep me complaining for the next several years.



Nycturne said:


> On another front, I'm getting pretty close to pulling the trigger on getting this through a beta review and start pulling folks into a beta pool. Also getting the website + early marketing material + social media presence ready. I just wish the Mac app wasn't what feels like 2 weeks behind the iPhone/iPad app.



Good luck! 



MEJHarrison said:


> Makes no difference to me.  I'm not bothered with more posts in this thread.



+1


----------



## DT

Nycturne said:


> I could always complain about React Native and Java.




I had a project I started in iOS native, Swift, coming from a previous project in Obj-C I [though I] had a decent handle on XCode though it had been years, with an S, since my last XCode work, realized it was unnecessarily complex given the nature of the project (and in discussing Enterprise iOS management with the client, I got some glazed over looks ...).

I'd wanted to try React Native (I had plinked around in React for web) to see if it allowed for quicker design iteration (since I do a significant amount of web dev so the layout approach looked familiar), and with this app, it's mostly just UI (so to speak), it's a pretty simple presentment layer, it does deal with the camera, but the data, the code logic, etc., is all in the API.

It also allowed me to address (with the client) there being an easy path to Android (while almost for sure, it still wasn't 100% given the target devices would be iOS based).  Spent a few months in React Native, early on added Expo to the the toolchain because I needed several things in that SDK - even then I ran into a few needs that would've required an eject to native, but eventually the SDK updated, added what I needed, but then the tools would break, libraries go out of compatibility, etc., it was like herding cats.

This particular project was for internal use only, so I didn't need to deal with the App Store, and with React  + Expo I considered just requiring an Expo client for consumption, so I could stay out of the native workflow altogether.

Then, mobile got back-burnered, we focused on the web side - for a long time - like a year   When I came back to React, everything was busted.  And I don't use React for my web development (in fact, I avoid most "heavy" JS frameworks), so it's not like I had been working around the technology in the interim and could just hop right back into it.

So sitting down and thinking about what I needed, what I had already (like a mobile friendly BS4 layout already in place), and not needing features like push notifications, I wound up knocking out a quick PWA as a POC (for myself).  A manifest and a worker JS file and I had an app I could save in Safari that provided offline caching, and with a couple of network detect libs and a little local storage, I had a functional offline implementation for the one functional area that needs it.  So I'm about 99% confident that when - which could wind up being if - I make it back to the mobile part of this effort, I'll just go PWA.

Short version:  turns out it wasn't a nail, so I didn't need a hammer


----------



## Nycturne

DT said:


> it was like herding cats.




This is a good description of my experience. I jumped into an existing team and project using React Native. But the point of having a mobile client was to access the platform APIs in a way that the browser couldn’t. So it was a patchwork of orphaned/outdated dependencies and native modules, growing tech debt, and not enough resources to pay off enough of it to reduce the burden it was imposing.

I’m not against React Native in principle. But I think it’s being sold as a “use this to build a native, multi-platform app” to some teams, and they aren’t reading the fine print and understanding the limitations/costs they would be taking on doing so, and treat it like something that it isn’t.


----------



## MEJHarrison

Andropov said:


> Learning TDD has been in my to-do list since forever. None of the companies I've worked for had tests as a core part of the development process, and I just didn't bother with my own apps.




That's sort of my position.  So the last time I did a SwiftUI project (2 years ago?), I _also_ took that opportunity to try out TDD.  It helps that I'm a weirdo who actually loves writing tests.

One thing that's caught my attention lately is just testing the public interface.  As long as my code does the right thing and passes the tests, does it really matter how it arrives at those results internally?  So you only test behaviors available through the public API and don't test private methods.

Testing is a surprisingly complex topic and at times an art form.



Andropov said:


> Thankfully, I no longer have to work in that project (yay!). And I've been told we'll soon have a new team lead which has strong skills in TDD, so I may learn soon




I'm trying to be that guy on our team.  The problem is they just keep shoveling work at us so quickly, we don't have the time usually to slow down and do testing.



Andropov said:


> On the architecture front though, sometimes I feel like a lot of people are just drinking the kool-aid. I thought maybe that'd change after I acquired more experience, but my opinion has only changed for the worse. For my own apps, I've been using MVVM since I started with SwiftUI (MVC for my old UIKit apps). And while I've run into problems I have to work on (massive ViewModel), it's far less tedious than prematurely abstracting everything in case ever want to reuse it (but with no intention on doing so).




That abstraction is super important in the testing world.  The tighter your code is coupled together, the harder it is to test.  With interfaces, you can just give it something that _resembles _the real thing, but is just a bunch of fake code.  So if you're testing Thing A, and Thing A depends on Thing B, by giving it a fake Thing B, it allows you to test Thing A in isolation.  Then you can give Thing A a real Thing B and do integration tests.  If you have a new team lead who's into TDD, you might get used to the idea of using interfaces.



Andropov said:


> Ooh it's been a while since I last touched Unity. I've been participating on a game jam every summer for the last 2 years, but other than that, I haven't done much lately.




I've not played with it a ton.  But moving from an entry-level iMac to a Mac Studio will hopefully be a different experience.  I'd still love to dive in there and play around some more.  I just wish they full development experience was available on a Mac.  But at this point, we can't preview things like Windows people.  Every change you want to test needs to be built and deployed to the headset.  Not the end of the world, but it would be great to skip the deployment every damn time.



Andropov said:


> I _do_ always complain about React Native. Tried it once for a couple days, and it was enough to keep me complaining for the next several years.




I don't care for React Native either.  But probably for different reasons.  I do this stuff as a hobby (I'm a web developer by day).  I have no real need to have my code work on both iOS and Android.  So there's no reason to pick a tool that can do both.  I did once (with Native Script), just to try it out.  I wasn't a fan.  I'm not looking to take the skills I already have and start writing mobile apps.  I'd rather learn to do that stuff the correct way, and for me that's going full native.  I'll admit those tools might make good business sense, but I'm not running a business.  So I have the luxury of ignoring that crap.

The other problem I had (at least with Native Script 3-4 years back) is that the bare minimum app looked like it was just running in a giant web control, and it probably was.  So extra work would have been needed just to have the out-of-the-box look and feel actually look and feel like an iOS app.  I found that super annoying.


----------



## Andropov

MEJHarrison said:


> That abstraction is super important in the testing world.  The tighter your code is coupled together, the harder it is to test.  With interfaces, you can just give it something that _resembles _the real thing, but is just a bunch of fake code.  So if you're testing Thing A, and Thing A depends on Thing B, by giving it a fake Thing B, it allows you to test Thing A in isolation.  Then you can give Thing A a real Thing B and do integration tests.  If you have a new team lead who's into TDD, you might get used to the idea of using interfaces.



I get why it's testable. But combined with an architecture as obnoxious as VIPER, having everything be a protocol meant adding ever more boilerplate. For reference, VIPER architecture looks like this (for every single view you want to add):




I don't know how to convey this without writing an absurdly long post. Quick version: yeah, having FooView, FooPresenter and FooInteractor conform to FooViewProtocol, FooPresenterProtocol and FooInteractorProtocol makes each one of them very easy to be tested. But does it make sense testing, for example, FooPresenter in isolation? In my opinion, no.

I found an example of a short app to demo testing a VIPER based project. Here you can see the implementation of one of the Presenters in the project, SearchTrainPresenter. You can see that basically all methods in the protocol are limited to one liners calling other layers of the architecture. That is, in my experience, the way most Presenters work when implementing VIPER in Swift. So how do the tests for the presenter look like? Well, here is their test file. Essentially, it only tests whether the presenter has called the other layers of the architecture (Interactor/View). This looks like pure madness to me. How could this test ever be meaningful? How could it be cost effective? Genuinely asking.

And, while I know it's anecdotal evidence: none of the places where I've seen VIPER used had tests. At all.



MEJHarrison said:


> One thing that's caught my attention lately is just testing the public interface.  As long as my code does the right thing and passes the tests, does it really matter how it arrives at those results internally?  So you only test behaviors available through the public API and don't test private methods.
> 
> Testing is a surprisingly complex topic and at times an art form.
> 
> I'm trying to be that guy on our team.  The problem is they just keep shoveling work at us so quickly, we don't have the time usually to slow down and do testing.



I think I might add tests to my app after I finish the feature I'm currently working on (couple months, I hope). I don't know if it will help me catch bugs before I sent the app to app review (I hope it does!) but it'll sure help me start decoupling some parts of the codebase that I want decoupled.

The app has been out long enough for me to break things between versions. Every time that happens, I try to think: Could I have avoided that with a test? How? So I already have a good idea of how do I want the tests to look.



MEJHarrison said:


> I'd rather learn to do that stuff the correct way, and for me that's going full native.  I'll admit those tools might make good business sense, but I'm not running a business.  So I have the luxury of ignoring that crap.
> 
> The other problem I had (at least with Native Script 3-4 years back) is that the bare minimum app looked like it was just running in a giant web control, and it probably was.  So extra work would have been needed just to have the out-of-the-box look and feel actually look and feel like an iOS app.  I found that super annoying.



Yeah, that seems like the experience I had with React Native. Out of the box it provided an acceptable Android app and a mediocre iOS app (not bad!). But polishing the app further required so much extra work that I'd rather write two fully native apps. But I'll admit part of that was because I just found React difficult


----------



## MEJHarrison

Andropov said:


> And, while I know it's anecdotal evidence: none of the places where I've seen VIPER used had tests. At all.




It's frustrating at my job because we all know we should be writing tests, but in practice, it just doesn't happen.



Andropov said:


> I think I might add tests to my app after I finish the feature I'm currently working on (couple months, I hope). I don't know if it will help me catch bugs before I sent the app to app review (I hope it does!) but it'll sure help me start decoupling some parts of the codebase that I want decoupled.
> 
> The app has been out long enough for me to break things between versions. Every time that happens, I try to think: Could I have avoided that with a test? How? So I already have a good idea of how do I want the tests to look.




My experience when I tried TDD is that while I think I went overboard by testing literally every line of code I possibly could, it absolutely made life quicker and easier as time went on.  Since I was also learning SwiftUI, I made quite a few bad design choices.  The tests let me step back, re-write large chunks of code, and still have confidence that everything was working as expected.  It was very freeing.  It led to more experimentation since between tests and source control, I could go wild while still having a safety net.  If I tried something and it was a disaster, I knew it instantly and could be back to stable code in seconds.

Testing is a lot like exercise.  Everyone knows they _*should*_ be doing it and that it will make them healthier and probably live longer.  Yet so many people have a hard time just doing it.


----------



## Nycturne

MEJHarrison said:


> Testing is a lot like exercise.  Everyone knows they _*should*_ be doing it and that it will make them healthier and probably live longer.  Yet so many people have a hard time just doing it.




And much like exercise, the longer you go without doing it, the harder it is to get back into the habit.


----------



## Citysnaps

DT said:


> I'm considering starting a more general development thread, everything outside of native iOS/Mac, different tech stacks, languages, frameworks, DB design, tools, maybe some light infrastructure/DEVOPS type talk, maybe actual project talk, like who/what/where are working on / past projects, etc.  I don't want to distract from this thread since it seems a lot of dev folks are focused here - and don't want it just to be me yammering on in an empty room




Hardware, too?  Or just software?


----------



## DT

citypix said:


> Hardware, too?  Or just software?




Firstly, I think the consensus was to just sort of broaden the scope of this thread a bit and include development topics outside the scope of the title.

Either way, here or a new thread, I'm sure everyone would appreciate some hardware talk, you know, relating to development (vs. like a "build thread" for Winders™ PeeCees ...)  Like Raspberry Pi running custom apps and sensors for a DIY garage door opener,  etc.


----------



## Nycturne

MEJHarrison said:


> One thing that's caught my attention lately is just testing the public interface. As long as my code does the right thing and passes the tests, does it really matter how it arrives at those results internally? So you only test behaviors available through the public API and don't test private methods.



Back on the testing topic, this caught my eye. I’ve always tested this way for two reasons:

1) Getting into private methods is always a right pain. 
2) Code coverage tools can be useful not just to show you what code you aren’t exercising, but also what code might be unreachable via public methods. And if it is unreachable, then perhaps you can cull it.

I finally got to cull around 1k LOC this evening after a big refactor to align my MVVM design closer to the ideal. Deleting code sometimes feels just as good as writing it. 

Also learned a few tricks for using Combine effectively to avoid data dependency snarls in some trickier portions of the code, and realizing that a Combine publisher or subscriber is a pretty good interface for testing. Not a bad evening.



MEJHarrison said:


> That abstraction is super important in the testing world. The tighter your code is coupled together, the harder it is to test. With interfaces, you can just give it something that _resembles _the real thing, but is just a bunch of fake code. So if you're testing Thing A, and Thing A depends on Thing B, by giving it a fake Thing B, it allows you to test Thing A in isolation. Then you can give Thing A a real Thing B and do integration tests. If you have a new team lead who's into TDD, you might get used to the idea of using interfaces.




Apple had a talk not long ago about Protocol Oriented Programming. Where you define the protocol first, and even use extensions of the protocol to share code between implementers rather than base classes. I like the idea because shared code that lives with the protocol is pretty easy to test when the interface is already clearly defined.   

I’ve tried to follow it, but I’ve not been super great at it. With this refactor I’m planning on making another attempt as a way to compose view models, as I identify targets for further refactoring and patterns emerging between the different view models. 



DT said:


> Firstly, I think the consensus was to just sort of broaden the scope of this thread a bit and include development topics outside the scope of the title.
> 
> Either way, here or a new thread, I'm sure everyone would appreciate some hardware talk, you know, relating to development (vs. like a "build thread" for Winders™ PeeCees ...)  Like Raspberry Pi running custom apps and sensors for a DIY garage door opener,  etc.




Could always combine the two and talk about Swift on the Raspberry Pi. 

And yes, I have a couple Swift on Linux projects, including a YAML-driven aquarium light controller that lives on a Pi.


----------



## Andropov

Nycturne said:


> Apple had a talk not long ago about Protocol Oriented Programming. Where you define the protocol first, and even use extensions of the protocol to share code between implementers rather than base classes. I like the idea because shared code that lives with the protocol is pretty easy to test when the interface is already clearly defined.
> 
> I’ve tried to follow it, but I’ve not been super great at it. With this refactor I’m planning on making another attempt as a way to compose view models, as I identify targets for further refactoring and patterns emerging between the different view models.



How long ago is _not long ago_? I've tried to search for it but I only found one from WWDC '15.


----------



## Nycturne

Andropov said:


> How long ago is _not long ago_? I've tried to search for it but I only found one from WWDC '15.



Further back than I thought apparently. Geez, the talk was 7 years ago?


----------



## DT

Nycturne said:


> This is a good description of my experience. I jumped into an existing team and project using React Native. But the point of having a mobile client was to access the platform APIs in a way that the browser couldn’t. So it was a patchwork of orphaned/outdated dependencies and native modules, growing tech debt, and not enough resources to pay off enough of it to reduce the burden it was imposing.
> 
> I’m not against React Native in principle. But I think it’s being sold as a “use this to build a native, multi-platform app” to some teams, and they aren’t reading the fine print and understanding the limitations/costs they would be taking on doing so, and treat it like something that it isn’t.




The dependency management was such a chore, and then - since I saw right up front RN out-of-the-box lacked support for a few things - I wound up using Expo, so I've got frameworks on top of abstraction layers, with hopefully some working native at some point. Good luck 

Seriously, I think it's probably a good fit with one or more of the following cases for your App:

Will be cross-platform
Doesn't need the highest possible performance
Doesn't require specific hardware / OS services
You have existing React and/or JS skill resources

It's also seems to be a good fit for rapid prototyping, and if you use Expo, you can push immediate updates to your team/client - or for that matter, if you're using only React Native /Expo, so you're never dealing with the native code (like for an internal app that doesn't need to go through the normal distribution/validation mechanisms).


----------



## Nycturne

Yup, this aligns a lot with my own thinking. The problem is that the team I joined built it explicitly to access specific hardware/OS services. It was originally written by a team of web developers, so I’m not surprised, but oof.

One thing has come of this thread: I’ve been pushing code coverage numbers up on my own project. Since I just did a big refactor to clean things up, it was ripe for a bunch of tests. Also used the opportunity to leverage more of the Combine tricks I’ve been picking up. Since the project is starting to sneak up on the 20k LOC mark, cleaning it up while fixing bugs seems like a good idea. Should probably be firing off the first beta build (iOS only sadly, Mac still has a couple key things it needs to catch up on in the UI department) for review here in the next few days.


----------



## Andropov

Nycturne said:


> Since the project is starting to sneak up on the 20k LOC mark, cleaning it up while fixing bugs seems like a good idea.



Mine is ~12k lines (8.5k lines of Swift and 1.8k lines of Metal) and it's already a pain to change or refactor some parts of it. I'll take that as my cue to spend some time cleaning code up 

Fun story: I worked at a place (for just five days, I quit after seeing the code) that 'managed' a project with 90k lines of code for an iPad app that was relatively small. Turns out, every line of code was copy-pasted tens to hundreds of times throughout the code, often inside switch statements that spanned thousands of lines of code. I didn't even believe it was posible to write that much code that bad. It was WILD.

Most of the bugs they wanted fixed were features that literally had disappeared because someone had implemented something in a switch, then copy-pasted it to all other switch cases throughout the codebase that looked similar, and inadvertently pasted over a different implementation, effectively removing the feature from the code.


----------



## Nycturne

Andropov said:


> Mine is ~12k lines (8.5k lines of Swift and 1.8k lines of Metal) and it's already a pain to change or refactor some parts of it. I'll take that as my cue to spend some time cleaning code up




Just pure Swift code, but the iOS app by itself without any dependencies that I have written is around 15k right now. Add in the macOS/tvOS specific code, and one library I’ve written that underpins the whole thing and you can add a couple more thousand LOC. 

But some of the complexity is in that I’ve done things like custom results builders to make components from UIKit feel more SwiftUI-like to use. So my custom UISplitViewController has a result builder that builds up the outline view on the sidebar, which is used to update the diffable data source under the hood. Seems to work pretty well, but it’s a good chunk of code to enable.

The trick I’m running into is dealing with a set of views that are all similar, but need to be defined differently, and currently depend on @AppStorage. So while the views themselves are getting well composed from shared components, the view models still share a good chunk of copy-paste. The common code between them amounts to something like 10 lines of code so maybe I’m over-optimizing, but at the same time, it means a lot of copy-pasted tests to ensure each place behaves correctly. 



Andropov said:


> Fun story: I worked at a place (for just five days, I quit after seeing the code) that 'managed' a project with 90k lines of code for an iPad app that was relatively small. Turns out, every line of code was copy-pasted tens to hundreds of times throughout the code, often inside switch statements that spanned thousands of lines of code. I didn't even believe it was posible to write that much code that bad. It was WILD.
> 
> Most of the bugs they wanted fixed were features that literally had disappeared because someone had implemented something in a switch, then copy-pasted it to all other switch cases throughout the codebase that looked similar, and inadvertently pasted over a different implementation, effectively removing the feature from the code.




All I can say is: “oof”.


----------



## Andropov

Nycturne said:


> The trick I’m running into is dealing with a set of views that are all similar, but need to be defined differently, and currently depend on @AppStorage. So while the views themselves are getting well composed from shared components, the view models still share a good chunk of copy-paste. The common code between them amounts to something like 10 lines of code so maybe I’m over-optimizing, but at the same time, it means a lot of copy-pasted tests to ensure each place behaves correctly.



I think I'm only using the @AppStorage wrapper for storing objects that are only part of the state of the view. Like to keep track of whether the sidebar was toggled on or off the last time the user used the app and things like that.

Btw, I think I get why Apple made the @FetchRequest wrapper for views instead of ViewModels, breaking the MVVM pattern (I think we talked about this at some point, right?). Turns out, since they all use the same NSManagedObjectContext (as it's injected to the environment) + the properties are always accessed from the main thread (as all Views execute in the main thread) + there's some under-the-hood magic to keep the results updated (so you don't try to retrieve properties from a NSManagedObject that no longer exists in the database), you basically avoid all likely causes of a CoreData crash unless you execute additional logic in the view. So for the typical, basic use case, you can forget about the many quirks of CoreData and avoid crashes as long as you only access that in the View.

If the wrapper were available in the ViewModels, even a single call to Task that later accessed the fetched results would result in the properties being accessed from a different thread/context leading to a crash. Or maybe receiving data from a server (which must use a background thread) and trying to update the CoreData objects from there. 

Even if you kept track of the context, you could try to retrieve properties from an object that no longer exists in the database because other thread has deleted the object from the context.

Basically, there is a lot of underlying complexity that is hidden under the @FetchRequest wrapper as long as you use its entities as a read-only and main thread only, which is something that Views obviously encourage (but ViewModels wouldn't). I must say I'm not a big fan of disguising complex topics as simple like this wrapper does by implicitly assuming things.


----------



## DT

Just some random thoughts-of-the-day 

I really dislike when there's too much syntactic fluff / sugar, especially if comes by way of a framework and/or libraries, that hide too much of the implementation details (when people ask about "Should I learn X" where X is a framework, I always say, "No, learn, Y first" where Y is the language itself).

I don't think you need nuts-and-bolts all the time, but one place where I really can't stand it is in a data access layer.  Some of modern executions are horrific in their inefficient data access.  I come from an old school DB type background ("_You kids get off my data model !_"), where the DB was carefully designed, performance and execution plan analysis was done,  JFC, I've seen developers - in particular people using Python + Django / Flask or Ruby + Rails, who rely on the ORM-type wrapper, implement some of the most god awful data access because it's happening under the hood and there's zero awareness.

I worked with a startup and outsourced some of the dev work, we were running on dev level Heroku instances for testing/soft launch, and the DB kept squawking about number of queries per X time, and I was a bit confused as the data access as I would've designed it, wouldn't be causing so much noise.  Well, I was able to finally watch some real time logging from the app as it made DB calls, and HFC, I've never seen such painfully poor data access logic (that of course, they never saw, because they were just using data access wrappers that hid the DB level implementation).


----------



## Nycturne

Andropov said:


> I think I'm only using the @AppStorage wrapper for storing objects that are only part of the state of the view. Like to keep track of whether the sidebar was toggled on or off the last time the user used the app and things like that.




I’m doing something similar. But consider the scenario where you have a number of views that display a collection of items, and each one can be sorted. That sort can and should be consistent across uses of the app. But not all collections support the same sorts. Maybe one collection only really makes sense with a single sort. 



Andropov said:


> If the wrapper were available in the ViewModels, even a single call to Task that later accessed the fetched results would result in the properties being accessed from a different thread/context leading to a crash. Or maybe receiving data from a server (which must use a background thread) and trying to update the CoreData objects from there.




Oh, I get the risks involved. But a chunk of it is that FetchRequest has also been a bit wonky and hard to debug once you get into the realm of trying to do sorting and search that I kinda gave up and dropped down to the next layer in the stack: NSFetchedResultsController. 

The upside is that I now have a composable, generic component that supports search and sorting, and is displayed by another generic component that can exist in all these views. So at the very least all these views that have similar structure are really just saying they compose/configure this generic component. But they also configure the type being fetched. Fun. 



DT said:


> I really dislike when there's too much syntactic fluff / sugar, especially if comes by way of a framework and/or libraries, that hide too much of the implementation details (when people ask about "Should I learn X" where X is a framework, I always say, "No, learn, Y first" where Y is the language itself).
> 
> I don't think you need nuts-and-bolts all the time, but one place where I really can't stand it is in a data access layer. Some of modern executions are horrific in their inefficient data access. I come from an old school DB type background ("_You kids get off my data model !_"), where the DB was carefully designed, performance and execution plan analysis was done, JFC, I've seen developers - in particular people using Python + Django / Flask or Ruby + Rails, who rely on the ORM-type wrapper, implement some of the most god awful data access because it's happening under the hood and there's zero awareness.




Maybe because I like digging in, I don’t mind if there is syntactic sugar so long as I can either get around it as needed, and I understand what it does. 

One thing I’ve been seeing while building this is Apple’s approach of building things that are well layered and let you operate at the level you actually need. When one layer isn’t sufficient, you can drop down as needed, trading off simplicity for control. So I’ve generally let myself use the sugar as the first step to get something working and stable, and then building the specific components I need at lower levels when the simpler approach isn’t enough. It’s not a bad way to develop when you are the only engineer on the project, IMO.

That said, I do think SwiftUI is a little too opaque at times. Their approach of “invalidate, rebuild, render” in three different passes isn’t well documented, and crashes occurring in the render pass aren’t fun to debug. “Oh, there’s an NSToolbar frame in the call stack, I guess this is toolbar related? Hmm, what change do I need to make to mine to stop it from crashing on me?”


----------



## Andropov

Nycturne said:


> Oh, I get the risks involved. But a chunk of it is that FetchRequest has also been a bit wonky and hard to debug once you get into the realm of trying to do sorting and search that I kinda gave up and dropped down to the next layer in the stack: NSFetchedResultsController.
> 
> The upside is that I now have a composable, generic component that supports search and sorting, and is displayed by another generic component that can exist in all these views. So at the very least all these views that have similar structure are really just saying they compose/configure this generic component. But they also configure the type being fetched. Fun.



Oh, I've seen those problems with FetchRequest in a project too. Sometimes it fired on its own and returned empty data for valid requests and the UI jumped around as a result. The solution the engineer that was working in the project at that time was to remove the FetchRequest from the view, fetch the entities from the ViewModel, and store them in a @Published array in the ViewModel for the view to use. While this solved the janky UI, some hard to debug crashes appeared as a result, since the arrays were not being updated to match the actual state of the context, or the objects were being accessed from the wrong thread. That's why I ended up having to spend most of the last week reading about CoreData  And that, ultimately, gave me the idea that Apple probably made the FetchRequest only available to views as a way to hid a lot of the complexity of CoreData under a nice easy wrapper for the basic use cases, even if it doesn't follow the MVVM architecture they suggest.



Nycturne said:


> Maybe because I like digging in, I don’t mind if there is syntactic sugar so long as I can either get around it as needed, and I understand what it does.
> 
> One thing I’ve been seeing while building this is Apple’s approach of building things that are well layered and let you operate at the level you actually need. When one layer isn’t sufficient, you can drop down as needed, trading off simplicity for control. So I’ve generally let myself use the sugar as the first step to get something working and stable, and then building the specific components I need at lower levels when the simpler approach isn’t enough. It’s not a bad way to develop when you are the only engineer on the project, IMO.
> 
> That said, I do think SwiftUI is a little too opaque at times. Their approach of “invalidate, rebuild, render” in three different passes isn’t well documented, and crashes occurring in the render pass aren’t fun to debug. “Oh, there’s an NSToolbar frame in the call stack, I guess this is toolbar related? Hmm, what change do I need to make to mine to stop it from crashing on me?”



I think the problem is that a lot of the new syntactic sugar doesn't clearly state what it does under the hood (not even in the docs), nor what assumption it's implicitly working with. That lowers the bar for the knowledge required to use some frameworks, but can be misguiding. And sometimes, specially while debugging, it's a bit of a nightmare since you may have no idea about what's happening underneath.


----------



## Cmaier

Andropov said:


> Oh, I've seen those problems with FetchRequest in a project too. Sometimes it fired on its own and returned empty data for valid requests and the UI jumped around as a result. The solution the engineer that was working in the project at that time was to remove the FetchRequest from the view, fetch the entities from the ViewModel, and store them in a @Published array in the ViewModel for the view to use. While this solved the janky UI, some hard to debug crashes appeared as a result, since the arrays were not being updated to match the actual state of the context, or the objects were being accessed from the wrong thread. That's why I ended up having to spend most of the last week reading about CoreData  And that, ultimately, gave me the idea that Apple probably made the FetchRequest only available to views as a way to hid a lot of the complexity of CoreData under a nice easy wrapper for the basic use cases, even if it doesn't follow the MVVM architecture they suggest.
> 
> 
> I think the problem is that a lot of the new syntactic sugar doesn't clearly state what it does under the hood (not even in the docs), nor what assumption it's implicitly working with. That lowers the bar for the knowledge required to use some frameworks, but can be misguiding. And sometimes, specially while debugging, it's a bit of a nightmare since you may have no idea about what's happening underneath.




I’ve mentioned before that a huge problem I am having with SwiftUI is the implicit behavior of stuff.  I much prefer spelling everything out.


----------



## Nycturne

Andropov said:


> Oh, I've seen those problems with FetchRequest in a project too. Sometimes it fired on its own and returned empty data for valid requests and the UI jumped around as a result. The solution the engineer that was working in the project at that time was to remove the FetchRequest from the view, fetch the entities from the ViewModel, and store them in a @Published array in the ViewModel for the view to use. While this solved the janky UI, some hard to debug crashes appeared as a result, since the arrays were not being updated to match the actual state of the context, or the objects were being accessed from the wrong thread. That's why I ended up having to spend most of the last week reading about CoreData  And that, ultimately, gave me the idea that Apple probably made the FetchRequest only available to views as a way to hid a lot of the complexity of CoreData under a nice easy wrapper for the basic use cases, even if it doesn't follow the MVVM architecture they suggest.




While I can understand the desire to make CoreData “simpler“, I don’t think it buys a whole lot with something this complex. CoreData is one of Apple’s most complex frameworks that isn’t a UI framework, and there’s a lot going on. It’s also a framework where there’s a lot of ways to do something because of the nature of CoreData, but so many of them aren’t a good way to do it, as that previous engineer discovered. 

Two things I’ve learned through my CoreData trip which might be relevant:
- NSArray Swift bridging can break the custom _PFArray class Apple uses to keep fetched results performant when they are potentially unbounded and large. 
- ForEach doesn’t like RandomAccessCollection classes, and will wander out of bounds if items are removed from the collection and triggers an update. 

So my FetchedResultsController delegate has to preserve the NSArray nature of the results, but also wrap it in a struct so that ForEach doesn’t throw a fit. Fun. 



Andropov said:


> I think the problem is that a lot of the new syntactic sugar doesn't clearly state what it does under the hood (not even in the docs), nor what assumption it's implicitly working with. That lowers the bar for the knowledge required to use some frameworks, but can be misguiding. And sometimes, specially while debugging, it's a bit of a nightmare since you may have no idea about what's happening underneath.




The lack of docs is the real killer. The AppKit/UIKit way is documented enough that you can get a good feel for what’s going on between it and experimentation. I feel like SwiftUI lacks any description of the operating model that the other UI frameworks provide to developers. 



Cmaier said:


> I’ve mentioned before that a huge problem I am having with SwiftUI is the implicit behavior of stuff.  I much prefer spelling everything out.




How much of that is the fact that SwiftUI is using Swift itself as a descriptive language for the UI, compared to something more purpose-built like JSX? Honestly, it feels almost like a bit of a learning trap to have your view description and your view bindings all be described in the same place. Makes it easier to get business logic all mixed into it as well. Nothing like a storyboard or nib.  

Of course, switching over to a reactive paradigm doesn’t help existing devs much. I think I’m finally starting to settle in and really appreciate how it works. And this is after spending the pandemic in a React Native app…


----------



## Andropov

Cmaier said:


> I’ve mentioned before that a huge problem I am having with SwiftUI is the implicit behavior of stuff.  I much prefer spelling everything out.



I agree, but it's not only limited to SwiftUI. Swift concurrency comes to mind... I'd argue that undocumented implicit behaviours matter less in UI frameworks than in other frameworks.



Nycturne said:


> While I can understand the desire to make CoreData “simpler“, I don’t think it buys a whole lot with something this complex. CoreData is one of Apple’s most complex frameworks that isn’t a UI framework, and there’s a lot going on. It’s also a framework where there’s a lot of ways to do something because of the nature of CoreData, but so many of them aren’t a good way to do it, as that previous engineer discovered.
> 
> Two things I’ve learned through my CoreData trip which might be relevant:
> - NSArray Swift bridging can break the custom _PFArray class Apple uses to keep fetched results performant when they are potentially unbounded and large.
> - ForEach doesn’t like RandomAccessCollection classes, and will wander out of bounds if items are removed from the collection and triggers an update.
> 
> So my FetchedResultsController delegate has to preserve the NSArray nature of the results, but also wrap it in a struct so that ForEach doesn’t throw a fit. Fun.



Oh, looks like I'm in for a world of pain.



Nycturne said:


> How much of that is the fact that SwiftUI is using Swift itself as a descriptive language for the UI, compared to something more purpose-built like JSX? Honestly, it feels almost like a bit of a learning trap to have your view description and your view bindings all be described in the same place. Makes it easier to get business logic all mixed into it as well. Nothing like a storyboard or nib.



XIBs had other (worse) problems though. The generated XML code was effectively unreadable, so you couldn't do proper version control and pull request reviewing with that. Now the UI can be reviewed on pull requests too, and it's much more immediately clear what's wrong with the code in case of a bug. Some of the worst silly bugs I've seen were caused by having two sources of truth for view layouts (XIB + code in the ViewController), where the XIB had some forgotten checkbox checked somewhere and was interfering with whatever the ViewController was doing.

Also, merge conflicts, which used to be a huge pain point with XIBs, are now trivially solved with SwiftUI views in most cases.

And, personally, I like having views simply react to changes in state, without having to keep track of how/when/why state changed.


----------



## Nycturne

Andropov said:


> I agree, but it's not only limited to SwiftUI. Swift concurrency comes to mind... I'd argue that undocumented implicit behaviours matter less in UI frameworks than in other frameworks.




Hmm, I’ve had less trouble with concurrency, other than where it is simply not supported or has unhelpful adapters (a byte iterator for a HTTP request? Really?). 



Andropov said:


> Oh, looks like I'm in for a world of pain.




This blog post from a former Apple engineer helped me refine my own implementation: https://davedelong.com/blog/2021/04/03/core-data-and-swiftui/



Andropov said:


> XIBs had other (worse) problems though. The generated XML code was effectively unreadable, so you couldn't do proper version control and pull request reviewing with that. Now the UI can be reviewed on pull requests too, and it's much more immediately clear what's wrong with the code in case of a bug. Some of the worst silly bugs I've seen were caused by having two sources of truth for view layouts (XIB + code in the ViewController), where the XIB had some forgotten checkbox checked somewhere and was interfering with whatever the ViewController was doing.
> 
> Also, merge conflicts, which used to be a huge pain point with XIBs, are now trivially solved with SwiftUI views in most cases.




Oh yeah, very familar with XIBs and the workarounds we had to do in a _very large_ project that used them. Code reviews with before/after screenshots, warning the team when we were touching them, etc. But I also remember when it was the nib that was checked in. I more bring it up since XIBs provide cleaner separation of view _description_ and _logic_.

I do think the benefits outweigh the downsides here, but I can also pontificate a little on what could have been done better. JSX I think does provide a clearer delineation between the description and the logic, and is used in another reactive programming UI framework.



Andropov said:


> And, personally, I like having views simply react to changes in state, without having to keep track of how/when/why state changed.




As I said in my previous post, I am starting to appreciate reactive programming. But it does take a mental model adjustment coming from MVC land.


----------



## Andropov

Nycturne said:


> Hmm, I’ve had less trouble with concurrency, other than where it is simply not supported or has unhelpful adapters (a byte iterator for a HTTP request? Really?).



Maybe it's just that I'm not always entirely sure about how some of the async/await code I'm writing is scheduled later, in which thread I should expect it to run, those kind of things. I understand GCD better, I have a clearer picture of what's happening underneath.



Nycturne said:


> This blog post from a former Apple engineer helped me refine my own implementation: https://davedelong.com/blog/2021/04/03/core-data-and-swiftui/



Oh god that's going to be extremely helpful. Thanks! I can't believe I didn't stumble upon that post myself earlier. Very interesting read. It's not all that different to how the code we have is implemented. I though most of  the problem was due to context issues, but after reading that post I'm starting to think that the actual problem is that some NSManagedObjects are declared with non-optional properties (like here). But after reading the article *I think* we got the context related code right, so the only other possible culprit is the fake non-optionals.

Interesting that he made a property wrapper for the Views too, after saying that he didn't like mixing UI code and CoreData too.



Nycturne said:


> Oh yeah, very familar with XIBs and the workarounds we had to do in a _very large_ project that used them. Code reviews with before/after screenshots, warning the team when we were touching them, etc. But I also remember when it was the nib that was checked in. I more bring it up since XIBs provide cleaner separation of view _description_ and _logic_.
> 
> I do think the benefits outweigh the downsides here, but I can also pontificate a little on what could have been done better. JSX I think does provide a clearer delineation between the description and the logic, and is used in another reactive programming UI framework.



Sure, I just meant that I think it's been a step in the right direction. And I believe most companies now have moved from XIBs/Storyboards to writing UIs in code (using frameworks like SnapKit or even totally by hand) where the logic is arguably even more coupled to the UI since they're part of the same class. SwiftUI is more ergonomic for that too.

I don't know enough about JSX to have an opinion on how it compares against that.


----------



## Nycturne

Andropov said:


> Maybe it's just that I'm not always entirely sure about how some of the async/await code I'm writing is scheduled later, in which thread I should expect it to run, those kind of things. I understand GCD better, I have a clearer picture of what's happening underneath.




And the answer is: it depends. Because you have the single concurrent pool, there’s no answer as to what thread things happen on. It’s either the current thread, or some other thread. And when that happens depends on the true suspension points that exist, rather than the potential ones.

I have noticed that it will avoid trying to jump threads until it really has to. So it is possible to run an async function on the main thread and hang it.



Andropov said:


> Oh god that's going to be extremely helpful. Thanks! I can't believe I didn't stumble upon that post myself earlier. Very interesting read. It's not all that different to how the code we have is implemented. I though most of  the problem was due to context issues, but after reading that post I'm starting to think that the actual problem is that some NSManagedObjects are declared with non-optional properties (like here). But after reading the article *I think* we got the context related code right, so the only other possible culprit is the fake non-optionals.




Yeah, this is something where I kinda wish CoreData emitted implicitly unwrapped optionals for non-optional fields, as it’s the best description of a non-optional CoreData property (IMO). And when there is a problem with a property turning out to be nil, at least you get a more sensible crash event you can debug versus just asserting it will never be nil.

I still use auto-generated property accessors so I have some ugly unwrapping logic to handle the auto-generated optionals, but I think I might move away from that in the near future now that the object graph is getting to be stable and start using implicitly unwrapped optionals for the core properties.



Andropov said:


> Interesting that he made a property wrapper for the Views too, after saying that he didn't like mixing UI code and CoreData too.




Yeah, I think it was a bit odd. I just created something that can function as a stand-alone view model, but is easier to hold onto references for, compose, etc.

But there were some real nuggets of useful information that helped me understand good practices that really kept performance up.



Andropov said:


> Sure, I just meant that I think it's been a step in the right direction. And I believe most companies now have moved from XIBs/Storyboards to writing UIs in code (using frameworks like SnapKit or even totally by hand) where the logic is arguably even more coupled to the UI since they're part of the same class. SwiftUI is more ergonomic for that too.
> 
> I don't know enough about JSX to have an opinion on how it compares against that.




I agree that it’s an improvement, yes.


----------



## Andropov

Nycturne said:


> Yeah, this is something where I kinda wish CoreData emitted implicitly unwrapped optionals for non-optional fields, as it’s the best description of a non-optional CoreData property (IMO). And when there is a problem with a property turning out to be nil, at least you get a more sensible crash event you can debug versus just asserting it will never be nil.
> 
> I still use auto-generated property accessors so I have some ugly unwrapping logic to handle the auto-generated optionals, but I think I might move away from that in the near future now that the object graph is getting to be stable and start using implicitly unwrapped optionals for the core properties.



I have to disagree on this one. Too many scenarios where a non-optional CoreData NSManagedObject can return nil. Firing a fault in a NSManagedObject that has been removed from the context (but to which you still hold a reference) returns nil for its properties, for example. Regular optionals are the way to go IMO, unless you're not usually removing objects from the database that are also used by the UI. And even then... I think I prefer dealing with cumbersome logic to handle many optional properties than have unexpected crashes that are hard to trace.

BTW (unrelated), I read this article from the Asahi Linux project recently, it was very interesting.


----------



## Nycturne

Andropov said:


> I have to disagree on this one. Too many scenarios where a non-optional CoreData NSManagedObject can return nil. Firing a fault in a NSManagedObject that has been removed from the context (but to which you still hold a reference) returns nil for its properties, for example. Regular optionals are the way to go IMO, unless you're not usually removing objects from the database that are also used by the UI. And even then... I think I prefer dealing with cumbersome logic to handle many optional properties than have unexpected crashes that are hard to trace.




You are probably right. I saw the pattern in the view models of handling this translation, and was wondering how to address it. That said, it may actually be the right pattern, especially since it’s not difficult to observe managed object properties with Combine and chain them to the relevant view model properties. 

Considering I did just get a crash report of an out-of-bounds issue that can only really happen when a child object disappears from the parent, this is likely related to the scenario you are describing. 



Andropov said:


> BTW (unrelated), I read this article from the Asahi Linux project recently, it was very interesting.



Interesting that it’s the driver that’s responsible for managing the load/unload behavior of state that the tiles need. Makes sense in hindsight.


----------



## Andropov

Nycturne said:


> And the answer is: it depends. Because you have the single concurrent pool, there’s no answer as to what thread things happen on. It’s either the current thread, or some other thread. And when that happens depends on the true suspension points that exist, rather than the potential ones.
> 
> I have noticed that it will avoid trying to jump threads until it really has to. So it is possible to run an async function on the main thread and hang it.



I couldn't think of any good examples back when I posted that, but now I've thought of a couple. For example, if you call a async network function from a @MainActor context, are all parts of the network call executed on main thread, just yielding and hopping around when results come?

And —and this seems like a good one, since it breaks Swift playgrounds— what happens if you call async let (which is meant to spawn tasks concurrently) from an actor isolated method? 

I never had so many questions about weird corner cases with GCD. Though I've heard the idea is to slowly make async and concurrent code "safe" at compile time, so maybe it'll all be worth it in the end.


----------



## Nycturne

Andropov said:


> I couldn't think of any good examples back when I posted that, but now I've thought of a couple. For example, if you call a async network function from a @MainActor context, are all parts of the network call executed on main thread, just yielding and hopping around when results come?




Yeah, Swift doesn’t explicitly specify the behavior of non-isolated async calls when called from an actor’s isolated context. MainActor is a bit interesting since it’s the only actor with an executor that is bound to a specific thread as well. There is a SE proposal to explicitly define the behavior rather than leave it as an internal implementation detail, though.

In cases where you are calling into system APIs like URLSession, then you are actually awaiting a continuation which is resumed by a callback happening outside of Swift concurrency. So the rules for concurrency don’t even really apply as the system library has “escaped” the Task and is doing work in other ways, and will update the task later.

But to get to the core of the question itself, it does depend on when the thread suspends and if it’s in an isolated context when it does (and odds are it won’t be). The current behavior is outlined in the motiviation section of the evolution proposal.



Andropov said:


> And —and this seems like a good one, since it breaks Swift playgrounds— what happens if you call async let (which is meant to spawn tasks concurrently) from an actor isolated method?




Since “async let“ is short-hand for creating a Task, you can use Task as an analogue for exploring this behavior. The results are interesting as there’s inference involved:

- If the child task doesn’t require actor isolation, you’re fine, it’s run on the global executor in parallel.
- If the child task does require actor isolation because it accesses an isolated property, it will wind up waiting for the actor executor to become free at the next suspension point before it executes.


----------



## Nycturne

So it looks like Local Network Permissions might be the ugly bit that my app will have to figure out how to deal with. Apple doesn’t provide a way to query for the permissions in advance, and will instead just fail URLRequests with ”no connectivity”. To check for permissions, you have to use either Bonjour (which the service I’m talking to doesn’t advertise itself on, so it’s not a great approach), or attempt a lower level connection using NWConnection to something local.

So I have testers that are having networking issues that look related to this, but the app currently has no way to differentiate between these failure modes to say “hey, this thing needs network access”, and I’m learning the hard way that having a “send logs” button in error dialogs doesn’t seem to actually encourage them to tap on it. 

Fun.


----------



## Andropov

Nycturne said:


> Since “async let“ is short-hand for creating a Task, you can use Task as an analogue for exploring this behavior. The results are interesting as there’s inference involved:
> 
> - If the child task doesn’t require actor isolation, you’re fine, it’s run on the global executor in parallel.
> - If the child task does require actor isolation because it accesses an isolated property, it will wind up waiting for the actor executor to become free at the next suspension point before it executes.



Oh that makes sense. I'm not sure how can it infer whether actor isolation is required or on in a Task spawned from a MainActor context though, since some things are only implicitly requiring to run on that thread (but don't enforce it, or only enforce it via crash at runtime, not at compile time).



Nycturne said:


> So it looks like Local Network Permissions might be the ugly bit that my app will have to figure out how to deal with. Apple doesn’t provide a way to query for the permissions in advance, and will instead just fail URLRequests with ”no connectivity”. To check for permissions, you have to use either Bonjour (which the service I’m talking to doesn’t advertise itself on, so it’s not a great approach), or attempt a lower level connection using NWConnection to something local.



That's... terrible. I hope Apple adds a way to check for those permissions on iOS 16. There shouldn't be any API that requires special permissions but offer no way to check if you have them.



Nycturne said:


> and I’m learning the hard way that having a “send logs” button in error dialogs doesn’t seem to actually encourage them to tap on it.


----------



## Nycturne

Andropov said:


> Oh that makes sense. I'm not sure how can it infer whether actor isolation is required or on in a Task spawned from a MainActor context though, since some things are only implicitly requiring to run on that thread (but don't enforce it, or only enforce it via crash at runtime, not at compile time).




It might help to read the Global Actors proposal here. While an actor _type_ has both object and actor intrinsicly linked, global actors are not linked to anything without being explicitly marked as having affinity with the global actor. This applies both to the built-in MainActor and any other global actor I create. So when I annotate properties, closures or classes with @MainActor, it will bring isolation to that global actor to the annotated bit of code. For example:



		Swift:
	

@MainActor MyViewController: UIViewController {
    /* ... */
}


This gives MyViewController affinity to the MainActor and will isolate properties to that actor. But without this annotation, code does not have any affinity to the MainActor. Just because something starts on the main thread doesn‘t mean it’s been isolated to the MainActor, and it’s generally safe to assume it isn’t isolated to any global actor if you haven’t annotated it to make it isolated.

But getting back to another bit of code example:



		Swift:
	

extension MyViewController {
    // This function picks up isolation from MyViewController’s @MainActor annotation.
    func doesAThing() async {
        self.someProperty = true
        let task = Task {
            // No references to isolated properties infers nonisolated task
            return callSomeNonisolatedFunction()
        }
        let isolatedTask = Task {
            // Reference to isolated self infers isolated task.
            // In this case, because self is MainActor isolated, this should
            // also be isolated to MainActor.
            self.someCounter += 1
        }
        // await the tasks here.
        self.someProperty = false
    }
}


What I noticed in playgrounds is that the first Task in the example will just run as early as possible, suggesting concurrent execution. While the second task waits for the isolated function to suspend so it can have access to the actor. I tested this by using print statements and was able to see how behavior become rigidly deterministic when the Task picked up the isolation implicitly via referencing an isolated property. But if the actor’s isolation isn’t required for the Task, it seems to not get it and will run concurrently, making it less deterministic on exactly when it will run as it runs on the global executor’s thread pool.



Andropov said:


> That's... terrible. I hope Apple adds a way to check for those permissions on iOS 16. There shouldn't be any API that requires special permissions but offer no way to check if you have them.




My workaround right now is to perform a dummy NWConnection to the server and check if I can reach the server, or if it returns that the connection is blocked or rejected, and closing the connection if it is accepted without doing anything. Since odds are that dozens or hundreds of requests will follow, the extra latency doing this isn’t a huge problem, but it is silly. 

But since it’s been this way since iOS 14, I‘m not holding out much hope. This has been a recurring ask to Apple since it was introduced.


----------



## Nycturne

Got my CarPlay entitlement yesterday. Nice. Played around with the simulator some more. Not nice. The simulator's Now Playing screen is basically useless for testing to see if you've hooked things up right. You are better off buying an aftermarket CarPlay display and using that as a test harness in your work office. Time to go shopping I think…

That framework is rather interesting. It acts a little like SwiftUI in how you build the UI via templates, but without all the necessary state management, so you get to build that yourself.


----------



## Andropov

Nycturne said:


> What I noticed in playgrounds is that the first Task in the example will just run as early as possible, suggesting concurrent execution. While the second task waits for the isolated function to suspend so it can have access to the actor. I tested this by using print statements and was able to see how behavior become rigidly deterministic when the Task picked up the isolation implicitly via referencing an isolated property. But if the actor’s isolation isn’t required for the Task, it seems to not get it and will run concurrently, making it less deterministic on exactly when it will run as it runs on the global executor’s thread pool.



If you start the first Task with Task(qos: .background) and the second Task with Task(qos: .userInitiated), the second task will actually start first. And spraying some print(Thread.isMainThread) around the code seems to indicate that everything is running in the main thread at all times. If I understood the proposal, this shouldn't be guaranteed to happen, right? The first task may execute somewhere else, since it's not isolated.



Nycturne said:


> You are better off buying an aftermarket CarPlay display and using that as a test harness in your work office.



I remember Overcast's dev on Twitter saying exactly that.


----------



## Nycturne

Andropov said:


> If you start the first Task with Task(qos: .background) and the second Task with Task(qos: .userInitiated), the second task will actually start first. And spraying some print(Thread.isMainThread) around the code seems to indicate that everything is running in the main thread at all times. If I understood the proposal, this shouldn't be guaranteed to happen, right? The first task may execute somewhere else, since it's not isolated.




I read a little more on this, and it looks like MainActor itself doesn’t always behave expectedly, and that Xcode 13 updates have included fixes here. Oof, that’s not good. And in my case, I was testing with an actor, rather than a global actor like MainActor. Double oof. That said, my Playground was able to demonstrate concurrent code executing not only non-deterministically, but also at a point where the caller was not suspended, but it’s likely my example should really be an actor example, not MainActor.

I will generally warn though that what I do know is that Swift attempts to be lazy about thread hops, and the compiler does seem to try to optimize context switches where it can, so we should try to ensure any tests we perform aren’t so simple that they can’t simply be inlined away.

I’ll take your point though. There’s an awful lot of things interacting here, and it’s not documented well enough to know exactly how it will behave without discussing a particular, very specific, example. It doesn’t help that the compiler is now a member of the party and trying to make sense of the dependencies on our behalf to make smarter decisions on what sort of code to emit. 



Andropov said:


> I remember Overcast's dev on Twitter saying exactly that.




I assume you might be referring to this tweet? https://www.twitter.com/i/web/status/1433156056083992576/

Yeah, that’s pretty much what I’m doing now, just a different unit.


----------



## Andropov

Nycturne said:


> I will generally warn though that what I do know is that Swift attempts to be lazy about thread hops, and the compiler does seem to try to optimize context switches where it can, so we should try to ensure any tests we perform aren’t so simple that they can’t simply be inlined away.



That's an interesting point. I wonder if the compiler will simply inline very short tasks. My guess is that it won't, and that scheduling is always done dynamically if you manually call Task (even if it's executed serially after that). On the other hand, if the task is something like a one-liner variable change, the compiler should have enough information to know that it's better to just inline it. Hmm.



Nycturne said:


> I assume you might be referring to this tweet?
> 
> Yeah, that’s pretty much what I’m doing now, just a different unit.



I can't remember if that was the tweet, but definitely something he tweeted around that time.


----------



## Nycturne

Andropov said:


> That's an interesting point. I wonder if the compiler will simply inline very short tasks. My guess is that it won't, and that scheduling is always done dynamically if you manually call Task (even if it's executed serially after that). On the other hand, if the task is something like a one-liner variable change, the compiler should have enough information to know that it's better to just inline it. Hmm.




This is what I get for testing one thing and assuming it is the other. So it looks like global actors are more aggressive than non-global actors, and so all child tasks of a global actor will also require that global actor implicitly, unless you detach the task. Yet my tests show that non-global actors will pick up on isolation based on if isolated properties are accessed or not.  This kinda makes sense… a global actor doesn’t have any particular state to isolate, so it’s the task itself that is isolated.

I’ve attached some code you can run in a playground that demonstrates what I mean. For both the global actor and main actor, the child task cannot run until the current task suspends, which is when Task.sleep() is called. Since this is a true suspension point, you will see doAThing() continue to execute until it reaches that suspension point, which is where the child task gets a chance to run.  For the non-global actor, the child task runs in parallel, and multiple runs will show different ordering of the print statements. But since you can see that in many cases, the ordering would make no sense if the code was running within the actor. But the moment you uncomment the “self.isActive = true” within the child task, it behaves the same as the global and main actors.

As for checking threads, I don’t do a ton of that unless there’s a specific issue I’m trying to track down, as Swift’s scheduler is inherently lazy. You’ll tend to stay on a thread until a suspension point is reached, but when you awake from suspend what thread you awaken on will depend on a few factors, such as if the function itself is isolated or not, and if it is isolated, is it a global actor or not. So from my experience it’s generally better to consider what actor you are on.

But as you‘ve shown me here, the implicit nature of child tasks taking on global actor isolation of the parent, and the fact that many SwiftUI and UIKit types are annotated with @MainActor means you can find yourself isolated on the MainActor without knowing it. That’s not a super-great for someone new to this, even if it makes some level of sense after thinking through it.

So ultimately, if you want to run something that needs to detach from the global actor it is a part of and run in a non-isolated context (and UI code will generally be isolated to the main actor), then you must use Task.detached manually. But this generally should be the case where you might do a lot of work before a suspend point can pass the global actor off to other work.

EDIT: Just want to add that I have really appreciated the back and forth here. It’s really helped me solidify my understanding around Swift concurrency and how to use it better. 



		Code:
	

import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true

actor MyActor {
    private var isActive: Bool = false
   
    func doAThing() async {
        guard !isActive else { return }
        isActive = true
        let task = Task { () -> Int in
            print("Starting Sub Task")
            // Uncomment the below to force actor isolation on this.
            //self.isActive = true
            return await SomeStruct.doSomething(2)
        }
        print("Herf")
        let someResult = await SomeStruct.doSomething(1)
        print("Derf")
        print(someResult)
        isActive = false
        print("Waiting...")
        print(await task.result)
    }
}

struct SomeStruct {
    static func doSomething(_ value: Int) async -> Int {
        print("hello \(value)")
        try! await Task.sleep(nanoseconds: 10000)
        return value
    }
}

@MainActor class MyMainActorClass {
    private var isActive: Bool = false
   
    func doAThing() async {
        guard !isActive else { return }
        isActive = true
        let task = Task { () -> Int in
            print("Starting Sub Task")
            //self.isActive = true
            return await SomeStruct.doSomething(2)
        }
        print("Herf")
        let someResult = await SomeStruct.doSomething(1)
        print("Derf")
        print(someResult)
        isActive = false
        print("Waiting...")
        print(await task.result)
    }
}

@globalActor
struct MyGlobalActor {
    actor ActorType { }
    static let shared: ActorType = ActorType()
}

@MyGlobalActor class MyGlobalActorClass {
    private var isActive: Bool = false
   
    func doAThing() async {
        guard !isActive else { return }
        isActive = true
        let task = Task { () -> Int in
            print("Starting Sub Task")
            //self.isActive = true
            return await SomeStruct.doSomething(2)
        }
        print("Herf")
        let someResult = await SomeStruct.doSomething(1)
        print("Derf")
        print(someResult)
        isActive = false
        print("Waiting...")
        print(await task.result)
    }
}

enum RunMode {
    case actor
    case mainActor
    case globalActor
}

let runMode: RunMode = .actor
if runMode == .actor {
    let actor = MyActor()
    Task { @MainActor in
        await actor.doAThing()
        print("Actor Task complete")
        PlaygroundPage.current.finishExecution()
    }
    print("Actor Task created")
} else if runMode == .globalActor {
    Task { @MyGlobalActor in
        let actorClass = MyGlobalActorClass()
        await actorClass.doAThing()
        print("GlobalActor Task Complete")
        PlaygroundPage.current.finishExecution()
    }
    print("GlobalActor Task Created")
} else {
    Task { @MainActor in
        let mainActorClass = MyMainActorClass()
        await mainActorClass.doAThing()
        print("MainActor Task Complete")
        PlaygroundPage.current.finishExecution()
    }
    print("MainActor Task Created")
}


----------



## Andropov

Nycturne said:


> This is what I get for testing one thing and assuming it is the other. So it looks like global actors are more aggressive than non-global actors, and so all child tasks of a global actor will also require that global actor implicitly, unless you detach the task. Yet my tests show that non-global actors will pick up on isolation based on if isolated properties are accessed or not.  This kinda makes sense… a global actor doesn’t have any particular state to isolate, so it’s the task itself that is isolated.
> 
> I’ve attached some code you can run in a playground that demonstrates what I mean. For both the global actor and main actor, the child task cannot run until the current task suspends, which is when Task.sleep() is called. Since this is a true suspension point, you will see doAThing() continue to execute until it reaches that suspension point, which is where the child task gets a chance to run.  For the non-global actor, the child task runs in parallel, and multiple runs will show different ordering of the print statements. But since you can see that in many cases, the ordering would make no sense if the code was running within the actor. But the moment you uncomment the “self.isActive = true” within the child task, it behaves the same as the global and main actors.



Oh that makes a lot of sense. I wasn't very aware of the differences between global and non-global actors, so that was very informative. Still think it's going to take some years and a lot of code to be ported from GCD and completion handler based APIs to async/await to fully see the benefits of all this though. Fortunately, it looks like we'll hear more of this since there are a bunch of new talks this WWDC planned for async/await and actors, I'm excited to see what's new.



Nycturne said:


> EDIT: Just want to add that I have really appreciated the back and forth here. It’s really helped me solidify my understanding around Swift concurrency and how to use it better.



Same! I learned a lot, writing about it really helps.


----------



## Nycturne

Andropov said:


> Oh that makes a lot of sense. I wasn't very aware of the differences between global and non-global actors, so that was very informative. Still think it's going to take some years and a lot of code to be ported from GCD and completion handler based APIs to async/await to fully see the benefits of all this though. Fortunately, it looks like we'll hear more of this since there are a bunch of new talks this WWDC planned for async/await and actors, I'm excited to see what's new.




Yeah, I just wish there was a little more flexibility bridging between synchronous and asynchronous contexts. For example, menus (and context menus) in SwiftUI only update when becoming visible, meaning if you have to check an asynchronous API to see if something should be present, or enabled, then it will tend to happen too late. I still have to do more testing, but I’m not even sure .task() can fetch state quickly enough for menus. And annotating @MainThread will force synchronous contexts to spin up a Task to call it, even from the main thread, ensuring that the call will not actually happen until the next iteration of the run loop. So that’s great.

I’ve got code interacting with CoreData that is growing increasingly reliant on concurrency to handle thread transitions. But because of how UI operates, and still isn’t really aware of concurrency, I have to follow a weird pattern. If it’s Core Data, and synchronous, assume it takes view context objects, and operates on the view context. If it’s asynchronous, it is safe to call with any context’s objects and it will either fetch the appropriate object for its internal context (editing/sync) or invoke into the object’s context to query details from it. But just to keep things clean, objects from other contexts than the view context don’t get passed around, so the only thing that should be flying around are the objects from the view context.


----------



## Nycturne

Some interesting stuff in SwiftUI with macOS 13 and iOS 16:

- NavigationView is dead. Long live NavigationStack.
- On this topic, it looks a bit like NavigationStack might actually provide a navigation stack for macOS outside of catalyst? Need to play with it once I can install the beta.
- NavigationPath makes it easier to serialize navigation state to do things like handle iPhone/iPad size class switching, handoff, and more. Heck, the entire navigation model is a lot more sensible, and you can create a navigation router for a stack that is surprisingly VIPER like. Still needs to be embedded in a view, but you could easily create a view modifier or wrapping view that defines your routes and compartmentalize that way. Overall, navigation looks so much better now than it did last year. Sidebars might be less jank under this approach.
- Context menus can provide preview views now, just to get back to my opening post in this thread.
- Sheets now support detents, finally. Can even specify custom detents.
- Custom grid layouts will make a couple things I’ve banged my head against easier (trying to multiple VStacks properly).
- ViewThatFits to pick between different layouts based on what fits the space available. Useful for portrait vs landscape on iPad, and possibly iPhone vs iPad/Mac/AppleTV.
- A full layout engine you can seemingly take control over that is declarative.
- You can now specify background tasks without dropping to UIKit, so background refresh, network tasks and the like can be done fully in SwiftUI.
- Custom windows can now be created for specific needs, such as supplementary windows (say a diagnostics tool, or a mini player window, camera window, etc).
- There’s some new ways to declare toolbars that looks like it feeds the new iPad toolbars, but should also help with Mac toolbars.
- PhotosPicker is now available without dropping to UIKit (just after I wrote a wrapper…). Somehow it seems like this is supported on macOS 13 too? Huh?
- Some Apple Pay and Wallet integration
- Menus can now have their order defined by the developer, no more being stuck with “first closest to button” ordering when UIKit doesn’t do this.
- Rename action/button. huh.
- A couple new gesture modifiers that give you access to the gesture location, including “continuous hover”.
- ImageRenderer, a way to generate images from SwiftUI views.

And as an aside, the new AppIntents framework looks a lot like they have been watching the SwiftUI team. A declarative intents API that doesn’t rely on those external model files. Seeing @resultsBuilder trickle out into other APIs as well.


----------



## Andropov

I didn't really understand NavigationPath just by having a quick look at the docs. I'd rather wait until the videos about it drop. I think a lot of people were waiting for this. I didn't hear about any changes on the Scenes API though, I hope that has changes too.

Interestingly, it looks like Apple encourages SwiftUI over AppKit rather than SwiftUI over Catalyst/UIKit as the default for Mac apps now. Hmmm. Maybe I'll make the switch.


----------



## Nycturne

Andropov said:


> I didn't really understand NavigationPath just by having a quick look at the docs. I'd rather wait until the videos about it drop. I think a lot of people were waiting for this. I didn't hear about any changes on the Scenes API though, I hope that has changes too.




NavigationPath is an odd duck in SwiftUI. It’s just a container of state, and I think one of the few concrete state containers in SwiftUI. But it handles the fact that your navigation stack‘s steps aren’t going to be all the same type. So when using NavigationLink(label:value:), which is the new recommendation now that NavigationView is deprecated, the NavigationPath will have the value pushed onto its internal stack, and account for the fact the stack values might look like: enum > struct > NSManagedObject

I’m not seeing anything on scenes, unfortunately. There’s a couple annoyances with scenes on macOS especially I wish were better. I really want to be able to know when a window isn’t the active window, but haven’t found a good way to do it without dropping to AppKit.



Andropov said:


> Interestingly, it looks like Apple encourages SwiftUI over AppKit rather than SwiftUI over Catalyst/UIKit as the default for Mac apps now. Hmmm. Maybe I'll make the switch.




Based on our previous discussions, I don’t think there‘s huge reason to go do a bunch of work if you are already living in one world. SwiftUI on AppKit doesn’t behave the same way as SwiftUI on UIKit on the Mac and will need to be accounted for. And if you need to support versions earlier than Ventura, the SwiftUI 4 changes that seem to be making things more consistent between AppKit and UIKit won’t really help you much until you can ditch Monterrey.

NavigationView is the real big one. I had to create my own navigation behaviors for macOS because of AppKit’s NavigationView doesn’t support stack based navigation. It’s not fun, and has been a source of developer pain. I’m actually rushing to get Ventura installed on a partition so I can explore the new NavigationStack and see if it actually resolves the issues I’m facing. Although I’m well aware I’m going to have some real pain ahead of me doing the work to enable dual Monterrey/Ventura behavior if I want to leverage it. *sigh*


----------



## MEJHarrison

Nycturne said:


> - On this topic, it looks a bit like NavigationStack might actually provide a navigation stack for macOS outside of catalyst? Need to play with it once I can install the beta.




I wanted to play with the new Regex Builder, but you need to be on Ventura.  I might just have to install Ventura on my laptop.  I'm looking forward to Regex Builder.  I'm old enough to have used Regex tons in my career.  So taking that and making it readable is super duper cool in my book.  Can't wait to play with it.

Unfortunately, I didn't think to take today off work, so the "What's new in Xcode/Swift/SwiftUI" videos will need to wait for tonight.


----------



## Cmaier

MEJHarrison said:


> I wanted to play with the new Regex Builder, but you need to be on Ventura.  I might just have to install Ventura on my laptop.  I'm looking forward to Regex Builder.  I'm old enough to have used Regex tons in my career.  So taking that and making it readable is super duper cool in my book.  Can't wait to play with it.
> 
> Unfortunately, I didn't think to take today off work, so the "What's new in Xcode/Swift/SwiftUI" videos will need to wait for tonight.




I saw some of that regex builder stuff (is there documentation for it somewhere yet?), and my initial thought was “regex strings were good enough for me in 1996, and get off my lawn!”

BTW, is regex builder something different than the swift stuff that lets you build up a regex? I am not following the news too closely because of work stuff.


----------



## MEJHarrison

Cmaier said:


> I saw some of that regex builder stuff (is there documentation for it somewhere yet?), and my initial thought was “regex strings were good enough for me in 1996, and get off my lawn!”
> 
> BTW, is regex builder something different than the swift stuff that lets you build up a regex? I am not following the news too closely because of work stuff.




I just caught a small snippet in the State of the Union video yesterday.  At one point, he very quickly showed Regex Builder.  It turns Regex patterns into readable code.  Let me see if I can find it...

Here's a screenshot from bought 11:45 in the State of the Union video.  He took one of the new Regex Expression Literals (that's just old school Regex we've been doing for years) and converted it on the fly to this.  I wanted to try it out myself, but will need to be running on Ventura for that to happen.


----------



## Nycturne

MEJHarrison said:


> I wanted to play with the new Regex Builder, but you need to be on Ventura.  I might just have to install Ventura on my laptop.  I'm looking forward to Regex Builder.  I'm old enough to have used Regex tons in my career.  So taking that and making it readable is super duper cool in my book.  Can't wait to play with it.




Result Builders are starting to pop up everywhere. It looks like the new App Intents API also pulls a little from this as well, but it was pretty late when I watched the platform state of the union. Not surprising though. The syntax has been growing on me and I like it compared to say, Java's approach to builders.



MEJHarrison said:


> Unfortunately, I didn't think to take today off work, so the "What's new in Xcode/Swift/SwiftUI" videos will need to wait for tonight.




I'm taking some time off to try to reset a little after spending 2 years working from home and spend a chunk of that working on my own project. The start of it just lined up perfectly with WWDC, but it also means I'm going to be somewhat distracted for the first week of being able to do my own thing during working hours. Go figure. But at least it means I can do some mindless refactoring and test writing while I go through the videos.

Got a chance to play with the new navigation behaviors in SwiftUI. It's a definite improvement, but not perfect. If I use safeAreaInset on the NavigationStack itself, it still gets attached to the root view rather than the NavigationStack view. But it does mean instead of dropping down all the way to UISplitViewController, I can instead use NavigationSplitView from SwiftUI and just drop down to UIKit on the detail view to attach my accessory view.

It also does provide a working stack navigation scheme for AppKit-based apps (unfortunately without any nice animations like on iOS). Good. Unfortunately, with Beta 1, AppKit-based apps will crash when you hit the back button. Fun.


----------



## Cmaier

MEJHarrison said:


> I just caught a small snippet in the State of the Union video yesterday.  At one point, he very quickly showed Regex Builder.  It turns Regex patterns into readable code.  Let me see if I can find it...
> 
> Here's a screenshot from bought 11:45 in the State of the Union video.  He took one of the new Regex Expression Literals (that's just old school Regex we've been doing for years) and converted it on the fly to this.  I wanted to try it out myself, but will need to be running on Ventura for that to happen.
> 
> View attachment 14728



Yeah, this is what i saw too.  It seems fine, though un programming my brain from decades of \s+[_A-Za-z]\s+ etc, etc. would be tough.  

I assume there are functions that let you build a Regex programmatically on-the-fly (e.g. something like Regex.append or whatever)?  When I use regexes in my code, it’s *usually* because I am giving the user a search interface.  Out of habit, I try to avoid using regexes to parse and things like that, for historical performance reasons (though I break that rule on occasion when I am in a hurry).


----------



## MEJHarrison

Cmaier said:


> Yeah, this is what i saw too.  It seems fine, though un programming my brain from decades of \s+[_A-Za-z]\s+ etc, etc. would be tough.
> 
> I assume there are functions that let you build a Regex programmatically on-the-fly (e.g. something like Regex.append or whatever)?  When I use regexes in my code, it’s *usually* because I am giving the user a search interface.  Out of habit, I try to avoid using regexes to parse and things like that, for historical performance reasons (though I break that rule on occasion when I am in a hurry).




We still use them for things like "did the user enter a valid email/phone/SSN/etc".  I still use them for little things here and there.  My problem is I use them so little, I usually need a few minutes for all that knowledge to return every time I need to use them.  A builder would suit me just fine.  I'd not really come across that idea before, so I found it really cool.


----------



## Cmaier

MEJHarrison said:


> We still use them for things like "did the user enter a valid email/phone/SSN/etc".  I still use them for little things here and there.  My problem is I use them so little, I usually need a few minutes for all that knowledge to return every time I need to use them.  A builder would suit me just fine.  I'd not really come across that idea before, so I found it really cool.



Having written a regex parser once, I always sort of think of it in the tree-like way this regex builder portrays them. I just am so used to the standard perl-like syntax that to me its just a nicer, more compact, representation at the moment. That may change as I get used to the new way.  I also am quite used to using string operations to dynamically create the regular expression, and I can see where more verbose and clearer syntax could be very helpful in code maintainability.


----------



## Nycturne

Andropov said:


> Oh that makes a lot of sense. I wasn't very aware of the differences between global and non-global actors, so that was very informative. Still think it's going to take some years and a lot of code to be ported from GCD and completion handler based APIs to async/await to fully see the benefits of all this though. Fortunately, it looks like we'll hear more of this since there are a bunch of new talks this WWDC planned for async/await and actors, I'm excited to see what's new.




On this topic, there’s one related to new Instruments tools for investigating what’s going on with Swift concurrency. Neat stuff. I’ll get some use out of that.


----------



## Andropov

Nycturne said:


> On this topic, there’s one related to new Instruments tools for investigating what’s going on with Swift concurrency. Neat stuff. I’ll get some use out of that.



Hadn't had time to see that yet! I've been busy with the Metal talks. I had that one bookmarked though, I hope I manage to watch it tomorrow.

I got a slot for one of the Metal Labs with an Apple engineer tonight, so I've been busy preparing for that.


----------



## Andropov

Saw the WWDC talk on "Bring multiple windows to your SwiftUI app" hoping they'd improved the Scenes API to be able to check for the foreground window... no luck


----------



## Nycturne

Andropov said:


> Saw the WWDC talk on "Bring multiple windows to your SwiftUI app" hoping they'd improved the Scenes API to be able to check for the foreground window... no luck




On Mac? Yeah, I’ve been trying to do something similar. I think the only real way is to drop down to UIKit/AppKit and inject the information as an environment value of some kind.


----------



## Nycturne

I’ve been banging my head against one particular puzzle related to Swift concurrency:

I have an identical network request I want to make to different endpoints and see which ones respond in a reasonable amount of time, so I can make a determination on which endpoint to use going forward. But I also want the data from the first endpoint to respond so that the app can take the response and continue processing it, without having to wait on the  results of the remaining requests which should all be identical. Concurrency task groups seem built for this at first, until you realize that the group cannot exit until all the child tasks have either completed or cancelled. This is bad if one of the endpoints is going to timeout. You don’t want to take that 100ms turnaround and make it many seconds waiting for that timeout to happen. It also means that if you cancel one of the requests right before it was going to respond you might not know that there is a better route that you should be picking for future requests instead.

I’m thinking I’ll have to fire the requests from a continuation instead of a task group. The first request to complete will resume the continuation, and the remaining requests will update the state.

But maybe I’m missing something better here?


----------



## Andropov

Nycturne said:


> I’ve been banging my head against one particular puzzle related to Swift concurrency:
> 
> I have an identical network request I want to make to different endpoints and see which ones respond in a reasonable amount of time, so I can make a determination on which endpoint to use going forward. But I also want the data from the first endpoint to respond so that the app can take the response and continue processing it, without having to wait on the  results of the remaining requests which should all be identical. Concurrency task groups seem built for this at first, until you realize that the group cannot exit until all the child tasks have either completed or cancelled. This is bad if one of the endpoints is going to timeout. You don’t want to take that 100ms turnaround and make it many seconds waiting for that timeout to happen. It also means that if you cancel one of the requests right before it was going to respond you might not know that there is a better route that you should be picking for future requests instead.
> 
> I’m thinking I’ll have to fire the requests from a continuation instead of a task group. The first request to complete will resume the continuation, and the remaining requests will update the state.
> 
> But maybe I’m missing something better here?



I don’t think you are. You need to update the state separately anyway if you want the function to return after the response from the first endpoint arrives, right?


----------



## Nycturne

Andropov said:


> I don’t think you are. You need to update the state separately anyway if you want the function to return after the response from the first endpoint arrives, right?




Some variation of it anyways. I’ve shifted between a couple different approaches around checking what network paths lead to the desired host (My code is aware of up to 3, but it’s possible none of those are even available). But interestingly, Combine seems suited to let me get rid of the continuation and solve it a bit more elegantly using one of two approaches that feed a publisher with completion handlers, and then read it out using concurrent code:

- Detached async task that updates state that is part of a CurrentValueSubject, setting details on the available paths (in priority order) once it knows the status on a given path, if all paths have been checked, and if there’s at least one path available. This fires on network changes, or otherwise when we think we need to recalculate the available paths. Tasks that want to make requests to the host can listen to the value subject as an AsyncSequence and wait for the state to be either “There’s no path and I’ve checked them all” or “there’s at least one good path”. Because of how CurrentValueSubject works, the current state is always available to new subscribers and so if there’s already one path (or more), this will return quickly. The task can then use the paths in priority order to attempt to reach the host and even do fallbacks if one returns an error (although timeouts are a lot harder). This makes network requests take a little longer in some cases as the request is waiting for the reachability checks to complete before doing it’s work rather than baking in the reachability check. 

- Use a PassthroughSubject when the request is fired to the host along multiple paths, and convert that to an AsyncSequence. The task waiting on the response can pull the first value out and then unsubscribe. The remaining network requests will update the cached state and then publish to the void. This makes early network calls more expensive as the device could get multiple responses. It also complicates handling of some aspects of path state when it comes to paths that are taking a while to respond.

Neat. I’m liking it. The tradeoff is: latency vs data/complexity. We are talking on the order of one request worth of latency vs 12KB extra data, generally. I might just be overanalyzing it, so I might just go with the one that uses the simplest state.


----------



## Andropov

Nycturne said:


> - Detached async task that updates state that is part of a CurrentValueSubject, setting details on the available paths (in priority order) once it knows the status on a given path, if all paths have been checked, and if there’s at least one path available. This fires on network changes, or otherwise when we think we need to recalculate the available paths. Tasks that want to make requests to the host can listen to the value subject as an AsyncSequence and wait for the state to be either “There’s no path and I’ve checked them all” or “there’s at least one good path”. Because of how CurrentValueSubject works, the current state is always available to new subscribers and so if there’s already one path (or more), this will return quickly. The task can then use the paths in priority order to attempt to reach the host and even do fallbacks if one returns an error (although timeouts are a lot harder). This makes network requests take a little longer in some cases as the request is waiting for the reachability checks to complete before doing it’s work rather than baking in the reachability check.



Hmm. I always try to avoid Combine (I don't like the readability of it, plus I'm unsure what will happen to it once Swift Concurrency matures), but it's a very elegant solution here. I don't think you could build this as elegantly without Combine. I was thinking you could build a @Published struct instead, holding both the state of the path-finding calls (whether it is still checking paths / at least one good path / no path and all have been checked) and a list of paths sorted by priority. But then, you'd have no way of making a Task wait until that @Published property changes to at least one good path / no path and all have been checked. Or at least I can't think of one. So Combine it is.



Nycturne said:


> On this topic, there’s one related to new Instruments tools for investigating what’s going on with Swift concurrency. Neat stuff. I’ll get some use out of that.



I finally got to watch that talk. One of my favourite talks this year, not only because of the new tool but also because it does an excellent job at explaining how to use concurrency in practice and some common ways people misuse it. It also shows how to do something equivalent to the 'old' concurrentPerform by spawning several detached Tasks, which I wasn't sure was the correct way to do it (I'm still somewhat concerned about what would happen if you spawn too many tasks, but since Apple is showing it on their sample code...).


----------



## Nycturne

Andropov said:


> Hmm. I always try to avoid Combine (I don't like the readability of it, plus I'm unsure what will happen to it once Swift Concurrency matures), but it's a very elegant solution here. I don't think you could build this as elegantly without Combine. I was thinking you could build a @Published struct instead, holding both the state of the path-finding calls (whether it is still checking paths / at least one good path / no path and all have been checked) and a list of paths sorted by priority. But then, you'd have no way of making a Task wait until that @Published property changes to at least one good path / no path and all have been checked. Or at least I can't think of one. So Combine it is.




Honestly, I don't think Combine is going anywhere, but we should continue to see deeper and deeper integration between the two as it matures. For a lot of the main thread publishing that SwiftUI uses, you don't need/want concurrency getting in the way. And concurrency is not really about providing Pub/Sub functionality. There's overlap because perhaps you want to update published state from async tasks, or you want to have a task wait for a particular state change to be published. That where the integration comes in, but one doesn't really need to replace the other. Combine added AsyncPublisher last year which conforms to AsyncSequence, and so allows you to await on the values coming from the publisher. It's the crux of how my approach works.

A way to think about what I've done is that I'm really just following reactive programming practices better by explicitly making my state a value that can be subscribed to. And I actually think that's a good thing. At the very least the data flow makes more sense to me now, and it's easier to decouple it this way since the pub/sub model is more decoupled by design. Network state changes trigger server state changes, which tasks that need the server state can subscribe to and await on.

As I get more used to reactive programming, I'm finding I'm leaning on Combine more and more, especially once I have a good strategy for unit testing publisher and subscriber behaviors. My internal services provide publishers that the ViewModel subscribes to, in order to get updates (or even other services). CoreData-based view models use publishers based on KVO to directly update @Published properties. The Combine-based version of observing an AVPlayer is much nicer to work with than hooking up everything directly. And my playback service that I'm in the middle of fixing up publishes its own state which I can feed both to my own ViewModel, and to MPNowPlayingInfoCenter, decoupling things nicely where before the service was an observable object that pushed out to MPNowPlayingInfoCenter before.

If anything, Combine really feels like the replacement for KVO in Swift (and it's a good one with a lot more power than KVO has in Obj-C). I really can't see Combine being replaced by concurrency for that reason alone.

EDIT: If we do see a Combine "replacement", my bet is more that it's going to be a version of Combine that runs on Linux as well and doesn't have the strict dependencies on things like RunLoops and GCD for thread hopping and instead can be told which global actor to receive events on, or if it should be received on a background task.



Andropov said:


> I finally got to watch that talk. One of my favourite talks this year, not only because of the new tool but also because it does an excellent job at explaining how to use concurrency in practice and some common ways people misuse it. It also shows how to do something equivalent to the 'old' concurrentPerform by spawning several detached Tasks, which I wasn't sure was the correct way to do it (I'm still somewhat concerned about what would happen if you spawn too many tasks, but since Apple is showing it on their sample code...).




Yeah, very useful talk.

Tasks by themselves don't need a ton of heap space, IIRC. Since they contain blocks, there's the same state you would need to keep for a dispatched block with GCD. The only real difference is that they also preserve their stack state while they are suspended. But I would not be surprised at all if tasks don't save off their stack until the first suspension. So for CPU-bound tasks that don't suspend, I suspect performance is going to be very close to GCD in the same scenario.

I had a bug where I created 20k network requests in a task group, and they would eventually start timing out because they would be waiting more than 60 seconds for URLSession to start sending the request. The number of tasks didn't seem to really be much of a problem on its own. Throttling the number of tasks in flight at a time in the task group is pretty easy and solved the issue until I can address the underlying problem that leads to needing that many network requests in the first place.


----------



## ArgoDuck

I’m enjoying this ongoing exchange between the two of you. I’ve only just started into Core Data - previously I developed a thin object model on top of SQLite - and I plan to leap into concurrency later this year. Thus, much of your discussion is over my head right now, but really useful for highlighting issues and possibilities.

Thanks! Continuing to follow with interest.


----------



## Nycturne

ArgoDuck said:


> I’m enjoying this ongoing exchange between the two of you. I’ve only just started into Core Data - previously I developed a thin object model on top of SQLite - and I plan to leap into concurrency later this year. Thus, much of your discussion is over my head right now, but really useful for highlighting issues and possibilities.
> 
> Thanks! Continuing to follow with interest.




Everything I know about Core Data, I learned from banging my head against what feels like a brick wall. There are times where I sometimes wished I went with something else, especially as I skew more and more towards reactive programming with my project. 

Out of curiosity, did you use any libraries on top of SQLite like GRDB, or just raw SQLite?


----------



## ArgoDuck

Nycturne said:


> Everything I know about Core Data, I learned from banging my head against what feels like a brick wall. There are times where I sometimes wished I went with something else, especially as I skew more and more towards reactive programming with my project.
> 
> Out of curiosity, did you use any libraries on top of SQLite like GRDB, or just raw SQLite?



I’m familiar with the head banging 

Raw, except I abstracted a little, creating my own fairly thin (but useful) library to make my use of SQLite more swift-like, or before that more C++ like. I actually developed mostly in windows until 2018 when I switched to Apple and Swift. What I did was a start toward something _like_ GRDB, just for my own use but with similar goals I guess. That is, though very experienced with SQL, I didn’t like mixing clumsy string interpolations etc into my code.

And had I known about GRDB, or thought to look, I would likely have gone that way instead…

My interest in Core Data is motivated by the ease of hosting the data store in iCloud for device sharing, and just steering a bit closer to an ‘Apple-ly’ way of doing things.

What this ‘way’ is, is something I’m still working through. Coming from C++ and OOP to POP, SwiftUI and Swift, functional programming and MVVM (or?) has been quite a transition!

Back to Core Data I’ve only recently started to grasp that it is more than just a wrapper around SQLite, actually thanks to your link to Dave De Long’s articles…

Anyway, this whole thread with all the contributors has been useful and stimulating. At some point, my own ideas might coalesce enough to be worth volunteering a remark or two.


----------



## Andropov

Nycturne said:


> As I get more used to reactive programming, I'm finding I'm leaning on Combine more and more, especially once I have a good strategy for unit testing publisher and subscriber behaviors. My internal services provide publishers that the ViewModel subscribes to, in order to get updates (or even other services). CoreData-based view models use publishers based on KVO to directly update @Published properties. The Combine-based version of observing an AVPlayer is much nicer to work with than hooking up everything directly. And my playback service that I'm in the middle of fixing up publishes its own state which I can feed both to my own ViewModel, and to MPNowPlayingInfoCenter, decoupling things nicely where before the service was an observable object that pushed out to MPNowPlayingInfoCenter before.



I always get a bit lost with the semantics on those topics. SwiftUI, when the UI is hooked up to ObservedObject properties, is considered reactive programming too, right? I know SwiftUI + async/await does not overlap Combine completely, but most of the things I had used Combine for in past projects were no longer needed when using SwiftUI. Well, IIRC the @Published 'magic' uses Combine under the hood, so I should rather say that I have no longer needed to use Combine _explicitly _(not as much).



Nycturne said:


> I had a bug where I created 20k network requests in a task group, and they would eventually start timing out because they would be waiting more than 60 seconds for URLSession to start sending the request. The number of tasks didn't seem to really be much of a problem on its own. Throttling the number of tasks in flight at a time in the task group is pretty easy and solved the issue until I can address the underlying problem that leads to needing that many network requests in the first place.



Oh, that's great. It's on the order of magnitude of the number of Tasks that I could end up having in a worst-case scenario.



ArgoDuck said:


> What this ‘way’ is, is something I’m still working through. Coming from C++ and OOP to POP, SwiftUI and Swift, functional programming and MVVM (or?) has been quite a transition!



I'm still skeptical about the whole POP thing, or at least skeptical of the way people are using it. I admit I haven't watched the _Protocol-Oriented-Programming in Swift_ talk that started all of this, but the way everyone understood it (in my obviously anecdotal experience of the people I've worked with) seems flawed to me. But I have to read a lot more about it before fully forming an opinion.

MVVM, on the other hand? I love it. Interestingly, now that I'm revisiting Apple's sample code (to see how they architected their more complex sample apps), they aren't using MVVM at all. Instead, they just inject the model via @EnvironmentObject to the views. Hmm.



ArgoDuck said:


> Back to Core Data I’ve only recently started to grasp that it is more than just a wrapper around SQLite



Yup, this was one of the *oooh* moments to me too, when I first started to work with CoreData.


----------



## Nycturne

Andropov said:


> I always get a bit lost with the semantics on those topics. SwiftUI, when the UI is hooked up to ObservedObject properties, is considered reactive programming too, right? I know SwiftUI + async/await does not overlap Combine completely, but most of the things I had used Combine for in past projects were no longer needed when using SwiftUI. Well, IIRC the @Published 'magic' uses Combine under the hood, so I should rather say that I have no longer needed to use Combine _explicitly _(not as much).




ObservableObjects are a reactive concept, yes. Reactive programming is the idea of declaring how a change in state in one part of an app translates to changes in other state (UI or data). SwiftUI is built around a “push-pull” model of reactive programming, where the invalidation by something like objectWillChange, or other publishers is the push, and then the running of a view’s body to calculate the new UI state is the pull. 

It’s hard to be “pure” reactive in languages like JS or Swift because they are imperative programming languages, but you can still model data flows in a reactive manner with them, and use imperative code to describe transformations, which is what SwiftUI and Combine does. 



Andropov said:


> I'm still skeptical about the whole POP thing, or at least skeptical of the way people are using it. I admit I haven't watched the _Protocol-Oriented-Programming in Swift_ talk that started all of this, but the way everyone understood it (in my obviously anecdotal experience of the people I've worked with) seems flawed to me. But I have to read a lot more about it before fully forming an opinion.




I do recommend the talk as it describes the nature of POP better than I ever could. But the Swift runtime is built on POP, as is SwiftUI. While I don’t think it is the savior of programming, it has let me kick most inheritance to the curb in favor of conformance, and for me, being able to compose conformances leads to more testable code. So I favor it over OOP style inheritance. NSObject and NSManagedObject are about all I subclass from these days.



Andropov said:


> MVVM, on the other hand? I love it. Interestingly, now that I'm revisiting Apple's sample code (to see how they architected their more complex sample apps), they aren't using MVVM at all. Instead, they just inject the model via @EnvironmentObject to the views. Hmm.




One reason I like MVVM is that the VM layer in SwiftUI can be as thin or as thick as needed, making it very pragmatic. Perhaps you can get away with just interacting with the model directly because it’s using NSManagedObject. Maybe you have a bunch of services and so you want that separation. Do what makes the most sense at the time, IMO. 

I tend to favor the approach of adding complexity as needed, so Apple’s approach here makes some sense to me.


----------



## ArgoDuck

Andropov said:


> MVVM, on the other hand? I love it. Interestingly, now that I'm revisiting Apple's sample code (to see how they architected their more complex sample apps), they aren't using MVVM at all. Instead, they just inject the model via @EnvironmentObject to the views. Hmm..



In the apple developer forums (IIRC, don’t have the link right now) one developer started something of a crusade to argue that SwiftUI largely _supplants_ the need for view models. I can kind of see the point, whilst also seeing that many models _need_ the VM layer to avoid potential mess.

I‘m a social scientist and statistician these days - I stopped being a full time developer quite a while ago - and many of my models are fairly pure computational expressions of some underlying scientific model. The question for me then is how can I make the app reasonably interesting? I have to expose parameters but what intermediate results can I expose as well? I’m an impatient sort!

SwiftUI has been good for this - I’m very excited by the new Chart capability! - but earlier I found myself building things into the _model_ purely for their presentational value. These are things that don’t translate directly to some SwiftUI element (eg they need aggregation or whatever). They don’t belong in the model, hence MVVM makes a lot of sense to me too.

I like @Nycturne’s comments above too! Hmm, still bouncing things around…


----------



## Nycturne

ArgoDuck said:


> In the apple developer forums (IIRC, don’t have the link right now) one developer started something of a crusade to argue that SwiftUI largely _supplants_ the need for view models. I can kind of see the point, whilst also seeing that many models _need_ the VM layer to avoid potential mess.




Yeah, I don’t fully buy it. I’d say that you don’t need to _start_ by building view models right out of the gate, but I wouldn’t leave tools unused if it helps make the code more maintainable. If your view model is _just_ wrapping the model, then it’s not really adding any value for sure. 

Apple‘s documentation now points out that State/Binding is recommended for view state, while ObservableObject is recommended for model objects. With that in mind, it isn’t too insane to convert a model object to a view model as things get more complex.



ArgoDuck said:


> SwiftUI has been good for this - I’m very excited by the new Chart capability! - but *earlier I found myself building things into the model purely for their presentational value*. These are things that don’t translate directly to some SwiftUI element (eg they need aggregation or whatever). They don’t belong in the model, hence MVVM makes a lot of sense to me too.




My current project doesn’t really need charts, but I am glad to see charting functionality built in. It’s long overdue, IMO.

As for the bolded bit, that’s why I started moving to MVVM in my current project. I still have views that take NSManagedObjects as ObservableObjects, but they are becoming rarer as the views want to display things that aren’t just straight pulled from the model. But when I’m at around 22kloc right now, it’s not surprising that just binding to the model isn’t good enough anymore.


----------



## Andropov

Nycturne said:


> I do recommend the talk as it describes the nature of POP better than I ever could. But the Swift runtime is built on POP, as is SwiftUI. While I don’t think it is the savior of programming, it has let me kick most inheritance to the curb in favor of conformance, and for me, being able to compose conformances leads to more testable code. So I favor it over OOP style inheritance. NSObject and NSManagedObject are about all I subclass from these days.



Finally watched it yesterday. It was quite enjoyable. I'll watch the other related talks next (_Protocol and Value Oriented Programming in UIKit Apps_), but I think I know by this point that the problem I have is not with POP itself, but rather how people are using it to add unnecessary abstractions to some parts of the code. Just like no one created a superclass for every class just in case you might want an alternative implementation in the future, protocols should be created/extracted as required, not upfront as part of the creation of a class, IMHO.

In any case, the talk had some *very* cool things. I liked most how it significantly extends the type information available to the compiler vs using inheritance in some common cases.



Nycturne said:


> One reason I like MVVM is that the VM layer in SwiftUI can be as thin or as thick as needed, making it very pragmatic. Perhaps you can get away with just interacting with the model directly because it’s using NSManagedObject. Maybe you have a bunch of services and so you want that separation. Do what makes the most sense at the time, IMO.
> 
> I tend to favor the approach of adding complexity as needed, so Apple’s approach here makes some sense to me.



Problem is, it's very common for developers to consider views interacting directly with models as a code smell. Having Views be able to access "more than what they need" (i.e, an entire model instead of just the parts relevant to the view) is also frowned upon. I had been adhering to that idea until very recently. However, seeing how Apple architected the _Fruta_ sample app (injecting the model to all subviews) has made me question that assumption. Creating a single, separate ViewModel for each view can create several 'sources of truth' if you're not careful and start passing value types around between ViewModels.

A good example of this just happened to me at work: I was implementing an image-editing workflow spanning several screens (a grid view with all the images -> a detail view of the image -> an image editor screen). After saving the edited image on the last screen, I noticed that previous views in the navigation hierarchy still showed the old, non-edited image. That's because each view had its own view model, and those were each acquiring a copy of the image. This should (ideally) not happen in SwiftUI. Having a single source of truth is an important goal of the framework.

The main problem of this approach is that the environment-shared model can become too big. I haven't found (yet) a good solution to that problem, that doesn't end up creating alternative sources of truth.



ArgoDuck said:


> SwiftUI has been good for this - I’m very excited by the new Chart capability! - but earlier I found myself building things into the _model_ purely for their presentational value. These are things that don’t translate directly to some SwiftUI element (eg they need aggregation or whatever). They don’t belong in the model, hence MVVM makes a lot of sense to me too.



The new charts are great. I haven't delved too deep into that yet, but I plan to replace some crude graphs I had in my side-project app with Swift charts as soon as I finish what I'm building now. I didn't have much faith in them, seeing how Apple's own graphs in the Health app are mediocre at best, but it seems to be a powerful framework after all.


----------



## Nycturne

Andropov said:


> Finally watched it yesterday. It was quite enjoyable. I'll watch the other related talks next (_Protocol and Value Oriented Programming in UIKit Apps_), but I think I know by this point that the problem I have is not with POP itself, but rather how people are using it to add unnecessary abstractions to some parts of the code. *Just like no one created a superclass for every class just in case you might want an alternative implementation in the future, protocols should be created/extracted as required, not upfront as part of the creation of a class, IMHO.*
> 
> In any case, the talk had some *very* cool things. I liked most how it significantly extends the type information available to the compiler vs using inheritance in some common cases.




I think TDD proponents might disagree with the bolded statement, in the sense that creating a protocol that represents a unit under test is the first step for being able to isolate that unit from others when testing. Since it’s pretty common that unit tests test objects in isolation under OOP, that unit tests in POP also test objects. So getting that level of duplication seems like a side effect of decoupling in a language like Swift.

I’ve had to use the technique to cleave off dependencies to Network and AVFoundation while testing. Extract a protocol from the Apple type I depend on, and then feed a mock implementation that unit tests can manipulate. But it does mean I have stuff like this in production code:



		Swift:
	

protocol AVPlayerProtocol {
    /* Stuff I care about */
}

extension AVPlayer: AVPlayerProtocol {}

final class MyType {
    private var avplayer: AVPlayerProtocol
    init(avplayer: AVPlayerProtocol) { /* ... */ }
}




Andropov said:


> Problem is, it's very common for developers to consider views interacting directly with models as a code smell. Having Views be able to access "more than what they need" (i.e, an entire model instead of just the parts relevant to the view) is also frowned upon. I had been adhering to that idea until very recently. However, seeing how Apple architected the _Fruta_ sample app (injecting the model to all subviews) has made me question that assumption.




Usually when I have the model directly accessible to the view, it is a small subset and used read-only. So think of things like cells in collection views and the like. And it’s usually some sort of CoreData object, so I’m not copying value types around.



Andropov said:


> Creating a single, separate ViewModel for each view can create several 'sources of truth' if you're not careful and start passing value types around between ViewModels.
> 
> A good example of this just happened to me at work: I was implementing an image-editing workflow spanning several screens (a grid view with all the images -> a detail view of the image -> an image editor screen). After saving the edited image on the last screen, I noticed that previous views in the navigation hierarchy still showed the old, non-edited image. That's because each view had its own view model, and those were each acquiring a copy of the image. This should (ideally) not happen in SwiftUI. Having a single source of truth is an important goal of the framework.




This scenario is why Combine is a developer-facing framework, IMO. You are right that there should be a single source of truth, and that should live in the model for cases like these. But Combine exists explicitly to make it possible for things like ViewModels to subscribe to the bits of the model that could update underneath it in places where SwiftUI as a framework shouldn’t be used to create these sort of bindings.



Andropov said:


> The main problem of this approach is that the environment-shared model can become too big. I haven't found (yet) a good solution to that problem, that doesn't end up creating alternative sources of truth.




Redux seems to handle this sort of thing fine by creating a store that contains all of the app’s state, and letting segments of the app subscribe just to the parts it actually cares about.

Creating one big ObservableObject isn’t realistic, but you could easily bring together a cluster of observables into a larger store and play around a bit with letting the store inject it’s children into the environment. Views could then “subscribe” to just the components they need out of the cluster rather than everything.


----------



## Nycturne

Here's a lesson for the day related to Concurrency and Combine: Race conditions are still the enemy. I have two actors. Actor one holds onto network state and updates itself based on NWPathMonitor, exposing a publisher that can be subscribed to. Actor two needs to update its own internal state based on the network state, and check to see if a network host is reachable via different paths. So it subscribes to the publisher provided by the first.

My first attempt used a non isolated sink for the publisher, which then spawned a task to update the second actor. Unfortunately, in cases where values are updated quickly, ordering cannot be guaranteed when creating these tasks. So I'd get all sorts of breaks. 

Instead, I have the second actor create a child task and subscribe to the publisher within that task using ".values". At least this way, you can guarantee ordering within the child task, and since is now in a concurrency context, you can ensure the actor receives the correct ordering as well.


----------



## Andropov

Nycturne said:


> I think TDD proponents might disagree with the bolded statement, in the sense that creating a protocol that represents a unit under test is the first step for being able to isolate that unit from others when testing. Since it’s pretty common that unit tests test objects in isolation under OOP, that unit tests in POP also test objects. So getting that level of duplication seems like a side effect of decoupling in a language like Swift.



Oh but TDD *is* a genuine need for an alternative implementation. I don't mind that. I just argue that protocol extraction should be done immediately before adding those tests, and not in hope of maybe adding tests in the future.



Nycturne said:


> I’ve had to use the technique to cleave off dependencies to Network and AVFoundation while testing. Extract a protocol from the Apple type I depend on, and then feed a mock implementation that unit tests can manipulate. But it does mean I have stuff like this in production code:
> 
> 
> 
> Swift:
> 
> 
> protocol AVPlayerProtocol {
> /* Stuff I care about */
> }
> 
> extension AVPlayer: AVPlayerProtocol {}
> 
> final class MyType {
> private var avplayer: AVPlayerProtocol
> init(avplayer: AVPlayerProtocol) { /* ... */ }
> }



I think that's perfectly fine code. Apple does almost exactly that in the POP talk to make the Renderer class they create testable. Again, no problem as long as the abstraction is actually used 



Nycturne said:


> Usually when I have the model directly accessible to the view, it is a small subset and used read-only. So think of things like cells in collection views and the like. And it’s usually some sort of CoreData object, so I’m not copying value types around.



Hmm. How do you make only a small subset of the model available to the view?



Nycturne said:


> This scenario is why Combine is a developer-facing framework, IMO. You are right that there should be a single source of truth, and that should live in the model for cases like these. But Combine exists explicitly to make it possible for things like ViewModels to subscribe to the bits of the model that could update underneath it in places where SwiftUI as a framework shouldn’t be used to create these sort of bindings.
> 
> Redux seems to handle this sort of thing fine by creating a store that contains all of the app’s state, and letting segments of the app subscribe just to the parts it actually cares about.
> 
> Creating one big ObservableObject isn’t realistic, but you could easily bring together a cluster of observables into a larger store and play around a bit with letting the store inject it’s children into the environment. Views could then “subscribe” to just the components they need out of the cluster rather than everything.



Yeah, Combine is a good match for this use case. For changes that affect the views, though, changes in the model listened in the view model would have to be re-published to a @Published property so the view could update... I'm not convinced.

I thought about breaking up my ObservableObject into smaller objects. So far that's my preferred route, but I'll have to sit down and carefully examine the options. I'm planning a refactor of my side-project app into two separate modules when I finish the updates to the renderer I've been working on. Basically I will try to extract the renderer itself to a different module, so it can be used like one would use a SceneKit or ARKit view, to completely decouple the renderer from the app itself. That should also allow me to practice with some of the things mentioned in this thread (better use protocols, handle the massive-observableObject problem, maybe even add tests). And DocC. I want to try DocC.



Nycturne said:


> My first attempt used a non isolated sink for the publisher, which then spawned a task to update the second actor. Unfortunately, in cases where values are updated quickly, ordering cannot be guaranteed when creating these tasks. So I'd get all sorts of breaks.



Ooh. Good catch. I didn't know that could happen.


----------



## Nycturne

Andropov said:


> Oh but TDD *is* a genuine need for an alternative implementation. I don't mind that. I just argue that protocol extraction should be done immediately before adding those tests, and not in hope of maybe adding tests in the future.
> 
> I think that's perfectly fine code. Apple does almost exactly that in the POP talk to make the Renderer class they create testable. Again, no problem as long as the abstraction is actually used




Yeah, I think my confusion is that in my mind, TDD means you _always_ have that protocol (since you are writing the tests against an unimplemented protocol before you write the implementation). So it was a little hard for me to try to guess where you drew the line. Sounds like we aren’t that different in mindset here. My mindset is that POP effectively is steering folks towards TDD-like behaviors, but if they aren’t leveraging it, it is a shame and a waste of energy.



Andropov said:


> Hmm. How do you make only a small subset of the model available to the view?




There’s not much enforcement in the language itself, for sure. This is more a “discipline” thing in my case. 

But for the most part, my views don’t use @EnvironmentObject often. Instead it all gets passed in. So I’m either passing in a View Model or a Core Data object that the view represents most of the time. The type of project I am working on makes this a natural approach. So a view isn’t going to be wandering around the object graph and poking at things. Code that applies transformations to the object graph doesn’t live within it, so a view model is effectively mandatory in my architecture for any UI that wants to make changes to the model, to get at the parts of the editor workflow that doesn’t exist in the object graph.



Andropov said:


> Yeah, Combine is a good match for this use case. For changes that affect the views, though, changes in the model listened in the view model would have to be re-published to a @Published property so the view could update... I'm not convinced.




True, but this is why there’s an .assign() that explicitly takes the @Published property’s publisher, and doesn’t even need you to store anything in an AnyCancellable. So the update binding from the model is literally a line of code in an initializer. 

My view models use this approach for binding individual properties to the Core Data object backing the view model. I came to this approach partly because of your own suggestion that with Core Data, you should just deal with the fact that properties are optional. Which this lets me do rather elegantly without leaking things like the default displayable value into the model. Below is something similar to what I have setup as a view model for items that are displayed in List/LazyVGrid. It also has the neat side effect that I can use this to effectively type-erase dataObject if I need it to, and support some rather different Core Data entities if I want, rather than making those entities conform to a protocol that starts handling things the View Model should really own, IMO. 



		Swift:
	

class CollectionItemViewModel: ObservableObject {
    @Published var title: String = "<<TITLE>>"
    @Published var subtitle: String = "<<SUBTITLE>>"
    @Published var thumbnail: UIImage? // Don’t actually do this, especially in LazyVGrid. 
    
    init(_ dataObject: MyCoreDataObject) {
        // KVO Publishers
        dataObject.publisher(for: \.albumName).compactMap({$0}).assign(to: &$title)
        dataObject.publisher(for: \.albumArtist).compactMap({$0}).assign(to: &$subtitle)
        // Custom Publisher
        dataObject.lazyThumbnailPublisher().assign(to: &$thumbnail)
    }
}


FYI, using this pattern also means you can subscribe UI to actors _without having to manage the isolation contexts except when hooking up the subscriber. _So I can have shared state like a download queue live inside an actor, but also have the ability to update the UI with the queue’s state from a ViewModel using very little code. Literally just need to wrap the subscription code in a Task and add await in the appropriate spot. The alternative is spinning up a long-lived Task that watches an AsyncSequence of updates, and managing that yourself. 



Andropov said:


> I thought about breaking up my ObservableObject into smaller objects. So far that's my preferred route, but I'll have to sit down and carefully examine the options. I'm planning a refactor of my side-project app into two separate modules when I finish the updates to the renderer I've been working on. Basically I will try to extract the renderer itself to a different module, so it can be used like one would use a SceneKit or ARKit view, to completely decouple the renderer from the app itself. That should also allow me to practice with some of the things mentioned in this thread (better use protocols, handle the massive-observableObject problem, maybe even add tests). And DocC. I want to try DocC.




That’s generally what I’m doing as well, since it means that at least in my cases, I am passing one specific object to a view, and environmentObject gets regulated to more “global” scope objects that actually need to be accessed from random parts of the API. Things like the download queue I mentioned above. 



Andropov said:


> Ooh. Good catch. I didn't know that could happen.




Using the actor playground we talked about earlier, I did mention it was non-deterministic behavior when it came to ordering, because of things executing concurrently. I just didn’t actually take my own learnings into account. Whoops.


----------



## Andropov

Nycturne said:


> My mindset is that POP effectively is steering folks towards TDD-like behaviors, but if they aren’t leveraging it, it is a shame and a waste of energy.



Yep, that was basically my point.



Nycturne said:


> But for the most part, my views don’t use @EnvironmentObject often. Instead it all gets passed in. So I’m either passing in a View Model or a Core Data object that the view represents most of the time. The type of project I am working on makes this a natural approach. So a view isn’t going to be wandering around the object graph and poking at things. Code that applies transformations to the object graph doesn’t live within it, so a view model is effectively mandatory in my architecture for any UI that wants to make changes to the model, to get at the parts of the editor workflow that doesn’t exist in the object graph.



I think that's going to depend a lot on the kind of app you're writing. For instance, my main side project is basically a Metal-backed view with a sidebar with a lot of buttons and switches for options and the like. I think using @EnvironmentObject there makes a lot of sense, because all the subviews need access to the shared state, and passing it around in the initializer to tens of subviews would be needlessly complicated.

On the other hand, if my app was navigation heavy, I can see @EnvironmentObject being much less useful. Also, I don't like that it can cause crash at runtime if you forget to inject it, I wish it could be ensured at compile time.



Nycturne said:


> True, but this is why there’s an .assign() that explicitly takes the @Published property’s publisher, and doesn’t even need you to store anything in an AnyCancellable. So the update binding from the model is literally a line of code in an initializer.
> 
> My view models use this approach for binding individual properties to the Core Data object backing the view model. I came to this approach partly because of your own suggestion that with Core Data, you should just deal with the fact that properties are optional. Which this lets me do rather elegantly without leaking things like the default displayable value into the model. Below is something similar to what I have setup as a view model for items that are displayed in List/LazyVGrid. It also has the neat side effect that I can use this to effectively type-erase dataObject if I need it to, and support some rather different Core Data entities if I want, rather than making those entities conform to a protocol that starts handling things the View Model should really own, IMO.
> 
> 
> 
> Swift:
> 
> 
> class CollectionItemViewModel: ObservableObject {
> @Published var title: String = "<<TITLE>>"
> @Published var subtitle: String = "<<SUBTITLE>>"
> @Published var thumbnail: UIImage? // Don’t actually do this, especially in LazyVGrid.
> 
> init(_ dataObject: MyCoreDataObject) {
> // KVO Publishers
> dataObject.publisher(for: \.albumName).compactMap({$0}).assign(to: &$title)
> dataObject.publisher(for: \.albumArtist).compactMap({$0}).assign(to: &$subtitle)
> // Custom Publisher
> dataObject.lazyThumbnailPublisher().assign(to: &$thumbnail)
> }
> }



This is... actually super nice. Damn, you're gonna make me consider using Combine more often after all. This has basically everything I want: the initializer ensures the model is present at compile time, it keeps the single source of truth (without having to worry about storing AnyCancellables), it's still purely reactive (no worries about stale data), and as you point out: the default displayed value is not present in the Model. Super nice. I can't really think of any drawbacks right now.

Those last couple weeks I've been thinking that keeping a single source of truth in SwiftUI is probable more important than any other traditional code writing guideline. Sometimes people obsess too much about things like separation of concerns IMHO, and it often leads to several sources of truth that may or may not be in sync. If I were to become super strict with one single thing, right now I think it'd be the single source of truth. SwiftUI does behave like magic at times if you ensure that (that and being careful with view identities).


----------



## Nycturne

Andropov said:


> Yep, that was basically my point.




Yeah, no worries. Please don't feel like I'm trying to prod at you here. I'm mostly just voicing my thoughts more to ensure I communicate effectively and avoid ambiguity.



Andropov said:


> I think that's going to depend a lot on the kind of app you're writing. For instance, my main side project is basically a Metal-backed view with a sidebar with a lot of buttons and switches for options and the like. I think using @EnvironmentObject there makes a lot of sense, because all the subviews need access to the shared state, and passing it around in the initializer to tens of subviews would be needlessly complicated.
> 
> On the other hand, if my app was navigation heavy, I can see @EnvironmentObject being much less useful. Also, I don't like that it can cause crash at runtime if you forget to inject it, I wish it could be ensured at compile time.




Yeah, agreed. So it really depends on how you can break down your view hierarchy and navigation, IMO.



Andropov said:


> This is... actually super nice. Damn, you're gonna make me consider using Combine more often after all. This has basically everything I want: the initializer ensures the model is present at compile time, it keeps the single source of truth (without having to worry about storing AnyCancellables), it's still purely reactive (no worries about stale data), and as you point out: the default displayed value is not present in the Model. Super nice. I can't really think of any drawbacks right now.
> 
> Those last couple weeks I've been thinking that keeping a single source of truth in SwiftUI is probable more important than any other traditional code writing guideline. Sometimes people obsess too much about things like separation of concerns IMHO, and it often leads to several sources of truth that may or may not be in sync. If I were to become super strict with one single thing, right now I think it'd be the single source of truth. SwiftUI does behave like magic at times if you ensure that (that and being careful with view identities).




Thanks for the kind words. As I've been moving to Combine for non-Core Data parts of my model, I've even been creating a couple property wrappers to make this a bit easier as well. So I can more easily have model objects that expose state that can be subscribed to. I'm surprised Apple doesn't already offer something like this as either part of @Published or another property wrapper. But they are pretty easy to create yourself.



		Swift:
	

actor NetworkMonitor {
    // Wrapper for CurrentValueSubject<T, Never>.
    // $state projected value is a ValueSubject<T>.Publisher (AnyPublisher<T, Never>) to avoid leaking out the setter.
    @ValueSubject var state: NetworkState = .init()
   
    // Needed if wanting a protocol conformance sadly, unless I'm missing something.
    var statePublisher: ValueSubject<NetworkState>.Publisher { $state }
   
    // This requires isolation access
    private func updateNetworkState(...) {
        /* ... */
        state = .init(/* New State */)
    }
}

// Actor publishers can get a little tricker in view models, but you can still subscribe to them
// in a way that is surprisingly readable if you keep it simple.
await networkMonitor.$state.receive(on: .main).map(NetworkViewState.init).assign(to: &$networkViewState)
// NetworkViewState in this example would be some simple struct that might contain things like a label icon and title or something.


One trick I ran across for when you need to use sink() and the like is to use a result builder to let you "collect" AnyCancellable into a collection like a set. I use this when I do need to pass the value to a function for processing or to trigger other side effects.



		Swift:
	

private var cancellables: Set<AnyCancellable> = Set()

cancellables.collect {
    // Using sink to call functions when you need side effects.
    dataObject.publisher(for: \.albumTitle).sink(receiveValue: updateTitle)
    dataObject.publisher(for: \.albumArtist).sink(receiveValue: updateArtist)
}

func updateTitle(title: String?) { ... }
func updateArtist(artist: String?) { ... }


And I agree with the source of truth aspect. There's even a whole philosophy (one followed by Redux/React projects) around making a single source of truth: https://github.com/pointfreeco/swift-composable-architecture

The thing I don't like about how Redux has been used in projects I've worked on though is that the store is usually one thing in the code. So the store's data objects all live in one spot in the code. Reducers sit in another. Actions in yet another. So trying to understand the flow of a part of the app, such as authentication, becomes tiresome as there isn't just code saying: Here's the authentication state and it's reducers/actions. It makes the code a lot harder to learn and digest as a new engineer to a team. I much prefer to be able to see whole verticals of the model more easily.


----------



## Andropov

Nycturne said:


> Yeah, no worries. Please don't feel like I'm trying to prod at you here. I'm mostly just voicing my thoughts more to ensure I communicate effectively and avoid ambiguity.



No need to worry  I was doing the same.

I'd like to add that I'm learning a lot from this conversation too. It helps that I can put a lot of what we talked about on this thread to practice at work (we target iOS 15+). There have been a lot of moments when I stumbled upon some piece of code and remembered something from this thread.



Nycturne said:


> I'm surprised Apple doesn't already offer something like this as either part of @Published or another property wrapper. But they are pretty easy to create yourself.



Could it be that some of this 'missing parts' of the API is just Apple trying to steer developers to certain architectures / out of some patterns? Kind of an extreme example, but I remember people complaining about how you can't create a ViewModel in a parent view and set it as a @StateObject in a child view elegantly (see this SO post). After watching some of the SwiftUI talks (specially, _Demystify SwiftUI_), I feel like trying to do something like that is just fighting the system (if you want SwiftUI to persist that @StateObject between view inits, you shouldn't also want to initialize it every time the view body of the parent is executed).



Nycturne said:


> One trick I ran across for when you need to use sink() and the like is to use a result builder to let you "collect" AnyCancellable into a collection like a set. I use this when I do need to pass the value to a function for processing or to trigger other side effects.
> 
> 
> 
> Swift:
> 
> 
> private var cancellables: Set<AnyCancellable> = Set()
> 
> cancellables.collect {
> // Using sink to call functions when you need side effects.
> dataObject.publisher(for: \.albumTitle).sink(receiveValue: updateTitle)
> dataObject.publisher(for: \.albumArtist).sink(receiveValue: updateArtist)
> }
> 
> func updateTitle(title: String?) { ... }
> func updateArtist(artist: String?) { ... }



This is very nice too.



Nycturne said:


> The thing I don't like about how Redux has been used in projects I've worked on though is that the store is usually one thing in the code. So the store's data objects all live in one spot in the code. Reducers sit in another. Actions in yet another. So trying to understand the flow of a part of the app, such as authentication, becomes tiresome as there isn't just code saying: Here's the authentication state and it's reducers/actions. It makes the code a lot harder to learn and digest as a new engineer to a team. I much prefer to be able to see whole verticals of the model more easily.



YES! That's the exact same feeling I had with VIPER codebases. Everything is very compartimentalized and that should be a positive thing, but trying to understand the app flow as a first-timer is an absolute nightmare because you have to hunt down every affected piece of code throughout all the codebase. If the app also had network calls or any other completion handler heavy user, code could become almost undecipherable. Additionally, as the number of affected files grows, it becomes harder to keep everything inside your 'mental model' of the code.


----------



## Nycturne

Andropov said:


> Could it be that some of this 'missing parts' of the API is just Apple trying to steer developers to certain architectures / out of some patterns? Kind of an extreme example, but I remember people complaining about how you can't create a ViewModel in a parent view and set it as a @StateObject in a child view elegantly (see this SO post). After watching some of the SwiftUI talks (specially, _Demystify SwiftUI_), I feel like trying to do something like that is just fighting the system (if you want SwiftUI to persist that @StateObject between view inits, you shouldn't also want to initialize it every time the view body of the parent is executed).




I wouldn’t be surprised if that’s the case. I need to fix some that StateObject nonsense in my own code as well, so there are traps, sadly. It just hasn’t been a priority until I can get some other areas cleaned up.

That said, my wrapper is in the same vein as @Published, but simpler and more suitable for model objects that want or need to publish from background threads. It is really syntactic sugar to reduce the amount of boilerplate needed to use CurrentValueSubject in model objects and make the code a hair cleaner, but it isn’t a huge win unless you are doing a lot of custom model objects or actors. 



		Swift:
	

// Using CurrentValueSubject directly
// You need to manage making the publisher accessible yourself and guard the Subject.
private var state: CurrentValueSubject<MyStateStruct, Never>
public var statePublisher: AnyPublisher<MyStateStruct, Never> { state.eraseToAnyPublisher() }
// Modify by changing value in the value subject
state.value = .init(/* New State */)
    
// Using Property Wrapper
// $state is the type-erased publisher, similar to @Published
@ValueSubject private(set) var state: MyStateStruct
// Modify like any other property:
state = .init(/* New State */)




Andropov said:


> YES! That's the exact same feeling I had with VIPER codebases. Everything is very compartimentalized and that should be a positive thing, but trying to understand the app flow as a first-timer is an absolute nightmare because you have to hunt down every affected piece of code throughout all the codebase. If the app also had network calls or any other completion handler heavy user, code could become almost undecipherable. Additionally, as the number of affected files grows, it becomes harder to keep everything inside your 'mental model' of the code.




And I think the thing is, this is more of a “how are files placed on disk” problem, than a fundamental issue with the approach. Just give me a folder called “Authentication” that includes the state, reducers, etc and let me see the whole component. Then we can compose the different components into the global store.


----------



## Andropov

After a few more months working with (mostly) async-await only projects, I've detected one common antipattern that is sadly very common in the async/await code I've seen from many devs now. It's too easy to do something like this:


		Swift:
	

func fakeSynchronousFunction() {
    Task {
        // Do some function-dependent stuff
        // ...
        await fooAsynchronousFunction()
    }
}

Instead of the expected:


		Swift:
	

func asyncFunction() async {
    // Do some function-dependent stuff
    // ...
    await fooAsynchronousFunction()
}


Because the later requires the caller function to support concurrency and it's easier to slap a Task than to refactor the entire call stack to properly support concurrency. This leads to weird bugs and overall janky experience in the apps once you get enough standalone Tasks flying around causing a million different combinations of race conditions. Think for example:


		Swift:
	

Button(
    action: {
        fakeSynchronousFunction()
        dismiss()
    },
    label: {
        Text("FooButton")
    }
)


The dismiss may or may not be executed before the fakeSynchronousFunction is actually finished, and each possibility may have different effects on the app. I can't think of any good example of a legit use case for a synchronous function that only contains a Task. IMHO it should be the caller's responsibility to handle wether the function is executed concurrently or not, I don't think it makes any sense to _force_ a function to execute detached from the current flow. I wish this raised a warning.


----------



## Nycturne

I generally agree with the sentiment, part of the issue is that the main thread is a synchronous context. So without the framework jumping the boundary for you, this anti-pattern is going to be common in one form or another, I think. Because right now, it’s up to the developer to migrate work into a Task if it is asynchronous.

I wonder if the right call in these sort of scenarios are extensions on the components that require manual wrapping, like Button. In this specific example, providing a task as an action would help avoid this mixing. One thing I also notice is that there are folks that don’t see extending APIs as an option, even when it is. That said, these sort of extensions would look a lot like a “synchronous function that only contains a Task”:



		Swift:
	

extension Button {
    init(task: () async -> Void, label: () -> Label) {
        self.init(action: { Task(operation: task) }, label: label)
    }
}


Another approach is to treat actions like these as red flags if they pass in closures rather than functions. Enforce better code locality for the action itself.


----------



## Andropov

Nycturne said:


> That said, these sort of extensions would look a lot like a “synchronous function that only contains a Task”:



The main problem is not so much having synchronous functions that only contain a Task as it is having such things in intermediate parts of the call stack. I have thought of another example:



		Swift:
	

class FooViewModel {
    var fooVar: Bool
    
    func fetchFooVar() {
        Task {
            // Network call to fetch current value of fooVar goes here...
            // ...
            fooVar = resultOfNetworkCall
        }
    }
}

struct FooView: View {
    let viewModel = FooViewModel()
    
    var body: some View {
        Button(
            action: {
                fetchFooVar()
                if viewModel.fooVar {
                    // Do something
                } else {
                    // Do something else
                }
            },
            label: {
                Text("Foo Button!")
            }
        )
    }
}


On the code above, it's not immediately clear to the programmer nor the compiler that the button action is using an old value of fooVar (the network call surely won't get there in time). However, if we avoid wrapping a single Task into a function, it forces us to write the code at the call site (the Button's action, in this case) where any problems that could arise from asynchronous behaviours become much more evident:


		Swift:
	

class FooViewModel {
    var fooVar: Bool
    
    func fetchFooVar() async {
        // Network call to fetch current value of fooVar goes here...
        // ...
        fooVar = resultOfNetworkCall
    }
}

struct FooView: View {
    let viewModel = FooViewModel()
    
    var body: some View {
        Button(
            action: {
                Task {
                    await fetchFooVar()
                }
                if viewModel.fooVar {
                    // Do something
                } else {
                    // Do something else
                }
            },
            label: {
                Text("Foo Button!")
            }
        )
    }
}


Now it's way more obvious that fooVar probably isn't updated in time. It's also trivial to solve: you can just put the if/else logic inside the Task too, and it will only execute after the await returns. The closure we use to build the Button only contains a Task, but it's not a problem since that closure is the outermost call site. It's almost beautiful how properly annotating code as async forces you to solve concurrency bugs.



Nycturne said:


> I wonder if the right call in these sort of scenarios are extensions on the components that require manual wrapping, like Button. In this specific example, providing a task as an action would help avoid this mixing. One thing I also notice is that there are folks that don’t see extending APIs as an option, even when it is. That said, these sort of extensions would look a lot like a “synchronous function that only contains a Task”:
> 
> 
> 
> Swift:
> 
> 
> extension Button {
> init(task: () async -> Void, label: () -> Label) {
> self.init(action: { Task(operation: task) }, label: label)
> }
> }



Love this idea. I honestly was waiting for Apple to implement some version of this on SwiftUI, didn't realize it's trivial to just write your own.


----------



## Nycturne

Andropov said:


> Swift:
> 
> 
> class FooViewModel {
> var fooVar: Bool
> 
> func fetchFooVar() {
> Task {
> // Network call to fetch current value of fooVar goes here...
> // ...
> fooVar = resultOfNetworkCall
> }
> }
> }
> 
> struct FooView: View {
> let viewModel = FooViewModel()
> 
> var body: some View {
> Button(
> action: {
> fetchFooVar()
> if viewModel.fooVar {
> // Do something
> } else {
> // Do something else
> }
> },
> label: {
> Text("Foo Button!")
> }
> )
> }
> }
> 
> 
> On the code above, it's not immediately clear to the programmer nor the compiler that the button action is using an old value of fooVar (the network call surely won't get there in time). However, if we avoid wrapping a single Task into a function, it forces us to write the code at the call site (the Button's action, in this case) where any problems that could arise from asynchronous behaviours become much more evident:




Yeah, anything involving async code boundaries is where you’re going to have issues. Depending on what the if/else does in this example though, this might actually be better done as a published variable instead:



		Swift:
	

class FooViewModel {
    @Published var fooVar: Bool
  
    func fetchFooVar() {
        Task {
            // Network call to fetch current value of fooVar goes here...
            // ...
            // Always update your published variables on the main actor.
            await MainActor.run {
              fooVar = resultOfNetworkCall
            }
        }
    }
}

// Now, we can use onReceive to act on the updated variable.
// But depending on what we want, we don’t even need onReceive.
// The view will be refreshed on the variable update, and can act on the new state.
Button(action: viewModel.fetchFooVar, label: { Text(“Foo Button!”) })
  .onReceive(publisher: $viewModel.fooVar, action: { newValue in
    if newValue { … }
    else { … }
  })


You get a similar sort of thing here, where the async code is all pretty much visible in one spot, but now you are using the published variable as the signal for the view to do something such as update internal state. I’d consider the fact that you have a variable being updated as a side-effect a part of the problem. So either leverage that pattern appropriately so that _any_ updates to the variable trigger the behaviors, or realize the variable doesn’t need to exist and have fetchFooVar return the value, which forces you to address the async code that way. 



Andropov said:


> Love this idea. I honestly was waiting for Apple to implement some version of this on SwiftUI, didn't realize it's trivial to just write your own.




It became clear that you will want to do stuff like this to approach specific patterns in your own code, and these type of convenience initializers are something Swift supports quite well and will enforce access restrictions in extensions which is nice. But for someone like me that started learning OOP with C++ where you can’t do this sort of extensibility, it’s not always clear that this sort of helper is what you want. One of the issues I have with OOP is that it tends to get you thinking that objects you didn’t write are off limits for new code. Even if it’s the natural “location” for new code to live.

An example is that I wrote a convenience initializer for NavigationLink which checks a flag plus the OS version. If things check out, it calls the new value-based initializer added in SwiftUI 4, or creates the appropriate destination itself based on the value and passes that to the old destination-based initializer. So now I can just use NavigationLink where I like with my initializer, and it will work for both the new and old navigation schemes.

Unfortunately, there’s still too many bugs in the new navigation setup for me to switch to it full time in my app, but at least I can monitor it. *sigh*


----------



## Andropov

Nycturne said:


> You get a similar sort of thing here, where the async code is all pretty much visible in one spot, but now you are using the published variable as the signal for the view to do something such as update internal state. I’d consider the fact that you have a variable being updated as a side-effect a part of the problem. So either leverage that pattern appropriately so that _any_ updates to the variable trigger the behaviors, or realize the variable doesn’t need to exist and have fetchFooVar return the value, which forces you to address the async code that way.



Yes, reorganizing the code to be reactive will solve many of the problems caused by having too many in-flight tasks. I still think there are many other, subtler problems that will arose if this kind of pattern is abused. Acting on value changes instead of getting/setting variables manually is one part of it —and would prevent many, many errors—, but ultimately I'd still want the dispatch of the functions to be specified at the call site rather than inside the function itself. 
Actually, I think reactivity in general and SwiftUI in particular are partially responsible of this pattern of putting single tasks inside non-async functions: as long as you are updating a @Published variable or something like that inside the function, you'll see no adverse effects of using that pattern (not at first, at least). Things start to get *very* messy once those functions update more than one or two variables: while a properly written async/await code might have awaited for the full set of variables to update (and then update the view with a full set of correct values), trying to do that with functions that internally dispatch Tasks may be a disaster. 

Writing this reminded me of something. I tried to write a minimal-example of it. Below are two versions of a code *with the same subtle bug*: 

With async/await all the way:


		Swift:
	

class FooViewModel {
    @Published var position: CGPoint
    @Published var size: CGFloat
    
    func getPosition() async -> CGPoint {
        return await networkFunctionToGetPosition()
    }
    func getSize() async -> CGFloat {
        return await networkFunctionToGetSize()
    }
    @MainActor func updateRectangle() async {
        self.position = await getPosition()
        self.size = await getSize()
    }
}
struct FooView: View {
    @StateObject var viewModel = FooViewModel()
    var body: some View {
        ZStack {
            Rectangle()
                .offset(
                    x: viewModel.position.x,
                    y: viewModel.position.y
                )
               .frame(
                   width: viewModel.size,
                   height: viewModel.size
               )
        }
    }
}


With async/await bridged to Tasks too soon:


		Swift:
	

class FooViewModel {
    @Published var position: CGPoint
    @Published var size: CGFloat
    
    func getPosition() {
        Task { @MainActor in
            self.position = await networkFunctionToGetPosition()
        }
    }
    func getSize() {
        Task { @MainActor in
            self.size = await networkFunctionToGetSize()
        }
    }
    func updateRectangle() {
        getPosition()
        getSize()
    }
}
struct FooView: View {
    @StateObject var viewModel = FooViewModel()
    var body: some View {
        ZStack {
            Rectangle()
                .offset(
                    x: viewModel.position.x,
                    y: viewModel.position.y
                )
               .frame(
                   width: viewModel.size,
                   height: viewModel.size
               )
        }
    }
}


Both have the same problem. They will work great _most of the time_ some part of the codebase calls the updatePosition() method of the view model. But every once in a while, the UI will jump around because the rectangle size was updated with a new size a frame before the position updated its value. The example is trivial to solve if you use async/await all the way up:



		Swift:
	

class FooViewModel {
    @Published var position: CGPoint
    @Published var size: CGFloat
    
    func getPosition() async -> CGPoint {
        return await networkFunctionToGetPosition()
    }
    func getSize() async -> CGFloat {
        return await networkFunctionToGetSize()
    }
    @MainActor func updateRectangle() async {
        let newPosition = await getPosition()
        let newSize = await getSize()
        self.position = newPosition
        self.size = newSize
    }
}
struct FooView: View {
    @StateObject var viewModel = FooViewModel()
    var body: some View {
        ZStack {
            Rectangle()
                .offset(
                    x: viewModel.position.x,
                    y: viewModel.position.y
                )
               .frame(
                   width: viewModel.size,
                   height: viewModel.size
               )
        }
    }
}


It's also easier to catch: you don't have to look up getSize nor getPosition to see whether they update size and position synchronously or not. The compiler will error out if you forget the await, and once you write the await you'll immediately see that there's a suspension point between the update of the position and the update of the size. It would be very hard IMHO to have it work in sync just by reacting to the changes of position and size.

This example is a minimal version of something I actually saw when someone else tried to fix a weird iOS-15-only main thread crash by peppering around Tasks on some imperative code that computed a complex layout. Sometimes the frame updates landed between updates and the layout had both new values for some variables and old values for others, causing unpredictable jittering and jumping around. Avoiding this intermediate states required getting rid of all the Tasks and moving the synchronous to asynchronous boundary to the outermost call site (the one function that computed the whole layout, which I changed from synchronous to async).

In any case, if someone new starts working with the codebase, knowing nothing about it, they shouldn't have to go through every function's implementation to see whether those functions have immediate effects or not. Writing a single Task inside a function instead of going async all the way also disallows the call site to explicitly detach the task with Task.detached, or to suspend the execution until the task is finished... you'd have to refactor the function if you ever need to do that.


----------



## Nycturne

I think we’d approach this particular bug differently. As you say, I would take a more Reactive approach. 

You’ve got two coupled variables, each with their own publisher backing it. So yeah, it’s easy to get them desync’d when any sort of async behavior occurs (not just from concurrency). One approach to ensure both get set in the same synchronous context, as you’ve done, and it does require that nobody breaks the implicit assumption that the fetches must complete before the published variables are updated. Another approach would be to join your coupled variables together to make the published variable more atomic: @Published var frame: CGRect.



		Swift:
	

@MainActor func updateRectangle() async {
    self.frame = CGRect(
        origin: await getPosition(),
        size: await getSize() // assuming this returns a CGSize instead for brevity
    )
}


I use this approach for state that must be updated at the same time, or is strongly coupled. An example is with a music player, all the different fields for the currently playing item is a single published value, rather than a published value for each field. In terms of performance, there's no real difference between using structs as published values, or individual fields that all get updated at once in the same synchronous context, so we are free to do what makes sense in the moment.

But I also follow a couple guidelines in my own MVVM process that I think skews my thinking here differently than yours:

- Views IMO shouldn't own actions. Much like a Button doesn't actually act on you tapping on it, but delegates it, a View should delegate it's actions to the View Model. In which case, which is the call site? The View, or the View Model? From my point of view, the View Model is the one that owns responsibility for determining if things need to become async or not, not the View. 
- View Models should generally not hold _unique_ state for itself unless it's specific to the view that observes it. Instead, it should help bind to the model in a way that makes sense. So in this sense, the View Model should further delegate manipulations of state to the model, and observe the changes, updating itself based on that. Effectively, the View Model follows the adapter pattern for state changes in this scenario. So I don't do a ton of stuff like updateRectangle() in my own code, and instead are taking observed updates from the model and either providing accessors, or in the case where adapters aren't necessary, bind the value's struct directly to the View Model's publisher.


----------



## Andropov

Nycturne said:


> You’ve got two coupled variables, each with their own publisher backing it. So yeah, it’s easy to get them desync’d when any sort of async behavior occurs (not just from concurrency). One approach to ensure both get set in the same synchronous context, as you’ve done, and it does require that nobody breaks the implicit assumption that the fetches must complete before the published variables are updated. Another approach would be to join your coupled variables together to make the published variable more atomic: @Published var frame: CGRect.



I agree this is indeed the right approach if the variables can be decoupled.



Nycturne said:


> - Views IMO shouldn't own actions. Much like a Button doesn't actually act on you tapping on it, but delegates it, a View should delegate it's actions to the View Model. In which case, which is the call site? The View, or the View Model? From my point of view, the View Model is the one that owns responsibility for determining if things need to become async or not, not the View.



I understand your point, and I don't want to get too tangled up on the Button example. I didn't mean to imply that having functions wrap a single task is _always_ a bad idea. It was a mere observation of how throwing Tasks around to bridge sync to async code was being abused in practice (in my experience, that is). I think that *by default* functions that contain a single task should be declared async to let the callers know that the function is not executed immediately, unless there's a good reason to not do so. At the very least a lot of care should be put in choosing where to bridge sync and async code. 

If a perform a bunch of writes to a database from *seemingly* non-async functions serially, I would expect those writes to perform in order:


		Swift:
	

func writeStuff(in repository: FooRepository){
    repository.writeA()
    repository.writeB()
    repository.writeC()
}


I wouldn't be amused if writeC happened before writeA, which is entirely possible if they're only dispatching work with Task without waiting for completion. This is particularly bad because I would have no way to serialize entries without changing the writeA/B/C implementation, as I have no way of knowing then each function has actually finished. 

I think it's also best to inform the compiler as much as possible of what's happening. Is the function executing work synchronously? No? Then annotate it as async or provide a completion handler. The compiler will help you to avoid forgetting this at the call sites. 
Is it safe to assume that the caller won't make assumptions about when the function is finished? Is the asynchronous work that the function is executing irrelevant for the callers (for example, writeA using a synchronous dispatch queue for writes, which would make the write functions behave as if it's synchronous to the callers)? Then the async or completion handler can be dropped from the function signature.


----------



## Nycturne

Andropov said:


> I understand your point, and I don't want to get too tangled up on the Button example. I didn't mean to imply that having functions wrap a single task is _always_ a bad idea. It was a mere observation of how throwing Tasks around to bridge sync to async code was being abused in practice (in my experience, that is). I think that *by default* functions that contain a single task should be declared async to let the callers know that the function is not executed immediately, unless there's a good reason to not do so. At the very least a lot of care should be put in choosing where to bridge sync and async code.




Oh no real disagreement on the abuse part. And the fact that concurrent code in JavaScript, C# and Swift all incur this need to switch between a synchronous context and an asynchronous one in some manual way kinda sucks. GCD has this issue too where I should generally have async functions provide completion callbacks, but if I don’t, these same sort of issues crop up. 

I guess my question becomes: how should these bridges be done? 

But maybe we aren’t that far off in thinking here. My take is that Views should be free of async code, and Models should be honest about what they are doing (which is another way of saying if it is async, it should be marked async like you say). Which leaves the ViewModel as that sort of boundary between the two worlds. Functions I write tend to become async if they themselves depend on anything async, up until the point where I then have to bridge because otherwise I’d “contaminate“ the view. So ultimately, for me, the view model is always the place where an action enters the asynchronous world, and things like Combine publishers and marking ViewModels as @MainActor let me simplify the older GCD pattern in many places and not need to manually call MainActor.run.


----------



## Andropov

Nycturne said:


> Oh no real disagreement on the abuse part. And the fact that concurrent code in JavaScript, C# and Swift all incur this need to switch between a synchronous context and an asynchronous one in some manual way kinda sucks. GCD has this issue too where I should generally have async functions provide completion callbacks, but if I don’t, these same sort of issues crop up.



Yes, it's technically possible to abuse GCD in the same way. I saw it less often in practice though. Probably because there were less places where asynchronous behaviour had to be handled. But I think it's ultimately for the best to have more of the multithread capabilities exposed to the compiler, even if some antipatterns arise while people get around to how to use it.



Nycturne said:


> I guess my question becomes: how should these bridges be done?
> 
> But maybe we aren’t that far off in thinking here. My take is that Views should be free of async code, and Models should be honest about what they are doing (which is another way of saying if it is async, it should be marked async like you say). Which leaves the ViewModel as that sort of boundary between the two worlds. Functions I write tend to become async if they themselves depend on anything async, up until the point where I then have to bridge because otherwise I’d “contaminate“ the view. So ultimately, for me, the view model is always the place where an action enters the asynchronous world, and things like Combine publishers and marking ViewModels as @MainActor let me simplify the older GCD pattern in many places and not need to manually call MainActor.run.



IMHO, as far up in the call chain as reasonable within the practical/architectural constraints of the app. The ViewModel is a good place to do it if you don't want actions handled on the View. It's also a natural place to do it since, as you say, it bridges the synchronous Views (always on the main thread) with the Models (hopefully async). And the potential of misuse is limited, since the ViewModels are often accessed by one view and one view alone.

Another thing I've noticed regarding how async/await is being implemented in practice: Actors are rarely/never seen, despite how powerful they are. Hm.


----------



## Nycturne

Andropov said:


> Another thing I've noticed regarding how async/await is being implemented in practice: Actors are rarely/never seen, despite how powerful they are. Hm.




I’ve noticed that in my own code, but partly because my model is built around CoreData, making the context’s background thread the “actor” I use the most outside of the MainActor. 

Actors are useful, but they can also be a pain due to being async by nature. I’ve played with them, but wind up mostly using them for internal services. Combine almost always gets involved as well.


----------



## Andropov

Nycturne said:


> I’ve noticed that in my own code, but partly because my model is built around CoreData, making the context’s background thread the “actor” I use the most outside of the MainActor.
> 
> Actors are useful, but they can also be a pain due to being async by nature. I’ve played with them, but wind up mostly using them for internal services. Combine almost always gets involved as well.




I think I've never written one for a production app. Just played around with them a bit.

Btw, regarding CoreData: I found out a few months ago that NSManagedObject properties subclass ObservableObject, so you can react to NSManagedObject changes in properties in a View. Makes some use-cases super easy to implement, especially if you allow models in the View (I guess it's a bit more cumbersome with Model-View-ViewModel patterns).


----------



## Nycturne

Andropov said:


> I think I've never written one for a production app. Just played around with them a bit.
> 
> Btw, regarding CoreData: I found out a few months ago that NSManagedObject properties subclass ObservableObject, so you can react to NSManagedObject changes in properties in a View. Makes some use-cases super easy to implement, especially if you allow models in the View (I guess it's a bit more cumbersome with Model-View-ViewModel patterns).






Yup, it is handy in some cases. If you want to use a pure MVVM pattern, you can always use combine as the binding mechanism. Not only is NSManagedObject observable, you can request publishers for specific properties.



		Swift:
	

class MyViewModel: ObservableObject {
  @Published var title: String = “Default Title”

  init(dataObject: MyManagedObject) {
    // This will assign the current value of title on the dataObject immediately if it isn’t nil.
    // And update the published variable any time it changes.
    dataObject.publisher(for: \.title).compactMap({ $0 }).assign(to: &$title)
  }
}


But because @Published can seemingly access MyViewModel’s objectWillChange(), I do wonder if this pattern might make sense as well:



		Swift:
	

class MyViewModel: ObservableObject {
  var title: String { dataObject.title ?? “Default Title” }
  @ObservedModel private var dataObject: MyManagedObject

  init(dataObject: MyManagedObject) {
    self.dataObject = dataObject
  }
}


Where @ObservedModel is a custom property wrapper that listens for the objectWillChange() from the NSManagedObject, and forwards it to MyViewModel’s objectWillChange(). I haven’t tried this, but someone has written up how to access the enclosing instance of a property wrapper that would be the basis of such a thing: https://www.swiftbysundell.com/articles/accessing-a-swift-property-wrappers-enclosing-instance/


----------

