So as a general rule, we need to conform View Models to MainActor for the sake of UI updates happening on the MainActor, but that causes contention on the MainActor when testing.

I wonder if there’s a way to default isolate, maybe via a parameter, to the MainActor, but for the sake of testing to allow it to be effectively nonisolated? #swiftlang #iosdev

@rhysmorgan I always make view models MainActor because they wind up having lots of things that rely on main actor (such as observing other main actor types, notifications, Combine, etc), but if you truly have view models that have no such dependencies, I wouldn’t think marking them main actor would be needed. In Swift 6, non-Sendable types just have to stay where they’re created, so if they’re created on the main actor, they should be fine there.
@rhysmorgan for me, non-Sendable types rarely work because I always bump into something that doesn’t quite work (like an unstructured Task), but if you’ve built a Swift 6 structured concurrency system without all those sharp edges, I’d expect non-Sendable types to be the right answer.
@cocoaphony Yes, I don’t have a proper Swift 6 structured concurrency system in the codebase I work in daily haha.
Trying to understand best practices around non-Sendable types - especially for View Models, as we move more features to Swift 6.
@rhysmorgan My experience in larger projects is that running tests in parallel is dicey and I avoid it. Getting everything perfect is too hard. There can't be any singletons in the system. Even basic stuff like NotificationCenter bite you unless you're really careful. I prefer to just accept that singletons (sharedInstance) are good actually, and make them testable. But the trade-off is that I don't even try to make them parallel on the same simulator (I shard to multiple simulators sometimes).
@rhysmorgan I don’t think that general rule holds up as well as it once did. I believe you should be defaulting to nonisolated and using MainActor only when it is truly essential. And it’s not that often!
@mattiem Oh, even for View Models? Is that with the approachable concurrency settings specifically, or in general?
@rhysmorgan @mattiem just switch default isolation to nonisolated.
@matadan @mattiem Oh, I always do. I hate the MainActor-default isolation mode.
What I want to achieve is the ability to use non-isolated View Models that can be observed from views, and ideally aren't split-isolation types.

@rhysmorgan @matadan I think NonisolatedNonsendingByDefault makes it much more ergonomic definitely (Approachable Concurrency turns that on).

Nonisolated is the best option here for this kind of work for sure.

I’m pretty sure you can do what you want here in general. The biggest problem is usually callbacks.

@mattiem @matadan Yes, nonisolated(nonsending) is one of the features that @pointfreeco have briefly covered at the start of their newest series.
Their new tooling for TCA 2.0 is going to allow features to be MainActor-isolated when necessary, and totally non-MainActor-isolated to avoid contention, and allow full parallelisation when testing.
I'm keen to figure out if there's a way to achieve the same thing with View Models, where the core of the feature isn't wrapped in that harness.
@rhysmorgan @matadan @pointfreeco I don’t think the concept of “ViewModel” is particularly special here. All that really matters is if you have dependencies on MainActor functions inside the type. You should try I bet it works!
@mattiem @rhysmorgan @pointfreeco I was going to say the same thing. ;)
@mattiem @matadan @pointfreeco OK, lol, this it totally breaking my brain now.
This all just compiles and works, in Swift 6 mode with Approachable Concurrency, even without the View Model being MainActor-isolated? Even tho the MainActor-isolated View is observing non-MainActor-isolated state? And this allows me to write non-MainActor-isolated Swift Testing tests, and avoid MainActor-contention? 🤯

@rhysmorgan @matadan @pointfreeco you got it. Here I wrote a bit about this technique in case it helps.

https://www.massicotte.org/blog/non-sendable-first-design/

Non-Sendable First Design

The easiest way to design concurrent systems in Swift was hiding in plain sight.

massicotte.org

@mattiem @matadan @pointfreeco I am... quite confused right now (about to read your post), but also very happy that this is possible.

https://github.com/rhysm94/NonIsolatedViewModels

Pushed this to GitHub as a demo, explicitly adding `nonisolated` to the View Model just to be 110% sure. And it just works. nonisolated View Model, properties updated from an async method being read from a MainActor-isolated View... and it just works. 🤯 So what I wanted is possible - no MainActor contention for tests!

@mattiem @matadan @pointfreeco

"Side-stepping the problem entirely, with a simpler type no less, almost feels like cheating."

Yep. Yep, it absolutely does feel like cheating! This is genuinely revolutionary – especially given I'm working in a codebase that's undergoing architectural + testing refactoring, and I can make these kinds of improvements at this early stage!!

@rhysmorgan @mattiem @pointfreeco nonisolated things can run on MainActor because they are not isolated to another isolation context. I think that’s right. It is difficult to get straight TBH.
@matadan @mattiem @pointfreeco I’m just shocked that even with an Observation happening from that nonisolated state, with the update being triggered from a nonisolated, @.concurrent async closure, Swift and SwiftUI are all happy, all OK!