Last active
March 10, 2022 07:41
-
-
Save jeneiv/be39be5eb930d7bf7f7023368d51b862 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
// MARK: - DI | |
// MARK: Injected Property Wrapper | |
@propertyWrapper | |
public struct Injected<DependencyType> { | |
private var dependency : DependencyType | |
private var injector : Injector | |
public init(injector : Injector = Injector.shared) { | |
self.injector = injector | |
dependency = try! injector.injected() | |
} | |
public var wrappedValue : DependencyType { | |
dependency | |
} | |
} | |
@propertyWrapper | |
public struct LazyInjected<DependencyType> { | |
private var injector: Injector | |
public init(injector: Injector = Injector.shared) { | |
self.injector = injector | |
} | |
public lazy var wrappedValue: DependencyType = { | |
try! injector.injected() | |
}() | |
} | |
// MARK: Injector | |
public class Injector { | |
public enum InjectorError: Error { | |
case unregisteredDependencyType(type: Any.Type) | |
} | |
public static let shared = Injector() | |
private var instances = [String: AnyObject]() | |
private var factories = [String: () -> Any]() | |
private var constructorFunctions = [String: () -> Any]() | |
private var staticTypes = [String: Any.Type]() | |
public func register<ServiceType>(staticType: Any.Type, for type: ServiceType.Type, override: Bool = false) { | |
let key = String(describing: type.self) + ".Type" | |
if !staticTypes.keys.contains(key) || override { | |
staticTypes[key] = staticType | |
} | |
} | |
public func registerInstance<DependencyType: AnyObject, ServiceType>(_ instance: DependencyType, type: ServiceType.Type, override: Bool = false) { | |
let key = String(describing: type.self) | |
removeDependencyIfOverwritten(type: type, overwritten: override) | |
if !instances.keys.contains(key) || override { | |
instances[key] = instance | |
} | |
} | |
public func registerFactory<DependencyType>(_ factory: @escaping () -> DependencyType, override: Bool = false) { | |
let key = String(describing: DependencyType.self) | |
removeDependencyIfOverwritten(type: DependencyType.self, overwritten: override) | |
if !factories.keys.contains(key) || override { | |
factories[key] = factory | |
} | |
} | |
public func registerConstructor<DependencyType: Any, ServiceType>(_ constructor: @escaping () -> DependencyType, type: ServiceType.Type, override: Bool = false) { | |
let key = String(describing: type.self) | |
removeDependencyIfOverwritten(type: type, overwritten: override) | |
if !constructorFunctions.keys.contains(key) || override { | |
constructorFunctions[key] = constructor | |
} | |
} | |
func injected<DependencyType>() throws -> DependencyType { | |
let key = String(describing: DependencyType.self) | |
if let constructorFunction = constructorFunctions[key], let dependency = constructorFunction() as? DependencyType { | |
return dependency | |
} else if let dependencyFactory = factories[key], let dependency = dependencyFactory() as? DependencyType { | |
return dependency | |
} else if let dependency = instances[key] as? DependencyType { | |
return dependency | |
} else if let dependency = staticTypes[key] as? DependencyType { | |
return dependency | |
} | |
throw InjectorError.unregisteredDependencyType(type: DependencyType.self) | |
} | |
private func removeDependencyIfOverwritten<ServiceType>(type: ServiceType.Type, overwritten: Bool) { | |
guard overwritten == true else { return } | |
let key = String(describing: type.self) | |
instances.removeValue(forKey: key) | |
factories.removeValue(forKey: key) | |
constructorFunctions.removeValue(forKey: key) | |
staticTypes.removeValue(forKey: key) | |
} | |
} | |
// MARK: - Examples | |
// MARK: Factory Closure Injection | |
protocol StringStore { | |
var strings : [String]? {get set} | |
} | |
struct UserDefaultsStringStore : StringStore { | |
private static let storedStringsKey = "accessToken" | |
var strings: [String]? { | |
get { | |
UserDefaults.standard.value(forKey: UserDefaultsStringStore.storedStringsKey) as? [String] | |
} | |
set { | |
UserDefaults.standard.set(newValue, forKey: UserDefaultsStringStore.storedStringsKey) | |
} | |
} | |
} | |
Injector.shared.registerFactory { () -> StringStore in | |
UserDefaultsStringStore() | |
} | |
// MARK: Constructor Function Injection | |
protocol SearchService { | |
func searchItems(byID id: String, completion: ([String]) -> Void) | |
} | |
struct RESTSearchService : SearchService { | |
func searchItems(byID id: String, completion: ([String]) -> Void) { | |
// Do the stuff! | |
completion(["lorem", "ipsum"]) | |
} | |
} | |
Injector.shared.registerConstructor(RESTSearchService.init, type: SearchService.self) | |
// MARK: Singleton Injection | |
protocol SessionStore : AnyObject { | |
var accessToken : String? {get set} | |
var refreshToken : String? {get set} | |
} | |
class UserDefaultsSessionStore : SessionStore { | |
// I know, it's totally inappropriate to do it this way, it's only an example. | |
private static let accessTokenKey = "accessToken" | |
private static let refreshTokenKey = "refreshTokenKey" | |
var accessToken: String? { | |
get { | |
UserDefaults.standard.value(forKey: UserDefaultsSessionStore.accessTokenKey) as? String | |
} | |
set { | |
UserDefaults.standard.set(newValue, forKey: UserDefaultsSessionStore.accessTokenKey) | |
} | |
} | |
var refreshToken: String? { | |
get { | |
UserDefaults.standard.value(forKey: UserDefaultsSessionStore.refreshTokenKey) as? String | |
} | |
set { | |
UserDefaults.standard.set(newValue, forKey: UserDefaultsSessionStore.refreshTokenKey) | |
} | |
} | |
} | |
Injector.shared.registerInstance(UserDefaultsSessionStore(), type: SessionStore.self) | |
struct Test { | |
@Injected var stringStore : StringStore | |
@Injected var searchService : SearchService | |
@Injected var sessionStore : SessionStore | |
} | |
let test = Test() | |
test.searchService.searchItems(byID: "lorem") { (response: [String]) in | |
print("Search Service Result: \(response)") | |
} | |
test.sessionStore.accessToken = "oiBYDS3Oyd4AOSdy2OAI4SBDY" | |
test.sessionStore.refreshToken = "k442vghVGVH12jgVjhgjvGjgG" | |
if let strings = test.stringStore.strings { | |
print("Strings: \(strings)") | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment