Skip to content

Commit

Permalink
Added support for protocols
Browse files Browse the repository at this point in the history
  • Loading branch information
Valentin Radu committed May 3, 2022
1 parent 3ba9b3a commit f7ed315
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 20 deletions.
Binary file modified Binaries/ToledoTool.artifactbundle.zip
Binary file not shown.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,26 @@ container.replaceProvider(ProfileDependencyProviderKey.self) { _ in
let mockedInstance = try await container.identityModel()
```

### Providing a resolving protocol

If you need your dependency to resolve to a protocol you can use `ResolvedTo`.

```swift
extension IdentityModel: IdentityModelProtocol { ... }

extension IdentityModel: AsyncThrowingDependency {
public typealias ResolvedTo = IdentityModelProtocol
public convenience init(with container: SharedContainer) async throws {
await self.init(profile: try await container.profile(),
settings: container.settings())
}
}
```

Now `try await container.identityModel()` will return `IdentityModelProtocol` instead of `IdentityModel`.
**Important note**: Make sure the dependency implements the protocol.


### Concurrency

Toledo uses Swift's concurrency model for `AsyncThrowingDependency` and a simple semaphore for the other dependencies to guarantee that shared instances are never instantiated more than once per container.
Expand Down
35 changes: 19 additions & 16 deletions Sources/Toledo/Dependency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,19 @@ public class SharedContainer {
}

public actor _AsyncThrowingDependencyProvider<V> where V: AsyncThrowingDependency {
public typealias Provider = (SharedContainer) async throws -> V
private var _value: V?
public typealias Provider = (SharedContainer) async throws -> V.ResolvedTo
private var _value: V.ResolvedTo?
private var _provider: Provider?
private var _task: Task<V, Error>?
private var _task: Task<V.ResolvedTo, Error>?

public init() {}

public func getValue(container: SharedContainer) async throws -> V {
public func getValue(container: SharedContainer) async throws -> V.ResolvedTo {
if let value = _value {
return value
}

let value: V
let value: V.ResolvedTo

if let task = _task {
value = try await task.value
Expand All @@ -66,7 +66,7 @@ public actor _AsyncThrowingDependencyProvider<V> where V: AsyncThrowingDependenc
value = try await task.value
} else {
let task = Task {
try await V(with: container)
try await V(with: container) as! V.ResolvedTo
}
_task = task
value = try await task.value
Expand All @@ -84,25 +84,25 @@ public actor _AsyncThrowingDependencyProvider<V> where V: AsyncThrowingDependenc
}

public class _ThrowingDependencyProvider<V> where V: ThrowingDependency {
public typealias Provider = (SharedContainer) throws -> V
private var _value: V?
public typealias Provider = (SharedContainer) throws -> V.ResolvedTo
private var _value: V.ResolvedTo?
private var _provider: Provider?
private let _sem = DispatchSemaphore(value: 1)

public init() {}

public func getValue(container: SharedContainer) throws -> V {
public func getValue(container: SharedContainer) throws -> V.ResolvedTo {
_sem.wait()
defer { _sem.signal() }

if let value = _value {
return value
}
let value: V
let value: V.ResolvedTo
if let provider = _provider {
value = try provider(container)
} else {
value = try V(with: container)
value = try V(with: container) as! V.ResolvedTo
}
_value = value
return value
Expand All @@ -118,25 +118,25 @@ public class _ThrowingDependencyProvider<V> where V: ThrowingDependency {
}

public class _DependencyProvider<V> where V: Dependency {
public typealias Provider = (SharedContainer) -> V
private var _value: V?
public typealias Provider = (SharedContainer) -> V.ResolvedTo
private var _value: V.ResolvedTo?
private var _provider: Provider?
private let _sem = DispatchSemaphore(value: 1)

public init() {}

public func getValue(container: SharedContainer) -> V {
public func getValue(container: SharedContainer) -> V.ResolvedTo {
_sem.wait()
defer { _sem.signal() }

if let value = _value {
return value
}
let value: V
let value: V.ResolvedTo
if let provider = _provider {
value = provider(container)
} else {
value = V(with: container)
value = V(with: container) as! V.ResolvedTo
}
_value = value
return value
Expand All @@ -152,13 +152,16 @@ public class _DependencyProvider<V> where V: Dependency {
}

public protocol AsyncThrowingDependency {
associatedtype ResolvedTo = Self
init(with: SharedContainer) async throws
}

public protocol ThrowingDependency {
associatedtype ResolvedTo = Self
init(with: SharedContainer) throws
}

public protocol Dependency {
associatedtype ResolvedTo = Self
init(with: SharedContainer)
}
10 changes: 9 additions & 1 deletion Tests/ToledoTests/Fixtures.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,19 @@ public struct B: Dependency {
}
}

public struct C: AsyncThrowingDependency {
public struct C: AsyncThrowingDependency, SomeProtocol {
public typealias ResolvedTo = SomeProtocol
let a: A
public init(with container: SharedContainer) async throws {
a = container.a()
}
}

public struct MockD: AsyncThrowingDependency, SomeProtocol {
public typealias ResolvedTo = SomeProtocol
public init(with container: SharedContainer) async throws {}
}

public struct LongLastingAsyncInit: AsyncThrowingDependency {
let id: UUID
public init(with container: SharedContainer) async throws {
Expand All @@ -51,6 +57,8 @@ public struct LongLastingSyncInit: Dependency {
}
}

public protocol SomeProtocol {}

extension MusicDeviceGroupID: Dependency {
public init(with _: SharedContainer) {
self = 2
Expand Down
17 changes: 14 additions & 3 deletions Tests/ToledoTests/ToledoTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ final class ToledoTests: XCTestCase {
XCTAssertEqual(b2.a.id, c1.a.id)
}

func testResolvedTo() async throws {
let container = SharedContainer()

await container.replaceProvider(CAsyncThrowingDependencyProviderKey.self) { container in
try await MockD(with: container)
}

let c = try await container.c()
XCTAssertEqual(ObjectIdentifier(type(of: c)), ObjectIdentifier(MockD.self))
}

func testExternalEntity() async throws {
let container = SharedContainer()
// test passes if there is a musicDeviceGroupID
Expand All @@ -23,13 +34,13 @@ final class ToledoTests: XCTestCase {
func testReplaceProvider() async throws {
let container = SharedContainer()
let uuid = UUID(uuidString: "93c92553-df0b-473a-80fa-5892675cd27b")!

container.replaceProvider(ADependencyProviderKey.self) { _ in
A(id: uuid)
}

let b = B(with: container)

XCTAssertEqual(b.a.id, uuid)
}

Expand Down

0 comments on commit f7ed315

Please sign in to comment.