Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7a4d9c554c | |||
| cf4bb0352a | |||
| 8c6a822eca | |||
| 1306f12804 | |||
| 764cb98a59 | |||
| 130d71ce3c | |||
| 490c2a4d56 | |||
| 6e98f270bf | |||
| b3d090c3fd | |||
| 4e5cf238c9 | |||
| f4f660d81d | |||
| 211b2fce97 | |||
| 3aa79d1c69 | |||
| 4d085d1329 | |||
| 84845c9b17 | |||
| f4c38f281e | |||
| 99315e32eb | |||
| b48fe9840a | |||
| 6a6fbf906b | |||
| 7e5a0c8ada | |||
| 1f7ce50035 | |||
| 6656116e12 | |||
| 3700f687a2 | |||
| 92499c9561 | |||
| 7c5ea83dc4 | |||
| 4793232d8c | |||
| 2c09b4d6d1 | |||
| 34e920a348 | |||
| 3357630deb | |||
| f23492ee40 | |||
| c817980dd8 | |||
| 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
|
||||
|
||||
+26
-2
@@ -1,13 +1,37 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 4.3.0
|
||||
|
||||
* Added `DependencyTagConvertible` protocol for better typed tags.
|
||||
[#50](https://github.com/AliSoftware/Dip/pull/50), [@gavrix](https://github.com/gavrix)
|
||||
* Auto-wiring. `DependencyContainer` resolves constructor arguments automatically.
|
||||
[#55](https://github.com/AliSoftware/Dip/pull/55), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
* Added `Resolvable` protocol to get a callback when dependencies graph is complete.
|
||||
[#57](https://github.com/AliSoftware/Dip/pull/57), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
* Removed `DipError.ResolutionFailed` error for better consistency.
|
||||
[#58](https://github.com/AliSoftware/Dip/pull/58), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
|
||||
|
||||
## 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.3.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,9 +49,19 @@
|
||||
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 */; };
|
||||
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 */; };
|
||||
09B035FC1C5D2AD6001EA5B7 /* AutoWiringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B035FB1C5D2AD6001EA5B7 /* AutoWiringTests.swift */; };
|
||||
09B035FD1C5D2AD6001EA5B7 /* AutoWiringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B035FB1C5D2AD6001EA5B7 /* AutoWiringTests.swift */; };
|
||||
09B035FE1C5D2AD6001EA5B7 /* AutoWiringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B035FB1C5D2AD6001EA5B7 /* AutoWiringTests.swift */; };
|
||||
09B036001C5D2B83001EA5B7 /* AutoWiring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B035FF1C5D2B83001EA5B7 /* AutoWiring.swift */; };
|
||||
09B036011C5D2B83001EA5B7 /* AutoWiring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B035FF1C5D2B83001EA5B7 /* AutoWiring.swift */; };
|
||||
09B036021C5D2B83001EA5B7 /* AutoWiring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B035FF1C5D2B83001EA5B7 /* AutoWiring.swift */; };
|
||||
09B036031C5D2B83001EA5B7 /* AutoWiring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B035FF1C5D2B83001EA5B7 /* AutoWiring.swift */; };
|
||||
09C20EC11C8B3BFD009A082B /* ThreadSafetyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C20EBF1C8B3BC3009A082B /* ThreadSafetyTests.swift */; };
|
||||
09C20EC21C8B3BFE009A082B /* ThreadSafetyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C20EBF1C8B3BC3009A082B /* ThreadSafetyTests.swift */; };
|
||||
09C20EC31C8B3BFF009A082B /* ThreadSafetyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C20EBF1C8B3BC3009A082B /* ThreadSafetyTests.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 */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -86,20 +96,23 @@
|
||||
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>"; };
|
||||
09B035FB1C5D2AD6001EA5B7 /* AutoWiringTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AutoWiringTests.swift; path = Sources/AutoWiringTests.swift; sourceTree = "<group>"; };
|
||||
09B035FF1C5D2B83001EA5B7 /* AutoWiring.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AutoWiring.swift; path = ../../Sources/AutoWiring.swift; sourceTree = "<group>"; };
|
||||
09C20EBF1C8B3BC3009A082B /* ThreadSafetyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ThreadSafetyTests.swift; path = Sources/ThreadSafetyTests.swift; sourceTree = "<group>"; };
|
||||
09D598321C6F9EC100F24D49 /* Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Utils.swift; path = Sources/Utils.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -166,6 +179,7 @@
|
||||
0919F4C81C16417000DC3B10 /* Definition.swift */,
|
||||
0919F4CC1C16417000DC3B10 /* RuntimeArguments.swift */,
|
||||
09873F551C1E0237000C02F6 /* AutoInjection.swift */,
|
||||
09B035FF1C5D2B83001EA5B7 /* AutoWiring.swift */,
|
||||
0982AF0B1C5183A000B62463 /* Utils.swift */,
|
||||
0919F4CB1C16417000DC3B10 /* Info.plist */,
|
||||
);
|
||||
@@ -180,7 +194,9 @@
|
||||
0919F4D21C16417000DC3B10 /* RuntimeArgumentsTests.swift */,
|
||||
0919F4CE1C16417000DC3B10 /* ComponentScopeTests.swift */,
|
||||
09873F751C1E0249000C02F6 /* AutoInjectionTests.swift */,
|
||||
2C15B94F1C25EF7800EA3486 /* ThreadSafetyTests.swift */,
|
||||
09C20EBF1C8B3BC3009A082B /* ThreadSafetyTests.swift */,
|
||||
09B035FB1C5D2AD6001EA5B7 /* AutoWiringTests.swift */,
|
||||
09D598321C6F9EC100F24D49 /* Utils.swift */,
|
||||
0919F4D11C16417000DC3B10 /* Info.plist */,
|
||||
);
|
||||
path = DipTests;
|
||||
@@ -490,6 +506,7 @@
|
||||
files = (
|
||||
0982AF0C1C5183A000B62463 /* Utils.swift in Sources */,
|
||||
0919F4D51C16417B00DC3B10 /* Definition.swift in Sources */,
|
||||
09B036001C5D2B83001EA5B7 /* AutoWiring.swift in Sources */,
|
||||
0919F4D41C16417B00DC3B10 /* Dip.swift in Sources */,
|
||||
09873F561C1E0237000C02F6 /* AutoInjection.swift in Sources */,
|
||||
0919F4D61C16417B00DC3B10 /* RuntimeArguments.swift in Sources */,
|
||||
@@ -501,8 +518,10 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0919F4E61C16419300DC3B10 /* ComponentScopeTests.swift in Sources */,
|
||||
2C15B9511C25F01200EA3486 /* ThreadSafetyTests.swift in Sources */,
|
||||
09C20EC11C8B3BFD009A082B /* ThreadSafetyTests.swift in Sources */,
|
||||
0919F4E41C16419300DC3B10 /* DefinitionTests.swift in Sources */,
|
||||
09D598331C6F9EC100F24D49 /* Utils.swift in Sources */,
|
||||
09B035FC1C5D2AD6001EA5B7 /* AutoWiringTests.swift in Sources */,
|
||||
0919F4E31C16419300DC3B10 /* DipTests.swift in Sources */,
|
||||
0919F4E51C16419300DC3B10 /* RuntimeArgumentsTests.swift in Sources */,
|
||||
09873F771C1E024E000C02F6 /* AutoInjectionTests.swift in Sources */,
|
||||
@@ -515,6 +534,7 @@
|
||||
files = (
|
||||
0982AF0D1C5183A000B62463 /* Utils.swift in Sources */,
|
||||
0919F4D91C16417C00DC3B10 /* Definition.swift in Sources */,
|
||||
09B036011C5D2B83001EA5B7 /* AutoWiring.swift in Sources */,
|
||||
0919F4D81C16417C00DC3B10 /* Dip.swift in Sources */,
|
||||
09873F7A1C1E0252000C02F6 /* AutoInjection.swift in Sources */,
|
||||
0919F4DA1C16417C00DC3B10 /* RuntimeArguments.swift in Sources */,
|
||||
@@ -526,8 +546,10 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0919F4EA1C16419400DC3B10 /* ComponentScopeTests.swift in Sources */,
|
||||
2C15B9521C25F01300EA3486 /* ThreadSafetyTests.swift in Sources */,
|
||||
09C20EC21C8B3BFE009A082B /* ThreadSafetyTests.swift in Sources */,
|
||||
0919F4E81C16419400DC3B10 /* DefinitionTests.swift in Sources */,
|
||||
09D598341C6F9EC100F24D49 /* Utils.swift in Sources */,
|
||||
09B035FD1C5D2AD6001EA5B7 /* AutoWiringTests.swift in Sources */,
|
||||
0919F4E71C16419400DC3B10 /* DipTests.swift in Sources */,
|
||||
0919F4E91C16419400DC3B10 /* RuntimeArgumentsTests.swift in Sources */,
|
||||
09873F781C1E024E000C02F6 /* AutoInjectionTests.swift in Sources */,
|
||||
@@ -540,6 +562,7 @@
|
||||
files = (
|
||||
0982AF0E1C5183A000B62463 /* Utils.swift in Sources */,
|
||||
0919F4DD1C16417D00DC3B10 /* Definition.swift in Sources */,
|
||||
09B036021C5D2B83001EA5B7 /* AutoWiring.swift in Sources */,
|
||||
0919F4DC1C16417D00DC3B10 /* Dip.swift in Sources */,
|
||||
09873F7B1C1E0253000C02F6 /* AutoInjection.swift in Sources */,
|
||||
0919F4DE1C16417D00DC3B10 /* RuntimeArguments.swift in Sources */,
|
||||
@@ -551,8 +574,10 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0919F4EE1C16419500DC3B10 /* ComponentScopeTests.swift in Sources */,
|
||||
2C15B9531C25F01500EA3486 /* ThreadSafetyTests.swift in Sources */,
|
||||
09C20EC31C8B3BFF009A082B /* ThreadSafetyTests.swift in Sources */,
|
||||
0919F4EC1C16419500DC3B10 /* DefinitionTests.swift in Sources */,
|
||||
09D598351C6F9EC100F24D49 /* Utils.swift in Sources */,
|
||||
09B035FE1C5D2AD6001EA5B7 /* AutoWiringTests.swift in Sources */,
|
||||
0919F4EB1C16419500DC3B10 /* DipTests.swift in Sources */,
|
||||
0919F4ED1C16419500DC3B10 /* RuntimeArgumentsTests.swift in Sources */,
|
||||
09873F791C1E024F000C02F6 /* AutoInjectionTests.swift in Sources */,
|
||||
@@ -565,6 +590,7 @@
|
||||
files = (
|
||||
0982AF0F1C5183A000B62463 /* Utils.swift in Sources */,
|
||||
0919F4E11C16417E00DC3B10 /* Definition.swift in Sources */,
|
||||
09B036031C5D2B83001EA5B7 /* AutoWiring.swift in Sources */,
|
||||
0919F4E01C16417E00DC3B10 /* Dip.swift in Sources */,
|
||||
09873F7C1C1E0254000C02F6 /* AutoInjection.swift in Sources */,
|
||||
0919F4E21C16417E00DC3B10 /* RuntimeArguments.swift in Sources */,
|
||||
@@ -840,7 +866,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.3.0;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
@@ -889,7 +915,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.3.0;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
|
||||
@@ -1,237 +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 Service: class {
|
||||
func getServiceName() -> String
|
||||
}
|
||||
|
||||
extension Service {
|
||||
func getServiceName() -> String {
|
||||
return "\(self.dynamicType)"
|
||||
}
|
||||
}
|
||||
|
||||
class ServiceImp1: Service {
|
||||
}
|
||||
|
||||
class ServiceImp2: Service {
|
||||
}
|
||||
|
||||
class DipTests: XCTestCase {
|
||||
|
||||
let container = DependencyContainer()
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
container.reset()
|
||||
}
|
||||
|
||||
func testThatItResolvesInstanceRegisteredWithoutTag() {
|
||||
//given
|
||||
container.register { ServiceImp1() as Service }
|
||||
|
||||
//when
|
||||
let serviceInstance = try! container.resolve() as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(serviceInstance is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItResolvesInstanceRegisteredWithTag() {
|
||||
//given
|
||||
container.register(tag: "service") { ServiceImp1() as Service }
|
||||
|
||||
//when
|
||||
let serviceInstance = try! container.resolve(tag: "service") as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(serviceInstance is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItResolvesDifferentInstancesRegisteredForDifferentTags() {
|
||||
//given
|
||||
container.register(tag: "service1") { ServiceImp1() as Service }
|
||||
container.register(tag: "service2") { ServiceImp2() as Service }
|
||||
|
||||
//when
|
||||
let service1Instance = try! container.resolve(tag: "service1") as Service
|
||||
let service2Instance = try! container.resolve(tag: "service2") as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service1Instance is ServiceImp1)
|
||||
XCTAssertTrue(service2Instance is ServiceImp2)
|
||||
}
|
||||
|
||||
func testThatNewRegistrationOverridesPreviousRegistration() {
|
||||
//given
|
||||
container.register { ServiceImp1() as Service }
|
||||
let service1 = try! container.resolve() as Service
|
||||
|
||||
//when
|
||||
container.register { ServiceImp2() as Service }
|
||||
let service2 = try! container.resolve() as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service1 is ServiceImp1)
|
||||
XCTAssertTrue(service2 is ServiceImp2)
|
||||
}
|
||||
|
||||
func testThatItCallsResolveDependenciesOnDefinition() {
|
||||
//given
|
||||
var resolveDependenciesCalled = false
|
||||
container.register { ServiceImp1() as Service }.resolveDependencies { (c, s) in
|
||||
resolveDependenciesCalled = true
|
||||
}
|
||||
|
||||
//when
|
||||
try! container.resolve() as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(resolveDependenciesCalled)
|
||||
}
|
||||
|
||||
func testThatItThrowsErrorIfCanNotFindDefinitionForType() {
|
||||
//given
|
||||
container.register { ServiceImp1() as ServiceImp1 }
|
||||
|
||||
//when
|
||||
do {
|
||||
try container.resolve() as Service
|
||||
XCTFail("Unexpectedly resolved protocol")
|
||||
}
|
||||
catch let DipError.DefinitionNotFound(key) {
|
||||
//then
|
||||
typealias F = () throws -> Service
|
||||
let expectedKey = DefinitionKey(protocolType: Service.self, factoryType: F.self, associatedTag: nil)
|
||||
XCTAssertEqual(key, expectedKey)
|
||||
}
|
||||
catch {
|
||||
XCTFail("Thrown unexpected error")
|
||||
}
|
||||
}
|
||||
|
||||
func testThatItThrowsErrorIfCanNotFindDefinitionForTag() {
|
||||
//given
|
||||
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) {
|
||||
//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")
|
||||
}
|
||||
}
|
||||
|
||||
func testThatItThrowsErrorIfCanNotFindDefinitionForFactoryWithArguments() {
|
||||
//given
|
||||
container.register { ServiceImp1() as Service }
|
||||
|
||||
//when
|
||||
do {
|
||||
try container.resolve(withArguments: "some string") as Service
|
||||
XCTFail("Unexpectedly resolved protocol")
|
||||
}
|
||||
catch let DipError.DefinitionNotFound(key) {
|
||||
//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")
|
||||
}
|
||||
}
|
||||
|
||||
func testThatItThrowsErrorIfConstructorThrows() {
|
||||
//given
|
||||
let failedKey = DefinitionKey(protocolType: Any.self, factoryType: Any.self)
|
||||
let expectedError = DipError.DefinitionNotFound(key: failedKey)
|
||||
container.register { () throws -> Service in throw expectedError }
|
||||
|
||||
//when
|
||||
do {
|
||||
try container.resolve() as Service
|
||||
}
|
||||
catch let DipError.ResolutionFailed(key, error) {
|
||||
//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")
|
||||
}
|
||||
}
|
||||
|
||||
func testThatItThrowsErrorIfFailsToResolveDependency() {
|
||||
//given
|
||||
let failedKey = DefinitionKey(protocolType: Any.self, factoryType: Any.self)
|
||||
let expectedError = DipError.DefinitionNotFound(key: failedKey)
|
||||
container.register { ServiceImp1() as Service }
|
||||
.resolveDependencies { container, service in
|
||||
//simulate throwing error when resolving dependency
|
||||
throw expectedError
|
||||
}
|
||||
|
||||
//when
|
||||
do {
|
||||
try container.resolve() as Service
|
||||
}
|
||||
catch let DipError.ResolutionFailed(key, error) {
|
||||
//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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
//
|
||||
// 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 Service: class { }
|
||||
private class ServiceImp1: Service { }
|
||||
private class ServiceImp2: Service { }
|
||||
|
||||
private protocol AutoWiredClient: class {
|
||||
var service1: Service! { get set }
|
||||
var service2: Service! { get set }
|
||||
}
|
||||
|
||||
private class AutoWiredClientImp: AutoWiredClient {
|
||||
var service1: Service!
|
||||
var service2: Service!
|
||||
|
||||
init(service1: Service, service2: ServiceImp2) {
|
||||
self.service1 = service1
|
||||
self.service2 = service2
|
||||
}
|
||||
init() {}
|
||||
}
|
||||
|
||||
class AutoWiringTests: XCTestCase {
|
||||
|
||||
let container = DependencyContainer()
|
||||
|
||||
#if os(Linux)
|
||||
var allTests: [(String, () throws -> Void)] {
|
||||
return [
|
||||
("testThatItCanResolveWithAutoWiring", testThatItCanResolveWithAutoWiring),
|
||||
("testThatItUsesAutoWireFactoryWithMostNumberOfArguments", testThatItUsesAutoWireFactoryWithMostNumberOfArguments),
|
||||
("testThatItThrowsAmbiguityErrorWhenUsingAutoWire", testThatItThrowsAmbiguityErrorWhenUsingAutoWire),
|
||||
("testThatItFirstTriesToUseTaggedFactoriesWhenUsingAutoWire", testThatItFirstTriesToUseTaggedFactoriesWhenUsingAutoWire),
|
||||
("testThatItFallbackToNotTaggedFactoryWhenUsingAutoWire", testThatItFallbackToNotTaggedFactoryWhenUsingAutoWire),
|
||||
("testThatItDoesNotTryToUseAutoWiringWhenCallingResolveWithArguments", testThatItDoesNotTryToUseAutoWiringWhenCallingResolveWithArguments),
|
||||
("testThatItDoesNotUseAutoWiringWhenFailedToResolveLowLevelDependency", testThatItDoesNotUseAutoWiringWhenFailedToResolveLowLevelDependency),
|
||||
("testThatItReusesInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgain", testThatItReusesInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgain),
|
||||
("testThatItReusesInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgainWithTheSameTagged", testThatItReusesInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgainWithTheSameTagged),
|
||||
("testThatItDoesNotReuseInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgainWithNoTag", testThatItDoesNotReuseInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgainWithNoTag)
|
||||
]
|
||||
}
|
||||
|
||||
func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#else
|
||||
override func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#endif
|
||||
|
||||
func testThatItCanResolveWithAutoWiring() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
|
||||
//when
|
||||
let client = try! container.resolve() as AutoWiredClient
|
||||
|
||||
//then
|
||||
let service1 = client.service1
|
||||
XCTAssertTrue(service1 is ServiceImp1)
|
||||
let service2 = client.service2
|
||||
XCTAssertTrue(service2 is ServiceImp2)
|
||||
}
|
||||
|
||||
func testThatItUsesAutoWireFactoryWithMostNumberOfArguments() {
|
||||
//given
|
||||
|
||||
//1 arg
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
|
||||
//1 arg
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: try self.container.resolve(), service2: $0) as AutoWiredClient }
|
||||
|
||||
//2 args
|
||||
var factoryWithMostNumberOfArgumentsCalled = false
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
.resolveDependencies { _ in
|
||||
factoryWithMostNumberOfArgumentsCalled = true
|
||||
}
|
||||
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve() as AutoWiredClient
|
||||
|
||||
//then
|
||||
XCTAssertTrue(factoryWithMostNumberOfArgumentsCalled)
|
||||
}
|
||||
|
||||
func testThatItThrowsAmbiguityErrorWhenUsingAutoWire() {
|
||||
//given
|
||||
|
||||
//1 arg
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
|
||||
//1 arg
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: try self.container.resolve(), service2: $0) as AutoWiredClient }
|
||||
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
//when
|
||||
AssertThrows(expression: try container.resolve() as AutoWiredClient) { error -> Bool in
|
||||
switch error {
|
||||
case DipError.AmbiguousDefinitions: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testThatItFirstTriesToUseTaggedFactoriesWhenUsingAutoWire() {
|
||||
//given
|
||||
|
||||
//1 arg
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
|
||||
//1 arg
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: try self.container.resolve(), service2: $0) as AutoWiredClient }
|
||||
|
||||
//2 args
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
|
||||
//1 arg tagged
|
||||
var taggedFactoryWithMostNumberOfArgumentsCalled = false
|
||||
container.register(tag: "tag", .ObjectGraph) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
|
||||
|
||||
//2 arg tagged
|
||||
container.register(tag: "tag", .ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }.resolveDependencies { _ in
|
||||
taggedFactoryWithMostNumberOfArgumentsCalled = true
|
||||
}
|
||||
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve(tag: "tag") as AutoWiredClient
|
||||
|
||||
//then
|
||||
XCTAssertTrue(taggedFactoryWithMostNumberOfArgumentsCalled)
|
||||
}
|
||||
|
||||
func testThatItFallbackToNotTaggedFactoryWhenUsingAutoWire() {
|
||||
//given
|
||||
|
||||
//1 arg
|
||||
var notTaggedFactoryWithMostNumberOfArgumentsCalled = false
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }.resolveDependencies {_ in
|
||||
notTaggedFactoryWithMostNumberOfArgumentsCalled = true
|
||||
}
|
||||
|
||||
//1 arg tagged
|
||||
container.register(tag: "tag", .ObjectGraph) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
|
||||
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve(tag: "other tag") as AutoWiredClient
|
||||
|
||||
//then
|
||||
XCTAssertTrue(notTaggedFactoryWithMostNumberOfArgumentsCalled)
|
||||
}
|
||||
|
||||
func testThatItDoesNotTryToUseAutoWiringWhenCallingResolveWithArguments() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
//when
|
||||
let service = try! container.resolve() as Service
|
||||
AssertThrows(expression: try container.resolve(withArguments: service) as AutoWiredClient,
|
||||
"Container should not use auto-wiring when resolving with runtime arguments")
|
||||
}
|
||||
|
||||
func testThatItDoesNotUseAutoWiringWhenFailedToResolveLowLevelDependency() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp() as AutoWiredClient }
|
||||
.resolveDependencies { container, resolved in
|
||||
resolved.service1 = try container.resolve() as Service
|
||||
resolved.service2 = try container.resolve() as ServiceImp2
|
||||
|
||||
//simulate that something goes wrong on the way
|
||||
throw DipError.DefinitionNotFound(key: DefinitionKey(protocolType: ServiceImp1.self, factoryType: Any.self))
|
||||
}
|
||||
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
.resolveDependencies { container, resolved in
|
||||
//auto-wiring should be performed only when definition for type to resolve is not found
|
||||
//but not for any other type along the way in the graph
|
||||
XCTFail("Auto-wiring should not be performed if instance was actually resolved.")
|
||||
}
|
||||
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
//then
|
||||
AssertThrows(expression: try container.resolve() as AutoWiredClient,
|
||||
"Container should not use auto-wiring when definition for resolved type is registered.")
|
||||
}
|
||||
|
||||
func testThatItReusesInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgain() {
|
||||
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
var anotherInstance: AutoWiredClient?
|
||||
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
.resolveDependencies { container, _ in
|
||||
if anotherInstance == nil {
|
||||
anotherInstance = try! container.resolve() as AutoWiredClient
|
||||
}
|
||||
}
|
||||
|
||||
//when
|
||||
let resolved = try! container.resolve() as AutoWiredClient
|
||||
|
||||
//then
|
||||
//when doing another auto-wiring during resolve we should reuse instance
|
||||
XCTAssertTrue((resolved as! AutoWiredClientImp) === (anotherInstance as! AutoWiredClientImp))
|
||||
}
|
||||
|
||||
func testThatItReusesInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgainWithTheSameTagged() {
|
||||
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
var anotherInstance: AutoWiredClient?
|
||||
|
||||
container.register(tag: "tag", .ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
.resolveDependencies { container, _ in
|
||||
if anotherInstance == nil {
|
||||
anotherInstance = try! container.resolve(tag: "tag") as AutoWiredClient
|
||||
}
|
||||
}
|
||||
|
||||
//when
|
||||
let resolved = try! container.resolve(tag: "tag") as AutoWiredClient
|
||||
|
||||
//then
|
||||
//when doing another auto-wiring during resolve we should reuse instance
|
||||
XCTAssertTrue((resolved as! AutoWiredClientImp) === (anotherInstance as! AutoWiredClientImp))
|
||||
}
|
||||
|
||||
func testThatItDoesNotReuseInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgainWithNoTag() {
|
||||
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
var anotherInstance: AutoWiredClient?
|
||||
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
.resolveDependencies { container, _ in
|
||||
if anotherInstance == nil {
|
||||
anotherInstance = try! container.resolve() as AutoWiredClient
|
||||
}
|
||||
}
|
||||
|
||||
//when
|
||||
let resolved = try! container.resolve(tag: "tag") as AutoWiredClient
|
||||
|
||||
//then
|
||||
//when doing another auto-wiring during resolve we should reuse instance
|
||||
XCTAssertTrue((resolved as! AutoWiredClientImp) !== (anotherInstance as! AutoWiredClientImp))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,333 @@
|
||||
//
|
||||
// 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 Service: class { }
|
||||
private class ServiceImp1: Service { }
|
||||
private class ServiceImp2: Service { }
|
||||
|
||||
private protocol Server: class {
|
||||
weak var client: Client? { get }
|
||||
}
|
||||
private protocol Client: class {
|
||||
var server: Server? { get }
|
||||
}
|
||||
|
||||
class DipTests: XCTestCase {
|
||||
|
||||
let container = DependencyContainer()
|
||||
|
||||
#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),
|
||||
("testThatItCallsDidResolveDependenciesOnResolvableIntance", testThatItCallsDidResolveDependenciesOnResolvableIntance),
|
||||
("testThatItResolvesCircularDependencies", testThatItResolvesCircularDependencies)
|
||||
]
|
||||
}
|
||||
|
||||
func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#else
|
||||
override func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#endif
|
||||
|
||||
func testThatItResolvesInstanceRegisteredWithoutTag() {
|
||||
//given
|
||||
container.register { ServiceImp1() as Service }
|
||||
|
||||
//when
|
||||
let serviceInstance = try! container.resolve() as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(serviceInstance is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItResolvesInstanceRegisteredWithTag() {
|
||||
//given
|
||||
container.register(tag: "service") { ServiceImp1() as Service }
|
||||
|
||||
//when
|
||||
let serviceInstance = try! container.resolve(tag: "service") as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(serviceInstance is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItResolvesDifferentInstancesRegisteredForDifferentTags() {
|
||||
//given
|
||||
container.register(tag: "service1") { ServiceImp1() as Service }
|
||||
container.register(tag: "service2") { ServiceImp2() as Service }
|
||||
|
||||
//when
|
||||
let service1Instance = try! container.resolve(tag: "service1") as Service
|
||||
let service2Instance = try! container.resolve(tag: "service2") as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service1Instance is ServiceImp1)
|
||||
XCTAssertTrue(service2Instance is ServiceImp2)
|
||||
}
|
||||
|
||||
func testThatNewRegistrationOverridesPreviousRegistration() {
|
||||
//given
|
||||
container.register { ServiceImp1() as Service }
|
||||
let service1 = try! container.resolve() as Service
|
||||
|
||||
//when
|
||||
container.register { ServiceImp2() as Service }
|
||||
let service2 = try! container.resolve() as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service1 is ServiceImp1)
|
||||
XCTAssertTrue(service2 is ServiceImp2)
|
||||
}
|
||||
|
||||
func testThatItCallsResolveDependenciesOnDefinition() {
|
||||
//given
|
||||
var resolveDependenciesCalled = false
|
||||
container.register { ServiceImp1() as Service }.resolveDependencies { (c, s) in
|
||||
resolveDependenciesCalled = true
|
||||
}
|
||||
|
||||
//when
|
||||
try! container.resolve() as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(resolveDependenciesCalled)
|
||||
}
|
||||
|
||||
func testThatItThrowsErrorIfCanNotFindDefinitionForType() {
|
||||
//given
|
||||
container.register { ServiceImp1() as ServiceImp1 }
|
||||
|
||||
//when
|
||||
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)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func testThatItThrowsErrorIfCanNotFindDefinitionForTag() {
|
||||
//given
|
||||
container.register(tag: "some tag") { ServiceImp1() as Service }
|
||||
|
||||
//when
|
||||
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)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func testThatItThrowsErrorIfCanNotFindDefinitionForFactoryWithArguments() {
|
||||
//given
|
||||
container.register { ServiceImp1() as Service }
|
||||
|
||||
//when
|
||||
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)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func testThatItThrowsErrorIfConstructorThrows() {
|
||||
//given
|
||||
let failedKey = DefinitionKey(protocolType: Any.self, factoryType: Any.self)
|
||||
let expectedError = DipError.DefinitionNotFound(key: failedKey)
|
||||
container.register { () throws -> Service in throw expectedError }
|
||||
|
||||
//when
|
||||
AssertThrows(expression: try container.resolve() as Service) { error in
|
||||
switch error {
|
||||
case let DipError.DefinitionNotFound(key) where key == failedKey: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testThatItThrowsErrorIfFailsToResolveDependency() {
|
||||
//given
|
||||
let failedKey = DefinitionKey(protocolType: Any.self, factoryType: Any.self)
|
||||
let expectedError = DipError.DefinitionNotFound(key: failedKey)
|
||||
container.register { ServiceImp1() as Service }
|
||||
.resolveDependencies { container, service in
|
||||
//simulate throwing error when resolving dependency
|
||||
throw expectedError
|
||||
}
|
||||
|
||||
//when
|
||||
AssertThrows(expression: try container.resolve() as Service) { error in
|
||||
switch error {
|
||||
case let DipError.DefinitionNotFound(key) where key == failedKey: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testThatItCallsDidResolveDependenciesOnResolvableIntance() {
|
||||
|
||||
class ResolvableService: Service, Resolvable {
|
||||
var didResolveDependenciesCalled = false
|
||||
|
||||
func didResolveDependencies() {
|
||||
XCTAssertFalse(didResolveDependenciesCalled, "didResolveDependencies should be called only once per instance")
|
||||
didResolveDependenciesCalled = true
|
||||
}
|
||||
}
|
||||
|
||||
container.register { ResolvableService() as Service }
|
||||
.resolveDependencies { _, service in
|
||||
XCTAssertFalse((service as! ResolvableService).didResolveDependenciesCalled, "didResolveDependencies should not be called yet")
|
||||
return
|
||||
}
|
||||
|
||||
container.register(tag: "graph", .ObjectGraph) { ResolvableService() as Service }
|
||||
.resolveDependencies { _, service in
|
||||
XCTAssertFalse((service as! ResolvableService).didResolveDependenciesCalled)
|
||||
return
|
||||
}
|
||||
|
||||
container.register(tag: "singleton", .Singleton) { ResolvableService() as Service }
|
||||
.resolveDependencies { _, service in
|
||||
XCTAssertFalse((service as! ResolvableService).didResolveDependenciesCalled)
|
||||
return
|
||||
}
|
||||
|
||||
let service = try! container.resolve() as Service
|
||||
XCTAssertTrue((service as! ResolvableService).didResolveDependenciesCalled)
|
||||
|
||||
let graphService = try! container.resolve(tag: "graph") as Service
|
||||
XCTAssertTrue((graphService as! ResolvableService).didResolveDependenciesCalled)
|
||||
|
||||
let singletonService = try! container.resolve(tag: "singleton") as Service
|
||||
let _ = try! container.resolve(tag: "singleton") as Service
|
||||
XCTAssertTrue((singletonService as! ResolvableService).didResolveDependenciesCalled)
|
||||
}
|
||||
|
||||
func testThatItResolvesCircularDependencies() {
|
||||
|
||||
class ResolvableServer: Server, Resolvable {
|
||||
weak var client: Client?
|
||||
weak var secondClient: Client?
|
||||
|
||||
init(client: Client) {
|
||||
self.client = client
|
||||
}
|
||||
|
||||
var didResolveDependenciesCalled = false
|
||||
|
||||
func didResolveDependencies() {
|
||||
XCTAssertFalse(didResolveDependenciesCalled, "didResolveDependencies should be called only once per instance")
|
||||
didResolveDependenciesCalled = true
|
||||
|
||||
XCTAssertNotNil(self.client)
|
||||
XCTAssertNotNil(self.secondClient)
|
||||
XCTAssertNotNil(self.client?.server)
|
||||
XCTAssertNotNil(self.secondClient)
|
||||
XCTAssertNotNil(self.secondClient?.server)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ResolvableClient: Client, Resolvable {
|
||||
var server: Server?
|
||||
var secondServer: Server?
|
||||
|
||||
init() {}
|
||||
|
||||
var didResolveDependenciesCalled = false
|
||||
|
||||
func didResolveDependencies() {
|
||||
XCTAssertFalse(didResolveDependenciesCalled, "didResolveDependencies should be called only once per instance")
|
||||
didResolveDependenciesCalled = true
|
||||
|
||||
XCTAssertNotNil(self.server)
|
||||
XCTAssertNotNil(self.secondServer)
|
||||
XCTAssertNotNil(self.server?.client)
|
||||
XCTAssertNotNil(self.secondServer?.client)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
container.register(.ObjectGraph) { try ResolvableServer(client: self.container.resolve()) as Server }
|
||||
.resolveDependencies { (container: DependencyContainer, server: Server) in
|
||||
let server = server as! ResolvableServer
|
||||
server.secondClient = try container.resolve() as Client
|
||||
}
|
||||
|
||||
container.register(.ObjectGraph) { ResolvableClient() as Client }
|
||||
.resolveDependencies { (container: DependencyContainer, client: Client) in
|
||||
let client = client as! ResolvableClient
|
||||
client.server = try container.resolve() as Server
|
||||
client.secondServer = try container.resolve() as Server
|
||||
}
|
||||
|
||||
let client = (try! container.resolve() as Client) as! ResolvableClient
|
||||
let server = client.server as! ResolvableServer
|
||||
let secondServer = client.secondServer as! ResolvableServer
|
||||
let secondClient = server.secondClient as! ResolvableClient
|
||||
|
||||
XCTAssertTrue(client === server.client)
|
||||
XCTAssertTrue(client === server.secondClient)
|
||||
XCTAssertTrue(client === secondServer.client)
|
||||
XCTAssertTrue(client === secondServer.secondClient)
|
||||
XCTAssertTrue(client === secondClient)
|
||||
XCTAssertTrue(server === secondServer)
|
||||
|
||||
XCTAssertTrue(client.didResolveDependenciesCalled)
|
||||
XCTAssertTrue(server.didResolveDependenciesCalled)
|
||||
}
|
||||
|
||||
}
|
||||
+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)", file: file, line: line)
|
||||
}
|
||||
}
|
||||
|
||||
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,11 @@
|
||||
import XCTest
|
||||
|
||||
XCTMain([
|
||||
DipTests(),
|
||||
DefinitionTests(),
|
||||
RuntimeArgumentsTests(),
|
||||
ComponentScopeTests(),
|
||||
AutoInjectionTests(),
|
||||
ThreadSafetyTests(),
|
||||
AutoWiringTests()
|
||||
])
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,7 +8,9 @@ let container = DependencyContainer()
|
||||
|
||||
### Auto-Injection
|
||||
|
||||
If you follow Single Responsibility Principle chances are very high that you will end up with more than two collaborating components in your system. Let's say you have a component that depends on few others. Using _Dip_ you can register all of the dependencies in a container as well as that component itself and register a factory that will create that component and feed it with the dependencies resolving them with a container:
|
||||
On the previous page you saw how auto-wiring helps us get rid of boilerplate code when registering and resolving components with consturctor injection. Auto-injection solves the same problem for property injection.
|
||||
|
||||
Let's say you have following related components:
|
||||
*/
|
||||
|
||||
protocol Service: class {
|
||||
@@ -21,13 +23,18 @@ class ServiceImp: Service {
|
||||
var tracker: Tracker?
|
||||
}
|
||||
|
||||
/*:
|
||||
When you register them in a container you will end up with something like this:
|
||||
*/
|
||||
|
||||
container.register() { TrackerImp() as Tracker }
|
||||
container.register() { LoggerImp() as Logger }
|
||||
|
||||
container.register() { ServiceImp() as Service }
|
||||
.resolveDependencies { container, service in
|
||||
(service as! ServiceImp).logger = try container.resolve() as Logger
|
||||
(service as! ServiceImp).tracker = try container.resolve() as Tracker
|
||||
let service = service as! ServiceImp
|
||||
service.logger = try container.resolve() as Logger
|
||||
service.tracker = try container.resolve() as Tracker
|
||||
}
|
||||
|
||||
let service = try! container.resolve() as Service
|
||||
@@ -35,10 +42,8 @@ service.logger
|
||||
service.tracker
|
||||
|
||||
/*:
|
||||
Not bad so far. Though that `resolveDependencies` block looks heavy. It would be cool if we can get rid of it. Alternatively you can use _constructor injection_ here, which is actually more prefereable by default but not always possible (see [circular dependencies](Circular%20dependencies)).
|
||||
Now let's say that you have a bunch of components in your app that require `Logger` or `Tracker` too. You will need to resolve them in a factory for each component again and again. That can be a lot of boilerplate code, simple but still duplicated.
|
||||
|
||||
That is one of the scenarios when auto-injection can be useful. It works with property injection and with it the previous code will transform to this:
|
||||
Notice that the same boilerplate code that we saw in constructor injection now moved to `resolveDepedencies` block.
|
||||
With auto-injection your code transforms to this:
|
||||
*/
|
||||
|
||||
class AutoInjectedServiceImp: Service {
|
||||
@@ -60,10 +65,12 @@ As you can see we added two private properties to our implementation of `Service
|
||||
|
||||
What is happening under the hood is that after concrete instance of resolved type is created (`Service` in that case), container will iterate through its properties using `Mirror`. For each of the properties wrapped with `Injected<T>` or `InjectedWeak<T>` it will search a definition that can be used to create an instance of wrapped type and use it to create and inject a concrete instance in a `value` property of a wrapper. The fact that wrappers are _classes_ or _reference types_ makes it possible at runtime to inject dependency in instance of resolved type.
|
||||
|
||||
You can provide closure that will be called when the dependency will be injected in the property. It is similar to `didSet` property observer.
|
||||
The requirement for auto-injection is that types injected types should be registered in a container and should use factories with no runtime arguments.
|
||||
|
||||
Auto-injected properties can be marked with tag. Then container will search for definition tagged by the same tag to resolve this property.
|
||||
|
||||
You can provide closure that will be called when the dependency will be injected in the property. It is similar to `didSet` property observer.
|
||||
|
||||
Auto-injected properties are required by default. That means that if container fails to resolve any of auto-injected properties of the instance (or any of its dependencies) it will fail resolution of the object graph in whole.
|
||||
*/
|
||||
|
||||
@@ -198,11 +205,9 @@ autoViewController.router.value
|
||||
/*:
|
||||
In such scenario when view controller is created by storyboard you will need to use property injection anyway, so the overhead of adding additional properties for auto-injection is smaller. Also all the boilerplate code of unwrapping injected properties (if you need that) can be moved to extension, cleaning implementation a bit.
|
||||
|
||||
> **Note**: For such cases concider using [DipUI](https://github.com/AliSoftware/Dip-UI). It is a small extension for Dip that allows you to do exactly what we need in this example - inject dependencies in instances created by storyboards. It does not require to use auto-injection feature.
|
||||
> **Note**: For such cases concider using [DipUI](https://github.com/AliSoftware/Dip-UI). It is a small extension for Dip that allows you to do exactly what we need in this example - inject dependencies in instances created by storyboards. It does not require to use auto-injection feature but plays nice with it.
|
||||
|
||||
So as you can see there are certain advantages and disadvatages of using auto-injection. It makes your definitions simpler, especially if there are circular dependencies involved or the number of dependencies is high. But it requires additional properties and some boilerplate code in your implementations, makes your implementatios tightly coupled with Dip. It has also some limitations like that it requires factories for auto-injected types that accept no runtime arguments to be registered in a container.
|
||||
|
||||
So you should decide for yourself whether you prefer to use auto-injection or "the standard" way. At the end they let you achieve the same result.
|
||||
So as you can see there are certain advantages and disadvatages of using auto-injection. It makes your definitions simpler, especially if there are circular dependencies involved or the number of dependencies is high, removing boilerplate calls to `resolve` method in `resolveDependencies` block of your definitions. But it requires additional properties and some boilerplate code in your implementations, makes your implementatios tightly coupled with Dip. You can avoid tight coupoling by using your own boxing classes instead of `Injected<T>` and `InjectedWeak<T>` (see `AutoInjectedPropertyBox`).
|
||||
*/
|
||||
|
||||
//: [Next: Testing](@next)
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
//: [Previous: Shared Instances](@previous)
|
||||
|
||||
import Dip
|
||||
import UIKit
|
||||
|
||||
/*:
|
||||
|
||||
### Auto-wiring
|
||||
|
||||
Among three main DI patterns - _constructor_, _property_ and _method_ injection - construction injection should be your choise by default. Dip makes use of this pattern very simple.
|
||||
|
||||
Let's say you have some VIPER module with following components:
|
||||
*/
|
||||
protocol Service {}
|
||||
protocol Interactor {
|
||||
var service: Service { get }
|
||||
}
|
||||
protocol Router {}
|
||||
protocol ViewOutput {}
|
||||
protocol Presenter {
|
||||
var router: Router { get }
|
||||
var interactor: Interactor { get }
|
||||
var view: ViewOutput { get }
|
||||
}
|
||||
|
||||
class RouterImp: Router {}
|
||||
class View: UIView, ViewOutput {}
|
||||
class ServiceImp: Service {}
|
||||
|
||||
/*:
|
||||
VIPER module by its nature consists of a lot of components, wired up using protocols. Using construction injection you can end up with following constructors for presenter and interactor:
|
||||
*/
|
||||
|
||||
class InteractorImp: Interactor {
|
||||
var service: Service
|
||||
|
||||
init(service: Service) {
|
||||
self.service = service
|
||||
}
|
||||
}
|
||||
|
||||
class PresenterImp: Presenter {
|
||||
let router: Router
|
||||
let interactor: Interactor
|
||||
let view: ViewOutput
|
||||
|
||||
init(view: ViewOutput, interactor: Interactor, router: Router) {
|
||||
self.view = view
|
||||
self.interactor = interactor
|
||||
self.router = router
|
||||
}
|
||||
}
|
||||
|
||||
/*:
|
||||
If you register these components in a container you will end up with rather boilerplate code:
|
||||
*/
|
||||
|
||||
let container = DependencyContainer()
|
||||
container.register { ServiceImp() as Service }
|
||||
container.register { RouterImp() as Router }
|
||||
container.register { View() as ViewOutput }
|
||||
|
||||
container.register { try InteractorImp(service: container.resolve()) as Interactor }
|
||||
container.register {
|
||||
try PresenterImp(
|
||||
view: container.resolve(),
|
||||
interactor: container.resolve(),
|
||||
router: container.resolve()) as Presenter
|
||||
}
|
||||
|
||||
|
||||
var presenter = try! container.resolve() as Presenter
|
||||
presenter.interactor.service
|
||||
|
||||
/*:
|
||||
While definition for `Interactor` looks fine, `Presenter`'s definition is overloaded with the same `resolve` calls to container.
|
||||
|
||||
The other option you have is to register factory with runtime arguments:
|
||||
*/
|
||||
|
||||
container.register { InteractorImp(service: $0) as Interactor }
|
||||
container.register { PresenterImp(view: $0, interactor: $1, router: $2) as Presenter }
|
||||
|
||||
/*:
|
||||
But then to resolve presenter or interactor you will first need to resolve their dependencies and pass them as arguments to `resolve` method:
|
||||
*/
|
||||
|
||||
let service = try! container.resolve() as Service
|
||||
let interactor = try! container.resolve(withArguments: service) as Interactor
|
||||
let view = try! container.resolve() as ViewOutput
|
||||
let router = try! container.resolve() as Router
|
||||
presenter = try! container.resolve(withArguments: view, interactor, router) as Presenter
|
||||
presenter.interactor.service
|
||||
|
||||
/*:
|
||||
Again to much of boilerplate code. Also it's easy to make a mistake in the order of arguments.
|
||||
|
||||
Auto-wiring solves this problem by combining these two approaches - you register factories with runtime arguments, but resolve components with just a call to `resolve()`. Container will resolve all consturctor arguments for you.
|
||||
*/
|
||||
|
||||
container.register { InteractorImp(service: $0) as Interactor }
|
||||
container.register { PresenterImp(view: $0, interactor: $1, router: $2) as Presenter }
|
||||
|
||||
presenter = try! container.resolve() as Presenter
|
||||
presenter.interactor.service
|
||||
|
||||
/*:
|
||||
You don't need to call `resolve` in a factory and care about order of arguments any more.
|
||||
|
||||
The only requirement is that all constructor arguments should be registered in the container and there should be no several factories with the same _number_ of arguments registered for the same components.
|
||||
|
||||
In very rare case when you have several different factories with different set of runtime arguments registered for the same component, when you try to resolve it container will try to use these factories one by one until one of them succeeds starting with a factory with most numbers of arguments. If it finds two factories with the same number of arguments it will throw an error.
|
||||
|
||||
You can use auto-wiring with tags. The tag that you pass to `resolve` method will be used to resolve each of the constructor arguments.
|
||||
*/
|
||||
|
||||
//: [Next: Auto-injection](@next)
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Timeline
|
||||
version = "3.0">
|
||||
<TimelineItems>
|
||||
</TimelineItems>
|
||||
</Timeline>
|
||||
+9
-2
@@ -31,11 +31,17 @@ container.register(factory: factory.someService)
|
||||
|
||||
/*:
|
||||
Optionally you can associate definitions with Integer or String tags. This way you can register different implementations for the same protocol.
|
||||
You can use String or Integer literals, or the `DependencyContainer.Tag` enum.
|
||||
You can use `DependencyContainer.Tag` enum, String or Integer literals, or instances of types that conform to `DependencyTagConvertible` protocol.
|
||||
*/
|
||||
|
||||
container.register(tag: "tag") { ServiceImp1() as Service }
|
||||
container.register(tag: DependencyContainer.Tag.Int(0)) { ServiceImp1() as Service }
|
||||
container.register(tag: .Int(0)) { ServiceImp1() as Service }
|
||||
|
||||
enum MyCustomTag: String, DependencyTagConvertible {
|
||||
case SomeTag
|
||||
}
|
||||
|
||||
container.register(tag: MyCustomTag.SomeTag) { ServiceImp1() as Service }
|
||||
|
||||
/*:
|
||||
We recommand you to use constants for the tags, to make the intent clear and avoid magic numbers and typos.
|
||||
@@ -44,6 +50,7 @@ You can remove all registered definitions or register and remove them one by one
|
||||
*/
|
||||
|
||||
let serviceDefinition = container.register { ServiceImp1() as Service }
|
||||
container
|
||||
container.remove(serviceDefinition)
|
||||
|
||||
container.reset()
|
||||
|
||||
@@ -158,6 +158,6 @@ If you want to know more about Dependency Injection in general we recomend you t
|
||||
|
||||
*/
|
||||
|
||||
//: [Next: Auto-Injection](@next)
|
||||
//: [Next: Auto-wiring](@next)
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<page name='Scopes'/>
|
||||
<page name='Circular dependencies'/>
|
||||
<page name='Shared Instances'/>
|
||||
<page name='Auto-wiring'/>
|
||||
<page name='Auto-injection'/>
|
||||
<page name='Testing'/>
|
||||
</pages>
|
||||
|
||||
@@ -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.3.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
|
||||
@@ -183,9 +205,27 @@ container.register(.ObjectGraph) { ServerImp() as Server }
|
||||
```
|
||||
More information about circular dependencies you can find in the Playground.
|
||||
|
||||
### Auto-Injection
|
||||
### Auto-wiring
|
||||
|
||||
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.
|
||||
When you use constructor injection to inject dependencies in your component auto-wiring enables you to resolve it just with one call to `resolve` method without carying about how to resolve all constructor arguments - container will resolve them for you.
|
||||
|
||||
```swift
|
||||
class PresenterImp: Presenter {
|
||||
init(view: ViewOutput, interactor: Interactor, router: Router) { ... }
|
||||
...
|
||||
}
|
||||
|
||||
container.register { RouterImp() as Router }
|
||||
container.register { View() as ViewOutput }
|
||||
container.register { InteractorImp() as Interactor }
|
||||
container.register { PresenterImp(view: $0, interactor: $1, router: $2) as Presenter }
|
||||
|
||||
let presenter = try! container.resolve() as Presenter
|
||||
```
|
||||
|
||||
### Auto-injection
|
||||
|
||||
Auto-injection lets your resolve all property 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 +254,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 +264,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
|
||||
|
||||
|
||||
@@ -17,6 +17,11 @@ private let FAKE_STARSHIPS = false
|
||||
/* ---- */
|
||||
|
||||
|
||||
enum DependencyTags: Int, DependencyTagConvertible {
|
||||
case Hardcoded
|
||||
case Dummy
|
||||
}
|
||||
|
||||
// MARK: Dependency Container for Providers
|
||||
func configureContainer(dip: DependencyContainer) {
|
||||
|
||||
@@ -47,16 +52,16 @@ func configureContainer(dip: DependencyContainer) {
|
||||
// 2) Register fake starships provider
|
||||
|
||||
//Here we register different implementations for the same protocol using tags
|
||||
dip.register(tag: "hardcoded") { HardCodedStarshipProvider() as StarshipProviderAPI }
|
||||
dip.register(tag: DependencyTags.Hardcoded) { HardCodedStarshipProvider() as StarshipProviderAPI }
|
||||
|
||||
//Here we register factory that will require a runtime argument
|
||||
dip.register(tag: "dummy") { DummyStarshipProvider(pilotName: $0) as StarshipProviderAPI }
|
||||
dip.register(tag: DependencyTags.Dummy) { DummyStarshipProvider(pilotName: $0) as StarshipProviderAPI }
|
||||
|
||||
//Here we use constructor injection, but instead of providing dependencies manually container resolves them for us
|
||||
dip.register() {
|
||||
FakeStarshipProvider(
|
||||
dummyProvider: try dip.resolve(tag: "dummy", withArguments: "Main Pilot"),
|
||||
hardCodedProvider: try dip.resolve(tag: "hardcoded")) as StarshipProviderAPI
|
||||
dummyProvider: try dip.resolve(tag: DependencyTags.Dummy, withArguments: "Main Pilot"),
|
||||
hardCodedProvider: try dip.resolve(tag: DependencyTags.Hardcoded)) as StarshipProviderAPI
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
@@ -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.
|
||||
@@ -94,28 +122,27 @@ public final class Injected<T>: _InjectedPropertyBox<T>, _AnyInjectedPropertyBox
|
||||
- didInject: block that will be called when concrete instance is injected in this property.
|
||||
Similar to `didSet` property observer. Default value does nothing.
|
||||
*/
|
||||
public override init(required: Bool = true, tag: DependencyContainer.Tag? = nil, didInject: T -> () = { _ in }) {
|
||||
public override init(required: Bool = true, tag: DependencyTagConvertible? = nil, didInject: T -> () = { _ in }) {
|
||||
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.
|
||||
@@ -164,43 +195,39 @@ public final class InjectedWeak<T>: _InjectedPropertyBox<T>, _AnyInjectedPropert
|
||||
- didInject: block that will be called when concrete instance is injected in this property.
|
||||
Similar to `didSet` property observer. Default value does nothing.
|
||||
*/
|
||||
public override init(required: Bool = true, tag: DependencyContainer.Tag? = nil, didInject: T -> () = { _ in }) {
|
||||
public override init(required: Bool = true, tag: DependencyTagConvertible? = nil, didInject: T -> () = { _ in }) {
|
||||
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?
|
||||
|
||||
init(required: Bool = true, tag: DependencyContainer.Tag?, didInject: T -> () = { _ in }) {
|
||||
init(required: Bool = true, tag: DependencyTagConvertible?, didInject: T -> () = { _ in }) {
|
||||
self.required = required
|
||||
self.tag = tag
|
||||
self.tag = tag?.dependencyTag
|
||||
self.didInject = didInject
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
//
|
||||
// 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 DependencyContainer {
|
||||
|
||||
/// Tries to resolve instance using auto-wire factories
|
||||
func _resolveByAutoWiring<T>(key: DefinitionKey) throws -> T? {
|
||||
typealias NoArgumentsFactory = () throws -> T
|
||||
guard key.factoryType == NoArgumentsFactory.self else { return nil }
|
||||
|
||||
let autoWiringDefinitions = self.autoWiringDefinitionsFor(T.self, tag: key.associatedTag)
|
||||
return try _resolveEnumeratingKeys(autoWiringDefinitions, tag: key.associatedTag)
|
||||
}
|
||||
|
||||
private func autoWiringDefinitionsFor(type: Any.Type, tag: DependencyContainer.Tag?) -> [(DefinitionKey, _Definition)] {
|
||||
var definitions = self.definitions
|
||||
.map({ ($0.0, $0.1 as! _Definition) })
|
||||
|
||||
//filter definitions
|
||||
definitions = definitions
|
||||
.filter({ $0.1.supportsAutoWiring() })
|
||||
.filter({ $0.0.protocolType == type })
|
||||
.filter({ $0.0.associatedTag == tag || $0.0.associatedTag == nil })
|
||||
|
||||
//order definitions
|
||||
definitions = definitions
|
||||
.sort({ $0.1.numberOfArguments > $1.1.numberOfArguments })
|
||||
|
||||
definitions =
|
||||
//first will try to use tagged definitions
|
||||
definitions.filter({ $0.0.associatedTag == tag }) +
|
||||
//then will use not tagged definitions
|
||||
definitions.filter({ $0.0.associatedTag != tag })
|
||||
|
||||
return definitions
|
||||
}
|
||||
|
||||
/// Tries definitions one by one until one of them succeeds, otherwise returns nil
|
||||
private func _resolveEnumeratingKeys<T>(definitions: [(DefinitionKey, _Definition)], tag: DependencyContainer.Tag?) throws -> T? {
|
||||
for (index, definition) in definitions.enumerate() {
|
||||
//If the next definition matches current definition then they are ambigous
|
||||
if case definition? = definitions[next: index] {
|
||||
throw DipError.AmbiguousDefinitions(
|
||||
type: definition.0.protocolType,
|
||||
definitions: [definition.1, definitions[next: index]!.1]
|
||||
)
|
||||
}
|
||||
|
||||
if let resolved: T = _resolveKey(definition.0, tag: tag) {
|
||||
return resolved
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func _resolveKey<T>(key: DefinitionKey, tag: DependencyContainer.Tag?) -> T? {
|
||||
let key = DefinitionKey(protocolType: key.protocolType, factoryType: key.factoryType, associatedTag: tag)
|
||||
|
||||
return try? _resolveKey(key, builder: { definition throws -> T in
|
||||
guard let resolved = try definition._autoWiringFactory!(self, tag) as? T else {
|
||||
fatalError("Internal inconsistency exception! Expected type: \(T.self); Definition: \(definition)")
|
||||
}
|
||||
return resolved
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension CollectionType {
|
||||
subscript(safe index: Index) -> Generator.Element? {
|
||||
guard indices.contains(index) else { return nil }
|
||||
return self[index]
|
||||
}
|
||||
subscript(next index: Index) -> Generator.Element? {
|
||||
return self[safe: index.advancedBy(1)]
|
||||
}
|
||||
}
|
||||
|
||||
/// Definitions are matched if they are registered for the same tag and thier factoris accept the same number of runtime arguments.
|
||||
private func ~=(lhs: (DefinitionKey, _Definition), rhs: (DefinitionKey, _Definition)) -> Bool {
|
||||
return
|
||||
lhs.0.protocolType == rhs.0.protocolType &&
|
||||
lhs.0.associatedTag == rhs.0.associatedTag &&
|
||||
lhs.1.numberOfArguments == rhs.1.numberOfArguments
|
||||
}
|
||||
@@ -41,6 +41,7 @@ public struct DefinitionKey : Hashable, CustomStringConvertible {
|
||||
public var description: String {
|
||||
return "type: \(protocolType), factory: \(factoryType), tag: \(associatedTag.desc)"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Check two definition keys on equality by comparing their `protocolType`, `factoryType` and `associatedTag` properties.
|
||||
@@ -53,11 +54,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 +174,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,36 +190,59 @@ 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?
|
||||
|
||||
//Auto-wiring helpers
|
||||
|
||||
private(set) var autoWiringFactory: ((DependencyContainer, DependencyContainer.Tag?) throws -> T)?
|
||||
private(set) var numberOfArguments: Int = 0
|
||||
|
||||
convenience init(scope: ComponentScope, factory: F, autoWiringFactory: (DependencyContainer, DependencyContainer.Tag?) throws -> T, numberOfArguments: Int) {
|
||||
self.init(scope: scope, factory: factory)
|
||||
self.autoWiringFactory = autoWiringFactory
|
||||
self.numberOfArguments = numberOfArguments
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
///Dummy protocol to store definitions for different types in collection
|
||||
public protocol Definition: class { }
|
||||
|
||||
protocol _Definition: Definition {
|
||||
|
||||
var scope: ComponentScope { get }
|
||||
|
||||
var _autoWiringFactory: ((DependencyContainer, DependencyContainer.Tag?) throws -> Any)? { get }
|
||||
var _factory: Any { get }
|
||||
var numberOfArguments: Int { get }
|
||||
|
||||
func resolveDependenciesOf(resolvedInstance: Any, withContainer container: DependencyContainer) throws
|
||||
}
|
||||
|
||||
extension DefinitionOf: _Definition { }
|
||||
|
||||
extension DefinitionOf: CustomStringConvertible {
|
||||
public var description: String {
|
||||
return "type: \(T.self), factory: \(F.self), scope: \(scope), resolved instance: \(resolvedInstance.desc)"
|
||||
extension _Definition {
|
||||
func supportsAutoWiring() -> Bool {
|
||||
return _autoWiringFactory != nil && numberOfArguments > 0
|
||||
}
|
||||
}
|
||||
|
||||
extension DefinitionOf: _Definition {
|
||||
|
||||
var _resolveDependenciesBlock: ((DependencyContainer, Any) throws -> ())? {
|
||||
return resolveDependenciesBlock.map({ block in { try block($0, $1 as! T) } })
|
||||
}
|
||||
|
||||
var _autoWiringFactory: ((DependencyContainer, DependencyContainer.Tag?) throws -> Any)? {
|
||||
return autoWiringFactory.map({ factory in { try factory($0.0, $0.1)} })
|
||||
}
|
||||
|
||||
var _factory: Any {
|
||||
return factory
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension DefinitionOf: CustomStringConvertible {
|
||||
public var description: String {
|
||||
return "type: \(T.self), factory: \(F.self), scope: \(scope)"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,6 @@
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
// MARK: - DependencyContainer
|
||||
|
||||
/**
|
||||
`DependencyContainer` allows you to do _Dependency Injection_
|
||||
by associating abstractions to concrete implementations.
|
||||
@@ -33,6 +31,8 @@ public final class DependencyContainer {
|
||||
/**
|
||||
Use a tag in case you need to register multiple factories fo the same type,
|
||||
to differentiate them. Tags can be either String or Int, to your convenience.
|
||||
|
||||
- seealso: `DependencyTagConvertible`
|
||||
*/
|
||||
public enum Tag: Equatable {
|
||||
case String(StringLiteralType)
|
||||
@@ -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
|
||||
@@ -94,7 +94,9 @@ extension DependencyContainer {
|
||||
```
|
||||
*/
|
||||
public func register<T>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: () throws -> T) -> DefinitionOf<T, () throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory)
|
||||
let definition = DefinitionOf<T, () throws -> T>(scope: scope, factory: factory)
|
||||
register(definition, forTag: tag)
|
||||
return definition
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,25 +108,58 @@ extension DependencyContainer {
|
||||
- factory: The factory to register.
|
||||
|
||||
- returns: A registered definition.
|
||||
|
||||
|
||||
- note: You _should not_ call this method directly, instead call any of other `register` methods.
|
||||
You _should_ use this method only to register dependency with more runtime arguments
|
||||
than _Dip_ supports (currently it's up to six) like in the following example:
|
||||
You _should_ use this method only to register dependency with more runtime arguments
|
||||
than _Dip_ supports (currently it's up to six) like in the following example:
|
||||
|
||||
```swift
|
||||
public func register<T, Arg1, Arg2, Arg3, ...>(tag: Tag? = nil, scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, ...) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, ...) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory) as DefinitionOf<T, (Arg1, Arg2, Arg3, ...) throws -> T>
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory)
|
||||
}
|
||||
```
|
||||
|
||||
Though before you do so you should probably review your design and try to reduce number of depnedencies.
|
||||
*/
|
||||
public func registerFactory<T, F>(tag tag: Tag? = nil, scope: ComponentScope, factory: F) -> DefinitionOf<T, F> {
|
||||
@available(*, deprecated=4.3.0, message="Use registerFactory(tag:scope:factory:numberOfArguments:autoWiringFactory:) instead.")
|
||||
public func registerFactory<T, F>(tag tag: DependencyTagConvertible? = nil, scope: ComponentScope, factory: F) -> DefinitionOf<T, F> {
|
||||
let definition = DefinitionOf<T, F>(scope: scope, factory: factory)
|
||||
register(definition, forTag: tag)
|
||||
return definition
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Register generic factory and auto-wiring factory and associate it with an optional tag.
|
||||
|
||||
- parameters:
|
||||
- tag: The arbitrary tag to associate this factory with. Pass `nil` to associate with any tag. Default value is `nil`.
|
||||
- scope: The scope to use for instance created by the factory.
|
||||
- factory: The factory to register.
|
||||
- numberOfArguments: The number of factory arguments. Will be used on auto-wiring to sort definitions.
|
||||
- autoWiringFactory: The factory to be used on auto-wiring to resolve component.
|
||||
|
||||
- returns: A registered definition.
|
||||
|
||||
- note: You _should not_ call this method directly, instead call any of other `register` methods.
|
||||
You _should_ use this method only to register dependency with more runtime arguments
|
||||
than _Dip_ supports (currently it's up to six) like in the following example:
|
||||
|
||||
```swift
|
||||
public func register<T, Arg1, Arg2, Arg3, ...>(tag: Tag? = nil, scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, ...) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, ...) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: ...) { container, tag in
|
||||
try factory(try container.resolve(tag: tag), ...)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Though before you do so you should probably review your design and try to reduce number of depnedencies.
|
||||
*/
|
||||
public func registerFactory<T, F>(tag tag: DependencyTagConvertible? = nil, scope: ComponentScope, factory: F, numberOfArguments: Int, autoWiringFactory: (DependencyContainer, Tag?) throws -> T) -> DefinitionOf<T, F> {
|
||||
let definition = DefinitionOf<T, F>(scope: scope, factory: factory, autoWiringFactory: autoWiringFactory, numberOfArguments: numberOfArguments)
|
||||
register(definition, forTag: tag)
|
||||
return definition
|
||||
}
|
||||
|
||||
/**
|
||||
Register definiton in the container and associate it with an optional tag.
|
||||
Will override already registered definition for the same type and factory, associated with the same tag.
|
||||
@@ -134,14 +169,15 @@ extension DependencyContainer {
|
||||
- definition: The definition to register in the container.
|
||||
|
||||
*/
|
||||
public func register<T, F>(definition: DefinitionOf<T, F>, forTag tag: Tag? = nil) {
|
||||
let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag)
|
||||
public func register<T, F>(definition: DefinitionOf<T, F>, forTag tag: DependencyTagConvertible? = nil) {
|
||||
let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag?.dependencyTag)
|
||||
register(definition, forKey: key)
|
||||
}
|
||||
|
||||
func register(definition: Definition, forKey key: DefinitionKey) {
|
||||
threadSafe {
|
||||
definitions[key] = definition
|
||||
resolvedInstances.singletons[key] = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,10 +195,7 @@ extension DependencyContainer {
|
||||
|
||||
- parameter tag: The arbitrary tag to use to lookup definition.
|
||||
|
||||
- throws: An error of type `DipError`:
|
||||
`ResolutionFailed` - if some error was thrown during resolution;
|
||||
`DefinitionNotFound` - if no matching definition was registered in that container.
|
||||
`AutoInjectionFailed` - if failed to auto-inject required property
|
||||
- throws: `DipError.DefinitionNotFound`, `DipError.AutoInjectionFailed`, `DipError.AmbiguousDefinitions`
|
||||
|
||||
- returns: An instance of type `T`.
|
||||
|
||||
@@ -176,7 +209,7 @@ extension DependencyContainer {
|
||||
```
|
||||
|
||||
*/
|
||||
public func resolve<T>(tag tag: Tag? = nil) throws -> T {
|
||||
public func resolve<T>(tag tag: DependencyTagConvertible? = nil) throws -> T {
|
||||
return try resolve(tag: tag) { (factory: () throws -> T) in try factory() }
|
||||
}
|
||||
|
||||
@@ -187,11 +220,8 @@ extension DependencyContainer {
|
||||
- tag: The arbitrary tag to use to lookup definition.
|
||||
- builder: Generic closure that accepts generic factory and returns inctance created by that factory.
|
||||
|
||||
- throws: An error of type `DipError`:
|
||||
`ResolutionFailed` - if some error was thrown during resolution;
|
||||
`DefinitionNotFound` - if no matching definition was registered in that container.
|
||||
`AutoInjectionFailed` - if failed to auto-inject required property
|
||||
|
||||
- throws: `DipError.DefinitionNotFound`, `DipError.AutoInjectionFailed`, `DipError.AmbiguousDefinitions`
|
||||
|
||||
- returns: An instance of type `T`.
|
||||
|
||||
- note: You _should not_ call this method directly, instead call any of other
|
||||
@@ -207,59 +237,65 @@ extension DependencyContainer {
|
||||
|
||||
Though before you do so you should probably review your design and try to reduce the number of dependencies.
|
||||
*/
|
||||
public func resolve<T, F>(tag tag: Tag? = nil, builder: F throws -> T) throws -> T {
|
||||
let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag)
|
||||
public func resolve<T, F>(tag tag: DependencyTagConvertible? = nil, builder: F throws -> T) throws -> T {
|
||||
let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag?.dependencyTag)
|
||||
|
||||
do {
|
||||
return try _resolveKey(key, builder: builder)
|
||||
//first we try to find defintion that exactly matches parameters
|
||||
return try _resolveKey(key, builder: { definition throws -> T in
|
||||
guard let factory = definition._factory as? F else {
|
||||
throw DipError.DefinitionNotFound(key: key)
|
||||
}
|
||||
return try builder(factory)
|
||||
})
|
||||
}
|
||||
catch {
|
||||
switch error {
|
||||
case let DipError.DefinitionNotFound(errorKey) where key == errorKey:
|
||||
throw error
|
||||
//then if no definition found we try atuo-wiring
|
||||
return try threadSafe {
|
||||
guard let resolved: T = try _resolveByAutoWiring(key) else {
|
||||
throw error
|
||||
}
|
||||
return resolved
|
||||
}
|
||||
default:
|
||||
throw DipError.ResolutionFailed(key: key, underlyingError: error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Lookup definition by the key and use it to resolve instance. Fallback to the key with `nil` tag.
|
||||
func _resolveKey<T, F>(key: DefinitionKey, builder: F throws -> T) throws -> T {
|
||||
return try threadSafe {
|
||||
let nilTagKey = key.associatedTag.map { _ in DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: nil) }
|
||||
|
||||
guard let definition = (self.definitions[key] ?? self.definitions[nilTagKey]) as? DefinitionOf<T, F> else {
|
||||
/// Lookup definition by the key and use it to resolve instance. Fallback to the key with `nil` tag.
|
||||
func _resolveKey<T>(key: DefinitionKey, builder: _Definition throws -> T) throws -> T {
|
||||
return try threadSafe {
|
||||
let nilTagKey = key.associatedTag.map { _ in DefinitionKey(protocolType: T.self, factoryType: key.factoryType, associatedTag: nil) }
|
||||
|
||||
guard let definition = (self.definitions[key] ?? self.definitions[nilTagKey]) as? _Definition else {
|
||||
throw DipError.DefinitionNotFound(key: key)
|
||||
}
|
||||
return try self._resolveDefinition(definition, usingKey: (definition.scope == .ObjectGraph ? key : nil), builder: builder)
|
||||
return try self._resolveDefinition(definition, usingKey: 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>(definition: _Definition, usingKey key: DefinitionKey, builder: _Definition 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 {
|
||||
let resolvedInstance = try builder(definition.factory)
|
||||
let resolvedInstance = try builder(definition)
|
||||
|
||||
//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)
|
||||
|
||||
//we perform auto-injection as the last step to be able to reuse instances
|
||||
//stored when manually resolving dependencies in resolveDependencies block
|
||||
try definition.resolveDependenciesOf(resolvedInstance, withContainer: self)
|
||||
try autoInjectProperties(resolvedInstance)
|
||||
|
||||
return resolvedInstance
|
||||
@@ -271,30 +307,44 @@ extension DependencyContainer {
|
||||
///Before `resolve()` returns pool is drained.
|
||||
class ResolvedInstances {
|
||||
var resolvedInstances = [DefinitionKey: Any]()
|
||||
var singletons = [DefinitionKey: Any]()
|
||||
var resolvableInstances = [Resolvable]()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if let resolvable = instance as? Resolvable {
|
||||
resolvableInstances.append(resolvable)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
// We call didResolveDependencies only at this point
|
||||
// because this is a point when dependencies graph is complete.
|
||||
for resolvedInstance in resolvableInstances {
|
||||
resolvedInstance.didResolveDependencies()
|
||||
}
|
||||
resolvedInstances.removeAll()
|
||||
resolvableInstances.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,14 +366,15 @@ extension DependencyContainer {
|
||||
- tag: The tag used to register definition.
|
||||
- definition: The definition to remove
|
||||
*/
|
||||
public func remove<T, F>(definition: DefinitionOf<T, F>, forTag tag: Tag? = nil) {
|
||||
let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag)
|
||||
public func remove<T, F>(definition: DefinitionOf<T, F>, forTag tag: DependencyTagConvertible? = nil) {
|
||||
let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag?.dependencyTag)
|
||||
remove(definitionForKey: key)
|
||||
}
|
||||
|
||||
func remove(definitionForKey key: DefinitionKey) {
|
||||
threadSafe {
|
||||
definitions[key] = nil
|
||||
resolvedInstances.singletons[key] = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,6 +384,7 @@ extension DependencyContainer {
|
||||
public func reset() {
|
||||
threadSafe {
|
||||
definitions.removeAll()
|
||||
resolvedInstances.singletons.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,16 +398,54 @@ extension DependencyContainer: CustomStringConvertible {
|
||||
|
||||
}
|
||||
|
||||
extension DependencyContainer.Tag: IntegerLiteralConvertible {
|
||||
//MARK: - Resolvable
|
||||
|
||||
public init(integerLiteral value: IntegerLiteralType) {
|
||||
self = .Int(value)
|
||||
/// Conform to this protocol when you need to have a callback when all the dependencies are injected.
|
||||
public protocol Resolvable {
|
||||
/// This method is called by the container when all dependencies of the instance are resolved.
|
||||
func didResolveDependencies()
|
||||
}
|
||||
|
||||
//MARK: - DependencyTagConvertible
|
||||
|
||||
/// Implement this protocol of your type if you want to use its instances as `DependencyContainer`'s tags.
|
||||
/// `DependencyContainer.Tag`, `String`, `Int` and any `RawRepresentable` with `RawType` of `String` or `Int` by default confrom to this protocol.
|
||||
public protocol DependencyTagConvertible {
|
||||
var dependencyTag: DependencyContainer.Tag { get }
|
||||
}
|
||||
|
||||
extension DependencyContainer.Tag: DependencyTagConvertible {
|
||||
public var dependencyTag: DependencyContainer.Tag {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
extension String: DependencyTagConvertible {
|
||||
public var dependencyTag: DependencyContainer.Tag {
|
||||
return .String(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Int: DependencyTagConvertible {
|
||||
public var dependencyTag: DependencyContainer.Tag {
|
||||
return .Int(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension DependencyTagConvertible where Self: RawRepresentable, Self.RawValue == Int {
|
||||
public var dependencyTag: DependencyContainer.Tag {
|
||||
return .Int(rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
extension DependencyTagConvertible where Self: RawRepresentable, Self.RawValue == String {
|
||||
public var dependencyTag: DependencyContainer.Tag {
|
||||
return .String(rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
extension DependencyContainer.Tag: StringLiteralConvertible {
|
||||
|
||||
|
||||
public init(stringLiteral value: StringLiteralType) {
|
||||
self = .String(value)
|
||||
}
|
||||
@@ -370,6 +460,14 @@ extension DependencyContainer.Tag: StringLiteralConvertible {
|
||||
|
||||
}
|
||||
|
||||
extension DependencyContainer.Tag: IntegerLiteralConvertible {
|
||||
|
||||
public init(integerLiteral value: IntegerLiteralType) {
|
||||
self = .Int(value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public func ==(lhs: DependencyContainer.Tag, rhs: DependencyContainer.Tag) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (.String(lhsString), .String(rhsString)):
|
||||
@@ -381,20 +479,14 @@ public func ==(lhs: DependencyContainer.Tag, rhs: DependencyContainer.Tag) -> Bo
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - DipError
|
||||
|
||||
/**
|
||||
Errors thrown by `DependencyContainer`'s methods.
|
||||
|
||||
- seealso: `resolve(tag:)`
|
||||
*/
|
||||
public enum DipError: ErrorType, CustomStringConvertible {
|
||||
/**
|
||||
Thrown by `resolve(tag:)` if some error was thrown during resolution.
|
||||
|
||||
- parameters:
|
||||
- key: The key, which is associated with definition used to resolve instance
|
||||
- underlyingError: The error that caused resolution to fail
|
||||
*/
|
||||
case ResolutionFailed(key: DefinitionKey, underlyingError: ErrorType)
|
||||
|
||||
/**
|
||||
Thrown by `resolve(tag:)` if no matching definition was registered in container.
|
||||
@@ -412,15 +504,25 @@ public enum DipError: ErrorType, CustomStringConvertible {
|
||||
- underlyingError: The error that caused auto-injection to fail
|
||||
*/
|
||||
case AutoInjectionFailed(label: String?, type: Any.Type, underlyingError: ErrorType)
|
||||
|
||||
/**
|
||||
Thrown by `resolve(tag:)` if found ambigous definitions registered for resolved type
|
||||
|
||||
- parameters:
|
||||
- type: The type that failed to be resolved
|
||||
- definitions: Ambiguous definitions
|
||||
*/
|
||||
case AmbiguousDefinitions(type: Any.Type, definitions: [Definition])
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case let .ResolutionFailed(key, error):
|
||||
return "Failed to resolve type \(key.protocolType). \(error)"
|
||||
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)"
|
||||
case let .AmbiguousDefinitions(type, definitions):
|
||||
return "Ambiguous definitions for \(type):\n" +
|
||||
definitions.map({ "\($0)" }).joinWithSeparator(";\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,87 +42,88 @@ extension DependencyContainer {
|
||||
|
||||
- seealso: `registerFactory(tag:scope:factory:)`
|
||||
*/
|
||||
public func register<T, Arg1>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1) throws -> T) -> DefinitionOf<T, (Arg1) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory)
|
||||
public func register<T, Arg1>(tag tag: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1) throws -> T) -> DefinitionOf<T, (Arg1) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: 1) { container, tag in try factory(try container.resolve(tag: tag)) }
|
||||
}
|
||||
|
||||
/**
|
||||
Resolve a dependency using one runtime argument.
|
||||
|
||||
- note: When resolving type container will first try to use definition that matches types of arguments that you pass to resolve method. If it fails or no such definition is found container will try to _auto-wire_ component. For that it will iterate through all the definitions registered for that type which factories accept any number of runtime arguments and are tagged with the same tag, passed to `resolve` method, or with no tag. Container will try to use these definitions to resolve a component one by one until one of them succeeds, starting with tagged definitions in order of decreasing their's factories number of arguments.
|
||||
|
||||
- parameters:
|
||||
- tag: The arbitrary tag to lookup registered definition.
|
||||
- arg1: The first argument to pass to the definition's factory.
|
||||
|
||||
- throws: An error of type `DipError`:
|
||||
`ResolutionFailed` - if some error was thrown during resolution;
|
||||
`DefinitionNotFound` - if no matching definition was registered in that container.
|
||||
- throws: `DipError.DefinitionNotFound`, `DipError.AutoInjectionFailed`, `DipError.AmbiguousDefinitions`
|
||||
|
||||
- returns: An instance of type `T`.
|
||||
|
||||
- seealso: `register(tag:_:factory:)`, `resolve(tag:builder:)`
|
||||
*/
|
||||
public func resolve<T, Arg1>(tag tag: Tag? = nil, withArguments arg1: Arg1) throws -> T {
|
||||
public func resolve<T, Arg1>(tag tag: DependencyTagConvertible? = nil, withArguments arg1: Arg1) throws -> T {
|
||||
return try resolve(tag: tag) { (factory: (Arg1) throws -> T) in try factory(arg1) }
|
||||
}
|
||||
|
||||
// MARK: 2 Runtime Arguments
|
||||
|
||||
/// - seealso: `register(tag:scope:factory:)`
|
||||
public func register<T, Arg1, Arg2>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2) throws -> T) -> DefinitionOf<T, (Arg1, Arg2) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory)
|
||||
public func register<T, Arg1, Arg2>(tag tag: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2) throws -> T) -> DefinitionOf<T, (Arg1, Arg2) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: 2) { container, tag in try factory(try container.resolve(tag: tag), try container.resolve(tag: tag)) }
|
||||
}
|
||||
|
||||
/// - seealso: `resolve(tag:_:)`
|
||||
public func resolve<T, Arg1, Arg2>(tag tag: Tag? = nil, withArguments arg1: Arg1, _ arg2: Arg2) throws -> T {
|
||||
public func resolve<T, Arg1, Arg2>(tag tag: DependencyTagConvertible? = nil, withArguments arg1: Arg1, _ arg2: Arg2) throws -> T {
|
||||
return try resolve(tag: tag) { (factory: (Arg1, Arg2) throws -> T) in try factory(arg1, arg2) }
|
||||
}
|
||||
|
||||
// MARK: 3 Runtime Arguments
|
||||
|
||||
/// - seealso: `register(tag:scope:factory:)`
|
||||
public func register<T, Arg1, Arg2, Arg3>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory)
|
||||
public func register<T, Arg1, Arg2, Arg3>(tag tag: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: 3) { container, tag in try factory(try container.resolve(tag: tag), try container.resolve(), try container.resolve(tag: tag)) }
|
||||
}
|
||||
|
||||
/// - seealso: `resolve(tag:withArguments:)`
|
||||
public func resolve<T, Arg1, Arg2, Arg3>(tag tag: Tag? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3) throws -> T {
|
||||
public func resolve<T, Arg1, Arg2, Arg3>(tag tag: DependencyTagConvertible? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3) throws -> T {
|
||||
return try resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3) throws -> T) in try factory(arg1, arg2, arg3) }
|
||||
}
|
||||
|
||||
// MARK: 4 Runtime Arguments
|
||||
|
||||
/// - seealso: `register(tag:scope:factory:)`
|
||||
public func register<T, Arg1, Arg2, Arg3, Arg4>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory)
|
||||
public func register<T, Arg1, Arg2, Arg3, Arg4>(tag tag: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: 4) { container, tag in try factory(try container.resolve(tag: tag), try container.resolve(tag: tag), try container.resolve(tag: tag), try container.resolve(tag: tag)) }
|
||||
}
|
||||
|
||||
/// - seealso: `resolve(tag:withArguments:)`
|
||||
public func resolve<T, Arg1, Arg2, Arg3, Arg4>(tag tag: Tag? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4) throws -> T {
|
||||
public func resolve<T, Arg1, Arg2, Arg3, Arg4>(tag tag: DependencyTagConvertible? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4) throws -> T {
|
||||
return try resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4) throws -> T) in try factory(arg1, arg2, arg3, arg4) }
|
||||
}
|
||||
|
||||
// MARK: 5 Runtime Arguments
|
||||
|
||||
/// - seealso: `register(tag:scope:factory:)`
|
||||
public func register<T, Arg1, Arg2, Arg3, Arg4, Arg5>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4, Arg5) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4, Arg5) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory)
|
||||
public func register<T, Arg1, Arg2, Arg3, Arg4, Arg5>(tag tag: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4, Arg5) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4, Arg5) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: 5) { container, tag in try factory(try container.resolve(tag: tag), try container.resolve(tag: tag), try container.resolve(tag: tag), try container.resolve(tag: tag), try container.resolve(tag: tag)) }
|
||||
}
|
||||
|
||||
/// - seealso: `resolve(tag:withArguments:)`
|
||||
public func resolve<T, Arg1, Arg2, Arg3, Arg4, Arg5>(tag tag: Tag? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5) throws -> T {
|
||||
public func resolve<T, Arg1, Arg2, Arg3, Arg4, Arg5>(tag tag: DependencyTagConvertible? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5) throws -> T {
|
||||
return try resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5) throws -> T) in try factory(arg1, arg2, arg3, arg4, arg5) }
|
||||
}
|
||||
|
||||
// MARK: 6 Runtime Arguments
|
||||
|
||||
/// - seealso: `register(tag:scope:factory:)`
|
||||
public func register<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory)
|
||||
public func register<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6>(tag tag: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: 6) { container, tag in try factory(try container.resolve(tag: tag), try container.resolve(tag: tag), try container.resolve(tag: tag), try container.resolve(tag: tag), try container.resolve(tag: tag), try container.resolve(tag: tag)) }
|
||||
}
|
||||
|
||||
/// - seealso: `resolve(tag:withArguments:)`
|
||||
public func resolve<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6>(tag tag: Tag? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6) throws -> T {
|
||||
public func resolve<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6>(tag tag: DependencyTagConvertible? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6) throws -> T {
|
||||
return try resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) throws -> T) in try factory(arg1, arg2, arg3, arg4, arg5, arg6) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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