Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6656116e12 | |||
| 92499c9561 | |||
| 7c5ea83dc4 | |||
| 4793232d8c | |||
| 2c09b4d6d1 | |||
| 34e920a348 | |||
| 3357630deb | |||
| f23492ee40 | |||
| 328d1b98a5 | |||
| 9f77677014 | |||
| b13a38cf9e | |||
| edb0d3e576 | |||
| bd8a503085 | |||
| 6af8f18a63 | |||
| bcf2329312 | |||
| b01b7fb055 | |||
| 0515e7358d | |||
| 463f9a37a3 | |||
| 6c802d2c39 | |||
| 967a2342b4 | |||
| d7b3db1476 | |||
| f65949bafc | |||
| ba420c27a1 | |||
| ef66212ba3 | |||
| 1d71eb08ca | |||
| 689ff2ed29 | |||
| 3b4dab4c97 |
@@ -32,3 +32,7 @@ Carthage
|
||||
# `pod install` in .travis.yml
|
||||
#
|
||||
# Pods/
|
||||
|
||||
# SPM
|
||||
.build/
|
||||
Packages
|
||||
|
||||
+14
-2
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "DipTests",
|
||||
dependencies: [
|
||||
.Package(url: "../../../Dip", majorVersion: 4, minor: 2),
|
||||
]
|
||||
)
|
||||
|
||||
+86
-112
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
+92
-11
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
+48
-21
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,10 @@
|
||||
import XCTest
|
||||
|
||||
XCTMain([
|
||||
DipTests(),
|
||||
DefinitionTests(),
|
||||
RuntimeArgumentsTests(),
|
||||
ComponentScopeTests(),
|
||||
AutoInjectionTests(),
|
||||
ThreadSafetyTests()
|
||||
])
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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)"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user