Skip to content

Instantly share code, notes, and snippets.

@jeneiv
Last active March 10, 2022 07:41
Show Gist options
  • Save jeneiv/be39be5eb930d7bf7f7023368d51b862 to your computer and use it in GitHub Desktop.
Save jeneiv/be39be5eb930d7bf7f7023368d51b862 to your computer and use it in GitHub Desktop.
// 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