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

Throwing errors #22

Merged
merged 4 commits into from
Dec 2, 2015
Merged
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@
[#11](https://github.com/AliSoftware/Dip/pull/11), [@ilyapuchka](https://github.com/ilyapuchka)
* Added methods to register/remove individual definitions.
[#11](https://github.com/AliSoftware/Dip/pull/11), [@ilyapuchka](https://github.com/ilyapuchka)
* All `resolve` methods now can throw error if type can not be resolved.
[#15](https://github.com/AliSoftware/Dip/issues/15), [@ilyapuchka](https://github.com/ilyapuchka)

#### Breaking Changes

* Removed container thread-safety to enable recursion calls to `resolve`.
**Access to container from multiple threads should be handled by clients** from now on.
* All `resolve` methods now can throw.

## 3.1.0

Expand Down
6 changes: 4 additions & 2 deletions Dip/Dip.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
094526B61BEA520B0034E72A /* Definition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094526B51BEA520B0034E72A /* Definition.swift */; };
094526B81BEA536A0034E72A /* RuntimeArgumentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094526B71BEA536A0034E72A /* RuntimeArgumentsTests.swift */; };
0989323F1BEBC8CD00ACDA2B /* ComponentScopeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0989323E1BEBC8CD00ACDA2B /* ComponentScopeTests.swift */; };
09969C551BEB7C0A00F93C70 /* Dip.podspec in Resources */ = {isa = PBXBuildFile; fileRef = 09969C541BEB7C0A00F93C70 /* Dip.podspec */; };
098A740D1C0C5CBC005C1296 /* DefinitionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 098A740C1C0C5CBC005C1296 /* DefinitionTests.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand All @@ -40,7 +40,7 @@
094526B51BEA520B0034E72A /* Definition.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = Definition.swift; sourceTree = "<group>"; tabWidth = 2; };
094526B71BEA536A0034E72A /* RuntimeArgumentsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuntimeArgumentsTests.swift; sourceTree = "<group>"; };
0989323E1BEBC8CD00ACDA2B /* ComponentScopeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComponentScopeTests.swift; sourceTree = "<group>"; };
09969C541BEB7C0A00F93C70 /* Dip.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Dip.podspec; path = ../Dip.podspec; sourceTree = "<group>"; };
098A740C1C0C5CBC005C1296 /* DefinitionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefinitionTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -100,6 +100,7 @@
094526A01BEA1CFF0034E72A /* DipTests.swift */,
094526B71BEA536A0034E72A /* RuntimeArgumentsTests.swift */,
0989323E1BEBC8CD00ACDA2B /* ComponentScopeTests.swift */,
098A740C1C0C5CBC005C1296 /* DefinitionTests.swift */,
094526A21BEA1CFF0034E72A /* Info.plist */,
);
path = DipTests;
Expand Down Expand Up @@ -223,6 +224,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
098A740D1C0C5CBC005C1296 /* DefinitionTests.swift in Sources */,
094526A11BEA1CFF0034E72A /* DipTests.swift in Sources */,
0989323F1BEBC8CD00ACDA2B /* ComponentScopeTests.swift in Sources */,
094526B81BEA536A0034E72A /* RuntimeArgumentsTests.swift in Sources */,
Expand Down
3 changes: 2 additions & 1 deletion Dip/Dip.xcodeproj/xcshareddata/xcschemes/Dip.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES">
<Testables>
<TestableReference
skipped = "NO">
Expand Down
35 changes: 24 additions & 11 deletions Dip/Dip/Dip.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public class DependencyContainer {
container.register(tag: "service") { ServiceImp() as Service }
container.register(.ObjectGraph) { ServiceImp() as Service }
container.register { [unowned container]
ClientImp(service: container.resolve() as Service) as Client
ClientImp(service: try! container.resolve() as Service) as Client
}
```
*/
Expand Down Expand Up @@ -152,18 +152,21 @@ public class DependencyContainer {
If no definition was registered with this `tag` for this `protocol`,
it will try to resolve the definition associated with `nil` (no tag).

Will throw `DipError.DefinitionNotFound` if no registered definition found
that would match type, runtime arguments and tag.

- parameter tag: The arbitrary tag to look for when resolving this protocol.

**Example**:
```swift
let service = container.resolve() as Service
let service = container.resolve(tag: "service") as Service
let service: Service = container.resolve()
let service = try! container.resolve() as Service
let service = try! container.resolve(tag: "service") as Service
let service: Service = try! container.resolve()
```

*/
public func resolve<T>(tag tag: Tag? = nil) -> T {
return resolve(tag: tag) { (factory: ()->T) in factory() }
public func resolve<T>(tag tag: Tag? = nil) throws -> T {
return try resolve(tag: tag) { (factory: ()->T) in factory() }
}

/**
Expand All @@ -178,21 +181,20 @@ public class DependencyContainer {
(currently it's up to six) like in this example:

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

Though before you do that you should probably review your design and try to reduce the number of dependencies.

*/
public func resolve<T, F>(tag tag: Tag? = nil, builder: F->T) -> T {
public func resolve<T, F>(tag tag: Tag? = nil, builder: F->T) throws -> T {
let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag)
let nilTagKey = tag.map { _ in DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: nil) }

guard let definition = (self.definitions[key] ?? self.definitions[nilTagKey]) as? DefinitionOf<T, F> else {
fatalError("No definition registered with " + (tag == nil ? "\(key)" : "\(key) or \(nilTagKey)") + ". "
+ "Check the tag, type you try to resolve, number, order and types of runtime arguments passed to `resolve()`.")
throw DipError.DefinitionNotFound(key)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is indeed way better, I like that change 👍

}

let usingKey: DefinitionKey? = definition.scope == .ObjectGraph ? key : nil
Expand Down Expand Up @@ -302,6 +304,17 @@ public func ==(lhs: DependencyContainer.Tag, rhs: DependencyContainer.Tag) -> Bo
}
}

enum DipError: ErrorType, CustomStringConvertible {
case DefinitionNotFound(DefinitionKey)

var description: String {
switch self {
case let .DefinitionNotFound(key):
return "No definition registered for \(key). Check the tag, type you try to resolve, number, order and types of runtime arguments passed to `resolve()`."
}
}
}

extension Dictionary {
subscript(key: Key?) -> Value? {
get {
Expand Down
24 changes: 12 additions & 12 deletions Dip/Dip/RuntimeArguments.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ extension DependencyContainer {

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

// MARK: 2 Runtime Arguments
Expand All @@ -66,8 +66,8 @@ extension DependencyContainer {
}

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

// MARK: 3 Runtime Arguments
Expand All @@ -77,8 +77,8 @@ extension DependencyContainer {
}

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

// MARK: 4 Runtime Arguments
Expand All @@ -88,8 +88,8 @@ extension DependencyContainer {
}

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

// MARK: 5 Runtime Arguments
Expand All @@ -99,8 +99,8 @@ extension DependencyContainer {
}

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

// MARK: 6 Runtime Arguments
Expand All @@ -110,8 +110,8 @@ extension DependencyContainer {
}

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

}
26 changes: 13 additions & 13 deletions Dip/DipTests/ComponentScopeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ class ComponentScopeTests: XCTestCase {
container.register { ServiceImp1() as Service }

//when
let service1 = container.resolve() as Service
let service2 = container.resolve() as Service
let service1 = try! container.resolve() as Service
let service2 = try! container.resolve() as Service

//then
XCTAssertFalse((service1 as! ServiceImp1) === (service2 as! ServiceImp1))
Expand All @@ -67,8 +67,8 @@ class ComponentScopeTests: XCTestCase {
container.register(.Singleton) { ServiceImp1() as Service }

//when
let service1 = container.resolve() as Service
let service2 = container.resolve() as Service
let service1 = try! container.resolve() as Service
let service2 = try! container.resolve() as Service

//then
XCTAssertTrue((service1 as! ServiceImp1) === (service2 as! ServiceImp1))
Expand All @@ -90,14 +90,14 @@ class ComponentScopeTests: XCTestCase {

func testThatItReusesInstanceInObjectGraphScopeDuringResolve() {
//given
container.register(.ObjectGraph) { [unowned container] in Client(server: container.resolve()) as Client }
container.register(.ObjectGraph) { [unowned container] in Client(server: try! container.resolve()) as Client }

container.register(.ObjectGraph) { Server() as Server }.resolveDependencies { container, server in
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related to this PR I know, but now that I read that again I'm wondering: shouldn't we use [unowned container] (or at least [weak container]) here? It seems to me that container strongly reference resolveDependenciesBlock which itself strongly references container, hence a strong reference cycle, right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, cause here we don't capture container from outer context, we pass it as an argument at runtime. So the reference should be released as soon as block returns. I suppose...

Speaking of unowned I tried to use factory block without [unowned container] it (like in line 93) and it still looks like there is no retain cycle... I test it by having a strong reference to container and a weak reference. When I release a strong reference weak also is being released. That would not happen in retain cycle... but if I think of it there should be one if there is no [unowned container] or [weak container], cause container holds definitions and they hold their factories... Maybe it's just too late at night to think about that 😪 🌃

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, cause here we don't capture container from outer context, we pass it as an argument at runtime. So the reference should be released as soon as block returns.

Don't we? I mean, container captures resolveDependenciesBlock right? So unless that block don't use the container argument in the closure, this container is captured automatically by the closure and not "released as soon as the block returns" (as the resolveDependenciesBlock will still be captured and kept as a property in the container). So this captured container will only be released when the block itself is released (set back to nil).

(If the block were not stored as a strong property by the container but used right away, we would be able to mark it @noescape, which is not the case here)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've created separate issue #23 to discuss that. Will probably add some unit test for that.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

server.client = container.resolve() as Client
server.client = try! container.resolve() as Client
}

//when
let client = container.resolve() as Client
let client = try! container.resolve() as Client

//then
let server = client.server
Expand All @@ -106,16 +106,16 @@ class ComponentScopeTests: XCTestCase {

func testThatItDoesNotReuseInstanceInObjectGraphScopeInNextResolve() {
//given
container.register(.ObjectGraph) { [unowned container] in Client(server: container.resolve()) as Client }
container.register(.ObjectGraph) { [unowned container] in Client(server: try! container.resolve()) as Client }
container.register(.ObjectGraph) { Server() as Server }.resolveDependencies { container, server in
server.client = container.resolve() as Client
server.client = try! container.resolve() as Client
}

//when
let client = container.resolve() as Client
let client = try! container.resolve() as Client
let server = client.server

let anotherClient = container.resolve() as Client
let anotherClient = try! container.resolve() as Client
let anotherServer = anotherClient.server

//then
Expand All @@ -127,12 +127,12 @@ class ComponentScopeTests: XCTestCase {
//given
var service2: Service?
container.register(.ObjectGraph) { ServiceImp1() as Service }.resolveDependencies { (c, _) in
service2 = c.resolve(tag: "service") as Service
service2 = try! c.resolve(tag: "service") as Service
}
container.register(tag: "service", .ObjectGraph) { ServiceImp2() as Service}

//when
let service1 = container.resolve(tag: "tag") as Service
let service1 = try! container.resolve(tag: "tag") as Service

//then
XCTAssertTrue(service1 is ServiceImp1)
Expand Down
68 changes: 68 additions & 0 deletions Dip/DipTests/DefinitionTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//
// Dip
//
// Copyright (c) 2015 Olivier Halligon <[email protected]>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

import XCTest
@testable import Dip

class DefinitionTests: XCTestCase {

typealias F1 = ()->Service
typealias F2 = (String)->Service

let tag1 = DependencyContainer.Tag.String("tag1")
let tag2 = DependencyContainer.Tag.String("tag2")

func testThatDefinitionKeyIsEqualBy_Type_Factory_Tag() {
let equalKey1 = DefinitionKey(protocolType: Service.self, factoryType: F1.self, associatedTag: tag1)
let equalKey2 = DefinitionKey(protocolType: Service.self, factoryType: F1.self, associatedTag: tag1)

XCTAssertEqual(equalKey1, equalKey2)
XCTAssertEqual(equalKey1.hashValue, equalKey2.hashValue)
}

func testThatDefinitionKeysWithDifferentTypesAreNotEqual() {
let keyWithDifferentType1 = DefinitionKey(protocolType: Service.self, factoryType: F1.self, associatedTag: nil)
let keyWithDifferentType2 = DefinitionKey(protocolType: AnyObject.self, factoryType: F1.self, associatedTag: nil)

XCTAssertNotEqual(keyWithDifferentType1, keyWithDifferentType2)
XCTAssertNotEqual(keyWithDifferentType1.hashValue, keyWithDifferentType2.hashValue)
}

func testThatDefinitionKeysWithDifferentFactoriesAreNotEqual() {
let keyWithDifferentFactory1 = DefinitionKey(protocolType: Service.self, factoryType: F1.self, associatedTag: nil)
let keyWithDifferentFactory2 = DefinitionKey(protocolType: Service.self, factoryType: F2.self, associatedTag: nil)

XCTAssertNotEqual(keyWithDifferentFactory1, keyWithDifferentFactory2)
XCTAssertNotEqual(keyWithDifferentFactory1.hashValue, keyWithDifferentFactory2.hashValue)
}

func testThatDefinitionKeysWithDifferentTagsAreNotEqual() {
let keyWithDifferentTag1 = DefinitionKey(protocolType: Service.self, factoryType: F1.self, associatedTag: tag1)
let keyWithDifferentTag2 = DefinitionKey(protocolType: Service.self, factoryType: F1.self, associatedTag: tag2)

XCTAssertNotEqual(keyWithDifferentTag1, keyWithDifferentTag2)
XCTAssertNotEqual(keyWithDifferentTag1.hashValue, keyWithDifferentTag2.hashValue)
}

}
Loading