Compare commits

...

59 Commits

Author SHA1 Message Date
Olivier Halligon 8f3fad759e Merge branch 'release/4.0.0' 2015-12-12 22:24:29 +01:00
Ilya Puchka d6908e1feb Update CHANGELOG.md 2015-12-12 21:16:59 +01:00
Ilya Puchka 288673ecb3 Update README.md 2015-12-12 17:54:40 +01:00
Ilya Puchka 14c13d7f35 Update CHANGELOG.md 2015-12-12 17:54:19 +01:00
Ilya Puchka 11a9c7fb70 added note on migration to 4.0.0 in CHANGELOG 2015-12-12 11:56:57 +01:00
Ilya Puchka c670a220d5 added remark about throwing errors in README 2015-12-12 11:51:17 +01:00
Ilya Puchka 049737dd2f bumped version to 4.0.0 2015-12-12 11:25:05 +01:00
Ilya Puchka 79adaed6d3 made DefinitionKey public 2015-12-12 11:23:48 +01:00
AliSoftware c7f9c82f81 Merge pull request #26 from AliSoftware/feature/targets
Added targets for OS X, tvOS and watchOS2
2015-12-12 01:19:00 +01:00
Ilya Puchka ad9c095041 make travis build with carthage 2015-12-11 22:19:43 +01:00
Ilya Puchka 409f0f1990 sudo installer on travis 2015-12-11 22:05:28 +01:00
Ilya Puchka 685809e6ec install carthage on travis 2015-12-11 22:00:24 +01:00
Ilya Puchka dcea032624 use specific tvOS and watchOS simulators on travis 2015-12-11 21:49:12 +01:00
Ilya Puchka ace73a2672 use xcode7.2 on travis 2015-12-11 21:37:34 +01:00
Ilya Puchka ff6645d012 added build for tvOS and watchOS to travis script 2015-12-11 21:30:46 +01:00
Ilya Puchka e3448d9148 minor CHANGELOG fixes 2015-12-11 00:55:57 +01:00
Olivier Halligon b14c4aeb10 Fix CodeSigning DevelopmentTeam to None
To be able to build in Release on any computer
2015-12-11 00:35:05 +01:00
Ilya Puchka 0adc671bd1 updated CHANGELOG 2015-12-08 01:15:54 +01:00
Ilya Puchka 1d7533a516 added targets for OS X, tvOS, watchOS2 2015-12-08 01:06:51 +01:00
Ilya Puchka 6ad4b82ec0 container made final class 2015-12-07 20:15:39 +01:00
Ilya Puchka 6bc96bc8af fixed tests 2015-12-02 22:09:27 +01:00
Ilya Puchka 8b9879a9a2 removed usage of unowned container 2015-12-02 20:58:09 +01:00
Ilya Puchka 406e47206a Merge pull request #22 from ilyapuchka/feature/throwing-errors
Throwing errors
2015-12-02 20:56:28 +01:00
Ilya Puchka 69e8eef0f1 updated docs 2015-11-30 22:59:53 +01:00
Ilya Puchka 78e789cbce enabled code coverage 2015-11-30 15:20:47 +01:00
Ilya Puchka ebbe9a1513 added test on throwing errors and keys eqaulity 2015-11-30 15:20:47 +01:00
Ilya Puchka bc0511261f throwing errors 2015-11-30 15:20:47 +01:00
Ilya Puchka 6a0fa295cb removed deprecated methods and foundation imports 2015-11-30 13:28:46 +01:00
Ilya Puchka 73d8990afa CHANGELOG minor update 2015-11-30 13:28:46 +01:00
Ilya Puchka d7d8f28a36 definition and container description 2015-11-30 13:28:46 +01:00
Ilya Puchka e97883efb0 reverted DefinitionOf back to final class 2015-11-30 13:28:46 +01:00
Olivier Halligon 342fdf9c92 Add @ilyapuchka as author in the Podspec 2015-11-30 12:42:28 +01:00
Olivier Halligon feb5064b93 Merge branch 'release/3.1.0' 2015-11-30 12:30:20 +01:00
Ilya Puchka 9778236416 Merge branch 'release/3.1.0' into develop 2015-11-30 11:02:00 +01:00
Ilya Puchka 184e1f4543 CHANGELOG minor fix 2015-11-30 10:55:12 +01:00
Olivier Halligon b50046c071 Merge branch 'hotfix/migration-note' into develop 2015-11-30 00:00:10 +01:00
Olivier Halligon 8be1139114 CHANGELOG re-org 2015-11-29 23:34:29 +01:00
Ilya Puchka afc8e49b18 bumped project version 2015-11-27 12:52:21 +01:00
Ilya Puchka 333fa96b62 added name for first runtime argument in resolve 2015-11-27 12:47:41 +01:00
Ilya Puchka d611ea240e added scope argument 2015-11-27 12:38:03 +01:00
Ilya Puchka ee16baacfc Merge branch 'master' into release/3.1.0 2015-11-26 23:37:02 +01:00
Ilya Puchka b4221e25a1 Merge pull request #17 from AliSoftware/hotfix/migration-note
Added note on migration from 2.0.0 to 3.0.0
2015-11-26 17:38:04 +01:00
Ilya Puchka 0edd3747e8 added note on migration from 2.0.0 to 3.0.0 2015-11-26 17:21:16 +01:00
Ilya Puchka 0c93e868f0 Added documentation note on definition generic parameters 2015-11-22 21:04:00 +01:00
Olivier Halligon ada995590f Fix depreciation warnings in Sample App 2015-11-22 19:02:04 +01:00
Olivier Halligon c0e5df3443 Merge branch 'feature/circular-dependencies' into develop 2015-11-22 16:52:08 +01:00
Olivier Halligon 37d42281c1 CHANGELOG 2015-11-22 16:51:32 +01:00
Olivier Halligon 6c56e12eeb Including whole MIT licence in comment headers
As license says: "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software." ;-)
2015-11-22 16:49:57 +01:00
Olivier Halligon 948b3cd780 [Playground] Nitpicking on the Circular Dependencies page 2015-11-22 16:49:57 +01:00
Ilya Puchka adde50a3d0 added references to articles about service locator 2015-11-22 16:49:57 +01:00
Ilya Puchka 450a2f8a72 updated playground and added page about circular dependencies 2015-11-22 16:49:57 +01:00
Ilya Puchka 59719a0c37 updated documentation 2015-11-22 16:48:44 +01:00
Ilya Puchka f6afdfd08e added tag property on DefinitionOf 2015-11-22 16:47:14 +01:00
Ilya Puchka abf2202eeb not mutating resolveDependencies method 2015-11-22 16:47:14 +01:00
Ilya Puchka 938108f2ab converted DefinitionOf to struct 2015-11-22 16:47:14 +01:00
Ilya Puchka 9d619d6a25 added note on thread safety in readme 2015-11-22 16:47:14 +01:00
Ilya Puchka 58efc586db added scoped function to increment/decrement recursion depth 2015-11-22 16:44:39 +01:00
Ilya Puchka 3a2aecbe48 circular dependencies 2015-11-22 16:44:39 +01:00
Olivier Halligon 2708f7e434 Merge branch 'release/3.0.0' into develop 2015-11-22 14:32:49 +01:00
35 changed files with 1991 additions and 461 deletions
+7 -3
View File
@@ -1,5 +1,5 @@
language: objective-c
osx_image: xcode7
osx_image: xcode7.2
# cache: cocoapods
# before_install:
@@ -7,9 +7,13 @@ osx_image: xcode7
install:
- gem install xcpretty --no-rdoc --no-ri --no-document --quiet
- curl -OlL "https://github.com/Carthage/Carthage/releases/download/0.11/Carthage.pkg" && sudo installer -pkg "Carthage.pkg" -target / && rm "Carthage.pkg"
script:
- set -o pipefail && xcodebuild test -workspace Dip.xcworkspace -scheme Dip -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 6,OS=latest' ONLY_ACTIVE_ARCH=NO | xcpretty -c
- set -o pipefail && xcodebuild test -workspace Dip.xcworkspace -scheme Dip-iOS -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 6,OS=latest' ONLY_ACTIVE_ARCH=NO | xcpretty -c
- set -o pipefail && xcodebuild test -workspace Dip.xcworkspace -scheme Dip-OSX -sdk macosx -destination 'platform=OS X,arch=x86_64' ONLY_ACTIVE_ARCH=NO | xcpretty -c
- set -o pipefail && xcodebuild test -workspace Dip.xcworkspace -scheme Dip-tvOS -sdk appletvsimulator -destination 'platform=tvOS Simulator,name=Apple TV 1080p,OS=latest' ONLY_ACTIVE_ARCH=NO | xcpretty -c
- set -o pipefail && xcodebuild -workspace Dip.xcworkspace -scheme Dip-watchOS -sdk watchsimulator -destination 'platform=watchOS Simulator,name=Apple Watch - 38mm,OS=latest' ONLY_ACTIVE_ARCH=NO | xcpretty - c
- set -o pipefail && xcodebuild test -workspace Dip.xcworkspace -scheme DipSampleApp -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 6,OS=latest' ONLY_ACTIVE_ARCH=NO | xcpretty -c
- pod lib lint --quick
- carthage build --no-skip-current
+69
View File
@@ -1,5 +1,53 @@
# CHANGELOG
## 4.0.0
#### New Features
* Added support for circular dependencies:
* Added `ObjectGraph` scope to reuse resolved instances
* Added `resolveDependencies` method on `DefinitionOf` class to resolve dependencies of resolved instance.
[#11](https://github.com/AliSoftware/Dip/pull/11), [@ilyapuchka](https://github.com/ilyapuchka)
* Added methods to register/remove individual definitions.
[#11](https://github.com/AliSoftware/Dip/pull/11), [@ilyapuchka](https://github.com/ilyapuchka)
* All `resolve` methods now can throw error if type can not be resolved.
[#15](https://github.com/AliSoftware/Dip/issues/15), [@ilyapuchka](https://github.com/ilyapuchka)
* `DependencyContainer` is marked as `final`.
* Added support for OSX, tvOS and watchOS2.
[#26](https://github.com/AliSoftware/Dip/pull/26), [@ilyapuchka](https://github.com/ilyapuchka)
#### Breaking Changes
* Removed container thread-safety to enable recursion calls to `resolve`.
**Access to container from multiple threads should be handled by clients** from now on.
* All `resolve` methods now can throw.
### Note on migration from 3.x to 4.0.0:
* Errors
In 4.0.0 each `resolve` method can throw `DefinitionNotFound(DefinitionKey)` error, so you need to call it using `try!` or `try?`, or catch the error if it's appropriate for your case. See [#15](https://github.com/AliSoftware/Dip/issues/15) for rationale of this change.
* Thread safety
In 4.0.0 `DependencyContainer` drops any guarantee of thread safety. From now on code that uses Dip must ensure that it's methods are called from a single thread. For example if you have registered type as a singleton and later two threads try to resolve it at the same time you can have two different instances of type instead of one as expected. This change was required to enable recursive calls of `resolve` method to resolve circular dependencies.
* Removed methods
Methods deprecated in 3.1.0 are now removed.
## 3.1.0
#### New
* Added name for the first runtime argument in `resolve(tag:withArguments: … )` methods to make more clear separation between tag and factory runtime arguments.
#### Depreciations
* `resolve(tag:_: … )` methods are deprecated in favor of those new `resolve(tag:withArguments: … )` methods.
* Deprecated `register(tag:instance:)` method in favor of `register(.Singleton) { … }`.
## 3.0.0
* Added support for factories with up to six runtime arguments.
@@ -7,6 +55,27 @@
* Parameter `tag` is now named in all register/resolve methods.
* Playground added to project.
[#10](https://github.com/AliSoftware/Dip/pull/10), [@ilyapuchka](https://github.com/ilyapuchka)
### Note on migration from 2.0.0 to 3.0.0:
If you used tags to register and resolve your components you have to add `tag` name for tag parameter. Don't forget to add it both in `register` and `resolve` methods. If you forget to add it in `resolve` call then tag value will be treated as first runtime argument for a factory, but there is no such factory registerd, so resolve will fail.
**Example**:
This code:
```swift
container.register("some tag") { SomeClass() as SomeProtocol }
container.resolve("some tag") as SomeProtocol
```
becomes this:
```swift
container.register(tag: "some tag") { SomeClass() as SomeProtocol }
container.resolve(tag: "some tag") as SomeProtocol
```
## 2.0.0
+8 -4
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "Dip"
s.version = "3.0.0"
s.version = "4.0.0"
s.summary = "A simple Dependency Resolver: Dependency Injection using Protocol resolution."
s.description = <<-DESC
@@ -19,12 +19,16 @@ Pod::Spec.new do |s|
s.homepage = "https://github.com/AliSoftware/Dip"
s.license = 'MIT'
s.author = { "Olivier Halligon" => "olivier@halligon.net" }
s.authors = { "Olivier Halligon" => "olivier@halligon.net", "Ilya Puchka" => "ilya@puchka.me" }
s.source = { :git => "https://github.com/AliSoftware/Dip.git", :tag => s.version.to_s }
s.social_media_url = 'https://twitter.com/aligatr'
s.platform = :ios, '8.0'
s.ios.deployment_target = '8.0'
s.osx.deployment_target = '10.9'
s.tvos.deployment_target = '9.0'
s.watchos.deployment_target = '2.0'
s.requires_arc = true
s.source_files = 'Dip/Dip/**/*'
s.source_files = 'Dip/Dip/**/*.swift'
end
File diff suppressed because it is too large Load Diff
@@ -14,9 +14,9 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "094526901BEA1CFF0034E72A"
BlueprintIdentifier = "0903B3571C161543002241C1"
BuildableName = "Dip.framework"
BlueprintName = "Dip"
BlueprintName = "Dip-OSX"
ReferencedContainer = "container:Dip.xcodeproj">
</BuildableReference>
</BuildActionEntry>
@@ -32,9 +32,9 @@
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0945269A1BEA1CFF0034E72A"
BuildableName = "DipTests.xctest"
BlueprintName = "DipTests"
BlueprintIdentifier = "0903B3601C161543002241C1"
BuildableName = "Dip-OSXTests.xctest"
BlueprintName = "Dip-OSXTests"
ReferencedContainer = "container:Dip.xcodeproj">
</BuildableReference>
</TestableReference>
@@ -42,9 +42,9 @@
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "094526901BEA1CFF0034E72A"
BlueprintIdentifier = "0903B3571C161543002241C1"
BuildableName = "Dip.framework"
BlueprintName = "Dip"
BlueprintName = "Dip-OSX"
ReferencedContainer = "container:Dip.xcodeproj">
</BuildableReference>
</MacroExpansion>
@@ -64,9 +64,9 @@
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "094526901BEA1CFF0034E72A"
BlueprintIdentifier = "0903B3571C161543002241C1"
BuildableName = "Dip.framework"
BlueprintName = "Dip"
BlueprintName = "Dip-OSX"
ReferencedContainer = "container:Dip.xcodeproj">
</BuildableReference>
</MacroExpansion>
@@ -82,9 +82,9 @@
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "094526901BEA1CFF0034E72A"
BlueprintIdentifier = "0903B3571C161543002241C1"
BuildableName = "Dip.framework"
BlueprintName = "Dip"
BlueprintName = "Dip-OSX"
ReferencedContainer = "container:Dip.xcodeproj">
</BuildableReference>
</MacroExpansion>
@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0710"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0903B3771C1615EC002241C1"
BuildableName = "Dip.framework"
BlueprintName = "Dip-iOS"
ReferencedContainer = "container:Dip.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0903B3801C1615EC002241C1"
BuildableName = "Dip-iOSTests.xctest"
BlueprintName = "Dip-iOSTests"
ReferencedContainer = "container:Dip.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0903B3771C1615EC002241C1"
BuildableName = "Dip.framework"
BlueprintName = "Dip-iOS"
ReferencedContainer = "container:Dip.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0903B3771C1615EC002241C1"
BuildableName = "Dip.framework"
BlueprintName = "Dip-iOS"
ReferencedContainer = "container:Dip.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0903B3771C1615EC002241C1"
BuildableName = "Dip.framework"
BlueprintName = "Dip-iOS"
ReferencedContainer = "container:Dip.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0710"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0903B3A51C1618AF002241C1"
BuildableName = "Dip.framework"
BlueprintName = "Dip-tvOS"
ReferencedContainer = "container:Dip.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0903B3AE1C1618AF002241C1"
BuildableName = "Dip-tvOSTests.xctest"
BlueprintName = "Dip-tvOSTests"
ReferencedContainer = "container:Dip.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0903B3A51C1618AF002241C1"
BuildableName = "Dip.framework"
BlueprintName = "Dip-tvOS"
ReferencedContainer = "container:Dip.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0903B3A51C1618AF002241C1"
BuildableName = "Dip.framework"
BlueprintName = "Dip-tvOS"
ReferencedContainer = "container:Dip.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0903B3A51C1618AF002241C1"
BuildableName = "Dip.framework"
BlueprintName = "Dip-tvOS"
ReferencedContainer = "container:Dip.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0710"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0903B4031C162862002241C1"
BuildableName = "Dip.framework"
BlueprintName = "Dip-watchOS"
ReferencedContainer = "container:Dip.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0903B4031C162862002241C1"
BuildableName = "Dip.framework"
BlueprintName = "Dip-watchOS"
ReferencedContainer = "container:Dip.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0903B4031C162862002241C1"
BuildableName = "Dip.framework"
BlueprintName = "Dip-watchOS"
ReferencedContainer = "container:Dip.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
+61 -15
View File
@@ -22,24 +22,22 @@
// THE SOFTWARE.
//
import Foundation
///Internal representation of a key used to associate definitons and factories by tag, type and factory.
struct DefinitionKey : Hashable, Equatable, CustomDebugStringConvertible {
var protocolType: Any.Type
var factoryType: Any.Type
var associatedTag: DependencyContainer.Tag?
public struct DefinitionKey : Hashable, Equatable, CustomStringConvertible {
private(set) public var protocolType: Any.Type
private(set) public var factoryType: Any.Type
private(set) public var associatedTag: DependencyContainer.Tag?
var hashValue: Int {
public var hashValue: Int {
return "\(protocolType)-\(factoryType)-\(associatedTag)".hashValue
}
var debugDescription: String {
public var description: String {
return "type: \(protocolType), factory: \(factoryType), tag: \(associatedTag)"
}
}
func ==(lhs: DefinitionKey, rhs: DefinitionKey) -> Bool {
public func ==(lhs: DefinitionKey, rhs: DefinitionKey) -> Bool {
return
lhs.protocolType == rhs.protocolType &&
lhs.factoryType == rhs.factoryType &&
@@ -50,20 +48,62 @@ func ==(lhs: DefinitionKey, rhs: DefinitionKey) -> Bool {
public enum ComponentScope {
/// Indicates that a new instance of the component will be created each time it's resolved.
case Prototype
/// Indicates that instances will be reused during resolve but will be discurded when topmost `resolve` method returns.
case ObjectGraph
/// Indicates that resolved component should be retained by container and always reused.
case Singleton
}
///Definition of type T describes how instances of this type should be created when they are resolved by container.
public final class DefinitionOf<T>: Definition {
let factory: Any
let scope: ComponentScope
/**
Definition of type T describes how instances of this type should be created when this type is resolved by container.
- Generic parameter `T` is the type of the instance to resolve
- Generic parameter `F` is the type of block-factory that creates an instance of T.
For example `DefinitionOf<Service,(String)->Service>` is the type of definition that during resolution will produce instance of type `Service` using closure that accepts `String` argument.
*/
public final class DefinitionOf<T, F>: Definition {
init(factory: Any, scope: ComponentScope = .Prototype) {
/**
Sets the block that will be used to resolve dependencies of the component.
This block will be called before `resolve` returns.
- parameter block: block to use to resolve dependencies
- note:
If you have circular dependencies at least one of them should use this block
to resolve it's dependencies. Otherwise code enter infinite loop.
**Example**
```swift
container.register { ClientImp(service: container.resolve() as Service) as Client }
var definition = container.register { ServiceImp() as Service }
definition.resolveDependencies { container, service in
service.delegate = container.resolve() as Client
}
```
*/
public func resolveDependencies(block: (DependencyContainer, T) -> ()) -> DefinitionOf<T, F> {
guard resolveDependenciesBlock == nil else {
fatalError("You can not change resolveDependencies block after it was set.")
}
resolveDependenciesBlock = block
return self
}
let factory: F
var scope: ComponentScope
var resolveDependenciesBlock: ((DependencyContainer, T) -> ())?
init(factory: F, scope: ComponentScope) {
self.factory = factory
self.scope = scope
}
///Will be stored only if scope is `Singleton`
var resolvedInstance: T? {
get {
guard scope == .Singleton else { return nil }
@@ -79,4 +119,10 @@ public final class DefinitionOf<T>: Definition {
}
///Dummy protocol to store definitions for different types in collection
protocol Definition {}
protocol Definition: class {}
extension DefinitionOf: CustomStringConvertible {
public var description: String {
return "type: \(T.self), factory: \(F.self), scope: \(scope), resolved instance: \(resolvedInstance)"
}
}
+20 -4
View File
@@ -1,9 +1,25 @@
//
// Dip.h
// Dip
// Dip
//
// Created by Ilya Puchka on 04.11.15.
// Copyright © 2015 AliSoftware. All rights reserved.
// 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 <Foundation/Foundation.h>
+161 -64
View File
@@ -22,15 +22,13 @@
// THE SOFTWARE.
//
import Foundation
// MARK: - DependencyContainer
/**
* _Dip_'s Dependency Containers allow you to do very simple **Dependency Injection**
* by associating `protocols` to concrete implementations
*/
public class DependencyContainer {
_Dip_'s Dependency Containers allow you to do very simple **Dependency Injection**
by associating `protocols` to concrete implementations
*/
public final class DependencyContainer {
/**
Use a tag in case you need to register multiple instances or factories
@@ -42,8 +40,7 @@ public class DependencyContainer {
case Int(IntegerLiteralType)
}
private var dependencies = [DefinitionKey : Definition]()
private var lock: OSSpinLock = OS_SPINLOCK_INIT
var definitions = [DefinitionKey : Definition]()
/**
Designated initializer for a DependencyContainer
@@ -66,9 +63,18 @@ public class DependencyContainer {
Clear all the previously registered dependencies on this container.
*/
public func reset() {
lockAndDo {
dependencies.removeAll()
}
definitions.removeAll()
}
/**
Removes previously registered definition from container.
- parameter tag: tag used to register definition
- parameter definition: 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)
definitions[key] = nil
}
// MARK: Register dependencies
@@ -77,24 +83,22 @@ public class DependencyContainer {
Register a Void->T factory associated with optional tag.
- parameter tag: The arbitrary tag to associate this factory with when registering with that protocol. Pass `nil` to associate with any tag. Default value is `nil`.
- parameter scope: scope to use for this compone
- parameter factory: The factory to register, with return type of protocol you want to register it for
- returns: definition created for provided type and factory
- note: You must cast the factory return type to the protocol you want to register it with (e.g `MyClass() as MyAPI`)
- note: You must cast the factory return type to the protocol you want to register it for.
**Example**:
```swift
container.register { ServiceImp() as Service }
container.register(tag: "service") { ServiceImp() as Service }
container.register(.ObjectGraph) { ServiceImp() as Service }
container.register { ClientImp(service: try! container.resolve() as Service) as Client }
```
*/
public func register<T>(tag tag: Tag? = nil, factory: ()->T) -> DefinitionOf<T> {
return register(tag: tag, factory: factory, scope: .Prototype) as DefinitionOf<T>
}
/**
Register a Singleton instance associated with optional tag.
- parameter tag: The arbitrary tag to associate this instance with when registering with that protocol. `nil` to associate with any tag.
- parameter instance: The instance to register, with return type of protocol you want to register it for
- note: You must cast the instance to the protocol you want to register it with (e.g `MyClass() as MyAPI`)
*/
public func register<T>(tag tag: Tag? = nil, @autoclosure(escaping) instance factory: ()->T) -> DefinitionOf<T> {
return register(tag: tag, factory: { factory() }, scope: .Singleton)
public func register<T>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: ()->T) -> DefinitionOf<T, ()->T> {
return registerFactory(tag: tag, scope: scope, factory: factory)
}
/**
@@ -103,40 +107,63 @@ public class DependencyContainer {
- parameter tag: The arbitrary tag to look for when resolving this protocol.
- parameter factory: generic factory that should be used to create concrete instance of type
- parameter scope: scope of the component. Default value is `Prototype`
- returns: definition created for provided type and factory
- 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 this example:
```swift
public func register<T, Arg1, Arg2, Arg3, ...>(tag: Tag? = nil, factory: (Arg1, Arg2, Arg3, ...) -> T) -> DefinitionOf<T> {
return register(tag: tag, factory: factory, scope: .Prototype) as DefinitionOf<T>
public func register<T, Arg1, Arg2, Arg3, ...>(tag: Tag? = nil, scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, ...) -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, ...) -> T> {
return registerFactory(tag: tag, scope: scope, factory: factory) as DefinitionOf<T, (Arg1, Arg2, Arg3, ...) -> T>
}
```
Though before you do that you should probably review your design and try to reduce number of depnedencies.
*/
public func register<T, F>(tag tag: Tag? = nil, factory: F, scope: ComponentScope) -> DefinitionOf<T> {
public func registerFactory<T, F>(tag tag: Tag? = nil, scope: ComponentScope, factory: F) -> DefinitionOf<T, F> {
let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag)
let definition = DefinitionOf<T>(factory: factory, scope: scope)
lockAndDo {
dependencies[key] = definition
}
let definition = DefinitionOf<T, F>(factory: factory, scope: scope)
definitions[key] = definition
return definition
}
/**
Registers new definiton in container and associate it with provided tag.
Will override already registered definition for the same type and factory associated with the same tag.
- parameter tag: The arbitrary tag to associate definition with
- parameter definition: definition to register in 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)
definitions[key] = definition
}
// MARK: Resolve dependencies
/**
Resolve a dependency.
If no instance/factory was registered with this `tag` for this `protocol`, it will try to resolve the instance/factory associated with `nil` (no tag).
If no definition was registered with this `tag` for this `protocol`,
it will try to resolve the definition associated with `nil` (no tag).
Will throw `DipError.DefinitionNotFound` if no registered definition found
that would match type, runtime arguments and tag.
- parameter tag: The arbitrary tag to look for when resolving this protocol.
**Example**:
```swift
let service = try! container.resolve() as Service
let service = try! container.resolve(tag: "service") as Service
let service: Service = try! container.resolve()
```
*/
public func resolve<T>(tag tag: Tag? = nil) -> T {
return resolve(tag: tag) { (factory: ()->T) in factory() }
public func resolve<T>(tag tag: Tag? = nil) throws -> T {
return try resolve(tag: tag) { (factory: ()->T) in factory() }
}
/**
@@ -144,54 +171,100 @@ public class DependencyContainer {
- parameter tag: The arbitrary tag to look for when resolving this protocol.
- parameter builder: Generic closure that accepts generic factory and returns inctance produced by that factory
- returns: resolved instance of type T
- note: You should not call this method directly, instead call any of other `resolve` methods. (see `RuntimeArguments.swift`).
You _should_ use this method only to resolve dependency with more runtime arguments than _Dip_ supports
(currently it's up to six) like in this example:
```swift
public func resolve<T, Arg1, Arg2, Arg3, ...>(tag tag: Tag? = nil, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, ...) -> T {
return resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, ...) -> T) in factory(arg1, arg2, arg3, ...) }
public func resolve<T, Arg1, Arg2, Arg3, ...>(tag tag: Tag? = nil, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, ...) throws -> T {
return try resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, ...) -> T) in factory(arg1, arg2, arg3, ...) }
}
```
Though before you do that you should probably review your design and try to reduce the number of dependencies.
*/
public func resolve<T, F>(tag tag: Tag? = nil, builder: F->T) -> T {
public func resolve<T, F>(tag tag: Tag? = nil, builder: F->T) throws -> T {
let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag)
let nilTagKey = tag.map { _ in DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: nil) }
var resolved: T!
lockAndDo { [unowned self] in
resolved = self._resolve(key, nilTagKey: nilTagKey, builder: builder)
guard let definition = (self.definitions[key] ?? self.definitions[nilTagKey]) as? DefinitionOf<T, F> else {
throw DipError.DefinitionNotFound(key)
}
return resolved
let usingKey: DefinitionKey? = definition.scope == .ObjectGraph ? key : nil
return _resolve(tag, key: usingKey, definition: definition, builder: builder)
}
/// Actually resolve dependency
private func _resolve<T, F>(key: DefinitionKey, nilTagKey: DefinitionKey?, builder: F->T) -> T {
guard let definition = (self.dependencies[key] ?? self.dependencies[nilTagKey]) as? DefinitionOf<T> else {
fatalError("No instance factory registered with \(key) or \(nilTagKey)")
private func _resolve<T, F>(tag: Tag? = nil, key: DefinitionKey?, definition: DefinitionOf<T, F>, builder: F->T) -> T {
return resolvedInstances.resolve {
if let previouslyResolved: T = resolvedInstances.previouslyResolved(key, definition: definition) {
return previouslyResolved
}
else {
let resolvedInstance = builder(definition.factory)
//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) {
return previouslyResolved
}
resolvedInstances.storeResolvedInstance(resolvedInstance, forKey: key)
definition.resolvedInstance = resolvedInstance
definition.resolveDependenciesBlock?(self, resolvedInstance)
return resolvedInstance
}
}
if let resolvedInstance = definition.resolvedInstance {
return resolvedInstance
}
else {
let resolved = builder(definition.factory as! F)
definition.resolvedInstance = resolved
return resolved
}
}
// MARK: - Private
private func lockAndDo(@noescape block: Void->Void) {
OSSpinLockLock(&lock)
defer { OSSpinLockUnlock(&lock) }
block()
let resolvedInstances = ResolvedInstances()
///Pool to hold instances, created during call to `resolve()`.
///Before `resolve()` returns pool is drained.
class ResolvedInstances {
var resolvedInstances = [DefinitionKey: Any]()
func storeResolvedInstance<T>(instance: T, forKey key: DefinitionKey?) {
self.resolvedInstances[key] = instance
}
func previouslyResolved<T, F>(key: DefinitionKey?, definition: DefinitionOf<T, F>) -> T? {
return (definition.resolvedInstance ?? self.resolvedInstances[key]) as? T
}
private var depth: Int = 0
func resolve<T>(@noescape block: ()->T) -> T {
depth++
let resolved = block()
depth--
if depth == 0 {
resolvedInstances.removeAll()
}
return resolved
}
}
}
extension DependencyContainer: CustomStringConvertible {
public var description: String {
return "Definitions:\n" + definitions.map { "Key with tag: \($0.0.associatedTag)\n\tDefinition: \($0.1)\n" }.joinWithSeparator("\n")
}
}
extension DependencyContainer.Tag: IntegerLiteralConvertible {
@@ -228,9 +301,33 @@ public func ==(lhs: DependencyContainer.Tag, rhs: DependencyContainer.Tag) -> Bo
}
}
extension Dictionary {
subscript(key: Key?) -> Value! {
guard let key = key else { return nil }
return self[key]
public enum DipError: ErrorType, CustomStringConvertible {
case DefinitionNotFound(DefinitionKey)
public var description: String {
switch self {
case let .DefinitionNotFound(key):
return "No definition registered for \(key). Check the tag, type you try to resolve, number, order and types of runtime arguments passed to `resolve()`."
}
}
}
extension Dictionary {
subscript(key: Key?) -> Value? {
get {
guard let key = key else { return nil }
return self[key]
}
set {
guard let key = key else { return }
self[key] = newValue
}
}
}
extension Optional: CustomStringConvertible {
public var description: String {
return self.map { "\($0)" } ?? "nil"
}
}
+39 -40
View File
@@ -22,8 +22,6 @@
// THE SOFTWARE.
//
import Foundation
// MARK: - Register/resolve dependencies with runtime arguments
extension DependencyContainer {
@@ -41,10 +39,10 @@ extension DependencyContainer {
When you resolve it container will match the type and tag as well as __number__, __types__ and __order__
of runtime arguments that you pass to `resolve` method.
- seealso: `register(tag:factory:scope:)`
- seealso: `registerFactory(tag:scope:factory:)`
*/
public func register<T, Arg1>(tag tag: Tag? = nil, factory: (Arg1) -> T) -> DefinitionOf<T> {
return register(tag: tag, factory: factory, scope: .Prototype) as DefinitionOf<T>
public func register<T, Arg1>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1) -> T) -> DefinitionOf<T, (Arg1) -> T> {
return registerFactory(tag: tag, scope: scope, factory: factory) as DefinitionOf<T, (Arg1) -> T>
}
/**
@@ -54,65 +52,66 @@ extension DependencyContainer {
- parameter tag: The arbitrary tag to look for when resolving this protocol.
- parameter arg1: First argument to be passed to factory
- seealso: `resolve(tag:)`
- seealso: `resolve(tag:builder:)`
*/
public func resolve<T, Arg1>(tag tag: Tag? = nil, _ arg1: Arg1) -> T {
return resolve(tag: tag) { (factory: (Arg1) -> T) in factory(arg1) }
public func resolve<T, Arg1>(tag tag: Tag? = nil, withArguments arg1: Arg1) throws -> T {
return try resolve(tag: tag) { (factory: (Arg1) -> T) in factory(arg1) }
}
// MARK: 2 Runtime Arguments
/// - seealso: `register(:factory:scope:)`
public func register<T, Arg1, Arg2>(tag tag: Tag? = nil, factory: (Arg1, Arg2) -> T) -> DefinitionOf<T> {
return register(tag: tag, factory: factory, scope: .Prototype) as DefinitionOf<T>
public func register<T, Arg1, Arg2>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2) -> T) -> DefinitionOf<T, (Arg1, Arg2) -> T> {
return registerFactory(tag: tag, scope: scope, factory: factory) as DefinitionOf<T, (Arg1, Arg2) -> T>
}
/// - seealso: `resolve(tag:_:)`
public func resolve<T, Arg1, Arg2>(tag tag: Tag? = nil, _ arg1: Arg1, _ arg2: Arg2) -> T {
return resolve(tag: tag) { (factory: (Arg1, Arg2) -> T) in factory(arg1, arg2) }
public func resolve<T, Arg1, Arg2>(tag tag: Tag? = nil, withArguments arg1: Arg1, _ arg2: Arg2) throws -> T {
return try resolve(tag: tag) { (factory: (Arg1, Arg2) -> T) in factory(arg1, arg2) }
}
// MARK: 3 Runtime Arguments
public func register<T, Arg1, Arg2, Arg3>(tag tag: Tag? = nil, factory: (Arg1, Arg2, Arg3) -> T) -> DefinitionOf<T> {
return register(tag: tag, factory: factory, scope: .Prototype) as DefinitionOf<T>
public func register<T, Arg1, Arg2, Arg3>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3) -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3) -> T> {
return registerFactory(tag: tag, scope: scope, factory: factory) as DefinitionOf<T, (Arg1, Arg2, Arg3) -> T>
}
/// - seealso: `resolve(tag:_:)`
public func resolve<T, Arg1, Arg2, Arg3>(tag tag: Tag? = nil, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3) -> T {
return resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3) -> T) in factory(arg1, arg2, arg3) }
/// - seealso: `resolve(tag:withArguments:)`
public func resolve<T, Arg1, Arg2, Arg3>(tag tag: Tag? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3) throws -> T {
return try resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3) -> T) in factory(arg1, arg2, arg3) }
}
// MARK: 4 Runtime Arguments
public func register<T, Arg1, Arg2, Arg3, Arg4>(tag tag: Tag? = nil, factory: (Arg1, Arg2, Arg3, Arg4) -> T) -> DefinitionOf<T> {
return register(tag: tag, factory: factory, scope: .Prototype) as DefinitionOf<T>
public func register<T, Arg1, Arg2, Arg3, Arg4>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4) -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4) -> T> {
return registerFactory(tag: tag, scope: scope, factory: factory) as DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4) -> T>
}
/// - seealso: `resolve(tag:_:)`
public func resolve<T, Arg1, Arg2, Arg3, Arg4>(tag tag: Tag? = nil, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4) -> T {
return resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4) -> T) in factory(arg1, arg2, arg3, arg4) }
/// - 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 {
return try resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4) -> T) in factory(arg1, arg2, arg3, arg4) }
}
// MARK: 4 Runtime Arguments
public func register<T, Arg1, Arg2, Arg3, Arg4, Arg5>(tag tag: Tag? = nil, factory: (Arg1, Arg2, Arg3, Arg4, Arg5) -> T) -> DefinitionOf<T> {
return register(tag: tag, factory: factory, scope: .Prototype) as DefinitionOf<T>
}
/// - seealso: `resolve(tag:_:)`
public func resolve<T, Arg1, Arg2, Arg3, Arg4, Arg5>(tag tag: Tag? = nil, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5) -> T {
return resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5) -> T) in factory(arg1, arg2, arg3, arg4, arg5) }
}
// MARK: 5 Runtime Arguments
public func register<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6>(tag tag: Tag? = nil, factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> T) -> DefinitionOf<T> {
return register(tag: tag, factory: factory, scope: .Prototype) as DefinitionOf<T>
public func register<T, Arg1, Arg2, Arg3, Arg4, Arg5>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4, Arg5) -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4, Arg5) -> T> {
return registerFactory(tag: tag, scope: scope, factory: factory) as DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4, Arg5) -> T>
}
/// - seealso: `resolve(tag:_:)`
public func resolve<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6>(tag tag: Tag? = nil, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6) -> T {
return resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> T) in factory(arg1, arg2, arg3, arg4, arg5, arg6) }
/// - 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 {
return try resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5) -> T) in factory(arg1, arg2, arg3, arg4, arg5) }
}
// MARK: 6 Runtime Arguments
public func register<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> T> {
return registerFactory(tag: tag, scope: scope, factory: factory) as DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> T>
}
/// - 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 {
return try resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> T) in factory(arg1, arg2, arg3, arg4, arg5, arg6) }
}
}
+142
View File
@@ -0,0 +1,142 @@
//
// 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
class ComponentScopeTests: XCTestCase {
let container = DependencyContainer()
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
container.reset()
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testThatPrototypeIsDefaultScope() {
let def = container.register { ServiceImp1() as Service }
XCTAssertEqual(def.scope, ComponentScope.Prototype)
}
func testThatCallingInScopeChangesScope() {
let def = container.register(ComponentScope.Singleton) { ServiceImp1() as Service }
XCTAssertEqual(def.scope, ComponentScope.Singleton)
}
func testThatItResolvesTypeAsNewInstanceForPrototypeScope() {
//given
container.register { ServiceImp1() as Service }
//when
let service1 = try! container.resolve() as Service
let service2 = try! container.resolve() as Service
//then
XCTAssertFalse((service1 as! ServiceImp1) === (service2 as! ServiceImp1))
}
func testThatItReusesInstanceForSingletonScope() {
//given
container.register(.Singleton) { ServiceImp1() as Service }
//when
let service1 = try! container.resolve() as Service
let service2 = try! container.resolve() as Service
//then
XCTAssertTrue((service1 as! ServiceImp1) === (service2 as! ServiceImp1))
}
class Server {
weak var client: Client?
init() {}
}
class Client {
var server: Server
init(server: Server) {
self.server = server
}
}
func testThatItReusesInstanceInObjectGraphScopeDuringResolve() {
//given
container.register(.ObjectGraph) { Client(server: try! self.container.resolve()) as Client }
container.register(.ObjectGraph) { Server() as Server }.resolveDependencies { container, server in
server.client = try! container.resolve() as Client
}
//when
let client = try! container.resolve() as Client
//then
let server = client.server
XCTAssertTrue(server.client === client)
}
func testThatItDoesNotReuseInstanceInObjectGraphScopeInNextResolve() {
//given
container.register(.ObjectGraph) { Client(server: try! self.container.resolve()) as Client }
container.register(.ObjectGraph) { Server() as Server }.resolveDependencies { container, server in
server.client = try! container.resolve() as Client
}
//when
let client = try! container.resolve() as Client
let server = client.server
let anotherClient = try! container.resolve() as Client
let anotherServer = anotherClient.server
//then
XCTAssertFalse(server === anotherServer)
XCTAssertFalse(client === anotherClient)
}
func testThatItDoesNotReuseInstanceInObjectGraphScopeResolvedForNilTag() {
//given
var service2: Service?
container.register(.ObjectGraph) { ServiceImp1() as Service }.resolveDependencies { (c, _) in
service2 = try! c.resolve(tag: "service") as Service
}
container.register(tag: "service", .ObjectGraph) { ServiceImp2() as Service}
//when
let service1 = try! container.resolve(tag: "tag") as Service
//then
XCTAssertTrue(service1 is ServiceImp1)
XCTAssertTrue(service2 is ServiceImp2)
}
}
+68
View File
@@ -0,0 +1,68 @@
//
// 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
class DefinitionTests: XCTestCase {
typealias F1 = ()->Service
typealias F2 = (String)->Service
let tag1 = DependencyContainer.Tag.String("tag1")
let tag2 = DependencyContainer.Tag.String("tag2")
func testThatDefinitionKeyIsEqualBy_Type_Factory_Tag() {
let equalKey1 = DefinitionKey(protocolType: Service.self, factoryType: F1.self, associatedTag: tag1)
let equalKey2 = DefinitionKey(protocolType: Service.self, factoryType: F1.self, associatedTag: tag1)
XCTAssertEqual(equalKey1, equalKey2)
XCTAssertEqual(equalKey1.hashValue, equalKey2.hashValue)
}
func testThatDefinitionKeysWithDifferentTypesAreNotEqual() {
let keyWithDifferentType1 = DefinitionKey(protocolType: Service.self, factoryType: F1.self, associatedTag: nil)
let keyWithDifferentType2 = DefinitionKey(protocolType: AnyObject.self, factoryType: F1.self, associatedTag: nil)
XCTAssertNotEqual(keyWithDifferentType1, keyWithDifferentType2)
XCTAssertNotEqual(keyWithDifferentType1.hashValue, keyWithDifferentType2.hashValue)
}
func testThatDefinitionKeysWithDifferentFactoriesAreNotEqual() {
let keyWithDifferentFactory1 = DefinitionKey(protocolType: Service.self, factoryType: F1.self, associatedTag: nil)
let keyWithDifferentFactory2 = DefinitionKey(protocolType: Service.self, factoryType: F2.self, associatedTag: nil)
XCTAssertNotEqual(keyWithDifferentFactory1, keyWithDifferentFactory2)
XCTAssertNotEqual(keyWithDifferentFactory1.hashValue, keyWithDifferentFactory2.hashValue)
}
func testThatDefinitionKeysWithDifferentTagsAreNotEqual() {
let keyWithDifferentTag1 = DefinitionKey(protocolType: Service.self, factoryType: F1.self, associatedTag: tag1)
let keyWithDifferentTag2 = DefinitionKey(protocolType: Service.self, factoryType: F1.self, associatedTag: tag2)
XCTAssertNotEqual(keyWithDifferentTag1, keyWithDifferentTag2)
XCTAssertNotEqual(keyWithDifferentTag1.hashValue, keyWithDifferentTag2.hashValue)
}
}
+96 -35
View File
@@ -1,9 +1,25 @@
//
// DipTests.swift
// DipTests
// Dip
//
// Created by Ilya Puchka on 04.11.15.
// Copyright © 2015 AliSoftware. All rights reserved.
// 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
@@ -13,17 +29,15 @@ protocol Service {
func getServiceName() -> String
}
class ServiceImp1: Service {
extension Service {
func getServiceName() -> String {
return "ServiceImp1"
return "\(self.dynamicType)"
}
}
class ServiceImp2: Service {
func getServiceName() -> String {
return "ServiceImp2"
}
}
class ServiceImp1: Service {}
class ServiceImp2: Service {}
class DipTests: XCTestCase {
@@ -39,7 +53,7 @@ class DipTests: XCTestCase {
container.register { ServiceImp1() as Service }
//when
let serviceInstance = container.resolve() as Service
let serviceInstance = try! container.resolve() as Service
//then
XCTAssertTrue(serviceInstance is ServiceImp1)
@@ -50,7 +64,7 @@ class DipTests: XCTestCase {
container.register(tag: "service") { ServiceImp1() as Service }
//when
let serviceInstance = container.resolve(tag: "service") as Service
let serviceInstance = try! container.resolve(tag: "service") as Service
//then
XCTAssertTrue(serviceInstance is ServiceImp1)
@@ -62,8 +76,8 @@ class DipTests: XCTestCase {
container.register(tag: "service2") { ServiceImp2() as Service }
//when
let service1Instance = container.resolve(tag: "service1") as Service
let service2Instance = container.resolve(tag: "service2") as Service
let service1Instance = try! container.resolve(tag: "service1") as Service
let service2Instance = try! container.resolve(tag: "service2") as Service
//then
XCTAssertTrue(service1Instance is ServiceImp1)
@@ -73,39 +87,86 @@ class DipTests: XCTestCase {
func testThatNewRegistrationOverridesPreviousRegistration() {
//given
container.register { ServiceImp1() as Service }
let service1 = container.resolve() as Service
let service1 = try! container.resolve() as Service
//when
container.register { ServiceImp2() as Service }
let service2 = container.resolve() as Service
let service2 = try! container.resolve() as Service
//then
XCTAssertTrue(service1 is ServiceImp1)
XCTAssertTrue(service2 is ServiceImp2)
}
func testThatItResolvesTypeAsNewInstanceEveryTime() {
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 DipError.DefinitionNotFound(let key) {
typealias F = ()->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 DipError.DefinitionNotFound(let key) {
typealias F = ()->Service
let expectedKey = DefinitionKey(protocolType: Service.self, factoryType: F.self, associatedTag: "other tag")
XCTAssertEqual(key, expectedKey)
}
catch {
XCTFail("Thrown unexpected error")
}
}
func testThatItThrowsErrorIfCanNotFindDefinitionForFactory() {
//given
container.register { ServiceImp1() as Service }
//when
let service1 = container.resolve() as Service
let service2 = container.resolve() as Service
//then
XCTAssertFalse((service1 as! ServiceImp1) === (service2 as! ServiceImp1))
}
func testThatItReusesInstanceRegisteredAsSingleton() {
//given
container.register(instance: ServiceImp1() as Service)
//when
let service1 = container.resolve() as Service
let service2 = container.resolve() as Service
//then
XCTAssertTrue((service1 as! ServiceImp1) === (service2 as! ServiceImp1))
do {
try container.resolve(withArguments: "some string") as Service
XCTFail("Unexpectedly resolved protocol")
}
catch DipError.DefinitionNotFound(let key) {
typealias F = (String)->Service
let expectedKey = DefinitionKey(protocolType: Service.self, factoryType: F.self, associatedTag: nil)
XCTAssertEqual(key, expectedKey)
}
catch {
XCTFail("Thrown unexpected error")
}
}
}
+42 -26
View File
@@ -1,9 +1,25 @@
//
// RuntimeArgumentsTests.swift
// DipTests
// Dip
//
// Created by Ilya Puchka on 04.11.15.
// Copyright © 2015 AliSoftware. All rights reserved.
// 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
@@ -34,13 +50,13 @@ class RuntimeArgumentsTests: XCTestCase {
func testThatItResolvesInstanceWithOneArgument() {
//given
let arg1 = 1
container.register { (a1: Int) -> Service in
container.register(factory: { (a1: Int) -> Service in
XCTAssertEqual(a1, arg1)
return ServiceImp1()
}
})
//when
let service = container.resolve(arg1) as Service
let service = try! container.resolve(withArguments: arg1) as Service
//then
XCTAssertTrue(service is ServiceImp1)
@@ -56,7 +72,7 @@ class RuntimeArgumentsTests: XCTestCase {
}
//when
let service = container.resolve(arg1, arg2) as Service
let service = try! container.resolve(withArguments: arg1, arg2) as Service
//then
XCTAssertTrue(service is ServiceImp1)
@@ -72,7 +88,7 @@ class RuntimeArgumentsTests: XCTestCase {
}
//when
let service = container.resolve(arg1, arg2, arg3) as Service
let service = try! container.resolve(withArguments: arg1, arg2, arg3) as Service
//then
XCTAssertTrue(service is ServiceImp1)
@@ -89,7 +105,7 @@ class RuntimeArgumentsTests: XCTestCase {
}
//when
let service = container.resolve(arg1, arg2, arg3, arg4) as Service
let service = try! container.resolve(withArguments: arg1, arg2, arg3, arg4) as Service
//then
XCTAssertTrue(service is ServiceImp1)
@@ -107,7 +123,7 @@ class RuntimeArgumentsTests: XCTestCase {
}
//when
let service = container.resolve(arg1, arg2, arg3, arg4, arg5) as Service
let service = try! container.resolve(withArguments: arg1, arg2, arg3, arg4, arg5) as Service
//then
XCTAssertTrue(service is ServiceImp1)
@@ -126,7 +142,7 @@ class RuntimeArgumentsTests: XCTestCase {
}
//when
let service = container.resolve(arg1, arg2, arg3, arg4, arg5, arg6) as Service
let service = try! container.resolve(withArguments: arg1, arg2, arg3, arg4, arg5, arg6) as Service
//then
XCTAssertTrue(service is ServiceImp1)
@@ -139,8 +155,8 @@ class RuntimeArgumentsTests: XCTestCase {
container.register { (a1: Int, a2: Int) in ServiceImp2() as Service }
//when
let service1 = container.resolve(arg1) as Service
let service2 = container.resolve(arg1, arg2) as Service
let service1 = try! container.resolve(withArguments: arg1) as Service
let service2 = try! container.resolve(withArguments: arg1, arg2) as Service
//then
XCTAssertTrue(service1 is ServiceImp1)
@@ -150,12 +166,12 @@ class RuntimeArgumentsTests: XCTestCase {
func testThatItRegistersDifferentFactoriesForDifferentTypesOfArguments() {
//given
let arg1 = 1, arg2 = "string"
container.register { (a1: Int) in ServiceImp1() as Service }
container.register { (a1: String) in ServiceImp2() as Service }
container.register(factory: { (a1: Int) in ServiceImp1() as Service })
container.register(factory: { (a1: String) in ServiceImp2() as Service })
//when
let service1 = container.resolve(arg1) as Service
let service2 = container.resolve(arg2) as Service
let service1 = try! container.resolve(withArguments: arg1) as Service
let service2 = try! container.resolve(withArguments: arg2) as Service
//then
XCTAssertTrue(service1 is ServiceImp1)
@@ -169,8 +185,8 @@ class RuntimeArgumentsTests: XCTestCase {
container.register { (a1: String, a2: Int) in ServiceImp2() as Service }
//when
let service1 = container.resolve(arg1, arg2) as Service
let service2 = container.resolve(arg2, arg1) as Service
let service1 = try! container.resolve(withArguments: arg1, arg2) as Service
let service2 = try! container.resolve(withArguments: arg2, arg1) as Service
//then
XCTAssertTrue(service1 is ServiceImp1)
@@ -181,11 +197,11 @@ class RuntimeArgumentsTests: XCTestCase {
//given
let arg1 = 1, arg2 = 2
container.register { (a1: Int, a2: Int) in ServiceImp1() as Service }
let service1 = container.resolve(arg1, arg2) as Service
let service1 = try! container.resolve(withArguments: arg1, arg2) as Service
//when
container.register { (a1: Int, a2: Int) in ServiceImp2() as Service }
let service2 = container.resolve(arg1, arg2) as Service
let service2 = try! container.resolve(withArguments: arg1, arg2) as Service
//then
XCTAssertTrue(service1 is ServiceImp1)
@@ -201,11 +217,11 @@ class RuntimeArgumentsTests: XCTestCase {
//when
let url: NSURL = NSURL(string: "http://example.com")!
let service1 = container.resolve(80, url) as Service
let service2 = container.resolve(80, NSURL(string: "http://example.com")) as Service
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 = container.resolve(80, NSURL(string: "http://example.com")! as NSURL!) as Service
let service4 = container.resolve(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
//then
XCTAssertEqual(service1.getServiceName(), name1)
@@ -0,0 +1,106 @@
//: [Previous: Scopes](@previous)
import Dip
let container = DependencyContainer()
/*:
### Circular Dependencies
Very often we encounter situations when we have circular dependencies between components. The most obvious example is delegation pattern. Dip can resolve such dependencies easily.
Let's say you have some network client and it's delegate defined like this:
*/
protocol NetworkClientDelegate: class {
var networkClient: NetworkClient { get }
}
protocol NetworkClient: class {
weak var delegate: NetworkClientDelegate? { get set }
}
class NetworkClientImp: NetworkClient {
weak var delegate: NetworkClientDelegate?
init() {}
}
class Interactor: NetworkClientDelegate {
let networkClient: NetworkClient
init(networkClient: NetworkClient) {
self.networkClient = networkClient
}
}
/*:
Note that:
- one of this classes uses _property injection_ (`NetworkClientImp`) you'll give the `delegate` value via its property directly, _after_ initialization
- and another uses _constructor injection_ (`Interactor`) you'll need to give the `networkclient` value via the constructor, _during_ initialization.
It's very important that _at least one_ of them uses property injection, because if you try to use constructor injection for both of them then you will enter infinite loop when you will call `resolve`.
Now you can register those classes in container:
*/
container.register(.ObjectGraph) {
Interactor(networkClient: try! container.resolve()) as NetworkClientDelegate
}
container.register(.ObjectGraph) { NetworkClientImp() as NetworkClient }
.resolveDependencies { (container, client) -> () in
client.delegate = try! container.resolve() as NetworkClientDelegate
}
/*:
Here you can spot the difference in the way we register classes.
- `Interactor` class uses constructor injection, so to register it we use the block factory where we call `resolve` to obtain instance of `NetworkClient` and pass it to constructor.
- `NetworkClientImp` uses property injection for it's delegate property. Again we use block factory to create instance, but to inject the delegate property we use the special `resolveDependencies` method. Block passed to this method will be called right _after_ the block factory. So you can use this block to perform additional setup or, like in this example, to resolve circular dependencies.
This way `DependencyContainer` breaks infinite recursion that would happen if we used constructor injection for both of our components.
*Note*: You can use container reference inside instance factory without using capture list, there will be [no retain cycle](https://github.com/AliSoftware/Dip/issues/23)
Now when you resolve `NetworkClientDelegate` you will get an instance of `Interactor` that will have client with delegate referencing the same `Interactor` instance:
*/
let interactor = try! container.resolve() as NetworkClientDelegate
interactor.networkClient.delegate === interactor // true: they are the same instances
/*:
**Warning**: Note that one of the properties (`delegate`) is defined as _weak_. That's crucial to avoid retain cycle. But now if you try to resolve `NetworkClient` first it's delegate will be released before `resolve` returns, bcause no one holds a reference to it except the container.
*/
let networkClient = try! container.resolve() as NetworkClient
networkClient.delegate // delegate was alread released =(
/*:
Note also that we used `.ObjectGraph` scope to register implementations. This is also very important to preserve consistency of objects relationships.
If we would have used `.Prototype` scope for both components then container would not reuse instances and we would have an infinite loop:
- Each attempt to resolve `NetworkClientDelegate` will create new instance of `Interactor`.
- It will resolve `NetworkClient` which will create new instance of `NetworkClientImp`.
- It will try to resolve it's delegate property and that will create new instance of `Interactor`
- And so on and so on.
If we would have used `.Prototype` for one of the components it will lead to the same infinite loop or one of the relationships will be invalid:
*/
container.reset()
container.register(.Prototype) {
Interactor(networkClient: try! container.resolve()) as NetworkClientDelegate
}
container.register(.ObjectGraph) { NetworkClientImp() as NetworkClient }
.resolveDependencies { (container, client) -> () in
client.delegate = try! container.resolve() as NetworkClientDelegate
}
let invalidInteractor = try! container.resolve() as NetworkClientDelegate
invalidInteractor.networkClient.delegate // that is not valid
//: [Next: Shared Instances](@next)
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Timeline
version = "3.0">
<TimelineItems>
</TimelineItems>
</Timeline>
@@ -39,6 +39,13 @@ container.register(tag: DependencyContainer.Tag.Int(0)) { ServiceImp1() as Servi
/*:
We recommand you to use constants for the tags, to make the intent clear and avoid magic numbers and typos.
You can remove all registered definitions or register and remove them one by one:
*/
let serviceDefinition = container.register { ServiceImp1() as Service }
container.remove(serviceDefinition)
container.reset()
//: [Next: Resolving Components](@next)
@@ -12,7 +12,7 @@ let container = DependencyContainer { container in
You resolve previously registered definition using `resolve` method:
*/
var service = container.resolve() as Service
var service = try! container.resolve() as Service
/*:
That code says that you want your `container` to give you an instance that was registered as implementation of `Service` protocol.
@@ -20,7 +20,7 @@ That code says that you want your `container` to give you an instance that was r
It's important to specify the same type that you used for registration. You can use either `as` syntax, or specify type of you variable when you define it:
*/
let otherService: Service = container.resolve()
let otherService: Service = try! container.resolve()
/*:
Both ways will let the `container` detect the type that you want to resolve as. We prefer the `as` syntax because it reads more naturally in Swift.
@@ -32,16 +32,16 @@ container.register(tag: "production") { ServiceImp1() as Service }
container.register(tag: "test") { ServiceImp2() as Service }
// Will give you a ServiceImp1 instance
let productionService = container.resolve(tag: "production") as Service
let productionService = try! container.resolve(tag: "production") as Service
// Will give you a ServiceImp2 instance
let testService = container.resolve(tag: "test") as Service
let testService = try! container.resolve(tag: "test") as Service
// Will give you a ServiceImp1 because one was registered without a tag on line 4
let defaultService = container.resolve() as Service
let defaultService = try! container.resolve() as Service
/*:
You can use runtime arguments to resolve components. Dip supports up to six arguments. For more details see ["Runtime arguments"](Runtime%20arguments).
*/
container.register { service in ClientImp1(service: service) as Client }
let client = container.resolve(service) as Client
let client = try! container.resolve(withArguments: service) as Client
//: [Next: Runtime Arguments](@next)
@@ -12,21 +12,21 @@ Dip lets you use runtime arguments to register and resolve your components.
Note that __types__, __number__ and __order__ of arguments matters and you can register different factories with different set of runtime arguments for the same protocol. To resolve using one of this factory you will need to pass runtime arguments of the same types, number and in the same order to `resolve` as you used in `register` method.
*/
container.register { (url: NSURL, port: Int) in ServiceImp3(name: "1", baseURL: url, port: port) as Service }
container.register { (port: Int, url: NSURL) in ServiceImp3(name: "2", baseURL: url, port: port) as Service }
container.register { (port: Int, url: NSURL?) in ServiceImp3(name: "3", baseURL: url!, port: port) as Service }
container.register { (port: Int, url: NSURL!) in ServiceImp3(name: "4", baseURL: url, port: port) as Service }
container.register { (url: NSURL, port: Int) in ServiceImp4(name: "1", baseURL: url, port: port) as Service }
container.register { (port: Int, url: NSURL) in ServiceImp4(name: "2", baseURL: url, port: port) as Service }
container.register { (port: Int, url: NSURL?) in ServiceImp4(name: "3", baseURL: url!, port: port) as Service }
container.register { (port: Int, url: NSURL!) in ServiceImp4(name: "4", baseURL: url, port: port) as Service }
let url: NSURL = NSURL(string: "http://example.com")!
let service1 = container.resolve(url, 80) as Service
let service2 = container.resolve(80, url) as Service
let service3 = container.resolve(80, NSURL(string: "http://example.com")) as Service
let service4 = container.resolve(80, NSURL(string: "http://example.com")! as NSURL!) as Service
let service1 = try! container.resolve(withArguments: url, 80) as Service
let service2 = try! container.resolve(withArguments: 80, url) as Service
let service3 = try! container.resolve(withArguments: 80, NSURL(string: "http://example.com")) as Service
let service4 = try! container.resolve(withArguments: 80, NSURL(string: "http://example.com")! as NSURL!) as Service
(service1 as! ServiceImp3).name
(service2 as! ServiceImp3).name
(service3 as! ServiceImp3).name
(service4 as! ServiceImp3).name
(service1 as! ServiceImp4).name
(service2 as! ServiceImp4).name
(service3 as! ServiceImp4).name
(service4 as! ServiceImp4).name
/*:
Note that all of the services were resolved using different factories.
@@ -35,12 +35,12 @@ _Dip_ supports up to six runtime arguments. If that is not enougth you can exten
*/
extension DependencyContainer {
public func register<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6>(tag tag: Tag? = nil, factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> T) -> DefinitionOf<T> {
return register(tag: tag, factory: factory, scope: .Prototype) as DefinitionOf<T>
public func register<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> T> {
return registerFactory(tag: tag, scope: scope, factory: factory) as DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> T>
}
public func resolve<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6>(tag tag: Tag? = nil, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6) -> T {
return resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> T) in factory(arg1, arg2, arg3, arg4, arg5, arg6) }
public func resolve<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6>(tag tag: Tag? = nil, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6) throws -> T {
return try resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> T) in factory(arg1, arg2, arg3, arg4, arg5, arg6) }
}
}
@@ -2,30 +2,46 @@
import Dip
let container = DependencyContainer()
/*:
### Scopes
Dip supports two different scopes of objects: _Prototype_ and _Singleton_.
Dip supports three different scopes of objects: _Prototype_, _ObjectGraph_ and _Singleton_.
* The `.Prototype` scope will make the `DependencyContainer` resolve your type as __a new instance every time__ you call `resolve`.
* The `.Singleton` scope will make the `DependencyContainer` retain the instance once resolved the first time, and reuse it in the next calls during the container lifetime.
* The `.Prototype` scope will make the `DependencyContainer` resolve your type as __a new instance every time__ you call `resolve`. This is the default scope.
* The `.ObjectGraph` scope is like `.Prototype` scope, but it will make the `DependencyContainer` to reuse resolved instances during one (recursive) call to `resolve` method. When this call returns, all resolved instances will be discarded and next call to `resolve` will produce new instances. This scope should be used to resolve [circular dependencies](Circular%20dependencies).
* The `.Singleton` scope will make the `DependencyContainer` retain the instance once resolved the first time, and reuse it in the next calls to `resolve` during the container lifetime.
The `.Prototype` scope is the default. To register a singleton, use `register(tag:instance:)`
The `.Prototype` scope is the default. To set a scope you pass it as an argument to `register` method.
*/
let container = DependencyContainer { container in
container.register(tag:"sharedService", instance: ServiceImp1() as Service)
container.register { ServiceImp1() as Service }
}
container.register { ServiceImp1() as Service }
container.register(tag: "prototype", .Prototype) { ServiceImp1() as Service }
container.register(tag: "object graph", .ObjectGraph) { ServiceImp2() as Service }
container.register(tag: "shared instance", .Singleton) { ServiceImp3() as Service }
let sharedService = container.resolve(tag: "sharedService") as Service
let sameSharedService = container.resolve(tag: "sharedService") as Service
sharedService as! ServiceImp1 === sameSharedService as! ServiceImp1
let service = try! container.resolve() as Service
let anotherService = try! container.resolve() as Service
// They are different instances as the scope defaults to .Prototype
service as! ServiceImp1 === anotherService as! ServiceImp1 // false
let service = container.resolve() as Service
let anotherService = container.resolve() as Service
service as! ServiceImp1 === anotherService as! ServiceImp1
let prototypeService = try! container.resolve(tag: "prototype") as Service
let anotherPrototypeService = try! container.resolve(tag: "prototype") as Service
// They are different instances:
prototypeService as! ServiceImp1 === anotherPrototypeService as! ServiceImp1 // false
//: [Next: Shared Instances](@next)
let graphService = try! container.resolve(tag: "object graph") as Service
let anotherGraphService = try! container.resolve(tag: "object graph") as Service
// still different instances the ObjectGraph scope only keep instances during one (recursive) resolution call,
// so the two calls on the two lines above are different calls and use different instances
graphService as! ServiceImp2 === anotherGraphService as! ServiceImp2 // false
let sharedService = try! container.resolve(tag: "shared instance") as Service
let sameSharedService = try! container.resolve(tag: "shared instance") as Service
// same instances, the singleton scope keep and reuse instances during the lifetime of the container
sharedService as! ServiceImp3 === sameSharedService as! ServiceImp3
//: [Next: Circular Dependencies](@next)
@@ -1,4 +1,4 @@
//: [Previous: Scopes](@previous)
//: [Previous: Circular Dependencies](@previous)
import Dip
import UIKit
@@ -20,6 +20,8 @@ Dip supports singletons, but it reduces cost of using them. Their singleton natu
- You can easyly change concrete implementations without the rest of your system even notice that something changed.
- Also it's easy to test - you just register another object in your tests. Even if you still want to use a singleton in your system.
Those features you got when using Dip limits tight coupling in your code and gives you back your code flexibility.
Probably the most common example is using a singleton in the network layer or "API client".
*/
@@ -43,7 +45,7 @@ Sure, this is very easy to code indeed. And nothing bad so far.
But probably if you wrote a unit test or integration test for that code first, you would have noticed a problem earilier. How you test that code? And how you ensure that your tests are idenpendent of the API client's state from the previous test?
Of cource you can work around all of the problems and the fact that `ApiClient` is a singleton, reset it's state somehow, or mock a class so that it will not return a singleton instance. But look - a moment before the singleton was your best friend and now you are fighting against it.
Think - why do you want API client to be a singleton in a first place? To queue or throttle requests? Then do your queue or throttler a singleton, not an API client. Or is there any other reason. Most likely API client itself does not have a requirement to have only one system during the whole lifecycle of your application. Imagine that in the future we need two API Clients, because you now have to address two different servers & plaforms? Imposing that singleton restricts now your flexibility a lot.
Think - why do you want API client to be a singleton in a first place? To queue or throttle requests? Then do your queue or throttler a singleton, not an API client. Or is there any other reason. Most likely API client itself does not have a requirement to have one and only one instance during the lifecycle of your application. Imagine that in the future we need two API Clients, because you now have to address two different servers & plaforms? Imposing that singleton restricts now your flexibility a lot.
Instead, inject API client in view controller with property injection or constructor injection.
*/
@@ -110,7 +112,7 @@ class DipViewController: UIViewController {
convenience init(dependencies: DependencyContainer) {
self.init()
self.apiClient = dependencies.resolve() as ApiClientProtocol
self.apiClient = try! dependencies.resolve() as ApiClientProtocol
}
init() {
@@ -125,7 +127,7 @@ class DipViewController: UIViewController {
var dipController = DipViewController(dependencies: container)
/*:
Of cource `DependencyContainer` should not be used as singleton too. Instead, inject it to objects that need to access it. And use a protocol for that. For example if your view controller needs to access API client, it does not need a reference to `DependencyContainer`, it only needs a reference to _something_ that can provide it an API client instance.
Of cource `DependencyContainer` should not be a singleton too. Instead, inject it to objects that need to access it. And use a protocol for that. For example if your view controller needs to access API client, it does not need a reference to `DependencyContainer`, it only needs a reference to _something_ that can provide it an API client instance.
*/
protocol ApiClientProvider {
@@ -134,7 +136,7 @@ protocol ApiClientProvider {
extension DependencyContainer: ApiClientProvider {
func apiClient() -> ApiClientProtocol {
return self.resolve() as ApiClientProtocol
return try! self.resolve() as ApiClientProtocol
}
}
@@ -150,7 +152,7 @@ dipController = DipViewController(apiClientProvider: container)
/*:
This way you also does not depend directly on Dip. Instead you provide a boundary between Dip that you don't have control of and your source code. So when something chagnes in Dip, you update only the boundary code.
Dependency injection is a pattern as well as singleton. And any pattern can be abused. That's why if you adopt DI in one part of your system it does not mean that you should inject everything and everywhere. The same with using protocols instead of concrete implementations. For every tool there is a right time and the same way as singleton can harm you the same way DI and protocols abuse can make your code unnececerry complex.
Dependency injection is a pattern as well as singleton. And any pattern can be abused. DI can be use in a [wrong way]((http://www.loosecouplings.com/2011/01/dependency-injection-using-di-container.html)), container can easily become a [service locator](http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/). That's why if you adopt DI in one part of your system it does not mean that you should inject everything and everywhere. The same with using protocols instead of concrete implementations. For every tool there is a right time and the same way as singleton can harm you the same way DI and protocols abuse can make your code unnececerry complex.
*/
@@ -57,7 +57,7 @@ Register fake implementation as `Service`:
func testThatDoSomethingIsCalled() {
let sut = Client()
sut.service = container.resolve() as Service
sut.service = try! container.resolve() as Service
sut.callService()
+10 -2
View File
@@ -8,8 +8,11 @@ public class ServiceImp1: Service {
public class ServiceImp2: Service {
public init() {}
}
public class ServiceImp3: Service {
public init() {}
}
public class ServiceImp4: Service {
public let name: String
@@ -19,7 +22,7 @@ public class ServiceImp3: Service {
}
public protocol Client {
public protocol Client: class {
var service: Service {get}
init(service: Service)
}
@@ -45,3 +48,8 @@ public class ServiceFactory {
}
}
public class ClientServiceImp: Service {
public weak var client: Client?
public init() {}
}
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='6.0' target-platform='ios' display-mode='rendered'>
<playground version='6.0' target-platform='ios' display-mode='raw'>
<pages>
<page name='What is Dip?'/>
<page name='Creating container'/>
@@ -7,6 +7,7 @@
<page name='Resolving components'/>
<page name='Runtime arguments'/>
<page name='Scopes'/>
<page name='Circular dependencies'/>
<page name='Shared Instances'/>
<page name='Testing'/>
</pages>
+71 -28
View File
@@ -54,13 +54,15 @@ The next paragraphs give you an overview of the Usage of _Dip_ directly, but if
## Usage
### Register instances and instance factories
*See [CHANGELOG.md](https://github.com/AliSoftware/Dip/blob/master/CHANGELOG.md) for instructions to migrate from 2.0.0 to 3.0.0*
First, create a `DependencyContainer` and use it to register instances and factories with protocols, using those methods:
### Register instance factories
* `register(instance: _)` will register a singleton instance with a given protocol.
* `register(factory: _)` will register an instance factory — which generates a new instance each time you `resolve()`.
* You need **cast the instance to the protocol type** you want to register it with (e.g. `register(instance: PlistUsersProvider() as UsersListProviderType)`).
First, create a `DependencyContainer` and use it to register instance factories with protocols, using those methods:
* `register(.Singleton) { … }` will register a singleton instance with a given protocol.
* `register(.Prototype) { … }` or `register(.ObjectGraph) { … }` will register an instance factory which generates a new instance each time you `resolve()`.
* You need **cast the instance to the protocol type** you want to register it with (e.g. `register { PlistUsersProvider() as UsersListProviderType }`).
Typically, to register your dependencies as early as possible in your app life-cycle, you will declare a `let dip: DependencyContainer = { … }()` somewhere (for example [in a dedicated `.swift` file](https://github.com/AliSoftware/Dip/blob/master/Example/DipSampleApp/DependencyContainers.swift#L22-L27)). In your (non-hosted, standalone) unit tests, you'll probably [reset them in your `func setUp()`](https://github.com/AliSoftware/Dip/blob/master/Example/Tests/SWAPIPersonProviderTests.swift#L17-L21) instead.
@@ -68,7 +70,20 @@ Typically, to register your dependencies as early as possible in your app life-c
* `resolve()` will return a new instance matching the requested protocol.
* Explicitly specify the return type of `resolve` so that Swift's type inference knows which protocol you're trying to resolve.
* If that protocol was registered as a singleton instance (using `register(instance: …)`, the same instance will be returned each time you call `resolve()` for this protocol type. Otherwise, the instance factory will generate a new instance each time.
```swift
container.register { ServiceImp() as Service }
let service = try! container.resolve() as Service
```
### Scopes
Dip provides three _scopes_ that you can use to register dependencies:
* The `.Prototype` scope will make the `DependencyContainer` resolve your type as __a new instance every time__ you call `resolve`. It's a default scope.
* The `.ObjectGraph` scope is like `.Prototype` scope but it will make the `DependencyContainer` to reuse resolved instances during one call to `resolve` method. When this call returns all resolved insances will be discarded and next call to `resolve` will produce new instances. This scope should be used to resolve circular dependencies.
* The `.Singleton` scope will make the `DependencyContainer` retain the instance once resolved the first time, and reuse it in the next calls to `resolve` during the container lifetime.
### Using block-based initialization
@@ -80,8 +95,8 @@ It may not seem to provide much, but given the fact that `DependencyContainers`
let dip: DependencyContainer = {
let dip = DependencyContainer()
dip.register(instance: ProductionEnvironment(analytics: true) as EnvironmentType)
dip.register(instance: WebService() as WebServiceAPI)
dip.register { ProductionEnvironment(analytics: true) as EnvironmentType }
dip.register { WebService() as WebServiceAPI }
return dip
}()
@@ -91,8 +106,8 @@ You can instead write this exact equivalent code, which is more compact, and ind
```swift
let dip = DependencyContainer { dip in
dip.register(instance: ProductionEnvironment(analytics: true) as EnvironmentType)
dip.register(instance: WebService() as WebServiceAPI)
dip.register { ProductionEnvironment(analytics: true) as EnvironmentType }
dip.register { WebService() as WebServiceAPI }
}
```
@@ -111,11 +126,11 @@ enum WebService: String {
}
let wsDependencies = DependencyContainer() { dip in
dip.register(tag: WebService.PersonWS.tag, instance: URLSessionNetworkLayer(baseURL: "http://prod.myapi.com/api/")! as NetworkLayer)
dip.register(tag: WebService.StashipWS.tag, instance: URLSessionNetworkLayer(baseURL: "http://dev.myapi.com/api/")! as NetworkLayer)
dip.register(tag: WebService.PersonWS.tag) { URLSessionNetworkLayer(baseURL: "http://prod.myapi.com/api/")! as NetworkLayer }
dip.register(tag: WebService.StashipWS.tag) { URLSessionNetworkLayer(baseURL: "http://dev.myapi.com/api/")! as NetworkLayer }
}
let networkLayer = dip.resolve(tag: WebService.PersonWS.tag) as NetworkLayer
let networkLayer = try! dip.resolve(tag: WebService.PersonWS.tag) as NetworkLayer
```
### Runtime arguments
@@ -129,24 +144,52 @@ let webServices = DependencyContainer() { webServices in
webServices.register { (port: Int, url: NSURL?) in WebServiceImp3(url!, port: port) as WebServiceAPI }
}
let service1 = webServices.resolve(NSURL(string: "http://example.url")!, 80) as WebServiceAPI // service1 is WebServiceImp1
let service2 = webServices.resolve(80, NSURL(string: "http://example.url")!) as WebServiceAPI // service2 is WebServiceImp2
let service3 = webServices.resolve(80, NSURL(string: "http://example.url")) as WebServiceAPI // service3 is WebServiceImp3
let service1 = try! webServices.resolve(withArguments: NSURL(string: "http://example.url")!, 80) as WebServiceAPI // service1 is WebServiceImp1
let service2 = try! webServices.resolve(withArguments: 80, NSURL(string: "http://example.url")!) as WebServiceAPI // service2 is WebServiceImp2
let service3 = try! webServices.resolve(withArguments: 80, NSURL(string: "http://example.url")) as WebServiceAPI // service3 is WebServiceImp3
```
Though Dip provides support for up to six runtime arguments out of the box you can extend this number using following code snippet for seven arguments:
```
func register<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7>(tag: Tag? = nil, factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7) -> T) -> DefinitionOf<T> {
return register(tag, factory: factory, scope: .Prototype) as DefinitionOf<T>
func register<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7>(tag: Tag? = nil, scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7) -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7) -> T)> {
return registerFactory(tag, scope: .Prototype, factory: factory) as DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7) -> T)>
}
func resolve<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7>(tag tag: Tag? = nil, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7) -> T {
return resolve(tag) { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7) -> T) in factory(arg1, arg2, arg3, arg4, arg5, arg6, arg7) }
func resolve<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7>(tag tag: Tag? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7) throws -> T {
return try resolve(tag) { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7) -> T) in factory(arg1, arg2, arg3, arg4, arg5, arg6, arg7) }
}
```
### Circular dependencies
_Dip_ supports circular dependencies. To resolve them use `ObjectGraph` scope and `resolveDependencies` method of `DefinitionOf` returned by `register` method.
```swift
container.register(.ObjectGraph) {
ClientImp(server: try! container.resolve() as Server) as Client
}
container.register(.ObjectGraph) { ServerImp() as Server }
.resolveDependencies { container, server in
server.client = try! container.resolve() as Client
}
```
More infromation about circular dependencies you can find in a playground.
### Thread safety
_Dip_ does not provide thread safety, so you need to make sure you always call `resolve` method of `DependencyContainer` from the single thread.
Otherwise if two threads try to resolve the same type they can get different instances where the same instance is expected.
### Errors
The resolve operation is potentially dangerous because you can use the wrong type, factory or a wrong tag. For that reason Dip throws an error
`DefinitionNotFond(DefinitionKey)` if it failed to resolve type. When calling `resolve` you need to use a `try` operator.
There are rare use cases where your application can recover from this kind of errors (for example you can register new types
when user unlocks some content). In most of the cases you can use `try!` to cause an exception at runtime if error was thrown
or `try?` if it is appropriate in your case to have `nil`. This way `try!` serves as an additional mark for developers that resolution can fail.
### Concrete Example
Somewhere in your App target, register the dependencies:
@@ -155,10 +198,10 @@ Somewhere in your App target, register the dependencies:
let dip: DependencyContainer = {
let dip = DependencyContainer()
let env = ProductionEnvironment(analytics: true)
dip.register(instance: env as EnvironmentType)
dip.register(instance: WebService() as WebServiceType)
dip.register() { name: String in DummyFriendsProvider(user: name) as FriendsProviderType }
dip.register(tag: "me") { _: String in PlistFriendsProvider(plist: "myfriends") as FriendsProviderType }
dip.register(.Singleton) { env as EnvironmentType }
dip.register(.Singleton) { WebService() as WebServiceType }
dip.register() { (name: String) in DummyFriendsProvider(user: name) as FriendsProviderType }
dip.register(tag: "me") { (_: String) in PlistFriendsProvider(plist: "myfriends") as FriendsProviderType }
return dip
}
```
@@ -170,17 +213,17 @@ Then to use dependencies throughout your app, use `dip.resolve()`, like this:
```swift
struct WebService {
let env: EnvironmentType = dip.resolve()
let env: EnvironmentType = try! dip.resolve()
func sendRequest(path: String, ) {
// ... use stuff like env.baseURL here
}
}
struct SomeViewModel {
let ws: WebServiceType = dip.resolve()
let ws: WebServiceType = try! dip.resolve()
var friendsProvider: FriendsProviderType
init(userName: String) {
friendsProvider = dip.resolve(tag: userName, userName)
friendsProvider = try! dip.resolve(tag: userName, userName)
}
func foo() {
ws.someMethodDeclaredOnWebServiceType()
@@ -210,7 +253,7 @@ This sample uses the Star Wars API provided by swapi.co to fetch Star Wars chara
## Credits
This library has been created by [**Olivier Halligon**](olivier@halligon.net).
I'd also like to thank **Ilya Puchka** for his big contribution to it, as he added a lot of great features to it.
I'd also like to thank [**Ilya Puchka**](https://twitter.com/ilyapuchka) for his big contribution to it, as he added a lot of great features to it.
**Dip** is available under the **MIT license**. See the `LICENSE` file for more info.
@@ -22,7 +22,7 @@ private let FAKE_STARSHIPS = false
let wsDependencies = DependencyContainer() { dip in
// Register the NetworkLayer, same for everyone here (but we have the ability to register a different one for a specific WebService if we wanted to)
dip.register(instance: URLSessionNetworkLayer(baseURL: "http://swapi.co/api/")! as NetworkLayer)
dip.register(.Singleton) { URLSessionNetworkLayer(baseURL: "http://swapi.co/api/")! as NetworkLayer }
}
@@ -33,13 +33,13 @@ let providerDependencies = DependencyContainer() { dip in
if FAKE_PERSONS {
// 1) Register the PersonProviderAPI singleton, one generic and one specific for a specific personID
dip.register(instance: DummyPilotProvider() as PersonProviderAPI)
dip.register(tag: 0, instance: PlistPersonProvider(plist: "mainPilot") as PersonProviderAPI)
dip.register(.Singleton) { DummyPilotProvider() as PersonProviderAPI }
dip.register(tag: 0, .Singleton) { PlistPersonProvider(plist: "mainPilot") as PersonProviderAPI }
} else {
// 1) Register the SWAPIPersonProvider (that hits the real swapi.co WebService)
dip.register(instance: SWAPIPersonProvider() as PersonProviderAPI)
dip.register(.Singleton) { SWAPIPersonProvider() as PersonProviderAPI }
}
@@ -52,7 +52,7 @@ let providerDependencies = DependencyContainer() { dip in
} else {
// 2) Register the SWAPIStarshipProvider (that hits the real swapi.co WebService)
dip.register(instance: SWAPIStarshipProvider() as StarshipProviderAPI)
dip.register(.Singleton) { SWAPIStarshipProvider() as StarshipProviderAPI }
}
+2 -2
View File
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9059" systemVersion="15B42" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="Nnt-Mi-Wf8">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9060" systemVersion="15B42" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="Nnt-Mi-Wf8">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9049"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9051"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
</dependencies>
<scenes>
@@ -9,7 +9,7 @@
import Foundation
struct SWAPIPersonProvider : PersonProviderAPI {
let ws = wsDependencies.resolve(tag: WebService.PersonWS.tag) as NetworkLayer
let ws = try! wsDependencies.resolve(tag: WebService.PersonWS.tag) as NetworkLayer
func fetchIDs(completion: [Int] -> Void) {
ws.request("people") { response in
@@ -9,7 +9,7 @@
import Foundation
struct SWAPIStarshipProvider : StarshipProviderAPI {
let ws = wsDependencies.resolve(tag: WebService.StarshipWS.tag) as NetworkLayer
let ws = try! wsDependencies.resolve(tag: WebService.StarshipWS.tag) as NetworkLayer
func fetchIDs(completion: [Int] -> Void) {
ws.request("starships") { response in
@@ -13,11 +13,11 @@ class PersonListViewController: UITableViewController, FetchableTrait {
var batchRequestID = 0
func fetchIDs(completion: [Int] -> Void) {
let provider = providerDependencies.resolve() as PersonProviderAPI
let provider = try! providerDependencies.resolve() as PersonProviderAPI
return provider.fetchIDs(completion)
}
func fetchOne(personID: Int, completion: Person? -> Void) {
let provider = providerDependencies.resolve(tag: .Int(personID)) as PersonProviderAPI
let provider = try! providerDependencies.resolve(tag: .Int(personID)) as PersonProviderAPI
return provider.fetch(personID, completion: completion)
}
@@ -14,7 +14,7 @@ class StarshipListViewController : UITableViewController, FetchableTrait {
var batchRequestID = 0
private func provider(tag:Int?) -> StarshipProviderAPI {
return providerDependencies.resolve(tag: tag.flatMap { .Int($0) })
return try! providerDependencies.resolve(tag: tag.flatMap { .Int($0) })
}
func fetchIDs(completion: [Int] -> Void) {
@@ -23,7 +23,7 @@ class SWAPIPersonProviderTests: XCTestCase {
func testFetchPersonIDs() {
let mock = NetworkMock(json: ["results": [fakePerson1, fakePerson2]])
wsDependencies.register(tag: WebService.PersonWS.tag, instance: mock as NetworkLayer)
wsDependencies.register(tag: WebService.PersonWS.tag, .Singleton) { mock as NetworkLayer }
let provider = SWAPIPersonProvider()
provider.fetchIDs { personIDs in
@@ -38,7 +38,7 @@ class SWAPIPersonProviderTests: XCTestCase {
func testFetchOnePerson() {
let mock = NetworkMock(json: fakePerson1)
wsDependencies.register(tag: WebService.PersonWS.tag, instance: mock as NetworkLayer)
wsDependencies.register(tag: WebService.PersonWS.tag, .Singleton) { mock as NetworkLayer }
let provider = SWAPIPersonProvider()
provider.fetch(1) { person in
@@ -58,7 +58,7 @@ class SWAPIPersonProviderTests: XCTestCase {
func testFetchInvalidPerson() {
let json = ["error":"whoops"]
let mock = NetworkMock(json: json)
wsDependencies.register(tag: WebService.PersonWS.tag, instance: mock as NetworkLayer)
wsDependencies.register(tag: WebService.PersonWS.tag, .Singleton) { mock as NetworkLayer }
let provider = SWAPIPersonProvider()
provider.fetch(12) { person in
@@ -23,7 +23,7 @@ class SWAPIStarshipProviderTests: XCTestCase {
func testFetchStarshipIDs() {
let mock = NetworkMock(json: ["results": [fakeShip1, fakeShip2]])
wsDependencies.register(tag: WebService.StarshipWS.tag, instance: mock as NetworkLayer)
wsDependencies.register(tag: WebService.StarshipWS.tag, .Singleton) { mock as NetworkLayer }
let provider = SWAPIStarshipProvider()
provider.fetchIDs { shipIDs in
@@ -38,7 +38,7 @@ class SWAPIStarshipProviderTests: XCTestCase {
func testFetchOneStarship() {
let mock = NetworkMock(json: fakeShip1)
wsDependencies.register(tag: WebService.StarshipWS.tag, instance: mock as NetworkLayer)
wsDependencies.register(tag: WebService.StarshipWS.tag, .Singleton) { mock as NetworkLayer }
let provider = SWAPIStarshipProvider()
provider.fetch(1) { starship in
@@ -57,7 +57,7 @@ class SWAPIStarshipProviderTests: XCTestCase {
func testFetchInvalidStarship() {
let json = ["error":"whoops"]
let mock = NetworkMock(json: json)
wsDependencies.register(tag: WebService.StarshipWS.tag, instance: mock as NetworkLayer)
wsDependencies.register(tag: WebService.StarshipWS.tag, .Singleton) { mock as NetworkLayer }
let provider = SWAPIStarshipProvider()
provider.fetch(12) { starship in