Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Defining dependencies with arguments #49

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions DIKit/DIKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,9 @@
isa = PBXNativeTarget;
buildConfigurationList = 4BD95D9C2008B1C9009CE38D /* Build configuration list for PBXNativeTarget "DIKit" */;
buildPhases = (
4BD95D852008B1C8009CE38D /* Headers */,
4BD95D832008B1C8009CE38D /* Sources */,
4BD95D842008B1C8009CE38D /* Frameworks */,
4BD95D852008B1C8009CE38D /* Headers */,
4BD95D862008B1C8009CE38D /* Resources */,
A24242A72343AA95002FAD54 /* SwiftLint */,
A24242A82343AAA0002FAD54 /* SwiftLint Autocorrect */,
Expand Down Expand Up @@ -300,7 +300,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "if [ \"${CONFIGURATION}\" = \"Debug\" ]; then\nif which swiftlint >/dev/null; then\nswiftlint autocorrect\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint or run brew install swiftlint from your CLI\"\nfi\nelse\necho \"warning: Skipping SwiftLint autocorrect, this is not a Debug build\"\nfi\n";
shellScript = "if [ \"${CONFIGURATION}\" = \"Debug\" ]; then\nif which swiftlint >/dev/null; then\nswiftlint autocorrect ./\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint or run brew install swiftlint from your CLI\"\nfi\nelse\necho \"warning: Skipping SwiftLint autocorrect, this is not a Debug build\"\nfi\n";
};
/* End PBXShellScriptBuildPhase section */

Expand Down
23 changes: 13 additions & 10 deletions DIKit/Sources/Component/Component.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,48 +7,51 @@
//
// Copyright © 2018 Ben John. All rights reserved.

public typealias ComponentFactory = () -> Any
public typealias ComponentFactory = (Any?) -> Any

class Component<T>: ComponentProtocol {
class Component<A, T>: ComponentProtocol {
let lifetime: Lifetime
let identifier: AnyHashable
let type: Any.Type
let componentFactory: ComponentFactory

init(lifetime: Lifetime, factory: @escaping () -> T) {
init(lifetime: Lifetime, factory: @escaping (_ argument: A) -> T) {
self.lifetime = lifetime
self.identifier = ComponentIdentifier(type: T.self)
self.identifier = ComponentIdentifier(type: T.self, argumentType: A.self)
self.type = T.self
self.componentFactory = { factory() }
self.componentFactory = { factory($0 as! A) } // swiftlint:disable:this force_cast
}

init(lifetime: Lifetime, tag: AnyHashable, factory: @escaping () -> T) {
init(lifetime: Lifetime, tag: AnyHashable, factory: @escaping (_ argument: A) -> T) {
self.lifetime = lifetime
self.identifier = ComponentIdentifier(tag: tag, type: T.self)
self.identifier = ComponentIdentifier(tag: tag, type: T.self, argumentType: A.self)
self.type = T.self
self.componentFactory = { factory() }
self.componentFactory = { factory($0 as! A) } // swiftlint:disable:this force_cast
}
}

struct ComponentIdentifier: Hashable {
let tag: AnyHashable?
let type: Any.Type
let argumentType: Any.Type

func hash(into hasher: inout Hasher) {
hasher.combine(String(describing: type))
hasher.combine(String(describing: argumentType))
if let tag = tag {
hasher.combine(tag)
}
}

static func == (lhs: ComponentIdentifier, rhs: ComponentIdentifier) -> Bool {
lhs.type == rhs.type && lhs.tag == rhs.tag
lhs.type == rhs.type && lhs.tag == rhs.tag && lhs.argumentType == rhs.argumentType
}
}

extension ComponentIdentifier {
init(type: Any.Type) {
init(type: Any.Type, argumentType: Any.Type) {
self.type = type
self.argumentType = argumentType
self.tag = nil
}
}
Expand Down
8 changes: 7 additions & 1 deletion DIKit/Sources/Container/DependencyContainer+Register.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ extension DependencyContainer {
/// - Parameters:
/// - lifetime: The *scope* of the `Component`, defaults to `Lifetime.singleton`.
/// - factory: The *factory* for the initialization of the `Component`.
public func register<T>(lifetime: Lifetime = .singleton, _ factory: @escaping () -> T) {
public func register<A, T>(lifetime: Lifetime = .singleton, _ factory: @escaping (A) -> T) {
let component = Component(lifetime: lifetime, factory: factory)
register(component)
}
Expand All @@ -38,4 +38,10 @@ extension DependencyContainer {
self.componentStack[component.identifier] = component
}
}

/// Convenience function without any arguments
public func register<T>(lifetime: Lifetime = .singleton, _ factory: @escaping () -> T) {
let newFactory: (()) -> T = { _ in factory() }
register(lifetime: lifetime, newFactory)
}
}
21 changes: 16 additions & 5 deletions DIKit/Sources/Container/DependencyContainer+Resolve.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@ extension DependencyContainer {
///
/// - Parameter tag: An optional *tag* to identify the Component. `nil` per default.
/// - Returns: The resolved `Optional<Component<T>>`.
func _resolve<T>(tag: AnyHashable? = nil) -> T? {
let identifier = ComponentIdentifier(tag: tag, type: T.self)
func _resolve<A, T>(_ argument: A, tag: AnyHashable? = nil) -> T? {
let identifier = ComponentIdentifier(tag: tag, type: T.self, argumentType: A.self)
guard let foundComponent = self.componentStack[identifier] else {
return nil
}
if foundComponent.lifetime == .factory {
return foundComponent.componentFactory() as? T
return foundComponent.componentFactory(argument) as? T
}
if let instanceOfComponent = self.instanceStack[identifier] as? T {
return instanceOfComponent
}
let instance = foundComponent.componentFactory() as! T
let instance = foundComponent.componentFactory(argument) as! T
self.instanceStack[identifier] = instance
return instance
}
Expand All @@ -37,7 +37,7 @@ extension DependencyContainer {
///
/// - Returns: `Bool` whether `Component<T>` is resolvable or not.
func resolvable<T>(type: T.Type, tag: AnyHashable? = nil) -> Bool {
let identifier = ComponentIdentifier(tag: tag, type: T.self)
let identifier = ComponentIdentifier(tag: tag, type: T.self, argumentType: Void.self)
return self.componentStack[identifier] != nil
}

Expand All @@ -48,10 +48,21 @@ extension DependencyContainer {
/// - Parameter tag: An optional *tag* to identify the Component. `nil` per default.
///
/// - Returns: The resolved `Component<T>`.
public func resolve<A, T>(_ argument: A, tag: AnyHashable? = nil) -> T {
if let t: T = _resolve(argument, tag: tag) {
return t
}
fatalError("Component `\(String(describing: T.self))` could not be resolved.")
}

/// Convenience function without any arguments
public func resolve<T>(tag: AnyHashable? = nil) -> T {
if let t: T = _resolve(tag: tag) {
return t
}
fatalError("Component `\(String(describing: T.self))` could not be resolved.")
}
func _resolve<T>(tag: AnyHashable? = nil) -> T? {
_resolve((), tag: tag)
}
}
4 changes: 4 additions & 0 deletions DIKit/Sources/DIKit+Inject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ public struct Inject<Component> {
public init(tag: AnyHashable? = nil) {
self.wrappedValue = resolve(tag: tag)
}

public init<A>(_ argument: A) {
self.wrappedValue = resolve(argument)
}
}

/// A property wrapper (SE-0258) to make a `Optional<Component>` injectable
Expand Down
7 changes: 7 additions & 0 deletions DIKit/Sources/DIKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
/// - Returns: The resolved `Component<T>`.
public func resolve<T>(tag: AnyHashable? = nil) -> T { DependencyContainer.shared.resolve(tag: tag) }

public func resolve<A, T>(_ argument: A, tag: AnyHashable? = nil) -> T { DependencyContainer.shared.resolve(argument, tag: tag) }

/// Resolves nil safe given `Component<T>`.
///
/// - Parameter tag: An optional *tag* to identify the Component. `nil` per default.
Expand All @@ -21,3 +23,8 @@ public func resolveOptional<T>(tag: AnyHashable? = nil) -> T? {
guard DependencyContainer.shared.resolvable(type: T.self, tag: tag) else { return nil }
return DependencyContainer.shared._resolve(tag: tag)
}

public func resolveOptional<A, T>(_ argument: A, tag: AnyHashable? = nil) -> T? {
guard DependencyContainer.shared.resolvable(type: T.self, tag: tag) else { return nil }
return DependencyContainer.shared._resolve(argument, tag: tag)
}
8 changes: 8 additions & 0 deletions DIKit/Sources/DIKitDSL.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ public func resolvable<T>(lifetime: Lifetime = .singleton, _ factory: @escaping
Component(lifetime: lifetime, factory: factory) as ComponentProtocol
}

public func resolvable<A, T>(lifetime: Lifetime = .singleton, _ factory: @escaping (_ argument: A) -> T) -> ComponentProtocol {
Component(lifetime: lifetime, factory: factory) as ComponentProtocol
}

public func resolvable<T>(
lifetime: Lifetime = .singleton,
tag: AnyHashable,
Expand All @@ -53,6 +57,10 @@ public func factory<T>(factory: @escaping () -> T) -> [ComponentProtocol] {
[resolvable(lifetime: .factory, factory)]
}

public func factory<A, T>(factory: @escaping (_ argument: A) -> T) -> [ComponentProtocol] {
[resolvable(lifetime: .factory, factory)]
}

public func factory<T>(tag: AnyHashable, factory: @escaping () -> T) -> [ComponentProtocol] {
[resolvable(lifetime: .factory, tag: tag, factory)]
}
Expand Down
12 changes: 6 additions & 6 deletions DIKit/Tests/DependencyContainerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,33 @@ class DependencyContainerTests: XCTestCase {
c.register(tag: "tag") { ComponentB() }
}

let componentAIdentifier = ComponentIdentifier(type: ComponentA.self)
let componentAIdentifier = ComponentIdentifier(type: ComponentA.self, argumentType: Void.self)
guard let componentA = dependencyContainer.componentStack.index(forKey: componentAIdentifier) else {
return XCTFail("ComponentStack does not contain `ComponentA`.")
}
let componentProtocolA = dependencyContainer.componentStack[componentA].value
XCTAssertEqual(componentProtocolA.lifetime, .singleton)
let instanceA = componentProtocolA.componentFactory()
let instanceA = componentProtocolA.componentFactory(())
XCTAssertTrue(instanceA is ComponentA)
XCTAssertFalse(instanceA is ComponentB)

let componentBIdentifier = ComponentIdentifier(type: ComponentB.self)
let componentBIdentifier = ComponentIdentifier(type: ComponentB.self, argumentType: Void.self)
guard let componentB = dependencyContainer.componentStack.index(forKey: componentBIdentifier) else {
return XCTFail("ComponentStack does not contain `ComponentB`.")
}
let componentProtocolB = dependencyContainer.componentStack[componentB].value
XCTAssertEqual(componentProtocolB.lifetime, .singleton)
let instanceB = componentProtocolB.componentFactory()
let instanceB = componentProtocolB.componentFactory(())
XCTAssertTrue(instanceB is ComponentB)
XCTAssertFalse(instanceB is ComponentA)

let taggedComponentBIdentifier = ComponentIdentifier(tag: "tag", type: ComponentB.self)
let taggedComponentBIdentifier = ComponentIdentifier(tag: "tag", type: ComponentB.self, argumentType: Void.self)
guard let taggedComponentB = dependencyContainer.componentStack.index(forKey: taggedComponentBIdentifier) else {
return XCTFail("ComponentStack does not contain `ComponentB`.")
}
let taggedComponentProtocolB = dependencyContainer.componentStack[taggedComponentB].value
XCTAssertEqual(taggedComponentProtocolB.lifetime, .singleton)
let taggedInstanceB = taggedComponentProtocolB.componentFactory()
let taggedInstanceB = taggedComponentProtocolB.componentFactory(())
XCTAssertTrue(taggedInstanceB is ComponentB)
XCTAssertFalse(taggedInstanceB is ComponentA)
}
Expand Down
2 changes: 1 addition & 1 deletion DIKitExample/Sources/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {

override init() {
super.init()
DependencyContainer.defined(by: modules { .app; .backend })
DependencyContainer.defined(by: modules { DependencyContainer.app; DependencyContainer.backend })
}
}
8 changes: 6 additions & 2 deletions DIKitExample/Sources/DependencyContainer+App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import DIKit
public extension DependencyContainer {
static var app = module {
// We create two different kind of storages. One for both kind of contexts.
factory(tag: StorageContext.systemdata) { LocalStorage() as LocalStorageProtocol }
factory(tag: StorageContext.userdata) { LocalStorage() as LocalStorageProtocol }
factory(tag: StorageContext.systemdata) { LocalStorage(name: "system") as LocalStorageProtocol }
factory(tag: StorageContext.userdata) { LocalStorage(name: "userdata") as LocalStorageProtocol }

factory { LocalStorage(name: "default") as LocalStorageProtocol }
factory { name in LocalStorage(name: name) as LocalStorageProtocol }
factory { (args: (name: String, other: Int)) in LocalStorage(name: args.name) as LocalStorageProtocol }
}
}
4 changes: 3 additions & 1 deletion DIKitExample/Sources/LocalStorageLayer/LocalStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ class LocalStorage: LocalStorageProtocol {
var id: ObjectIdentifier {
return ObjectIdentifier.init(self)
}
let name: String

init() {
init(name: String) {
self.name = name
print("LocalStorage init")
print("LocalStorage instance \(ObjectIdentifier.init(self))")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@

protocol LocalStorageProtocol {
var id: ObjectIdentifier { get }
var name: String { get }
}
7 changes: 7 additions & 0 deletions DIKitExample/Sources/Views/FirstViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,17 @@ import DIKitExampleBackend
class FirstViewController: UIViewController {
// MARK: - DIKit
@Inject var backend: BackendProtocol
@Inject var defaultLocalStorage: LocalStorageProtocol
@Inject("With just one parameter") var localStorageWithJustOneParameter: LocalStorageProtocol
@Inject((name: "Local Storage with multiple parameters", other: 1)) var localStorageWithMultipleParameters: LocalStorageProtocol

// MARK: - View lifecycle
override func viewWillAppear(_ animated: Bool) {
let result = backend.fetch()
print(result)

print(defaultLocalStorage.name)
print(localStorageWithJustOneParameter.name)
print(localStorageWithMultipleParameters.name)
}
}