Exercise 3: Why the Protocol Enables Substitution, and Why the Default Value Keeps App Code Unchanged — Possible Solution ================================================================================================================================== If TaskListViewModel's own apiClient property were typed as the concrete struct APIClient directly, Swift's own real type system would only ever accept an actual APIClient instance in that position - FakeAPIClient, despite implementing the exact same real methods, is a genuinely DIFFERENT concrete type, and Swift would reject passing it in as a real compile error, since the two types share no relationship to each other at all beyond having similarly-named methods. Typing apiClient as the protocol APIClientProtocol instead changes what the compiler actually requires: ANY type that conforms to that protocol - real APIClient, or FakeAPIClient, or any future implementation - satisfies the requirement equally, because Swift is checking conformance to a shared, abstract contract rather than identity with one specific concrete type. This is precisely what makes substitution possible at all: the protocol is the real, common ground both the real and fake implementations share, and it's the only thing TaskListViewModel's own code actually depends on or needs to know about. The default parameter value, init(apiClient: APIClientProtocol = APIClient()), solves a separate, related real problem: without it, every single existing call site constructing a TaskListViewModel throughout the real app would need to be found and updated to explicitly pass an APIClient() argument the moment this refactor happened, even though production code always wants the same real APIClient() everywhere anyway. The default value means TaskListViewModel() still compiles and behaves identically to before the refactor for ordinary real app code, while tests and Previews retain the option to explicitly override that default with FakeAPIClient() when they specifically need to. ANSWER: Depending on the protocol APIClientProtocol rather than the concrete APIClient struct is what makes substitution possible, because Swift only requires conformance to a shared abstract contract - not identity with one specific concrete type - so any conforming type, real or fake, satisfies the same requirement. The default parameter value means ordinary app code (TaskListViewModel()) needs no changes at all after the refactor, since it still resolves to the real APIClient() by default, while tests and Previews retain the separate option to explicitly pass in a fake instead. WHY THIS WORKS AS AN ANSWER ------------------------------ This correctly explains the real mechanism (protocol conformance vs. concrete-type identity) that enables substitution, and separately explains why the default parameter value specifically preserves backward compatibility for existing app code.