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

multiple-injection #38

Closed
wants to merge 1 commit into from
Closed
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: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
* Added auto-injection feature.
[#13](https://github.com/AliSoftware/Dip/pull/13), [@ilyapuchka](https://github.com/ilyapuchka)
* Factories and `resolveDependencies` blocks of `DefinitionOf` are now allowed to `throw`.
[#32](https://github.com/AliSoftware/Dip/pull/13), [@ilyapuchka](https://github.com/ilyapuchka)
[#32](https://github.com/AliSoftware/Dip/pull/32), [@ilyapuchka](https://github.com/ilyapuchka)
* Thread safety reimplemented with support for recursive methods calls.
[#31](https://github.com/AliSoftware/Dip/pull/31), [@mwoollard](https://github.com/mwoollard)
* Multi-injection to resolve an array of all the instances registere for the protocol.
[#38](https://github.com/AliSoftware/Dip/pull/38), [@ilyapuchka](https://github.com/ilyapuchka)


## 4.0.0
Expand Down
82 changes: 78 additions & 4 deletions Dip/Dip/Dip.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public final class DependencyContainer {
with the same protocol, to differentiate them. Tags can be either String
or Int, to your convenience.
*/
public enum Tag: Equatable {
public enum Tag: Equatable, Comparable {
case String(StringLiteralType)
case Int(IntegerLiteralType)
}
Expand Down Expand Up @@ -120,7 +120,7 @@ public final class DependencyContainer {
container.register { ClientImp(service: try! container.resolve() as Service) as Client }
```
*/
public func register<T>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: () throws -> T) -> DefinitionOf<T, () throws ->T > {
public func register<T>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: () throws -> T) -> DefinitionOf<T, () throws -> T> {
return registerFactory(tag: tag, scope: scope, factory: factory)
}

Expand Down Expand Up @@ -233,6 +233,51 @@ public final class DependencyContainer {
}
}

/**
Resolve dependency as array. Resulting array will contain instances of resolved type created by resolving all registered definitions for resolved type with matching factory.

- note: resolved instances will be ordered according to tags order.
Tags are compared by strings made of their associated values.

- returns: array of resolved instances

**Example**:
```swift
container.register() { ServiceImp0() as Service }
container.register(tag: "service1") { ServiceImp1() as Service }
container.register(tag: "service2") { ServiceImp2() as Service }

let allServices = try! container.resolveAll() as [Service]

allServices[0] // is ServiceImp0()
allServices[1] // is ServiceImp1()
allServices[2] // is ServiceImp2()
```

*/
public func resolveAll<T>() throws -> [T] {
return try resolveAll { (factory: () throws -> T) -> T in try factory() }
}

/**
Resolves all components registered for the type.

- note: You should use this method _only_ to resolve dependency with more runtime arguments than _Dip_ supports.

- seealso: `resolve(tag:builder:)`
*/
public func resolveAll<T, F>(builder: F throws -> T) rethrows -> [T] {
return try threadSafe {
return try self.definitions
.sort({ $0.0.associatedTag < $1.0.associatedTag })
.flatMap { (key, definition) in
guard let definition = definition as? DefinitionOf<T, F> else { return nil }
let usingKey: DefinitionKey? = definition.scope == .ObjectGraph ? key : nil
return try self._resolve(key: usingKey, definition: definition, builder: builder)
}
}
}

/// Actually resolve dependency
private func _resolve<T, F>(tag: Tag? = nil, key: DefinitionKey?, definition: DefinitionOf<T, F>, builder: F throws -> T) rethrows -> T {

Expand Down Expand Up @@ -340,14 +385,43 @@ extension DependencyContainer.Tag: StringLiteralConvertible {
}
}

/*
.String("a") == .Stirng("a")
.Int(1) == .Int(1)
.String("1") == .Int(1)
*/
public func ==(lhs: DependencyContainer.Tag, rhs: DependencyContainer.Tag) -> Bool {
switch (lhs, rhs) {
case let (.String(lhsString), .String(rhsString)):
return lhsString == rhsString
case let (.Int(lhsInt), .Int(rhsInt)):
return lhsInt == rhsInt
default:
return false
case let (.String(lhsString), .Int(rhsInt)):
return lhsString == String(rhsInt)
case let (.Int(lhsInt), .String(rhsString)):
return String(lhsInt) == rhsString
}
}

/**
Tags are compared by comparing strings created from their assiciated values.

.String("a") < .String("b")
.Int(0) < .Int(1)
.String("0") < .Int(1)
.Int(0) < .String("1")

*/
public func <(lhs: DependencyContainer.Tag, rhs: DependencyContainer.Tag) -> Bool {
switch (lhs, rhs) {
case let (.String(lhsString), .String(rhsString)):
return lhsString < rhsString
case let (.Int(lhsInt), .Int(rhsInt)):
return lhsInt < rhsInt
case let (.String(lhsString), .Int(rhsInt)):
return lhsString < String(rhsInt)
case let (.Int(lhsInt), .String(rhsString)):
return String(lhsInt) < rhsString
}
}

Expand Down
34 changes: 33 additions & 1 deletion Dip/Dip/RuntimeArguments.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,34 @@ extension DependencyContainer {
return try resolve(tag: tag) { (factory: (Arg1) throws -> T) in try factory(arg1) }
}

/**
Resolve dependency with runtime argument as array. Resulting array will contain instances of resolved type created by resolving all registered definitions for resolved type with matching factory.

- parameter arg1: Argument that will be used to resolve each instance
- returns: array of resolved instances

- seealso: `resolveAll()`, `resolve(tag:withArguments:)`
*/
public func resolveAll<T, Arg1>(arg1: Arg1) throws -> [T] {
return try resolveAll { (factory: (Arg1) throws -> T) in try factory(arg1) }
}

// MARK: 2 Runtime Arguments

/// - seealso: `register(:factory:scope:)`
public func register<T, Arg1, Arg2>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2) throws -> T) -> DefinitionOf<T, (Arg1, Arg2) throws -> T> {
return registerFactory(tag: tag, scope: scope, factory: factory)
}

/// - seealso: `resolve(tag:_:)`
/// - seealso: `resolve(tag:withArguments:)`
public func resolve<T, Arg1, Arg2>(tag tag: Tag? = nil, withArguments arg1: Arg1, _ arg2: Arg2) throws -> T {
return try resolve(tag: tag) { (factory: (Arg1, Arg2) throws -> T) in try factory(arg1, arg2) }
}

public func resolveAll<T, Arg1, Arg2>(arg1: Arg1, _ arg2: Arg2) throws -> [T] {
return try resolveAll { (factory: (Arg1, Arg2) throws -> T) in try factory(arg1, arg2) }
}

// MARK: 3 Runtime Arguments

public func register<T, Arg1, Arg2, Arg3>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3) throws -> T> {
Expand All @@ -81,6 +97,10 @@ extension DependencyContainer {
return try resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3) throws -> T) in try factory(arg1, arg2, arg3) }
}

public func resolveAll<T, Arg1, Arg2, Arg3>(arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3) throws -> [T] {
return try resolveAll { (factory: (Arg1, Arg2, Arg3) throws -> T) in try factory(arg1, arg2, arg3) }
}

// MARK: 4 Runtime Arguments

public func register<T, Arg1, Arg2, Arg3, Arg4>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4) throws -> T> {
Expand All @@ -92,6 +112,10 @@ extension DependencyContainer {
return try resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4) throws -> T) in try factory(arg1, arg2, arg3, arg4) }
}

public func resolveAll<T, Arg1, Arg2, Arg3, Arg4>(arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4) throws -> [T] {
return try resolveAll { (factory: (Arg1, Arg2, Arg3, Arg4) throws -> T) in try factory(arg1, arg2, arg3, arg4) }
}

// MARK: 5 Runtime Arguments

public func register<T, Arg1, Arg2, Arg3, Arg4, Arg5>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4, Arg5) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4, Arg5) throws -> T> {
Expand All @@ -103,6 +127,10 @@ extension DependencyContainer {
return try resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5) throws -> T) in try factory(arg1, arg2, arg3, arg4, arg5) }
}

public func resolveAll<T, Arg1, Arg2, Arg3, Arg4, Arg5>(arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5) throws -> [T] {
return try resolveAll { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5) throws -> T) in try factory(arg1, arg2, arg3, arg4, arg5) }
}

// MARK: 6 Runtime Arguments

public func register<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) throws -> T> {
Expand All @@ -114,4 +142,8 @@ extension DependencyContainer {
return try resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) throws -> T) in try factory(arg1, arg2, arg3, arg4, arg5, arg6) }
}

public func resolveAll<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6>(arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6) throws -> [T] {
return try resolveAll { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) throws -> T) in try factory(arg1, arg2, arg3, arg4, arg5, arg6) }
}

}
69 changes: 68 additions & 1 deletion Dip/DipTests/DipTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import XCTest
@testable import Dip

protocol Service {
protocol Service: class {
func getServiceName() -> String
}

Expand Down Expand Up @@ -182,4 +182,71 @@ class DipTests: XCTestCase {
}
}

func testThatItCanResolveAllImplementationsAsArray() {
container.register() { ServiceImp1() as Service }
container.register(tag: "service1") { ServiceImp1() as Service }
container.register(tag: "service2") { ServiceImp2() as Service }

let allServices = try! container.resolveAll() as [Service]

XCTAssertEqual(allServices.count, 3)
XCTAssertTrue(allServices[0] is ServiceImp1)
XCTAssertTrue(allServices[1] is ServiceImp1)
XCTAssertTrue(allServices[2] is ServiceImp2)

XCTAssertFalse(allServices[2] === allServices[1])
}

func testThatItDoesNotReusesInstancesInObjectGraphScopeWhenResolvingAsArray() {
container.register(.ObjectGraph) { ServiceImp1() as Service }
container.register(tag: "service1", .ObjectGraph) { ServiceImp1() as Service }
container.register(tag: "service1_1") { ServiceImp1() as Service }

let allServices = try! container.resolveAll() as [Service]

XCTAssertEqual(allServices.count, 3)

XCTAssertFalse(allServices[2] === allServices[1])
XCTAssertFalse(allServices[0] === allServices[1])
}

func testThatItCanRemoveAndReAddDefinition() {
let def1 = container.register() { ServiceImp1() as Service }
container.register(tag: "service2") { ServiceImp2() as Service }

container.remove(def1)

let allServices = try! container.resolveAll() as [Service]
XCTAssertEqual(allServices.count, 1)
XCTAssertTrue(allServices.last is ServiceImp2)

container.register() { ServiceImp1() as Service }

let newAllServices = try! container.resolveAll() as [Service]
XCTAssertEqual(newAllServices.count, 2)
XCTAssertTrue(newAllServices.first is ServiceImp1)
XCTAssertTrue(newAllServices.last is ServiceImp2)
}

func testTagsEquality() {
XCTAssertEqual(DependencyContainer.Tag.String("a"), DependencyContainer.Tag.String("a"))
XCTAssertNotEqual(DependencyContainer.Tag.String("a"), DependencyContainer.Tag.String("b"))

XCTAssertEqual(DependencyContainer.Tag.Int(0), DependencyContainer.Tag.Int(0))
XCTAssertNotEqual(DependencyContainer.Tag.Int(0), DependencyContainer.Tag.Int(1))

XCTAssertEqual(DependencyContainer.Tag.String("0"), DependencyContainer.Tag.Int(0))
XCTAssertEqual(DependencyContainer.Tag.Int(0), DependencyContainer.Tag.String("0"))

XCTAssertNotEqual(DependencyContainer.Tag.String("0"), DependencyContainer.Tag.Int(1))
XCTAssertNotEqual(DependencyContainer.Tag.Int(1), DependencyContainer.Tag.String("0"))
}

func testTagsComparison() {
XCTAssertLessThan(DependencyContainer.Tag.String("a"), DependencyContainer.Tag.String("b"))
XCTAssertLessThan(DependencyContainer.Tag.Int(0), DependencyContainer.Tag.Int(1))
XCTAssertLessThan(DependencyContainer.Tag.Int(0), DependencyContainer.Tag.String("1"))
XCTAssertLessThan(DependencyContainer.Tag.String("0"), DependencyContainer.Tag.Int(1))
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,4 @@ So as you can see there are certain advantages and disadvatages of using auto-in
So you should decide for yourself whether you prefer to use auto-injection or "the standard" way. At the end they let you achieve the same result.
*/

//: [Next: Testing](@next)
//: [Next: Multi-injection](@next)
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//: [Previous: Auto-injection](@previous)

import Dip

let container = DependencyContainer()

/*:
Let's say you are developing a killer e-mail client. Surely you want your users to feel comfortable using your service so you want to let them to use their existing accounts on other e-mail services like Gmail, Yahoo, Outlook. For that you will need some kind of screen where you will display the list of all 3'rd party services that you support. And of course you want to be able to easily add new services. Also you wish to split the work on different services between your teammates so that you ship this feature faster.

To enable all that you will need to define some common protocol that all 3'rd party services implementations will conform to.
Then you start to add concrete implementations:
*/

protocol ThirdPartyEmailService { /* … */ }

class GmailService: ThirdPartyEmailService { /* … */ init(){} }
class YahooService: ThirdPartyEmailService { /* … */ init(){} }
class OutlookService: ThirdPartyEmailService { /* … */ init(){} }

/*:
When ready you register them in the container.
*/

container.register(tag: "gmail") { GmailService() as ThirdPartyEmailService }
container.register(tag: "yahoo") { YahooService() as ThirdPartyEmailService }
container.register(tag: "outlook") { OutlookService() as ThirdPartyEmailService }

/*:
Then when you need to display all of them you can get them from the container just with one call.
*/

var thirdPartyServices = try! container.resolveAll() as [ThirdPartyEmailService]

/*:
When you realize that you need to support one more service you will only need to drop in it's implementation and register it in the container. It will appear in the list of your services without any other changes in your code.
*/

class YandexService: ThirdPartyEmailService { /* … */ init(){} }

container.register(tag: "yandex") { YandexService() as ThirdPartyEmailService }

thirdPartyServices = try! container.resolveAll() as [ThirdPartyEmailService]

/*:
Sharing to different services, providing different payment or goods delivery services, logging to different sources, fetching data at once from different kinds of content providers are some of other possible use cases for that feature.
*/

//: [Next: Testing](@next)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Timeline
version = "3.0">
<TimelineItems>
</TimelineItems>
</Timeline>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//: [Previous: Shared Instances](@previous)
//: [Previous: Multi-injection](@previous)


/*:
Expand Down
3 changes: 2 additions & 1 deletion DipPlayground.playground/contents.xcplayground
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='6.0' target-platform='ios' display-mode='rendered'>
<playground version='6.0' target-platform='ios' display-mode='raw'>
<pages>
<page name='What is Dip?'/>
<page name='Creating container'/>
Expand All @@ -10,6 +10,7 @@
<page name='Circular dependencies'/>
<page name='Shared Instances'/>
<page name='Auto-injection'/>
<page name='Multi-injection'/>
<page name='Testing'/>
</pages>
</playground>
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,17 @@ let client = try! container.resolve() as Client
```
You can find more use cases for auto-injection in the Playground available in this repository.

### Multi-injection

If you have several implementations of the same protocol and you wish to get them all as an array you can do that using `resolveAll` method:

```swift
container.register(tag: "facebook") { FacebookShare() as SharingService }
container.register(tag: "twitter") { TwitterShare() as SharingService }

let sharingServices = try! container.resolveAll() as [SharingService]
```

### Thread safety

`DependencyContainer` is thread safe, you can register and resolve components from different threads.
Expand Down