Michael Long
3 min readDec 29, 2022

--

In no particular order.

1) It's fairly apparent that you've never written a commercial-scale application that needs to communicate with multiple APIs and multiple services, each with varying session requirements that tend to require different default headers, requirements for cert pinning, etc.. In such environments managing and maintaining multiple URLSessions is fairly common.

2) Totally agree that one should conform to interfaces, however, many of those aforementioned network APIs may each depend on getting a correct configured session behind the scenes.

3) If a "real" network service has more than one fetch implementation, then you've made swapping it out that much more difficult for testing. Of course, you've indicated your disdain for testing several times, so I guess that one's a wash.

4) Nice setup on the "real men" straw man. I don't believe anyone ever suggested EVERYTHING go into a single MVVM "sink". Which is why a VM typically has injected services and why one might want to alter the services provided. And not to harp on this further, but real-world applications often have testing and code coverage requirements.

5) Moving on, in large-scale applications VM's typically have to merge multiple API services, manage user settings, analytics libraries, A/B testing and feature management tools, key stores, and so on. That work, tied to a specific view, has to happen somewhere. We don't want that code in the view and we do want it where we can test it. Hence the View Model.

6) BTW, a single "fetch" protocol looks pretty on a dependency chart, but again in many applications any given API call may need additional headers, parameters, and have its own function signature with parameters marshaled into JSON/XML. All of which, I might add, tends to preclude everything from being handled in a single simple-minded protocol extension. You could pass many of those things in as a URL parameter but then, as you mention, then the external code knows too much about the network request that may not be a network request.

7) For many applications DI is overkill. That said, once you begin to use it in a given application then you do, in fact, want to use it all of the time. Having half the app do things one way and the other half the other way is a recipe for disaster.

8) It's a little strange that you rant against DI and anti-patterns, but your solution to the A>B>C>D>E problem starts throwing everything into static Singletons that only serve to tightly couple dependencies and services together even further.

So let's talk Resolver.

Again, it's true that DI containers can be overkill for some applications. But to repeat, in commercial-grade financial applications like banking and trading, the number of screens and the amount of functionality and services and dependencies being managed starts to multiply exponentially and *some* mechanism for managing complexity is needed.

The fatal error aspect comes about from many people refusing to deal with optionals and is an indication that a registration is missing. That's a typical issue in Swinject and Resolver and other container-based systems where failure to provide a registration can result in a problem. One that's usually obvious the first time you run the app and attempt to test a feature. The error message tells you what's missing.

But things could still be better, and that's why I wrote Factory, the successor to Resolver that's compile-time safe.

As to locking, I’m not sure what to say here.

Look deeper into Resolver, or Swinject, or RxSwift, or any commercial-grade application library that's manipulating internal state (caching) and has to work correctly in multi-threaded environments. As I'm sure you're aware, failure to manage access to shared state can result in data corruption and crashed applications.

Further, the last thing you want is someone blindly adding a bunch of locks at all of the call sites in order to prevent a crash that could have been avoided. And, more than likely, creating their own race conditions and deadlocks in the process.

Handling such things is a real-world issue, it can't be ignored, and the difficulty in doing so is why Apple added Actors and the new concurrency model to Swift in the first place.

You stated that you didn’t bother looking further into the issue and I’m afraid that it shows.

Could the author of the Ray Wenderlich article have chosen better examples? Absolutely. Do they deserve this hack job? I think not.

--

--

Michael Long
Michael Long

Written by Michael Long

I write about Apple, Swift, and SwiftUI in particular, and technology in general. I'm also a Lead iOS Engineer at InRhythm, a modern digital consulting firm.

No responses yet