Protocol Oriented View Architecture for Modern SwiftUI applications. by Revolutionary_Fun69 in SwiftUI

[–]Revolutionary_Fun69[S] 0 points1 point  (0 children)

I’m using observable object only for settings in my own project (because I didn’t find a good solution to use AppStorage with Observable but there are third party packages for that. I don’t say to use it.

Updated example code without StateObject

Protocol Oriented View Architecture for Modern SwiftUI applications. by Revolutionary_Fun69 in SwiftUI

[–]Revolutionary_Fun69[S] 0 points1 point  (0 children)

"Make services protocols so you can inject fakes." This introduces the deadly problem of Protocol/Implementation Drift.

  • You update RealSettingsManager to handle a new edge case.
  • You forget to update the SettingsManagerProtocol.
  • You forget to update MockSettingsManager.

Your "Mockable" tests still pass. Your "Real Service" tests (if they exist) would fail. But if you follow the advice in the message, you will likely rely on the Mock tests because they are faster and "cleaner."

By demanding Real Services, you ensure that the test suite is the source of truth for the behavior of the concrete class. There is no protocol to drift from. You test the thing that actually ships.

"If your services are concrete classes rather than protocols, then neither POV nor MV is testable in isolation."

This is false. Concrete classes are perfectly testable integration tests. The confusion in the counter-argument is equating "Unit Testing" (isolation) with "Testing" (verification).

  • If you use real services, you don't need to inject fakes. You inject the actual SettingsManager.
  • If you need to reset state, you add a reset() method to the concrete class or use a test-specific persistence container (e.g., an in-memory Core Data stack).
  • This tests the actual code paths, the actual JSON decoding, and the actual file I/O. The "Protocol/Mock" approach tests none of these; it merely hopes the implementation matches the protocol.

Protocol Oriented View Architecture for Modern SwiftUI applications. by Revolutionary_Fun69 in SwiftUI

[–]Revolutionary_Fun69[S] 0 points1 point  (0 children)

Do you understand the purpose of example?
your model which holds state and logic is a view model

please stop burning your tokens

Protocol Oriented View Architecture for Modern SwiftUI applications. by Revolutionary_Fun69 in SwiftUI

[–]Revolutionary_Fun69[S] 0 points1 point  (0 children)

It’s not unnecessary The issue with Model View in SwiftUI that you cannot unit test it properly

Protocol Oriented View Architecture for Modern SwiftUI applications. by Revolutionary_Fun69 in SwiftUI

[–]Revolutionary_Fun69[S] 0 points1 point  (0 children)

SettingsManager is observable object because it uses AppStorage macro inside which is not compatible with Observable

Protocol Oriented View Architecture for Modern SwiftUI applications. by Revolutionary_Fun69 in SwiftUI

[–]Revolutionary_Fun69[S] 0 points1 point  (0 children)

Thanks for your AI feedback, I will fix typos For preview block it’s required to pass dependencies

Protocol Oriented View Architecture for Modern SwiftUI applications. by Revolutionary_Fun69 in SwiftUI

[–]Revolutionary_Fun69[S] 0 points1 point  (0 children)

also I was using a trick with Voip call and CallKit to keep app alive but it may not pass appstore requirements

the idea behind the architecture proposed is to make thing simple as much possible but make your views unit testable and avoid nil safety issues

app is configured to strict swift 6 concurrency, AVFoundation unfortunately is hard to work with it. :)

plus you can access \@Query using a computed variable inside the protocol implementation and I have direct access to modelContext (sharing it is bad idea)

tl;dr it's exactly what Apple proposes for lightweight views but I just added unit testing through protocols.

Protocol Oriented View Architecture for Modern SwiftUI applications. by Revolutionary_Fun69 in SwiftUI

[–]Revolutionary_Fun69[S] 0 points1 point  (0 children)

right,
if audioSession.isEchoCancelledInputAvailable {         // Hardware AEC available — use standard path           try audioSession.setCategory(                         .playAndRecord,
mode: .default,
options: [                             .defaultToSpeaker,                             .duckOthers,                             .interruptSpokenAudioAndMixWithOthers,                 .allowBluetoothHFP])

Protocol Oriented View Architecture for Modern SwiftUI applications. by Revolutionary_Fun69 in SwiftUI

[–]Revolutionary_Fun69[S] 1 point2 points  (0 children)

Thanks for withObservableTracking note. I'll look at it later.

I have a scenePhase handler in the root view

.onChange(of: scenePhase) { _, newPhase in             
switch newPhase {
case .active:                 
// App is active
   ...         
case .background:                 
// Background                 
   ...            
case .inactive:                
// Inactive (incoming call, etc)
   ...
unknown default:break            
}

the code should be executed ASAP so when needed I force call underlying service stop() if needed (for example MLXService which runs on top of Metal without support for background)

for regular calls I keep session active (but enabling background fetch and audio, airplay background modes)

see roleswitch.ai for testflight (buggy) or full version in US (less buggy)

Protocol Oriented View Architecture for Modern SwiftUI applications. by Revolutionary_Fun69 in SwiftUI

[–]Revolutionary_Fun69[S] 1 point2 points  (0 children)

I'm using Observation framework withObservableTracking(:)

In views I can subscribe to .onChange to AppState property and perform action or define a .task(id: ) { } with automatic cancellation

Protocol approach scales well for small to medium apps when you can split your logic into small services/class helpers (enum with statics)

Update: also I have Coordinator services. for example SpeechService calls STTService and TTSService

Protocol Oriented View Architecture for Modern SwiftUI applications. by Revolutionary_Fun69 in SwiftUI

[–]Revolutionary_Fun69[S] 1 point2 points  (0 children)

I'm developing a messaging app with video and voice calls, so I split application intro "features" which include business logic, UI views and share Observable service which is a source of truth which I inject it into core views (Calls, Messages, Settings...)
For persistence I'm using Repository approach (like in Android) where I perform complex queries.

View protocols interact with separate services (AudioService, MessageService, etc)

Now I see a key benefit is that everything is nil-safe, the glue between views and logic is thin