Created
November 15, 2021 16:56
-
-
Save TheiOSDude/929488c379ad17933e5f6b45041d6078 to your computer and use it in GitHub Desktop.
Swift DI with Factories
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
// Services conforming to protocols. | |
// SOLID: Depend on abstractions, not concrete implementations. Dependency Inversion principle | |
// DECLARE FIRST SERVICE | |
protocol MyFirstServiceProtocol { | |
func myFirstMethod() | |
} | |
class MyFirstServiceImplementation: MyFirstServiceProtocol { | |
func myFirstMethod() { | |
print("My First Service") | |
} | |
} | |
// DECLARE SECOND SERVICE, | |
protocol MySecondServiceProtocol { | |
func mySecondMethod() | |
} | |
class MySecondServiceImplementation: MySecondServiceProtocol { | |
func mySecondMethod() { | |
print("My second service") | |
} | |
} | |
// DECLARE FACTORIES, by protocol, allows us to swap out the implementating class later, when we subclass the `container`. | |
protocol FirstServicesFactory { | |
var firstService: MyFirstServiceProtocol { get } | |
} | |
protocol SecondServiceFactory { | |
var secondService: MySecondServiceProtocol { get } | |
} | |
// Container, each dependency factory must be conformed to here, to provide its implementing class. | |
class ServicesContainer: FirstServicesFactory, SecondServiceFactory { | |
var firstService: MyFirstServiceProtocol { | |
MyFirstServiceImplementation() | |
} | |
var secondService: MySecondServiceProtocol { | |
MySecondServiceImplementation() | |
} | |
} | |
// now for an example VM | |
final class MyViewModel { | |
// declare what service factories (Dependencies) you need for this class. | |
let services: FirstServicesFactory & SecondServiceFactory | |
init(services: ServicesContainer = ServicesContainer()) { | |
self.services = services | |
} | |
func doThat() { | |
// They become available, like so | |
services.secondService.mySecondMethod() | |
services.firstService.myFirstMethod() | |
} | |
} | |
// Another example. | |
final class MySecondViewModel { | |
let services: FirstServicesFactory | |
init(services: ServicesContainer = ServicesContainer()) { | |
self.services = services | |
} | |
func doThis() { | |
services.firstService.myFirstMethod() | |
} | |
} | |
class MockFirstService: MyFirstServiceProtocol { | |
func myFirstMethod() { | |
print ("I am the mocked implementation") | |
} | |
} | |
// What about mocks/stubs. | |
// We can subclass the ServicesContainer here, for a Mock Container, containing mock dependencies. | |
// You can override as little or as many services as you desire. | |
class MockServicesContainer: ServicesContainer { | |
override var firstService: MyFirstServiceProtocol { | |
return MockFirstService() | |
} | |
} | |
// Putting that theory to test | |
let thisVM = MyViewModel(services: ServicesContainer()) | |
thisVM.doThat() | |
let thisVMMocked = MyViewModel(services: MockServicesContainer()) | |
thisVMMocked.doThat() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here is a better version. Stop using protocols for everything. They're gonna look in protocol witness table anyway, which is less performant, so why not skip this lookup at all.