Compare commits

...

27 Commits

Author SHA1 Message Date
Ilya Puchka 6656116e12 Merge pull request #54 from AliSoftware/release/4.2.0
Release/4.2.0 to master
2016-02-27 15:27:14 +01:00
Ilya Puchka 92499c9561 fixed auto-injection error description 2016-02-27 14:55:16 +01:00
Ilya Puchka 7c5ea83dc4 Fixed usage of Tag in README 2016-02-27 13:37:51 +01:00
Ilya Puchka 4793232d8c bumped version to 4.2.0 2016-02-25 18:44:21 +01:00
Ilya Puchka 2c09b4d6d1 Merge pull request #46 from AliSoftware/feature/linux-tests
Building tests on linux with spm
2016-02-25 18:32:56 +01:00
Ilya Puchka 34e920a348 building tests on linux with spm 2016-02-25 18:08:06 +01:00
Ilya Puchka 3357630deb updated README and CHANGELOG 2016-02-25 00:26:39 +01:00
Ilya Puchka f23492ee40 Improved auto-injection docs 2016-02-25 00:26:20 +01:00
AliSoftware 328d1b98a5 Merge pull request #49 from AliSoftware/public-autoinjectedpropertybox-protocol
Public AutoInjectedPropertyBox protocol
2016-02-22 20:21:26 +01:00
Ilya Puchka 9f77677014 public AutoInjectedPropertyBox protocol 2016-02-22 15:13:06 +03:00
Ilya Puchka b13a38cf9e clear singleton instances on definition override 2016-02-12 19:50:08 +01:00
Ilya Puchka edb0d3e576 Merge pull request #42 from AliSoftware/feature/pthread
Implemented recursive lock using pthread mutex
2016-02-09 22:34:01 +01:00
Ilya Puchka bd8a503085 Merge pull request #43 from AliSoftware/feature/singleton-scope-fixed
Singleton scope fixed
2016-02-08 11:13:10 +01:00
Ilya Puchka 6af8f18a63 fixed typo 2016-02-08 10:55:53 +01:00
Ilya Puchka bcf2329312 added unit tests 2016-02-08 10:37:25 +01:00
Ilya Puchka b01b7fb055 added thread safety tests for Linux 2016-02-08 01:32:12 +01:00
Ilya Puchka 0515e7358d fixed getting lock 2016-02-07 21:06:26 +01:00
Ilya Puchka 463f9a37a3 added examples to component scope cases 2016-02-07 14:30:16 +01:00
Ilya Puchka 6c802d2c39 fixed releasing singleton instances when definition is removed or container is reset 2016-02-07 14:29:36 +01:00
Ilya Puchka 967a2342b4 storing singleton instances in container instead of definition 2016-02-07 14:00:39 +01:00
Ilya Puchka d7b3db1476 minor refactoring of internal methods 2016-02-07 14:00:39 +01:00
Ilya Puchka f65949bafc removed Foundation import 2016-02-06 20:41:15 +01:00
Ilya Puchka ba420c27a1 implemented recursive lock using pthread mutex 2016-02-06 20:40:04 +01:00
Ilya Puchka ef66212ba3 Merge pull request #41 from AliSoftware/feature/spm
Swift package manager support
2016-02-06 20:39:04 +01:00
Ilya Puchka 1d71eb08ca moved source files to Sources instead of symlink 2016-02-06 16:12:04 +01:00
Ilya Puchka 689ff2ed29 swift package manager support 2016-02-06 15:47:32 +01:00
AliSoftware 3b4dab4c97 Merge pull request #40 from AliSoftware/release/4.1.0
Release 4.1.0
2016-02-04 21:03:36 +01:00
21 changed files with 963 additions and 490 deletions
+4
View File
@@ -32,3 +32,7 @@ Carthage
# `pod install` in .travis.yml
#
# Pods/
# SPM
.build/
Packages
+14 -2
View File
@@ -1,13 +1,25 @@
# CHANGELOG
## 4.2.0
* Added support for Swift Package Manager.
[#41](https://github.com/AliSoftware/Dip/pull/41), [@ilyapuchka](https://github.com/ilyapuchka)
* Added Linux support.
[#42](https://github.com/AliSoftware/Dip/pull/42), [#46](https://github.com/AliSoftware/Dip/pull/46), [@ilyapuchka](https://github.com/ilyapuchka)
* Fixed the issue that could cause singleton instances to be reused between different containers.
[#43](https://github.com/AliSoftware/Dip/pull/43), [@ilyapuchka](https://github.com/ilyapuchka)
* Added public `AutoInjectedPropertyBox` protocol for user-defined auto-injected property wrappers.
[#49](https://github.com/AliSoftware/Dip/pull/49), [@ilyapuchka](https://github.com/ilyapuchka)
## 4.1.0
#### New features
* 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`. Improved errors handling.
[#32](https://github.com/AliSoftware/Dip/pull/13), [@ilyapuchka](https://github.com/ilyapuchka)
* Factories and `resolveDependencies` blocks of `DefinitionOf` are now allowed to `throw`. Improved errors handling.
[#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)
+2 -2
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "Dip"
s.version = "4.1.0"
s.version = "4.2.0"
s.summary = "A simple Dependency Resolver: Dependency Injection using Protocol resolution."
s.description = <<-DESC
@@ -30,5 +30,5 @@ Pod::Spec.new do |s|
s.requires_arc = true
s.source_files = 'Dip/Dip/**/*.swift'
s.source_files = 'Sources/**/*.swift'
end
+21 -13
View File
@@ -49,6 +49,9 @@
09873F7A1C1E0252000C02F6 /* AutoInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09873F551C1E0237000C02F6 /* AutoInjection.swift */; };
09873F7B1C1E0253000C02F6 /* AutoInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09873F551C1E0237000C02F6 /* AutoInjection.swift */; };
09873F7C1C1E0254000C02F6 /* AutoInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09873F551C1E0237000C02F6 /* AutoInjection.swift */; };
09D598331C6F9EC100F24D49 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09D598321C6F9EC100F24D49 /* Utils.swift */; };
09D598341C6F9EC100F24D49 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09D598321C6F9EC100F24D49 /* Utils.swift */; };
09D598351C6F9EC100F24D49 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09D598321C6F9EC100F24D49 /* Utils.swift */; };
2C15B9511C25F01200EA3486 /* ThreadSafetyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C15B94F1C25EF7800EA3486 /* ThreadSafetyTests.swift */; };
2C15B9521C25F01300EA3486 /* ThreadSafetyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C15B94F1C25EF7800EA3486 /* ThreadSafetyTests.swift */; };
2C15B9531C25F01500EA3486 /* ThreadSafetyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C15B94F1C25EF7800EA3486 /* ThreadSafetyTests.swift */; };
@@ -86,20 +89,21 @@
0903B3A61C1618AF002241C1 /* Dip.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Dip.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0903B3AF1C1618AF002241C1 /* Dip-tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Dip-tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
0903B4041C162862002241C1 /* Dip.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Dip.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0919F4C81C16417000DC3B10 /* Definition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Definition.swift; sourceTree = "<group>"; };
0919F4C81C16417000DC3B10 /* Definition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Definition.swift; path = ../../Sources/Definition.swift; sourceTree = "<group>"; };
0919F4C91C16417000DC3B10 /* Dip.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Dip.h; sourceTree = "<group>"; };
0919F4CA1C16417000DC3B10 /* Dip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dip.swift; sourceTree = "<group>"; };
0919F4CA1C16417000DC3B10 /* Dip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Dip.swift; path = ../../Sources/Dip.swift; sourceTree = "<group>"; };
0919F4CB1C16417000DC3B10 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
0919F4CC1C16417000DC3B10 /* RuntimeArguments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeArguments.swift; sourceTree = "<group>"; };
0919F4CE1C16417000DC3B10 /* ComponentScopeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComponentScopeTests.swift; sourceTree = "<group>"; };
0919F4CF1C16417000DC3B10 /* DefinitionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefinitionTests.swift; sourceTree = "<group>"; };
0919F4D01C16417000DC3B10 /* DipTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DipTests.swift; sourceTree = "<group>"; };
0919F4CC1C16417000DC3B10 /* RuntimeArguments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RuntimeArguments.swift; path = ../../Sources/RuntimeArguments.swift; sourceTree = "<group>"; };
0919F4CE1C16417000DC3B10 /* ComponentScopeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ComponentScopeTests.swift; path = Sources/ComponentScopeTests.swift; sourceTree = "<group>"; };
0919F4CF1C16417000DC3B10 /* DefinitionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DefinitionTests.swift; path = Sources/DefinitionTests.swift; sourceTree = "<group>"; };
0919F4D01C16417000DC3B10 /* DipTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DipTests.swift; path = Sources/DipTests.swift; sourceTree = "<group>"; };
0919F4D11C16417000DC3B10 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
0919F4D21C16417000DC3B10 /* RuntimeArgumentsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeArgumentsTests.swift; sourceTree = "<group>"; };
0982AF0B1C5183A000B62463 /* Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = "<group>"; };
09873F551C1E0237000C02F6 /* AutoInjection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutoInjection.swift; sourceTree = "<group>"; };
09873F751C1E0249000C02F6 /* AutoInjectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutoInjectionTests.swift; sourceTree = "<group>"; };
2C15B94F1C25EF7800EA3486 /* ThreadSafetyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreadSafetyTests.swift; sourceTree = "<group>"; };
0919F4D21C16417000DC3B10 /* RuntimeArgumentsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RuntimeArgumentsTests.swift; path = Sources/RuntimeArgumentsTests.swift; sourceTree = "<group>"; };
0982AF0B1C5183A000B62463 /* Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Utils.swift; path = ../../Sources/Utils.swift; sourceTree = "<group>"; };
09873F551C1E0237000C02F6 /* AutoInjection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AutoInjection.swift; path = ../../Sources/AutoInjection.swift; sourceTree = "<group>"; };
09873F751C1E0249000C02F6 /* AutoInjectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AutoInjectionTests.swift; path = Sources/AutoInjectionTests.swift; sourceTree = "<group>"; };
09D598321C6F9EC100F24D49 /* Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Utils.swift; path = Sources/Utils.swift; sourceTree = "<group>"; };
2C15B94F1C25EF7800EA3486 /* ThreadSafetyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ThreadSafetyTests.swift; path = Sources/ThreadSafetyTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -181,6 +185,7 @@
0919F4CE1C16417000DC3B10 /* ComponentScopeTests.swift */,
09873F751C1E0249000C02F6 /* AutoInjectionTests.swift */,
2C15B94F1C25EF7800EA3486 /* ThreadSafetyTests.swift */,
09D598321C6F9EC100F24D49 /* Utils.swift */,
0919F4D11C16417000DC3B10 /* Info.plist */,
);
path = DipTests;
@@ -503,6 +508,7 @@
0919F4E61C16419300DC3B10 /* ComponentScopeTests.swift in Sources */,
2C15B9511C25F01200EA3486 /* ThreadSafetyTests.swift in Sources */,
0919F4E41C16419300DC3B10 /* DefinitionTests.swift in Sources */,
09D598331C6F9EC100F24D49 /* Utils.swift in Sources */,
0919F4E31C16419300DC3B10 /* DipTests.swift in Sources */,
0919F4E51C16419300DC3B10 /* RuntimeArgumentsTests.swift in Sources */,
09873F771C1E024E000C02F6 /* AutoInjectionTests.swift in Sources */,
@@ -528,6 +534,7 @@
0919F4EA1C16419400DC3B10 /* ComponentScopeTests.swift in Sources */,
2C15B9521C25F01300EA3486 /* ThreadSafetyTests.swift in Sources */,
0919F4E81C16419400DC3B10 /* DefinitionTests.swift in Sources */,
09D598341C6F9EC100F24D49 /* Utils.swift in Sources */,
0919F4E71C16419400DC3B10 /* DipTests.swift in Sources */,
0919F4E91C16419400DC3B10 /* RuntimeArgumentsTests.swift in Sources */,
09873F781C1E024E000C02F6 /* AutoInjectionTests.swift in Sources */,
@@ -553,6 +560,7 @@
0919F4EE1C16419500DC3B10 /* ComponentScopeTests.swift in Sources */,
2C15B9531C25F01500EA3486 /* ThreadSafetyTests.swift in Sources */,
0919F4EC1C16419500DC3B10 /* DefinitionTests.swift in Sources */,
09D598351C6F9EC100F24D49 /* Utils.swift in Sources */,
0919F4EB1C16419500DC3B10 /* DipTests.swift in Sources */,
0919F4ED1C16419500DC3B10 /* RuntimeArgumentsTests.swift in Sources */,
09873F791C1E024F000C02F6 /* AutoInjectionTests.swift in Sources */,
@@ -840,7 +848,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 4.1.0;
CURRENT_PROJECT_VERSION = 4.2.0;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
@@ -889,7 +897,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 4.1.0;
CURRENT_PROJECT_VERSION = 4.2.0;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
+9
View File
@@ -0,0 +1,9 @@
import PackageDescription
let package = Package(
name: "DipTests",
dependencies: [
.Package(url: "../../../Dip", majorVersion: 4, minor: 2),
]
)
@@ -28,79 +28,97 @@ import XCTest
private protocol Server: class {
weak var client: Client? {get}
var anotherClient: Client? {get set}
var optionalClient: AnyObject? {get}
var optionalProperty: AnyObject? {get}
}
private protocol Client: class {
var server: Server? {get}
var anotherServer: Server? {get set}
var optionalServer: AnyObject? {get}
var optionalProperty: AnyObject? {get}
}
private class ServerImp: Server {
var _client = InjectedWeak<Client>() { _ in
AutoInjectionTests.clientDidInjectCalled = true
}
var client: Client? {
return _client.value
}
weak var anotherClient: Client?
weak var _optionalProperty = InjectedWeak<AnyObject>(required: false)
var optionalProperty: AnyObject? { return _optionalProperty?.value }
}
private class ClientImp: Client {
var _server = Injected<Server>() { _ in
AutoInjectionTests.serverDidInjectCalled = true
}
var server: Server? {
return _server.value
}
var anotherServer: Server?
var _optionalProperty = Injected<AnyObject>(required: false)
var optionalProperty: AnyObject? { return _optionalProperty.value }
var taggedServer = Injected<Server>(tag: "tagged")
}
private class Obj1 {
let obj2 = InjectedWeak<Obj2>()
let obj3 = Injected<Obj3>()
}
private class Obj2 {
let obj1 = Injected<Obj1>()
}
private class Obj3 {
weak var obj1: Obj1?
init(obj: Obj1) {
self.obj1 = obj
}
}
class AutoInjectionTests: XCTestCase {
static var serverDeallocated: Bool = false
static var clientDeallocated: Bool = false
static var serverDidInjectCalled: Bool = false
static var clientDidInjectCalled: Bool = false
private class ServerImp: Server {
deinit {
AutoInjectionTests.serverDeallocated = true
}
var _client = InjectedWeak<Client>() { _ in
AutoInjectionTests.clientDidInjectCalled = true
}
weak var client: Client? {
return _client.value
}
weak var anotherClient: Client?
weak var _optionalClient = InjectedWeak<AnyObject>(required: false)
var optionalClient: AnyObject? { return _optionalClient?.value }
}
private class ClientImp: Client {
deinit {
AutoInjectionTests.clientDeallocated = true
}
var _server = Injected<Server>() { _ in
AutoInjectionTests.serverDidInjectCalled = true
}
var anotherServer: Server?
var _optionalServer = Injected<AnyObject>(required: false)
var optionalServer: AnyObject? { return _optionalServer.value }
var server: Server? {
return _server.value
}
var taggedServer = Injected<Server>(tag: "tagged")
}
let container = DependencyContainer()
override func setUp() {
super.setUp()
container.reset()
AutoInjectionTests.serverDeallocated = false
AutoInjectionTests.clientDeallocated = false
AutoInjectionTests.clientDidInjectCalled = false
AutoInjectionTests.serverDidInjectCalled = false
#if os(Linux)
var allTests: [(String, () throws -> Void)] {
return [
("testThatItResolvesAutoInjectedDependencies", testThatItResolvesAutoInjectedDependencies),
("testThatItThrowsErrorIfFailsToAutoInjectDependency", testThatItThrowsErrorIfFailsToAutoInjectDependency),
("testThatItResolvesAutoInjectedSingletons", testThatItResolvesAutoInjectedSingletons),
("testThatItCallsResolveDependencyBlockWhenAutoInjecting", testThatItCallsResolveDependencyBlockWhenAutoInjecting),
("testThatItReusesResolvedAutoInjectedInstances", testThatItReusesResolvedAutoInjectedInstances),
("testThatItReusesAutoInjectedInstancesOnNextResolveOrAutoInjection", testThatItReusesAutoInjectedInstancesOnNextResolveOrAutoInjection),
("testThatThereIsNoRetainCycleBetweenAutoInjectedCircularDependencies", testThatThereIsNoRetainCycleBetweenAutoInjectedCircularDependencies),
("testThatItCallsDidInjectOnAutoInjectedProperty", testThatItCallsDidInjectOnAutoInjectedProperty),
("testThatNoErrorThrownWhenOptionalPropertiesAreNotAutoInjected", testThatNoErrorThrownWhenOptionalPropertiesAreNotAutoInjected),
("testThatItResolvesTaggedAutoInjectedProperties", testThatItResolvesTaggedAutoInjectedProperties)
]
}
func setUp() {
container.reset()
}
#else
override func setUp() {
container.reset()
}
#endif
func testThatItResolvesAutoInjectedDependencies() {
container.register(.ObjectGraph) { ServerImp() as Server }
container.register(.ObjectGraph) { ClientImp() as Client }
@@ -110,32 +128,12 @@ class AutoInjectionTests: XCTestCase {
XCTAssertTrue(client === server?.client)
}
func testThatItThrowsErrorIfFailsToInjectDependency() {
func testThatItThrowsErrorIfFailsToAutoInjectDependency() {
container.register(.ObjectGraph) { ClientImp() as Client }
do {
try container.resolve() as Client
XCTFail("Resolve should throw error")
}
catch { }
AssertThrows(expression: try container.resolve() as Client)
}
func testThatThereIsNoRetainCycleForCyrcularDependencies() {
//given
container.register(.ObjectGraph) { ServerImp() as Server }
container.register(.ObjectGraph) { ClientImp() as Client }
var client: Client? = try! container.resolve() as Client
XCTAssertNotNil(client)
//when
client = nil
//then
XCTAssertTrue(AutoInjectionTests.clientDeallocated)
XCTAssertTrue(AutoInjectionTests.serverDeallocated)
}
func testThatItResolvesAutoInjectedSingletons() {
//given
container.register(.Singleton) { ServerImp() as Server }
@@ -154,7 +152,7 @@ class AutoInjectionTests: XCTestCase {
XCTAssertTrue(server === sharedServer)
}
func testThatItCallsResolveDependencyBlockOnOriginalDefiniton() {
func testThatItCallsResolveDependencyBlockWhenAutoInjecting() {
var serverBlockWasCalled = false
//given
@@ -207,24 +205,6 @@ class AutoInjectionTests: XCTestCase {
func testThatItReusesAutoInjectedInstancesOnNextResolveOrAutoInjection() {
//given
class Obj1 {
let obj2 = InjectedWeak<Obj2>()
let obj3 = Injected<Obj3>()
}
class Obj2 {
let obj1 = Injected<Obj1>()
}
class Obj3 {
weak var obj1: Obj1?
init(obj: Obj1) {
self.obj1 = obj
}
}
container.register(.ObjectGraph) { Obj1() }
container.register(.ObjectGraph) { Obj2() }
container.register(.ObjectGraph) { Obj3(obj: try self.container.resolve()) }
@@ -249,19 +229,22 @@ class AutoInjectionTests: XCTestCase {
var client: Client? = try! container.resolve() as Client
//then
weak var server: Server? = client?.server
weak var weakServer: Server? = client?.server
weak var weakClient = client
XCTAssertNotNil(weakClient)
XCTAssertNotNil(server)
XCTAssertNotNil(weakServer)
client = nil
XCTAssertNil(weakClient)
XCTAssertNil(server)
XCTAssertNil(weakServer)
}
func testThatItCallsDidInjectOnAutoInjectedProperty() {
AutoInjectionTests.clientDidInjectCalled = false
AutoInjectionTests.serverDidInjectCalled = false
//given
container.register(.ObjectGraph) { ServerImp() as Server }
container.register(.ObjectGraph) { ClientImp() as Client }
@@ -279,17 +262,10 @@ class AutoInjectionTests: XCTestCase {
container.register(.ObjectGraph) { ServerImp() as Server }
container.register(.ObjectGraph) { ClientImp() as Client }
do {
//when
try container.resolve() as Client
}
catch {
//then
XCTFail("Container should not throw error if failed to resolve optional auto-injected properties.")
}
AssertNoThrow(expression: try container.resolve() as Client, "Container should not throw error if failed to resolve optional auto-injected properties.")
}
func testThatItResolvesAutoInjectedTaggedDependencies() {
func testThatItResolvesTaggedAutoInjectedProperties() {
//given
container.register(.ObjectGraph) { ServerImp() as Server }
container.register(tag: "tagged", .ObjectGraph) { ServerImp() as Server }
@@ -308,5 +284,3 @@ class AutoInjectionTests: XCTestCase {
}
@@ -25,14 +25,53 @@
import XCTest
@testable import Dip
private protocol Service: class {}
private class ServiceImp1: Service {}
private class ServiceImp2: Service {}
private class Server {
weak var client: Client?
init() {}
}
private class Client {
var server: Server
init(server: Server) {
self.server = server
}
}
class ComponentScopeTests: XCTestCase {
let container = DependencyContainer()
override func setUp() {
super.setUp()
#if os(Linux)
var allTests: [(String, () throws -> Void)] {
return [
("testThatPrototypeIsDefaultScope", testThatPrototypeIsDefaultScope),
("testThatScopeCanBeChanged", testThatScopeCanBeChanged),
("testThatItResolvesTypeAsNewInstanceForPrototypeScope", testThatItResolvesTypeAsNewInstanceForPrototypeScope),
("testThatItReusesInstanceForSingletonScope", testThatItReusesInstanceForSingletonScope),
("testThatSingletonIsNotReusedAcrossContainers", testThatSingletonIsNotReusedAcrossContainers),
("testThatSingletonIsReleasedWhenDefinitionIsRemoved", testThatSingletonIsReleasedWhenDefinitionIsRemoved),
("testThatSingletonIsReleasedWhenDefinitionIsOverridden", testThatSingletonIsReleasedWhenDefinitionIsOverridden),
("testThatSingletonIsReleasedWhenContainerIsReset", testThatSingletonIsReleasedWhenContainerIsReset),
("testThatItReusesInstanceInObjectGraphScopeDuringResolve", testThatItReusesInstanceInObjectGraphScopeDuringResolve),
("testThatItDoesNotReuseInstanceInObjectGraphScopeInNextResolve", testThatItDoesNotReuseInstanceInObjectGraphScopeInNextResolve),
("testThatItDoesNotReuseInstanceInObjectGraphScopeResolvedForNilTag", testThatItDoesNotReuseInstanceInObjectGraphScopeResolvedForNilTag)
]
}
func setUp() {
container.reset()
}
#else
override func setUp() {
container.reset()
}
#endif
func testThatPrototypeIsDefaultScope() {
let def = container.register { ServiceImp1() as Service }
@@ -68,20 +107,61 @@ class ComponentScopeTests: XCTestCase {
XCTAssertTrue(service1 === service2)
}
class Server {
weak var client: Client?
func testThatSingletonIsNotReusedAcrossContainers() {
//given
let def = container.register(.Singleton) { ServiceImp1() as Service }
let secondContainer = DependencyContainer()
secondContainer.register(def, forTag: nil)
init() {}
//when
let service1 = try! container.resolve() as Service
let service2 = try! secondContainer.resolve() as Service
//then
XCTAssertTrue(service1 !== service2, "Singleton instances should not be reused across containers")
}
class Client {
var server: Server
func testThatSingletonIsReleasedWhenDefinitionIsRemoved() {
//given
let def = container.register(.Singleton) { ServiceImp1() as Service }
let service1 = try! container.resolve() as Service
init(server: Server) {
self.server = server
}
//when
container.remove(def, forTag: nil)
container.register(def, forTag: nil)
//then
let service2 = try! container.resolve() as Service
XCTAssertTrue(service1 !== service2, "Singleton instances should be released when definition is removed from the container")
}
func testThatSingletonIsReleasedWhenDefinitionIsOverridden() {
//given
let def = container.register(.Singleton) { ServiceImp1() as Service }
let service1 = try! container.resolve() as Service
//when
container.register(def, forTag: nil)
//then
let service2 = try! container.resolve() as Service
XCTAssertTrue(service1 !== service2, "Singleton instances should be released when definition is overridden")
}
func testThatSingletonIsReleasedWhenContainerIsReset() {
//given
let def = container.register(.Singleton) { ServiceImp1() as Service }
let service1 = try! container.resolve() as Service
//when
container.reset()
container.register(def, forTag: nil)
//then
let service2 = try! container.resolve() as Service
XCTAssertTrue(service1 !== service2, "Singleton instances should be released when container is reset")
}
func testThatItReusesInstanceInObjectGraphScopeDuringResolve() {
//given
container.register(.ObjectGraph) { Client(server: try self.container.resolve()) as Client }
@@ -139,3 +219,4 @@ class ComponentScopeTests: XCTestCase {
}
}
@@ -25,13 +25,29 @@
import XCTest
@testable import Dip
private protocol Service {}
private class ServiceImp: Service {}
class DefinitionTests: XCTestCase {
typealias F1 = ()->Service
typealias F2 = (String)->Service
private typealias F1 = () -> Service
private typealias F2 = (String) -> Service
let tag1 = DependencyContainer.Tag.String("tag1")
let tag2 = DependencyContainer.Tag.String("tag2")
#if os(Linux)
var allTests: [(String, () throws -> Void)] {
return [
("testThatDefinitionKeyIsEqualBy_Type_Factory_Tag", testThatDefinitionKeyIsEqualBy_Type_Factory_Tag),
("testThatDefinitionKeysWithDifferentTypesAreNotEqual", testThatDefinitionKeysWithDifferentTypesAreNotEqual),
("testThatDefinitionKeysWithDifferentFactoriesAreNotEqual", testThatDefinitionKeysWithDifferentFactoriesAreNotEqual),
("testThatDefinitionKeysWithDifferentTagsAreNotEqual", testThatDefinitionKeysWithDifferentTagsAreNotEqual),
("testThatResolveDependenciesCallsResolveDependenciesBlock", testThatResolveDependenciesCallsResolveDependenciesBlock),
("testThatResolveDependenciesBlockIsNotCalledWhenPassedWrongInstance", testThatResolveDependenciesBlockIsNotCalledWhenPassedWrongInstance)
]
}
#endif
func testThatDefinitionKeyIsEqualBy_Type_Factory_Tag() {
let equalKey1 = DefinitionKey(protocolType: Service.self, factoryType: F1.self, associatedTag: tag1)
@@ -65,16 +81,16 @@ class DefinitionTests: XCTestCase {
XCTAssertNotEqual(keyWithDifferentTag1.hashValue, keyWithDifferentTag2.hashValue)
}
func testThatResolveDependenciesCallsResolveDependenciesBlockWhen() {
func testThatResolveDependenciesCallsResolveDependenciesBlock() {
var blockCalled = false
//given
let def = DefinitionOf<Service, () -> Service>(scope: .Prototype) { ServiceImp1() as Service }.resolveDependencies { container, service in
let def = DefinitionOf<Service, () -> Service>(scope: .Prototype) { ServiceImp() as Service }.resolveDependencies { container, service in
blockCalled = true
}
//when
try! def.resolveDependencies(DependencyContainer(), resolvedInstance: ServiceImp1())
try! def.resolveDependenciesOf(ServiceImp(), withContainer: DependencyContainer())
//then
XCTAssertTrue(blockCalled)
@@ -84,14 +100,15 @@ class DefinitionTests: XCTestCase {
var blockCalled = false
//given
let def = DefinitionOf<Service, () -> Service>(scope: .Prototype) { ServiceImp1() as Service }.resolveDependencies { container, service in
let def = DefinitionOf<Service, () -> Service>(scope: .Prototype) { ServiceImp() as Service }.resolveDependencies { container, service in
blockCalled = true
}
//when
try! def.resolveDependencies(DependencyContainer(), resolvedInstance: String())
try! def.resolveDependenciesOf(String(), withContainer: DependencyContainer())
//then
XCTAssertFalse(blockCalled)
}
}
@@ -25,31 +25,39 @@
import XCTest
@testable import Dip
protocol Service: class {
func getServiceName() -> String
}
extension Service {
func getServiceName() -> String {
return "\(self.dynamicType)"
}
}
class ServiceImp1: Service {
}
class ServiceImp2: Service {
}
private protocol Service: class { }
private class ServiceImp1: Service { }
private class ServiceImp2: Service { }
class DipTests: XCTestCase {
let container = DependencyContainer()
override func setUp() {
super.setUp()
#if os(Linux)
var allTests: [(String, () throws -> Void)] {
return [
("testThatItResolvesInstanceRegisteredWithoutTag", testThatItResolvesInstanceRegisteredWithoutTag),
("testThatItResolvesInstanceRegisteredWithTag", testThatItResolvesInstanceRegisteredWithTag),
("testThatItResolvesDifferentInstancesRegisteredForDifferentTags", testThatItResolvesDifferentInstancesRegisteredForDifferentTags),
("testThatNewRegistrationOverridesPreviousRegistration", testThatNewRegistrationOverridesPreviousRegistration),
("testThatItCallsResolveDependenciesOnDefinition", testThatItCallsResolveDependenciesOnDefinition),
("testThatItThrowsErrorIfCanNotFindDefinitionForType", testThatItThrowsErrorIfCanNotFindDefinitionForType),
("testThatItThrowsErrorIfCanNotFindDefinitionForTag", testThatItThrowsErrorIfCanNotFindDefinitionForTag),
("testThatItThrowsErrorIfCanNotFindDefinitionForFactoryWithArguments", testThatItThrowsErrorIfCanNotFindDefinitionForFactoryWithArguments),
("testThatItThrowsErrorIfConstructorThrows", testThatItThrowsErrorIfConstructorThrows),
("testThatItThrowsErrorIfFailsToResolveDependency", testThatItThrowsErrorIfFailsToResolveDependency)
]
}
func setUp() {
container.reset()
}
#else
override func setUp() {
container.reset()
}
#endif
func testThatItResolvesInstanceRegisteredWithoutTag() {
//given
container.register { ServiceImp1() as Service }
@@ -60,7 +68,7 @@ class DipTests: XCTestCase {
//then
XCTAssertTrue(serviceInstance is ServiceImp1)
}
func testThatItResolvesInstanceRegisteredWithTag() {
//given
container.register(tag: "service") { ServiceImp1() as Service }
@@ -119,18 +127,15 @@ class DipTests: XCTestCase {
container.register { ServiceImp1() as ServiceImp1 }
//when
do {
try container.resolve() as Service
XCTFail("Unexpectedly resolved protocol")
}
catch let DipError.DefinitionNotFound(key) {
AssertThrows(expression: try container.resolve() as Service) { error in
guard case let DipError.DefinitionNotFound(key) = error else { return false }
//then
typealias F = () throws -> Service
let expectedKey = DefinitionKey(protocolType: Service.self, factoryType: F.self, associatedTag: nil)
XCTAssertEqual(key, expectedKey)
}
catch {
XCTFail("Thrown unexpected error")
return true
}
}
@@ -139,18 +144,15 @@ class DipTests: XCTestCase {
container.register(tag: "some tag") { ServiceImp1() as Service }
//when
do {
try container.resolve(tag: "other tag") as Service
XCTFail("Unexpectedly resolved protocol")
}
catch let DipError.DefinitionNotFound(key) {
AssertThrows(expression: try container.resolve(tag: "other tag") as Service) { error in
guard case let DipError.DefinitionNotFound(key) = error else { return false }
//then
typealias F = () throws -> Service
let expectedKey = DefinitionKey(protocolType: Service.self, factoryType: F.self, associatedTag: "other tag")
XCTAssertEqual(key, expectedKey)
}
catch {
XCTFail("Thrown unexpected error")
return true
}
}
@@ -159,18 +161,15 @@ class DipTests: XCTestCase {
container.register { ServiceImp1() as Service }
//when
do {
try container.resolve(withArguments: "some string") as Service
XCTFail("Unexpectedly resolved protocol")
}
catch let DipError.DefinitionNotFound(key) {
AssertThrows(expression: try container.resolve(withArguments: "some string") as Service) { error in
guard case let DipError.DefinitionNotFound(key) = error else { return false }
//then
typealias F = (String) throws -> Service
let expectedKey = DefinitionKey(protocolType: Service.self, factoryType: F.self, associatedTag: nil)
XCTAssertEqual(key, expectedKey)
}
catch {
XCTFail("Thrown unexpected error")
return true
}
}
@@ -181,24 +180,16 @@ class DipTests: XCTestCase {
container.register { () throws -> Service in throw expectedError }
//when
do {
try container.resolve() as Service
}
catch let DipError.ResolutionFailed(key, error) {
AssertThrows(expression: try container.resolve() as Service) { error in
guard case let DipError.ResolutionFailed(key, error) = error else { return false }
guard case let DipError.DefinitionNotFound(subKey) = error where subKey == failedKey else { return false }
//then
typealias F = () throws -> Service
let expectedKey = DefinitionKey(protocolType: Service.self, factoryType: F.self)
XCTAssertEqual(key, expectedKey)
switch error {
case let DipError.DefinitionNotFound(subKey) where subKey == failedKey:
break
default:
XCTFail()
}
}
catch {
XCTFail("Thrown unexpected error")
return true
}
}
@@ -213,25 +204,19 @@ class DipTests: XCTestCase {
}
//when
do {
try container.resolve() as Service
}
catch let DipError.ResolutionFailed(key, error) {
AssertThrows(expression: try container.resolve() as Service) { error in
guard case let DipError.ResolutionFailed(key, error) = error else { return false }
guard case let DipError.DefinitionNotFound(subKey) = error where subKey == failedKey else { return false }
//then
typealias F = () throws -> Service
let expectedKey = DefinitionKey(protocolType: Service.self, factoryType: F.self)
XCTAssertEqual(key, expectedKey)
switch error {
case let DipError.DefinitionNotFound(subKey) where subKey == failedKey:
break
default:
XCTFail()
}
}
catch {
XCTFail("Thrown unexpected error")
return true
}
}
}
@@ -25,27 +25,57 @@
import XCTest
@testable import Dip
class ServiceImp3: Service {
private protocol Service {
var name: String { get }
}
private class ServiceImp: Service {
let name: String
init(name: String, baseURL: NSURL, port: Int) {
init(name: String, baseURL: String, port: Int) {
self.name = name
}
func getServiceName() -> String {
return name
}
}
private class ServiceImp1: Service {
let name: String = "ServiceImp1"
}
private class ServiceImp2: Service {
let name: String = "ServiceImp2"
}
class RuntimeArgumentsTests: XCTestCase {
let container = DependencyContainer()
override func setUp() {
super.setUp()
#if os(Linux)
var allTests: [(String, () throws -> Void)] {
return [
("testThatItResolvesInstanceWithOneArgument", testThatItResolvesInstanceWithOneArgument),
("testThatItResolvesInstanceWithTwoArguments", testThatItResolvesInstanceWithTwoArguments),
("testThatItResolvesInstanceWithThreeArguments", testThatItResolvesInstanceWithThreeArguments),
("testThatItResolvesInstanceWithFourArguments", testThatItResolvesInstanceWithFourArguments),
("testThatItResolvesInstanceWithFiveArguments", testThatItResolvesInstanceWithFiveArguments),
("testThatItResolvesInstanceWithSixArguments", testThatItResolvesInstanceWithSixArguments),
("testThatItRegistersDifferentFactoriesForDifferentNumberOfArguments", testThatItRegistersDifferentFactoriesForDifferentNumberOfArguments),
("testThatItRegistersDifferentFactoriesForDifferentTypesOfArguments", testThatItRegistersDifferentFactoriesForDifferentTypesOfArguments),
("testThatItRegistersDifferentFactoriesForDifferentOrderOfArguments", testThatItRegistersDifferentFactoriesForDifferentOrderOfArguments),
("testThatNewRegistrationWithSameArgumentsOverridesPreviousRegistration", testThatNewRegistrationWithSameArgumentsOverridesPreviousRegistration),
("testThatDifferentFactoriesRegisteredIfArgumentIsOptional", testThatDifferentFactoriesRegisteredIfArgumentIsOptional)
]
}
func setUp() {
container.reset()
}
#else
override func setUp() {
container.reset()
}
#endif
func testThatItResolvesInstanceWithOneArgument() {
//given
@@ -211,23 +241,20 @@ class RuntimeArgumentsTests: XCTestCase {
func testThatDifferentFactoriesRegisteredIfArgumentIsOptional() {
//given
let name1 = "1", name2 = "2", name3 = "3"
container.register { (port: Int, url: NSURL) in ServiceImp3(name: name1, baseURL: url, port: port) as Service }
container.register { (port: Int, url: NSURL?) in ServiceImp3(name: name2, baseURL: url!, port: port) as Service }
container.register { (port: Int, url: NSURL!) in ServiceImp3(name: name3, baseURL: url, port: port) as Service }
container.register { (port: Int, url: String) in ServiceImp(name: name1, baseURL: url, port: port) as Service }
container.register { (port: Int, url: String?) in ServiceImp(name: name2, baseURL: url!, port: port) as Service }
container.register { (port: Int, url: String!) in ServiceImp(name: name3, baseURL: url, port: port) as Service }
//when
let url: NSURL = NSURL(string: "http://example.com")!
let service1 = try! container.resolve(withArguments: 80, url) as Service
let service2 = try! container.resolve(withArguments: 80, NSURL(string: "http://example.com")) as Service
let service3 = try! container.resolve(withArguments: 80, NSURL(string: "http://example.com")! as NSURL!) as Service
let service4 = try! container.resolve(withArguments: 80, NSURL(string: "http://example.com")!) as Service
let service1 = try! container.resolve(withArguments: 80, "http://example.com") as Service
let service2 = try! container.resolve(withArguments: 80, "http://example.com" as String?) as Service
let service3 = try! container.resolve(withArguments: 80, "http://example.com" as String!) as Service
//then
XCTAssertEqual(service1.getServiceName(), name1)
XCTAssertEqual(service2.getServiceName(), name2)
XCTAssertEqual(service3.getServiceName(), name3)
XCTAssertEqual(service4.getServiceName(), name1) //implicitly unwrapped optional parameter is the same as not optional parameter
XCTAssertEqual(service1.name, name1)
XCTAssertEqual(service2.name, name2)
XCTAssertEqual(service3.name, name3)
}
}
@@ -0,0 +1,225 @@
//
// Dip
//
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
//
// 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
private protocol Server: class {
var client: Client? { get set }
}
private protocol Client: class {
var server: Server { get }
}
private class ClientImp: Client, Equatable {
var server: Server
init(server: Server) {
self.server = server
}
}
private func ==<T: ClientImp>(lhs: T, rhs: T) -> Bool {
return lhs === rhs
}
private class ServerImp: Server, Hashable {
weak var client: Client?
init() {}
var hashValue: Int {
return unsafeAddressOf(self).hashValue
}
}
private func ==<T: ServerImp>(lhs: T, rhs: T) -> Bool {
return lhs === rhs
}
private var resolvedServers = Set<ServerImp>()
private var resolvedClients = Array<ClientImp>()
private var container: DependencyContainer!
#if os(Linux)
import Glibc
private var lock: pthread_spinlock_t = 0
private let resolveClientSync: () -> Client? = {
var clientPointer: UnsafeMutablePointer<Void> = nil
clientPointer = dispatch_sync { _ in
let resolved = try! container.resolve() as Client
return UnsafeMutablePointer(Unmanaged.passUnretained(resolved as! ClientImp).toOpaque())
}
return Unmanaged<ClientImp>.fromOpaque(COpaquePointer(clientPointer)).takeUnretainedValue()
}
#else
let queue = NSOperationQueue()
let lock = NSRecursiveLock()
private let resolveClientSync: () -> Client? = {
var client: Client?
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
client = try! container.resolve() as Client
}
return client
}
#endif
let resolveServerAsync = {
let service = try! container.resolve() as Server
lock.lock()
resolvedServers.insert(service as! ServerImp)
lock.unlock()
}
let resolveClientAsync = {
let client = try! container.resolve() as Client
lock.lock()
resolvedClients.append(client as! ClientImp)
lock.unlock()
}
class ThreadSafetyTests: XCTestCase {
#if os(Linux)
init() {
pthread_spin_init(&lock, 0)
}
var allTests: [(String, () throws -> Void)] {
return [
("testSingletonThreadSafety", testSingletonThreadSafety),
("testFactoryThreadSafety", testFactoryThreadSafety),
("testCircularReferenceThreadSafety", testCircularReferenceThreadSafety)
]
}
func setUp() {
container = DependencyContainer()
}
func tearDown() {
resolvedServers.removeAll()
resolvedClients.removeAll()
}
#else
override func setUp() {
container = DependencyContainer()
}
override func tearDown() {
resolvedServers.removeAll()
resolvedClients.removeAll()
}
#endif
func testSingletonThreadSafety() {
container.register(.Singleton) { ServerImp() as Server }
for _ in 0..<100 {
#if os(Linux)
dispatch_async({ _ in
resolveServerAsync()
return nil
})
#else
queue.addOperationWithBlock(resolveServerAsync)
#endif
}
#if os(Linux)
sleep(1)
#else
queue.waitUntilAllOperationsAreFinished()
#endif
XCTAssertEqual(resolvedServers.count, 1, "Should create only one instance")
}
func testFactoryThreadSafety() {
container.register { ServerImp() as Server }
for _ in 0..<100 {
#if os(Linux)
dispatch_async({ _ in
resolveServerAsync()
return nil
})
#else
queue.addOperationWithBlock(resolveServerAsync)
#endif
}
#if os(Linux)
sleep(1)
#else
queue.waitUntilAllOperationsAreFinished()
#endif
XCTAssertEqual(resolvedServers.count, 100, "All instances should be different")
}
func testCircularReferenceThreadSafety() {
container.register(.ObjectGraph) {
ClientImp(server: try container.resolve()) as Client
}
container.register(.ObjectGraph) { ServerImp() as Server }
.resolveDependencies { container, server in
server.client = resolveClientSync()
}
for _ in 0..<100 {
#if os(Linux)
dispatch_async({ _ in
resolveClientAsync()
return nil
})
#else
queue.addOperationWithBlock(resolveClientAsync)
#endif
}
#if os(Linux)
sleep(1)
#else
queue.waitUntilAllOperationsAreFinished()
#endif
XCTAssertEqual(resolvedClients.count, 100, "Instances should be not reused in different object graphs")
for client in resolvedClients {
let service = client.server as! ServerImp
let serviceClient = service.client as! ClientImp
XCTAssertEqual(serviceClient, client, "Instances should be reused when resolving single object graph")
}
}
}
+94
View File
@@ -0,0 +1,94 @@
//
// Dip
//
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
//
// 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
#if os(Linux)
typealias FileString = StaticString
#else
typealias FileString = String
#endif
func AssertThrows<T>(file: FileString = __FILE__, line: UInt = __LINE__, @autoclosure expression: () throws -> T) {
AssertThrows(file, line: line, expression: expression, "")
}
func AssertThrows<T>(file: FileString = __FILE__, line: UInt = __LINE__, @autoclosure expression: () throws -> T, _ message: String) {
AssertThrows(expression: expression, checkError: { _ in true }, message)
}
func AssertThrows<T>(file: FileString = __FILE__, line: UInt = __LINE__, @autoclosure expression: () throws -> T, checkError: ErrorType -> Bool) {
AssertThrows(file, line: line, expression: expression, checkError: checkError, "")
}
func AssertThrows<T>(file: FileString = __FILE__, line: UInt = __LINE__, @autoclosure expression: () throws -> T, checkError: ErrorType -> Bool, _ message: String) {
do {
try expression()
XCTFail(message, file: file, line: line)
}
catch {
XCTAssertTrue(checkError(error), "Thrown unexpected error: \(error)")
}
}
func AssertNoThrow<T>(file: FileString = __FILE__, line: UInt = __LINE__, @autoclosure expression: () throws -> T) {
AssertNoThrow(file, line: line, expression: expression, "")
}
func AssertNoThrow<T>(file: FileString = __FILE__, line: UInt = __LINE__, @autoclosure expression: () throws -> T, _ message: String) {
do {
try expression()
}
catch {
XCTFail(message, file: file, line: line)
}
}
#if os(Linux)
import Glibc
typealias TMain = @convention(c) (UnsafeMutablePointer<Void>) -> UnsafeMutablePointer<Void>
func dispatch_async(block: TMain) {
var pid: pthread_t = 0
pthread_create(&pid, nil, block, nil)
}
func dispatch_sync(block: TMain) -> UnsafeMutablePointer<Void> {
var pid: pthread_t = 0
var result: UnsafeMutablePointer<Void> = nil
pthread_create(&pid, nil, block, nil)
pthread_join(pid, &result)
return result
}
extension pthread_spinlock_t {
mutating func lock() {
pthread_spin_lock(&self)
}
mutating func unlock() {
pthread_spin_unlock(&self)
}
}
#endif
+10
View File
@@ -0,0 +1,10 @@
import XCTest
XCTMain([
DipTests(),
DefinitionTests(),
RuntimeArgumentsTests(),
ComponentScopeTests(),
AutoInjectionTests(),
ThreadSafetyTests()
])
-152
View File
@@ -1,152 +0,0 @@
//
// Dip
//
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
//
// 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
protocol HashableService: class, Service, Hashable {}
extension HashableService {
var hashValue: Int {
return unsafeAddressOf(self).hashValue
}
}
extension ServiceImp1: HashableService {
}
extension ServiceImp2: HashableService {
}
internal func ==<T: HashableService>(lhs: T, rhs: T) -> Bool {
return lhs === rhs
}
class ThreadSafetyTests: XCTestCase {
class Server {
weak var client: Client?
init() {}
}
class Client {
var server: Server
init(server: Server) {
self.server = server
}
}
let container = DependencyContainer()
override func setUp() {
super.setUp()
container.reset()
}
func testSingletonThreadSafety() {
let queue = NSOperationQueue()
let lock = NSRecursiveLock()
var resultSet = Set<ServiceImp1>()
container.register(.Singleton) { ServiceImp1() as Service }
for _ in 1...100 {
queue.addOperationWithBlock {
let serviceInstance = try! self.container.resolve() as Service
lock.lock()
resultSet.insert(serviceInstance as! ServiceImp1)
lock.unlock()
}
}
queue.waitUntilAllOperationsAreFinished()
XCTAssertEqual(resultSet.count, 1)
}
func testFactoryThreadSafety() {
let queue = NSOperationQueue()
let lock = NSRecursiveLock()
var resultSet = Set<ServiceImp1>()
container.register() { ServiceImp1() as Service }
for _ in 1...100 {
queue.addOperationWithBlock {
let serviceInstance = try! self.container.resolve() as Service
lock.lock()
resultSet.insert(serviceInstance as! ServiceImp1)
lock.unlock()
}
}
queue.waitUntilAllOperationsAreFinished()
XCTAssertEqual(resultSet.count, 100)
}
func testCircularReferenceThreadSafety() {
let queue = NSOperationQueue()
let lock = NSLock()
container.register(.ObjectGraph) {
Client(server: try self.container.resolve()) as Client
}
container.register(.ObjectGraph) { Server() as Server }.resolveDependencies { container, server in
var client: Client?
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
client = try! container.resolve() as Client
}
server.client = client!
}
var results = Array<Client>()
for _ in 1...100 {
queue.addOperationWithBlock {
//when
let client = try! self.container.resolve() as Client
lock.lock()
results.append(client)
lock.unlock()
}
}
queue.waitUntilAllOperationsAreFinished()
for client in results {
let server = client.server
XCTAssertTrue(server.client === client)
}
}
}
+4 -18
View File
@@ -22,23 +22,9 @@
// THE SOFTWARE.
//
import Foundation
import PackageDescription
extension Dictionary {
subscript(key: Key?) -> Value? {
get {
guard let key = key else { return nil }
return self[key]
}
set {
guard let key = key else { return }
self[key] = newValue
}
}
}
let package = Package(
name: "Dip"
)
extension Optional {
var desc: String {
return self.map { "\($0)" } ?? "nil"
}
}
+29 -8
View File
@@ -20,6 +20,7 @@ It's aimed to be as simple as possible yet provide rich functionality usual for
This allows you to define the real, concrete types only in one place ([e.g. like this in your app](SampleApp/DipSampleApp/DependencyContainers.swift#L22-L27), and [resetting it in your `setUp` for each Unit Tests](SampleApp/Tests/SWAPIPersonProviderTests.swift#L17-L21)) and then [only work with `protocols` in your code](SampleApp/DipSampleApp/Providers/SWAPIStarshipProvider.swift#L12) (which only define an API contract), without worrying about the real implementation.
> You can easily use Dip along with Storyboards and Nibs - checkout [Dip-UI](https://github.com/AliSoftware/Dip-UI) extensions.
## Advantages of DI and loose coupling
@@ -47,6 +48,27 @@ If you use _Carthage_ add this line to your Cartfile:
github "AliSoftware/Dip"
```
If you use [_Swift Package Manager_](https://swift.org/package-manager/) add Dip as dependency to you `Package.swift`:
```
let package = Package(
name: "MyPackage",
dependencies: [
.Package(url: "https://github.com/AliSoftware/Dip.git", "4.2.0")
]
)
```
## Running tests
On OSX you can run tests from Xcode. On Linux you need to have Swift Package Manager installed and use it to build test executable:
```
cd Dip/DipTests
swift build
./.build/debug/DipTests
```
## Playground
Dip comes with a **Playground** to introduce you to Inversion of Control, Dependency Injection, and how to use Dip in practice.
@@ -142,7 +164,7 @@ enum WebService: String {
case Production
case Development
var tag: Tag { return Tag.String(self.rawValue) }
var tag: DependencyContainer.Tag { return DependencyContainer.Tag.String(self.rawValue) }
}
let wsDependencies = DependencyContainer() { dip in
@@ -185,7 +207,7 @@ More information about circular dependencies you can find in the Playground.
### Auto-Injection
Auto-injection lets your resolve all the dependencies of the instance (created manually or resolved by container) with just one call, also allowing a simpler syntax to register circular dependencies.
Auto-injection lets your resolve all the dependencies of the instance resolved by container with just one call, also allowing a simpler syntax to register circular dependencies.
```swift
protocol Server {
@@ -214,6 +236,8 @@ let client = try! container.resolve() as Client
```
You can find more use cases for auto-injection in the Playground available in this repository.
> Tip: You can use either `Injected<T>` and `InjectedWeak<T>` wrappers provided by Dip, or your own wrappers (even plain `Box<T>`) that conform to `AutoInjectedPropertyBox` protocol.
### Thread safety
`DependencyContainer` is thread safe, you can register and resolve components from different threads.
@@ -222,12 +246,9 @@ when you try to resolve component from one thread while it was not yet registere
### Errors
The resolve operation has a potential to fail because you can use the wrong type, factory or a wrong tag. For that reason Dip throws an error
`DefinitionNotFound(DefinitionKey)` if it failed to resolve a type. Thus when calling `resolve` you need to use a `try` operator.
There are very rare use cases when your application can recover from this kind of error. In most of the cases you can use `try!`
to cause an exception at runtime if error was thrown or `try?` if a dependency is optional.
This way `try!` serves as an additional mark for developers that resolution can fail.
Dip also provides helpful descriptions for errors that can occur when you call `resolve`. See the source code documentation to know more about that.
The resolve operation has a potential to fail because you can use the wrong type, factory or a wrong tag. For that reason Dip throws a `DipError` if it fails to resolve a type. Thus when calling `resolve` you need to use a `try` operator.
There are very rare use cases when your application can recover from this kind of error. In most of the cases you can use `try!` to cause an exception at runtime if error was thrown or `try?` if a dependency is optional. This way `try!` serves as an additional mark for developers that resolution can fail.
Dip also provides helpful descriptions for errors that can occur when you call `resolve`. See the source code documentation to know more about that.
### Concrete Example
@@ -22,8 +22,6 @@
// THE SOFTWARE.
//
//MARK: Public
extension DependencyContainer {
/**
@@ -34,7 +32,7 @@ extension DependencyContainer {
}
private func _resolveChild(child: Mirror.Child) throws {
guard let injectedPropertyBox = child.value as? _AnyInjectedPropertyBox else { return }
guard let injectedPropertyBox = child.value as? AutoInjectedPropertyBox else { return }
do {
try injectedPropertyBox.resolve(self)
@@ -46,14 +44,44 @@ extension DependencyContainer {
}
private protocol _AnyInjectedPropertyBox: class {
/**
Implement this protocol if you want to use your own type to wrap auto-injected properties
instead of using `Injected<T>` or `InjectedWeak<T>` types.
**Example**:
```swift
class MyCustomBox<T> {
private(set) var value: T?
init() {}
}
extension MyCustomBox: AutoInjectedPropertyBox {
static var wrappedType: Any.Type { return T.self }
func resolve(container: DependencyContainer) throws {
value = try container.resolve() as T
}
}
```
*/
public protocol AutoInjectedPropertyBox: class {
///The type of wrapped property.
static var wrappedType: Any.Type { get }
/**
This method will be called by `DependencyContainer` during processing resolved instance properties.
In this method you should resolve an instance for wrapped property and store a reference to it.
- parameter container: A container to be used to resolve an instance
-note: This method is not intended to be called manually, `DependencyContainer` will call it by itself.
*/
func resolve(container: DependencyContainer) throws
}
/**
Use this wrapper to identifiy strong properties of the instance that should be
Use this wrapper to identify _strong_ properties of the instance that should be
auto-injected by `DependencyContainer`. Type T can be any type.
- warning: Do not define this property as optional or container will not be able to inject it.
@@ -70,21 +98,21 @@ private protocol _AnyInjectedPropertyBox: class {
- seealso: `InjectedWeak`
*/
public final class Injected<T>: _InjectedPropertyBox<T>, _AnyInjectedPropertyBox {
public final class Injected<T>: _InjectedPropertyBox<T>, AutoInjectedPropertyBox {
var _value: Any? = nil {
public static var wrappedType: Any.Type {
return T.self
}
///Wrapped value.
public private(set) var value: T? {
didSet {
if let value = value { didInject(value) }
}
}
///Wrapped value.
public var value: T? {
return _value as? T
}
/**
Creates new wrapper for auto-injected property.
Creates a new wrapper for auto-injected property.
- parameters:
- required: Defines if the property is required or not.
@@ -98,24 +126,23 @@ public final class Injected<T>: _InjectedPropertyBox<T>, _AnyInjectedPropertyBox
super.init(required: required, tag: tag, didInject: didInject)
}
private func resolve(container: DependencyContainer) throws {
public func resolve(container: DependencyContainer) throws {
let resolved: T? = try super.resolve(container)
_value = resolved
value = resolved
}
}
/**
Use this wrapper to identifiy strong properties of the instance that should be
Use this wrapper to identify _weak_ properties of the instance that should be
auto-injected by `DependencyContainer`. Type T should be a **class** type.
Otherwise it will cause runtime exception when container will try to resolve the property.
Use this wrapper to define one of two circular dependencies to avoid retain cycle.
- note: The only difference between `InjectedWeak` and `Injected` is that `InjectedWeak` uses
_weak_ reference to store underlying value, when `Injected` uses _strong_ reference.
For that reason if you resolve instance that has _weak_ auto-injected property this property
will be released when `resolve` returns because no one else holds reference to it except
the container during dependency graph resolution.
For that reason if you resolve instance that has a _weak_ auto-injected property this property
will be released when `resolve` will complete.
Use `InjectedWeak<T>` to define one of two circular dependecies if another dependency is defined as `Injected<U>`.
This will prevent a retain cycle between resolved instances.
@@ -135,13 +162,17 @@ public final class Injected<T>: _InjectedPropertyBox<T>, _AnyInjectedPropertyBox
- seealso: `Injected`
*/
public final class InjectedWeak<T>: _InjectedPropertyBox<T>, _AnyInjectedPropertyBox {
public final class InjectedWeak<T>: _InjectedPropertyBox<T>, AutoInjectedPropertyBox {
//Only classes (means AnyObject) can be used as `weak` properties
//but we can not make <T: AnyObject> because that will prevent using protocol as generic type
//so we just rely on user reading documentation and passing AnyObject in runtime
//also we will throw fatal error if type can not be casted to AnyObject during resolution.
public static var wrappedType: Any.Type {
return T.self
}
weak var _value: AnyObject? = nil {
didSet {
if let value = value { didInject(value) }
@@ -154,7 +185,7 @@ public final class InjectedWeak<T>: _InjectedPropertyBox<T>, _AnyInjectedPropert
}
/**
Creates new wrapper for weak auto-injected property.
Creates a new wrapper for weak auto-injected property.
- parameters:
- required: Defines if the property is required or not.
@@ -168,22 +199,18 @@ public final class InjectedWeak<T>: _InjectedPropertyBox<T>, _AnyInjectedPropert
super.init(required: required, tag: tag, didInject: didInject)
}
private func resolve(container: DependencyContainer) throws {
public func resolve(container: DependencyContainer) throws {
let resolved: T? = try super.resolve(container)
guard let resolvedObject = resolved as? AnyObject else {
if required && !(resolved is AnyObject) {
fatalError("\(T.self) can not be casted to AnyObject. InjectedWeak wrapper should be used to wrap only classes.")
}
_value = resolvedObject
_value = resolved as? AnyObject
}
}
private class _InjectedPropertyBox<T> {
static var wrappedType: Any.Type {
return T.self
}
let required: Bool
let didInject: T -> ()
let tag: DependencyContainer.Tag?
@@ -197,10 +224,10 @@ private class _InjectedPropertyBox<T> {
private func resolve(container: DependencyContainer) throws -> T? {
let resolved: T?
if required {
resolved = try container.resolve(tag: tag, builder: { (factory: () throws -> T) in try factory() }) as T
resolved = try container.resolve(tag: tag) as T
}
else {
resolved = try? container.resolve(tag: tag, builder: { (factory: () throws -> T) in try factory() }) as T
resolved = try? container.resolve(tag: tag) as T
}
return resolved
}
@@ -53,11 +53,82 @@ public func ==(lhs: DefinitionKey, rhs: DefinitionKey) -> Bool {
///Component scope defines a strategy used by the `DependencyContainer` to manage resolved instances life cycle.
public enum ComponentScope {
/// A new instance will be created each time it's resolved.
/**
A new instance will be created every time it's resolved.
This is a default strategy. Use this strategy when you don't want instances to be shared
between different consumers (i.e. if it is not thread safe).
**Example**:
```
container.register { ServiceImp() as Service }
container.register {
ServiceConsumerImp(
service1: try container.resolve() as Service
service2: try container.resolve() as Service
) as ServiceConsumer
}
let consumer = container.resolve() as ServiceConsumer
consumer.service1 !== consumer.service2 //true
```
*/
case Prototype
/// Resolved instances will be reused until topmost `resolve(tag:)` method returns.
/**
Instance resolved with the same definition will be reused until topmost `resolve(tag:)` method returns.
When you resolve the same object graph again the container will create new instances.
Use this strategy if you want different object in objects graph to share the same instance.
- warning: Make sure this component is thread safe or accessed always from the same thread.
**Example**:
```
container.register(.ObjectGraph) { ServiceImp() as Service }
container.register {
ServiceConsumerImp(
service1: try container.resolve() as Service
service2: try container.resolve() as Service
) as ServiceConsumer
}
let consumer1 = container.resolve() as ServiceConsumer
let consumer2 = container.resolve() as ServiceConsumer
consumer1.service1 === consumer1.service2 //true
consumer2.service1 === consumer2.service2 //true
consumer1.service1 !== consumer2.service1 //true
```
*/
case ObjectGraph
/// Resolved instance will be retained by the container and always reused. Instance is retained not by container itself but by corresponding definition. Do not mix this lifecycle with _singleton pattern_. Instance will be not shared between defferent containers.
/**
Resolved instance will be retained by the container and always reused.
Do not mix this life cycle with _singleton pattern_.
Instance will be not shared between different containers.
- warning: Make sure this component is thread safe or accessed always from the same thread.
- note: When you override or remove definition from the container an instance
that was resolved with this definition will be released. When you reset
the container it will release all singleton instances.
**Example**:
```
container.register(.Singleton) { ServiceImp() as Service }
container.register {
ServiceConsumerImp(
service1: try container.resolve() as Service
service2: try container.resolve() as Service
) as ServiceConsumer
}
let consumer1 = container.resolve() as ServiceConsumer
let consumer2 = container.resolve() as ServiceConsumer
consumer1.service1 === consumer1.service2 //true
consumer2.service1 === consumer2.service2 //true
consumer1.service1 === consumer2.service1 //true
```
*/
case Singleton
}
@@ -102,7 +173,8 @@ public final class DefinitionOf<T, F>: Definition {
return self
}
func resolveDependencies(container: DependencyContainer, resolvedInstance: Any) throws {
/// Calls `resolveDependencies` block if it was set.
func resolveDependenciesOf(resolvedInstance: Any, withContainer container: DependencyContainer) throws {
guard let resolvedInstance = resolvedInstance as? T else { return }
try self.resolveDependenciesBlock?(container, resolvedInstance)
}
@@ -117,18 +189,6 @@ public final class DefinitionOf<T, F>: Definition {
self.scope = scope
}
///Will be stored only if scope is `Singleton`
var resolvedInstance: T? {
get {
guard scope == .Singleton else { return nil }
return _resolvedInstance
}
set {
guard scope == .Singleton else { return }
_resolvedInstance = newValue
}
}
private var _resolvedInstance: T?
}
@@ -137,16 +197,14 @@ public final class DefinitionOf<T, F>: Definition {
public protocol Definition: class { }
protocol _Definition: Definition {
var scope: ComponentScope { get }
}
extension DefinitionOf: _Definition { }
extension DefinitionOf: CustomStringConvertible {
public var description: String {
return "type: \(T.self), factory: \(F.self), scope: \(scope), resolved instance: \(resolvedInstance.desc)"
return "type: \(T.self), factory: \(F.self), scope: \(scope)"
}
}
+25 -22
View File
@@ -41,7 +41,7 @@ public final class DependencyContainer {
var definitions = [DefinitionKey : Definition]()
let resolvedInstances = ResolvedInstances()
let lock = NSRecursiveLock()
let lock = RecursiveLock()
/**
Designated initializer for a DependencyContainer
@@ -142,6 +142,7 @@ extension DependencyContainer {
func register(definition: Definition, forKey key: DefinitionKey) {
threadSafe {
definitions[key] = definition
resolvedInstances.singletons[key] = nil
}
}
@@ -231,16 +232,14 @@ extension DependencyContainer {
guard let definition = (self.definitions[key] ?? self.definitions[nilTagKey]) as? DefinitionOf<T, F> else {
throw DipError.DefinitionNotFound(key: key)
}
return try self._resolveDefinition(definition, usingKey: (definition.scope == .ObjectGraph ? key : nil), builder: builder)
return try self._resolveDefinition(definition, key: key, builder: builder)
}
}
/// Actually resolve dependency.
private func _resolveDefinition<T, F>(definition: DefinitionOf<T, F>, usingKey key: DefinitionKey?, builder: F throws -> T) rethrows -> T {
private func _resolveDefinition<T, F>(definition: DefinitionOf<T, F>, key: DefinitionKey, builder: F throws -> T) rethrows -> T {
return try resolvedInstances.resolve {
if let previouslyResolved: T = resolvedInstances.previouslyResolved(key, definition: definition) {
if let previouslyResolved: T = resolvedInstances.previouslyResolvedInstance(forKey: key, inScope: definition.scope) {
return previouslyResolved
}
else {
@@ -249,14 +248,13 @@ extension DependencyContainer {
//when builder calls factory it will in turn resolve sub-dependencies (if there are any)
//when it returns instance that we try to resolve here can be already resolved
//so we return it, throwing away instance created by previous call to builder
if let previouslyResolved: T = resolvedInstances.previouslyResolved(key, definition: definition) {
if let previouslyResolved: T = resolvedInstances.previouslyResolvedInstance(forKey: key, inScope: definition.scope) {
return previouslyResolved
}
resolvedInstances.storeResolvedInstance(resolvedInstance, forKey: key, definition: definition)
definition.resolvedInstance = resolvedInstance
resolvedInstances.storeResolvedInstance(resolvedInstance, forKey: key, inScope: definition.scope)
try definition.resolveDependenciesBlock?(self, resolvedInstance)
try definition.resolveDependenciesOf(resolvedInstance, withContainer: self)
//we perform auto-injection as the last step to be able to reuse instances
//stored when manually resolving dependencies in resolveDependencies block
@@ -271,28 +269,31 @@ extension DependencyContainer {
///Before `resolve()` returns pool is drained.
class ResolvedInstances {
var resolvedInstances = [DefinitionKey: Any]()
var singletons = [DefinitionKey: Any]()
func storeResolvedInstance<T, F>(instance: T, forKey key: DefinitionKey?, definition: DefinitionOf<T, F>) {
resolvedInstances[key] = instance
func storeResolvedInstance<T>(instance: T, forKey key: DefinitionKey, inScope scope: ComponentScope) {
switch scope {
case .Singleton: singletons[key] = instance
case .ObjectGraph: resolvedInstances[key] = instance
case .Prototype: break
}
}
func previouslyResolved<T, F>(key: DefinitionKey?, definition: DefinitionOf<T, F>) -> T? {
if let singleton = definition.resolvedInstance {
return singleton
func previouslyResolvedInstance<T>(forKey key: DefinitionKey, inScope scope: ComponentScope) -> T? {
switch scope {
case .Singleton: return singletons[key] as? T
case .ObjectGraph: return resolvedInstances[key] as? T
case .Prototype: return nil
}
else if let resolved = resolvedInstances[key] {
return resolved as? T
}
return nil
}
private var depth: Int = 0
func resolve<T>(@noescape block: () throws ->T) rethrows -> T {
depth++
depth = depth + 1
defer {
depth--
depth = depth - 1
if depth == 0 {
resolvedInstances.removeAll()
}
@@ -324,6 +325,7 @@ extension DependencyContainer {
func remove(definitionForKey key: DefinitionKey) {
threadSafe {
definitions[key] = nil
resolvedInstances.singletons[key] = nil
}
}
@@ -333,6 +335,7 @@ extension DependencyContainer {
public func reset() {
threadSafe {
definitions.removeAll()
resolvedInstances.singletons.removeAll()
}
}
@@ -420,7 +423,7 @@ public enum DipError: ErrorType, CustomStringConvertible {
case let .DefinitionNotFound(key):
return "No definition registered for \(key).\nCheck the tag, type you try to resolve, number, order and types of runtime arguments passed to `resolve()` and match them with registered factories for type \(key.protocolType)."
case let .AutoInjectionFailed(label, type, error):
return "Failed to auto-inject property \"\(label)\" of type \(type). \(error)"
return "Failed to auto-inject property \"\(label.desc)\" of type \(type). \(error)"
}
}
}
+84
View File
@@ -0,0 +1,84 @@
//
// Dip
//
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
//
// 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.
//
extension Dictionary {
subscript(key: Key?) -> Value? {
get {
guard let key = key else { return nil }
return self[key]
}
set {
guard let key = key else { return }
self[key] = newValue
}
}
}
extension Optional {
var desc: String {
return self.map { "\($0)" } ?? "nil"
}
}
#if os(Linux)
import Glibc
class RecursiveLock {
private var _lock = _initializeRecursiveMutex()
func lock() {
_lock.lock()
}
func unlock() {
_lock.unlock()
}
deinit {
pthread_mutex_destroy(&_lock)
}
}
private func _initializeRecursiveMutex() -> pthread_mutex_t {
var mutex: pthread_mutex_t = pthread_mutex_t()
var mta: pthread_mutexattr_t = pthread_mutexattr_t()
pthread_mutexattr_init(&mta)
pthread_mutexattr_settype(&mta, Int32(PTHREAD_MUTEX_RECURSIVE))
pthread_mutex_init(&mutex, &mta)
return mutex
}
extension pthread_mutex_t {
mutating func lock() {
pthread_mutex_trylock(&self)
}
mutating func unlock() {
pthread_mutex_unlock(&self)
}
}
#else
import Foundation
typealias RecursiveLock = NSRecursiveLock
#endif