Compare commits
123 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6656116e12 | |||
| 92499c9561 | |||
| 7c5ea83dc4 | |||
| 4793232d8c | |||
| 2c09b4d6d1 | |||
| 34e920a348 | |||
| 3357630deb | |||
| f23492ee40 | |||
| 328d1b98a5 | |||
| 9f77677014 | |||
| b13a38cf9e | |||
| edb0d3e576 | |||
| bd8a503085 | |||
| 6af8f18a63 | |||
| bcf2329312 | |||
| b01b7fb055 | |||
| 0515e7358d | |||
| 463f9a37a3 | |||
| 6c802d2c39 | |||
| 967a2342b4 | |||
| d7b3db1476 | |||
| f65949bafc | |||
| ba420c27a1 | |||
| ef66212ba3 | |||
| 1d71eb08ca | |||
| 689ff2ed29 | |||
| 126c5d3191 | |||
| 3b4dab4c97 | |||
| 7de69e1fa0 | |||
| a44d11b72f | |||
| 7352769a47 | |||
| 5be2995ef1 | |||
| 306a52f9d5 | |||
| 764024700f | |||
| ee6c9b6115 | |||
| da5cc8f1a7 | |||
| b7b586cba1 | |||
| b50691205a | |||
| d3d1f3cbf9 | |||
| 8d4c71fe5a | |||
| 591c1bfefc | |||
| 786b56b5ea | |||
| 4938023a60 | |||
| 0db1d2433d | |||
| fbb1bcc94f | |||
| f41d28382d | |||
| 7a242a7656 | |||
| 1c7e8419c7 | |||
| 03cf8f7b32 | |||
| a36efc1c5e | |||
| 19d1d38d63 | |||
| 2bae6bce60 | |||
| 66bca3d5b6 | |||
| 4dcdabc8ae | |||
| d8859fb44a | |||
| fef5eb10ac | |||
| fc337f0fe3 | |||
| d3759baff6 | |||
| bd45b97400 | |||
| 4479b58ace | |||
| 47043a213e | |||
| cc399031e7 | |||
| d50ca57d49 | |||
| 6f5ab994a0 | |||
| 33601418f3 | |||
| 691762242f | |||
| 345b6e164a | |||
| 4b0554b539 | |||
| d786eb17dd | |||
| d4d275cda6 | |||
| cdf9f43c9b | |||
| f8a4b40281 | |||
| 3803a72548 | |||
| 8f3fad759e | |||
| c6bf18181f | |||
| d6908e1feb | |||
| 288673ecb3 | |||
| 14c13d7f35 | |||
| 11a9c7fb70 | |||
| c670a220d5 | |||
| 049737dd2f | |||
| 79adaed6d3 | |||
| c7f9c82f81 | |||
| ad9c095041 | |||
| 409f0f1990 | |||
| 685809e6ec | |||
| dcea032624 | |||
| ace73a2672 | |||
| ff6645d012 | |||
| e3448d9148 | |||
| b14c4aeb10 | |||
| 0adc671bd1 | |||
| 1d7533a516 | |||
| 6ad4b82ec0 | |||
| 6bc96bc8af | |||
| 8b9879a9a2 | |||
| 406e47206a | |||
| 69e8eef0f1 | |||
| 78e789cbce | |||
| ebbe9a1513 | |||
| bc0511261f | |||
| 6a0fa295cb | |||
| 73d8990afa | |||
| d7d8f28a36 | |||
| e97883efb0 | |||
| 342fdf9c92 | |||
| 9778236416 | |||
| b50046c071 | |||
| 0c93e868f0 | |||
| ada995590f | |||
| c0e5df3443 | |||
| 37d42281c1 | |||
| 6c56e12eeb | |||
| 948b3cd780 | |||
| adde50a3d0 | |||
| 450a2f8a72 | |||
| 59719a0c37 | |||
| f6afdfd08e | |||
| abf2202eeb | |||
| 938108f2ab | |||
| 9d619d6a25 | |||
| 58efc586db | |||
| 3a2aecbe48 |
@@ -32,3 +32,7 @@ Carthage
|
||||
# `pod install` in .travis.yml
|
||||
#
|
||||
# Pods/
|
||||
|
||||
# SPM
|
||||
.build/
|
||||
Packages
|
||||
|
||||
+7
-3
@@ -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
|
||||
|
||||
@@ -1,5 +1,66 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 4.2.0
|
||||
|
||||
* Added support for Swift Package Manager.
|
||||
[#41](https://github.com/AliSoftware/Dip/pull/41), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
* Added Linux support.
|
||||
[#42](https://github.com/AliSoftware/Dip/pull/42), [#46](https://github.com/AliSoftware/Dip/pull/46), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
* Fixed the issue that could cause singleton instances to be reused between different containers.
|
||||
[#43](https://github.com/AliSoftware/Dip/pull/43), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
* Added public `AutoInjectedPropertyBox` protocol for user-defined auto-injected property wrappers.
|
||||
[#49](https://github.com/AliSoftware/Dip/pull/49), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
|
||||
|
||||
## 4.1.0
|
||||
|
||||
#### New features
|
||||
|
||||
* Added auto-injection feature.
|
||||
[#13](https://github.com/AliSoftware/Dip/pull/13), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
* Factories and `resolveDependencies` blocks of `DefinitionOf` are now allowed to `throw`. Improved errors handling.
|
||||
[#32](https://github.com/AliSoftware/Dip/pull/32), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
* Thread safety reimplemented with support for recursive methods calls.
|
||||
[#31](https://github.com/AliSoftware/Dip/pull/31), [@mwoollard](https://github.com/mwoollard)
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
+8
-4
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "Dip"
|
||||
s.version = "3.1.0"
|
||||
s.version = "4.2.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 = 'Sources/**/*.swift'
|
||||
end
|
||||
|
||||
+749
-164
File diff suppressed because it is too large
Load Diff
+11
-11
@@ -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,100 @@
|
||||
<?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"
|
||||
codeCoverageEnabled = "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>
|
||||
@@ -1,82 +0,0 @@
|
||||
//
|
||||
// Dip
|
||||
//
|
||||
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import 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?
|
||||
|
||||
var hashValue: Int {
|
||||
return "\(protocolType)-\(factoryType)-\(associatedTag)".hashValue
|
||||
}
|
||||
|
||||
var debugDescription: String {
|
||||
return "type: \(protocolType), factory: \(factoryType), tag: \(associatedTag)"
|
||||
}
|
||||
}
|
||||
|
||||
func ==(lhs: DefinitionKey, rhs: DefinitionKey) -> Bool {
|
||||
return
|
||||
lhs.protocolType == rhs.protocolType &&
|
||||
lhs.factoryType == rhs.factoryType &&
|
||||
lhs.associatedTag == rhs.associatedTag
|
||||
}
|
||||
|
||||
///Describes the lifecycle of instances created by container.
|
||||
public enum ComponentScope {
|
||||
/// Indicates that a new instance of the component will be created each time it's resolved.
|
||||
case Prototype
|
||||
/// 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
|
||||
|
||||
init(factory: Any, scope: ComponentScope = .Prototype) {
|
||||
self.factory = factory
|
||||
self.scope = scope
|
||||
}
|
||||
|
||||
var resolvedInstance: T? {
|
||||
get {
|
||||
guard scope == .Singleton else { return nil }
|
||||
return _resolvedInstance
|
||||
}
|
||||
set {
|
||||
guard scope == .Singleton else { return }
|
||||
_resolvedInstance = newValue
|
||||
}
|
||||
}
|
||||
|
||||
private var _resolvedInstance: T?
|
||||
}
|
||||
|
||||
///Dummy protocol to store definitions for different types in collection
|
||||
protocol Definition {}
|
||||
+20
-4
@@ -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>
|
||||
|
||||
@@ -1,240 +0,0 @@
|
||||
//
|
||||
// Dip
|
||||
//
|
||||
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// MARK: - DependencyContainer
|
||||
|
||||
/**
|
||||
* _Dip_'s Dependency Containers allow you to do very simple **Dependency Injection**
|
||||
* by associating `protocols` to concrete implementations
|
||||
*/
|
||||
public class DependencyContainer {
|
||||
|
||||
/**
|
||||
Use a tag in case you need to register multiple instances or factories
|
||||
with the same protocol, to differentiate them. Tags can be either String
|
||||
or Int, to your convenience.
|
||||
*/
|
||||
public enum Tag: Equatable {
|
||||
case String(StringLiteralType)
|
||||
case Int(IntegerLiteralType)
|
||||
}
|
||||
|
||||
private var dependencies = [DefinitionKey : Definition]()
|
||||
private var lock: OSSpinLock = OS_SPINLOCK_INIT
|
||||
|
||||
/**
|
||||
Designated initializer for a DependencyContainer
|
||||
|
||||
- parameter configBlock: A configuration block in which you typically put all you `register` calls.
|
||||
|
||||
- note: The `configBlock` is simply called at the end of the `init` to let you configure everything.
|
||||
It is only present for convenience to have a cleaner syntax when declaring and initializing
|
||||
your `DependencyContainer` instances.
|
||||
|
||||
- returns: A new DependencyContainer.
|
||||
*/
|
||||
public init(@noescape configBlock: (DependencyContainer->()) = { _ in }) {
|
||||
configBlock(self)
|
||||
}
|
||||
|
||||
// MARK: - Reset all dependencies
|
||||
|
||||
/**
|
||||
Clear all the previously registered dependencies on this container.
|
||||
*/
|
||||
public func reset() {
|
||||
lockAndDo {
|
||||
dependencies.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Register dependencies
|
||||
|
||||
/**
|
||||
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 factory: The factory to register, with return type of protocol you want to register it for
|
||||
|
||||
- note: You must cast the factory return type to the protocol you want to register it with (e.g `MyClass() as MyAPI`)
|
||||
*/
|
||||
public func register<T>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: ()->T) -> DefinitionOf<T> {
|
||||
return register(tag: tag, factory: factory, scope: scope) 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`)
|
||||
|
||||
**Deprecated**: Use `register(.Singleton){}` method instead to define singleton scope.
|
||||
*/
|
||||
|
||||
@available(*, deprecated, message="Use `register(.Singleton){}` method instead to define singleton scope.")
|
||||
public func register<T>(tag tag: Tag? = nil, @autoclosure(escaping) instance factory: ()->T) -> DefinitionOf<T> {
|
||||
return register(tag: tag, factory: { factory() }, scope: .Singleton)
|
||||
}
|
||||
|
||||
/**
|
||||
Register generic factory associated with optional tag.
|
||||
|
||||
- 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`
|
||||
|
||||
- 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>
|
||||
}
|
||||
```
|
||||
|
||||
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> {
|
||||
let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag)
|
||||
let definition = DefinitionOf<T>(factory: factory, scope: scope)
|
||||
lockAndDo {
|
||||
dependencies[key] = definition
|
||||
}
|
||||
return 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).
|
||||
|
||||
- parameter tag: The arbitrary tag to look for when resolving this protocol.
|
||||
*/
|
||||
public func resolve<T>(tag tag: Tag? = nil) -> T {
|
||||
return resolve(tag: tag) { (factory: ()->T) in factory() }
|
||||
}
|
||||
|
||||
/**
|
||||
Resolve a dependency using generic builder closure that accepts generic factory and returns created instance.
|
||||
|
||||
- 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
|
||||
|
||||
- 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, ...) }
|
||||
}
|
||||
```
|
||||
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
return resolved
|
||||
}
|
||||
|
||||
/// 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)")
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
extension DependencyContainer.Tag: IntegerLiteralConvertible {
|
||||
public init(integerLiteral value: IntegerLiteralType) {
|
||||
self = .Int(value)
|
||||
}
|
||||
}
|
||||
|
||||
extension DependencyContainer.Tag: StringLiteralConvertible {
|
||||
public typealias ExtendedGraphemeClusterLiteralType = StringLiteralType
|
||||
public typealias UnicodeScalarLiteralType = StringLiteralType
|
||||
|
||||
public init(stringLiteral value: StringLiteralType) {
|
||||
self = .String(value)
|
||||
}
|
||||
|
||||
public init(unicodeScalarLiteral value: UnicodeScalarLiteralType) {
|
||||
self.init(stringLiteral: value)
|
||||
}
|
||||
|
||||
public init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) {
|
||||
self.init(stringLiteral: value)
|
||||
}
|
||||
}
|
||||
|
||||
public func ==(lhs: DependencyContainer.Tag, rhs: DependencyContainer.Tag) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (.String(lhsString), .String(rhsString)):
|
||||
return lhsString == rhsString
|
||||
case let (.Int(lhsInt), .Int(rhsInt)):
|
||||
return lhsInt == rhsInt
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
extension Dictionary {
|
||||
subscript(key: Key?) -> Value! {
|
||||
guard let key = key else { return nil }
|
||||
return self[key]
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
//
|
||||
// Dip
|
||||
//
|
||||
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// MARK: - Register/resolve dependencies with runtime arguments
|
||||
|
||||
extension DependencyContainer {
|
||||
|
||||
// MARK: 1 Runtime Argument
|
||||
|
||||
/**
|
||||
Registers factory that accepts one runtime argument. You can use up to six runtime arguments.
|
||||
|
||||
- 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 factory: The factory to register, with return type of protocol you want to register it for
|
||||
|
||||
- note: You can have several factories with different number or types of arguments registered to for same type.
|
||||
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:)`
|
||||
*/
|
||||
public func register<T, Arg1>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1) -> T) -> DefinitionOf<T> {
|
||||
return register(tag: tag, factory: factory, scope: scope) as DefinitionOf<T>
|
||||
}
|
||||
|
||||
/**
|
||||
Resolve a dependency with runtime argument. Factories will be matched by tag and the type to resolve as well
|
||||
as __number__, __types__ and __order__ of runtime arguments that you pass to this method.
|
||||
|
||||
- parameter tag: The arbitrary tag to look for when resolving this protocol.
|
||||
- parameter arg1: First argument to be passed to factory
|
||||
|
||||
- seealso: `resolve(tag:)`
|
||||
*/
|
||||
public func resolve<T, Arg1>(tag tag: Tag? = nil, withArguments arg1: Arg1) -> T {
|
||||
return resolve(tag: tag) { (factory: (Arg1) -> T) in factory(arg1) }
|
||||
}
|
||||
|
||||
///**Deprecated** Use `resolve(tag:withArguments:)` method instead.
|
||||
@available(*, deprecated, message="Use `resolve(tag:withArguments:)` method instead.")
|
||||
public func resolve<T, Arg1>(tag tag: Tag? = nil, _ arg1: Arg1) -> T {
|
||||
return 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, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2) -> T) -> DefinitionOf<T> {
|
||||
return register(tag: tag, factory: factory, scope: scope) as DefinitionOf<T>
|
||||
}
|
||||
|
||||
/// - seealso: `resolve(tag:_:)`
|
||||
public func resolve<T, Arg1, Arg2>(tag tag: Tag? = nil, withArguments arg1: Arg1, _ arg2: Arg2) -> T {
|
||||
return resolve(tag: tag) { (factory: (Arg1, Arg2) -> T) in factory(arg1, arg2) }
|
||||
}
|
||||
|
||||
///**Deprecated** Use `resolve(tag:withArguments:_:)` method instead.
|
||||
@available(*, deprecated, message="Use `resolve(tag:withArguments:_:)` method instead.")
|
||||
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) }
|
||||
}
|
||||
|
||||
// MARK: 3 Runtime Arguments
|
||||
|
||||
public func register<T, Arg1, Arg2, Arg3>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3) -> T) -> DefinitionOf<T> {
|
||||
return register(tag: tag, factory: factory, scope: scope) as DefinitionOf<T>
|
||||
}
|
||||
|
||||
/// - seealso: `resolve(tag:withArguments:)`
|
||||
public func resolve<T, Arg1, Arg2, Arg3>(tag tag: Tag? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3) -> T {
|
||||
return resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3) -> T) in factory(arg1, arg2, arg3) }
|
||||
}
|
||||
|
||||
///**Deprecated** Use `resolve(tag:withArguments:_:_:)` method instead.
|
||||
@available(*, deprecated, message="Use `resolve(tag:withArguments:_:_:)` method instead.")
|
||||
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) }
|
||||
}
|
||||
|
||||
// MARK: 4 Runtime Arguments
|
||||
|
||||
public func register<T, Arg1, Arg2, Arg3, Arg4>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4) -> T) -> DefinitionOf<T> {
|
||||
return register(tag: tag, factory: factory, scope: scope) as DefinitionOf<T>
|
||||
}
|
||||
|
||||
/// - seealso: `resolve(tag:withArguments:)`
|
||||
public func resolve<T, Arg1, Arg2, Arg3, Arg4>(tag tag: Tag? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4) -> T {
|
||||
return resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4) -> T) in factory(arg1, arg2, arg3, arg4) }
|
||||
}
|
||||
|
||||
///**Deprecated** Use `resolve(tag:withArguments:_:_:_:)` method instead.
|
||||
@available(*, deprecated, message="Use `resolve(tag:withArguments:_:_:_:)` method instead.")
|
||||
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) }
|
||||
}
|
||||
|
||||
// MARK: 5 Runtime Arguments
|
||||
|
||||
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> {
|
||||
return register(tag: tag, factory: factory, scope: scope) as DefinitionOf<T>
|
||||
}
|
||||
|
||||
/// - seealso: `resolve(tag:withArguments:)`
|
||||
public func resolve<T, Arg1, Arg2, Arg3, Arg4, Arg5>(tag tag: Tag? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5) -> T {
|
||||
return resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5) -> T) in factory(arg1, arg2, arg3, arg4, arg5) }
|
||||
}
|
||||
|
||||
///**Deprecated** Use `resolve(tag:withArguments:_:_:_:_:)` method instead.
|
||||
@available(*, deprecated, message="Use `resolve(tag:withArguments:_:_:_:_:)` method instead.")
|
||||
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: 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> {
|
||||
return register(tag: tag, factory: factory, scope: scope) as DefinitionOf<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) -> T {
|
||||
return resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> T) in factory(arg1, arg2, arg3, arg4, arg5, arg6) }
|
||||
}
|
||||
|
||||
///**Deprecated** Use `resolve(tag:withArguments:_:_:_:_:_:)` method instead.
|
||||
@available(*, deprecated, message="Use `resolve(tag:withArguments:_:_:_:_:_:)` method instead.")
|
||||
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) }
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
//
|
||||
// DipTests.swift
|
||||
// DipTests
|
||||
//
|
||||
// Created by Ilya Puchka on 04.11.15.
|
||||
// Copyright © 2015 AliSoftware. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import Dip
|
||||
|
||||
protocol Service {
|
||||
func getServiceName() -> String
|
||||
}
|
||||
|
||||
class ServiceImp1: Service {
|
||||
func getServiceName() -> String {
|
||||
return "ServiceImp1"
|
||||
}
|
||||
}
|
||||
|
||||
class ServiceImp2: Service {
|
||||
func getServiceName() -> String {
|
||||
return "ServiceImp2"
|
||||
}
|
||||
}
|
||||
|
||||
class DipTests: XCTestCase {
|
||||
|
||||
let container = DependencyContainer()
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
container.reset()
|
||||
}
|
||||
|
||||
func testThatItResolvesInstanceRegisteredWithoutTag() {
|
||||
//given
|
||||
container.register { ServiceImp1() as Service }
|
||||
|
||||
//when
|
||||
let serviceInstance = container.resolve() as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(serviceInstance is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItResolvesInstanceRegisteredWithTag() {
|
||||
//given
|
||||
container.register(tag: "service") { ServiceImp1() as Service }
|
||||
|
||||
//when
|
||||
let serviceInstance = container.resolve(tag: "service") as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(serviceInstance is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItResolvesDifferentInstancesRegisteredForDifferentTags() {
|
||||
//given
|
||||
container.register(tag: "service1") { ServiceImp1() as Service }
|
||||
container.register(tag: "service2") { ServiceImp2() as Service }
|
||||
|
||||
//when
|
||||
let service1Instance = container.resolve(tag: "service1") as Service
|
||||
let service2Instance = container.resolve(tag: "service2") as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service1Instance is ServiceImp1)
|
||||
XCTAssertTrue(service2Instance is ServiceImp2)
|
||||
}
|
||||
|
||||
func testThatNewRegistrationOverridesPreviousRegistration() {
|
||||
//given
|
||||
container.register { ServiceImp1() as Service }
|
||||
let service1 = container.resolve() as Service
|
||||
|
||||
//when
|
||||
container.register { ServiceImp2() as Service }
|
||||
let service2 = container.resolve() as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service1 is ServiceImp1)
|
||||
XCTAssertTrue(service2 is ServiceImp2)
|
||||
}
|
||||
|
||||
func testThatItResolvesTypeAsNewInstanceEveryTime() {
|
||||
//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(.Singleton) { ServiceImp1() as Service }
|
||||
|
||||
//when
|
||||
let service1 = container.resolve() as Service
|
||||
let service2 = container.resolve() as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue((service1 as! ServiceImp1) === (service2 as! ServiceImp1))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "DipTests",
|
||||
dependencies: [
|
||||
.Package(url: "../../../Dip", majorVersion: 4, minor: 2),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -1,217 +0,0 @@
|
||||
//
|
||||
// RuntimeArgumentsTests.swift
|
||||
// DipTests
|
||||
//
|
||||
// Created by Ilya Puchka on 04.11.15.
|
||||
// Copyright © 2015 AliSoftware. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import Dip
|
||||
|
||||
class ServiceImp3: Service {
|
||||
|
||||
let name: String
|
||||
|
||||
init(name: String, baseURL: NSURL, port: Int) {
|
||||
self.name = name
|
||||
}
|
||||
|
||||
func getServiceName() -> String {
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
||||
class RuntimeArgumentsTests: XCTestCase {
|
||||
|
||||
let container = DependencyContainer()
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
container.reset()
|
||||
}
|
||||
|
||||
func testThatItResolvesInstanceWithOneArgument() {
|
||||
//given
|
||||
let arg1 = 1
|
||||
container.register { (a1: Int) -> Service in
|
||||
XCTAssertEqual(a1, arg1)
|
||||
return ServiceImp1()
|
||||
}
|
||||
|
||||
//when
|
||||
let service = container.resolve(withArguments: arg1) as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItResolvesInstanceWithTwoArguments() {
|
||||
//given
|
||||
let arg1 = 1, arg2 = 2
|
||||
container.register { (a1: Int, a2: Int) -> Service in
|
||||
XCTAssertEqual(a1, arg1)
|
||||
XCTAssertEqual(a2, arg2)
|
||||
return ServiceImp1()
|
||||
}
|
||||
|
||||
//when
|
||||
let service = container.resolve(withArguments: arg1, arg2) as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItResolvesInstanceWithThreeArguments() {
|
||||
let arg1 = 1, arg2 = 2, arg3 = 3
|
||||
container.register { (a1: Int, a2: Int, a3: Int) -> Service in
|
||||
XCTAssertEqual(a1, arg1)
|
||||
XCTAssertEqual(a2, arg2)
|
||||
XCTAssertEqual(a3, arg3)
|
||||
return ServiceImp1()
|
||||
}
|
||||
|
||||
//when
|
||||
let service = container.resolve(withArguments: arg1, arg2, arg3) as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItResolvesInstanceWithFourArguments() {
|
||||
let arg1 = 1, arg2 = 2, arg3 = 3, arg4 = 4
|
||||
container.register { (a1: Int, a2: Int, a3: Int, a4: Int) -> Service in
|
||||
XCTAssertEqual(a1, arg1)
|
||||
XCTAssertEqual(a2, arg2)
|
||||
XCTAssertEqual(a3, arg3)
|
||||
XCTAssertEqual(a4, arg4)
|
||||
return ServiceImp1()
|
||||
}
|
||||
|
||||
//when
|
||||
let service = container.resolve(withArguments: arg1, arg2, arg3, arg4) as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItResolvesInstanceWithFiveArguments() {
|
||||
let arg1 = 1, arg2 = 2, arg3 = 3, arg4 = 4, arg5 = 5
|
||||
container.register { (a1: Int, a2: Int, a3: Int, a4: Int, a5: Int) -> Service in
|
||||
XCTAssertEqual(a1, arg1)
|
||||
XCTAssertEqual(a2, arg2)
|
||||
XCTAssertEqual(a3, arg3)
|
||||
XCTAssertEqual(a4, arg4)
|
||||
XCTAssertEqual(a5, arg5)
|
||||
return ServiceImp1()
|
||||
}
|
||||
|
||||
//when
|
||||
let service = container.resolve(withArguments: arg1, arg2, arg3, arg4, arg5) as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItResolvesInstanceWithSixArguments() {
|
||||
let arg1 = 1, arg2 = 2, arg3 = 3, arg4 = 4, arg5 = 5, arg6 = 6
|
||||
container.register { (a1: Int, a2: Int, a3: Int, a4: Int, a5: Int, a6: Int) -> Service in
|
||||
XCTAssertEqual(a1, arg1)
|
||||
XCTAssertEqual(a2, arg2)
|
||||
XCTAssertEqual(a3, arg3)
|
||||
XCTAssertEqual(a4, arg4)
|
||||
XCTAssertEqual(a5, arg5)
|
||||
XCTAssertEqual(a6, arg6)
|
||||
return ServiceImp1()
|
||||
}
|
||||
|
||||
//when
|
||||
let service = container.resolve(withArguments: arg1, arg2, arg3, arg4, arg5, arg6) as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItRegistersDifferentFactoriesForDifferentNumberOfArguments() {
|
||||
//given
|
||||
let arg1 = 1, arg2 = 2
|
||||
container.register { (a1: Int) in ServiceImp1() as Service }
|
||||
container.register { (a1: Int, a2: Int) in ServiceImp2() as Service }
|
||||
|
||||
//when
|
||||
let service1 = container.resolve(withArguments: arg1) as Service
|
||||
let service2 = container.resolve(withArguments: arg1, arg2) as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service1 is ServiceImp1)
|
||||
XCTAssertTrue(service2 is ServiceImp2)
|
||||
}
|
||||
|
||||
func testThatItRegistersDifferentFactoriesForDifferentTypesOfArguments() {
|
||||
//given
|
||||
let arg1 = 1, arg2 = "string"
|
||||
container.register { (a1: Int) in ServiceImp1() as Service }
|
||||
container.register { (a1: String) in ServiceImp2() as Service }
|
||||
|
||||
//when
|
||||
let service1 = container.resolve(withArguments: arg1) as Service
|
||||
let service2 = container.resolve(withArguments: arg2) as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service1 is ServiceImp1)
|
||||
XCTAssertTrue(service2 is ServiceImp2)
|
||||
}
|
||||
|
||||
func testThatItRegistersDifferentFactoriesForDifferentOrderOfArguments() {
|
||||
//given
|
||||
let arg1 = 1, arg2 = "string"
|
||||
container.register { (a1: Int, a2: String) in ServiceImp1() as Service }
|
||||
container.register { (a1: String, a2: Int) in ServiceImp2() as Service }
|
||||
|
||||
//when
|
||||
let service1 = container.resolve(withArguments: arg1, arg2) as Service
|
||||
let service2 = container.resolve(withArguments: arg2, arg1) as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service1 is ServiceImp1)
|
||||
XCTAssertTrue(service2 is ServiceImp2)
|
||||
}
|
||||
|
||||
func testThatNewRegistrationWithSameArgumentsOverridesPreviousRegistration() {
|
||||
//given
|
||||
let arg1 = 1, arg2 = 2
|
||||
container.register { (a1: Int, a2: Int) in ServiceImp1() as Service }
|
||||
let service1 = container.resolve(withArguments: arg1, arg2) as Service
|
||||
|
||||
//when
|
||||
container.register { (a1: Int, a2: Int) in ServiceImp2() as Service }
|
||||
let service2 = container.resolve(withArguments: arg1, arg2) as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service1 is ServiceImp1)
|
||||
XCTAssertTrue(service2 is ServiceImp2)
|
||||
}
|
||||
|
||||
func testThatDifferentFactoriesRegisteredIfArgumentIsOptional() {
|
||||
//given
|
||||
let name1 = "1", name2 = "2", name3 = "3"
|
||||
container.register { (port: Int, url: NSURL) in ServiceImp3(name: name1, baseURL: url, port: port) as Service }
|
||||
container.register { (port: Int, url: NSURL?) in ServiceImp3(name: name2, baseURL: url!, port: port) as Service }
|
||||
container.register { (port: Int, url: NSURL!) in ServiceImp3(name: name3, baseURL: url, port: port) as Service }
|
||||
|
||||
//when
|
||||
let url: NSURL = NSURL(string: "http://example.com")!
|
||||
let service1 = container.resolve(withArguments: 80, url) as Service
|
||||
let service2 = container.resolve(withArguments: 80, NSURL(string: "http://example.com")) as Service
|
||||
|
||||
let service3 = container.resolve(withArguments: 80, NSURL(string: "http://example.com")! as NSURL!) as Service
|
||||
let service4 = container.resolve(withArguments: 80, NSURL(string: "http://example.com")!) as Service
|
||||
|
||||
//then
|
||||
XCTAssertEqual(service1.getServiceName(), name1)
|
||||
XCTAssertEqual(service2.getServiceName(), name2)
|
||||
XCTAssertEqual(service3.getServiceName(), name3)
|
||||
XCTAssertEqual(service4.getServiceName(), name1) //implicitly unwrapped optional parameter is the same as not optional parameter
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
//
|
||||
// Dip
|
||||
//
|
||||
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import Dip
|
||||
|
||||
private protocol Server: class {
|
||||
weak var client: Client? {get}
|
||||
var anotherClient: Client? {get set}
|
||||
var optionalProperty: AnyObject? {get}
|
||||
}
|
||||
|
||||
private protocol Client: class {
|
||||
var server: Server? {get}
|
||||
var anotherServer: Server? {get set}
|
||||
var optionalProperty: AnyObject? {get}
|
||||
}
|
||||
|
||||
private class ServerImp: Server {
|
||||
|
||||
var _client = InjectedWeak<Client>() { _ in
|
||||
AutoInjectionTests.clientDidInjectCalled = true
|
||||
}
|
||||
var client: Client? {
|
||||
return _client.value
|
||||
}
|
||||
|
||||
weak var anotherClient: Client?
|
||||
|
||||
weak var _optionalProperty = InjectedWeak<AnyObject>(required: false)
|
||||
var optionalProperty: AnyObject? { return _optionalProperty?.value }
|
||||
}
|
||||
|
||||
private class ClientImp: Client {
|
||||
|
||||
var _server = Injected<Server>() { _ in
|
||||
AutoInjectionTests.serverDidInjectCalled = true
|
||||
}
|
||||
var server: Server? {
|
||||
return _server.value
|
||||
}
|
||||
|
||||
var anotherServer: Server?
|
||||
|
||||
var _optionalProperty = Injected<AnyObject>(required: false)
|
||||
var optionalProperty: AnyObject? { return _optionalProperty.value }
|
||||
|
||||
var taggedServer = Injected<Server>(tag: "tagged")
|
||||
}
|
||||
|
||||
private class Obj1 {
|
||||
let obj2 = InjectedWeak<Obj2>()
|
||||
let obj3 = Injected<Obj3>()
|
||||
}
|
||||
|
||||
private class Obj2 {
|
||||
let obj1 = Injected<Obj1>()
|
||||
}
|
||||
|
||||
private class Obj3 {
|
||||
|
||||
weak var obj1: Obj1?
|
||||
|
||||
init(obj: Obj1) {
|
||||
self.obj1 = obj
|
||||
}
|
||||
}
|
||||
|
||||
class AutoInjectionTests: XCTestCase {
|
||||
|
||||
static var serverDidInjectCalled: Bool = false
|
||||
static var clientDidInjectCalled: Bool = false
|
||||
|
||||
let container = DependencyContainer()
|
||||
|
||||
#if os(Linux)
|
||||
var allTests: [(String, () throws -> Void)] {
|
||||
return [
|
||||
("testThatItResolvesAutoInjectedDependencies", testThatItResolvesAutoInjectedDependencies),
|
||||
("testThatItThrowsErrorIfFailsToAutoInjectDependency", testThatItThrowsErrorIfFailsToAutoInjectDependency),
|
||||
("testThatItResolvesAutoInjectedSingletons", testThatItResolvesAutoInjectedSingletons),
|
||||
("testThatItCallsResolveDependencyBlockWhenAutoInjecting", testThatItCallsResolveDependencyBlockWhenAutoInjecting),
|
||||
("testThatItReusesResolvedAutoInjectedInstances", testThatItReusesResolvedAutoInjectedInstances),
|
||||
("testThatItReusesAutoInjectedInstancesOnNextResolveOrAutoInjection", testThatItReusesAutoInjectedInstancesOnNextResolveOrAutoInjection),
|
||||
("testThatThereIsNoRetainCycleBetweenAutoInjectedCircularDependencies", testThatThereIsNoRetainCycleBetweenAutoInjectedCircularDependencies),
|
||||
("testThatItCallsDidInjectOnAutoInjectedProperty", testThatItCallsDidInjectOnAutoInjectedProperty),
|
||||
("testThatNoErrorThrownWhenOptionalPropertiesAreNotAutoInjected", testThatNoErrorThrownWhenOptionalPropertiesAreNotAutoInjected),
|
||||
("testThatItResolvesTaggedAutoInjectedProperties", testThatItResolvesTaggedAutoInjectedProperties)
|
||||
]
|
||||
}
|
||||
|
||||
func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#else
|
||||
override func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#endif
|
||||
|
||||
func testThatItResolvesAutoInjectedDependencies() {
|
||||
container.register(.ObjectGraph) { ServerImp() as Server }
|
||||
container.register(.ObjectGraph) { ClientImp() as Client }
|
||||
|
||||
let client = try! container.resolve() as Client
|
||||
let server = client.server
|
||||
XCTAssertTrue(client === server?.client)
|
||||
}
|
||||
|
||||
func testThatItThrowsErrorIfFailsToAutoInjectDependency() {
|
||||
container.register(.ObjectGraph) { ClientImp() as Client }
|
||||
|
||||
AssertThrows(expression: try container.resolve() as Client)
|
||||
}
|
||||
|
||||
func testThatItResolvesAutoInjectedSingletons() {
|
||||
//given
|
||||
container.register(.Singleton) { ServerImp() as Server }
|
||||
container.register(.Singleton) { ClientImp() as Client }
|
||||
|
||||
//when
|
||||
let sharedClient = try! container.resolve() as Client
|
||||
let sharedServer = try! container.resolve() as Server
|
||||
|
||||
let client = try! container.resolve() as Client
|
||||
let server = client.server
|
||||
|
||||
//then
|
||||
XCTAssertTrue(client === sharedClient)
|
||||
XCTAssertTrue(client === server?.client)
|
||||
XCTAssertTrue(server === sharedServer)
|
||||
}
|
||||
|
||||
func testThatItCallsResolveDependencyBlockWhenAutoInjecting() {
|
||||
var serverBlockWasCalled = false
|
||||
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServerImp() as Server }
|
||||
.resolveDependencies { (container, server) -> () in
|
||||
serverBlockWasCalled = true
|
||||
}
|
||||
|
||||
var clientBlockWasCalled = false
|
||||
container.register(.ObjectGraph) { ClientImp() as Client }
|
||||
.resolveDependencies { (container, client) -> () in
|
||||
clientBlockWasCalled = true
|
||||
}
|
||||
|
||||
//when
|
||||
try! container.resolve() as Client
|
||||
XCTAssertTrue(serverBlockWasCalled)
|
||||
|
||||
try! container.resolve() as Server
|
||||
XCTAssertTrue(clientBlockWasCalled)
|
||||
}
|
||||
|
||||
func testThatItReusesResolvedAutoInjectedInstances() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServerImp() as Server }
|
||||
.resolveDependencies { (container, server) -> () in
|
||||
server.anotherClient = try! container.resolve() as Client
|
||||
}
|
||||
|
||||
container.register(.ObjectGraph) { ClientImp() as Client }
|
||||
.resolveDependencies { (container, client) -> () in
|
||||
client.anotherServer = try! container.resolve() as Server
|
||||
}
|
||||
|
||||
//when
|
||||
let client = try! container.resolve() as Client
|
||||
|
||||
//then
|
||||
let server = client.server
|
||||
let anotherServer = client.anotherServer
|
||||
|
||||
XCTAssertTrue(server === anotherServer)
|
||||
|
||||
let oneClient = server!.client
|
||||
let anotherClient = server!.anotherClient
|
||||
|
||||
XCTAssertTrue(oneClient === anotherClient)
|
||||
XCTAssertTrue(client === anotherClient)
|
||||
}
|
||||
|
||||
func testThatItReusesAutoInjectedInstancesOnNextResolveOrAutoInjection() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { Obj1() }
|
||||
container.register(.ObjectGraph) { Obj2() }
|
||||
container.register(.ObjectGraph) { Obj3(obj: try self.container.resolve()) }
|
||||
|
||||
//when
|
||||
let obj2 = try! container.resolve() as Obj2
|
||||
|
||||
//then
|
||||
XCTAssertTrue(obj2 === obj2.obj1.value!.obj2.value!,
|
||||
"Auto-injected instance should be reused on next auto-injection")
|
||||
|
||||
XCTAssertTrue(obj2.obj1.value! === obj2.obj1.value!.obj3.value!.obj1,
|
||||
"Auto-injected instance should be reused on next resolve")
|
||||
}
|
||||
|
||||
func testThatThereIsNoRetainCycleBetweenAutoInjectedCircularDependencies() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServerImp() as Server }
|
||||
container.register(.ObjectGraph) { ClientImp() as Client }
|
||||
|
||||
//when
|
||||
var client: Client? = try! container.resolve() as Client
|
||||
|
||||
//then
|
||||
weak var weakServer: Server? = client?.server
|
||||
weak var weakClient = client
|
||||
|
||||
XCTAssertNotNil(weakClient)
|
||||
XCTAssertNotNil(weakServer)
|
||||
|
||||
client = nil
|
||||
|
||||
XCTAssertNil(weakClient)
|
||||
XCTAssertNil(weakServer)
|
||||
}
|
||||
|
||||
func testThatItCallsDidInjectOnAutoInjectedProperty() {
|
||||
AutoInjectionTests.clientDidInjectCalled = false
|
||||
AutoInjectionTests.serverDidInjectCalled = false
|
||||
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServerImp() as Server }
|
||||
container.register(.ObjectGraph) { ClientImp() as Client }
|
||||
|
||||
//when
|
||||
try! container.resolve() as Client
|
||||
|
||||
//then
|
||||
XCTAssertTrue(AutoInjectionTests.clientDidInjectCalled)
|
||||
XCTAssertTrue(AutoInjectionTests.serverDidInjectCalled)
|
||||
}
|
||||
|
||||
func testThatNoErrorThrownWhenOptionalPropertiesAreNotAutoInjected() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServerImp() as Server }
|
||||
container.register(.ObjectGraph) { ClientImp() as Client }
|
||||
|
||||
AssertNoThrow(expression: try container.resolve() as Client, "Container should not throw error if failed to resolve optional auto-injected properties.")
|
||||
}
|
||||
|
||||
func testThatItResolvesTaggedAutoInjectedProperties() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServerImp() as Server }
|
||||
container.register(tag: "tagged", .ObjectGraph) { ServerImp() as Server }
|
||||
container.register(.ObjectGraph) { ClientImp() as Client }
|
||||
|
||||
//when
|
||||
let client = try! container.resolve() as Client
|
||||
|
||||
//then
|
||||
let taggedServer = (client as! ClientImp).taggedServer.value!
|
||||
let server = client.server!
|
||||
|
||||
//server and tagged server should be resolved as different instances
|
||||
XCTAssertTrue(server !== taggedServer)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
//
|
||||
// Dip
|
||||
//
|
||||
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import Dip
|
||||
|
||||
private protocol Service: class {}
|
||||
private class ServiceImp1: Service {}
|
||||
private class ServiceImp2: Service {}
|
||||
|
||||
private class Server {
|
||||
weak var client: Client?
|
||||
|
||||
init() {}
|
||||
}
|
||||
|
||||
private class Client {
|
||||
var server: Server
|
||||
|
||||
init(server: Server) {
|
||||
self.server = server
|
||||
}
|
||||
}
|
||||
|
||||
class ComponentScopeTests: XCTestCase {
|
||||
|
||||
let container = DependencyContainer()
|
||||
|
||||
#if os(Linux)
|
||||
var allTests: [(String, () throws -> Void)] {
|
||||
return [
|
||||
("testThatPrototypeIsDefaultScope", testThatPrototypeIsDefaultScope),
|
||||
("testThatScopeCanBeChanged", testThatScopeCanBeChanged),
|
||||
("testThatItResolvesTypeAsNewInstanceForPrototypeScope", testThatItResolvesTypeAsNewInstanceForPrototypeScope),
|
||||
("testThatItReusesInstanceForSingletonScope", testThatItReusesInstanceForSingletonScope),
|
||||
("testThatSingletonIsNotReusedAcrossContainers", testThatSingletonIsNotReusedAcrossContainers),
|
||||
("testThatSingletonIsReleasedWhenDefinitionIsRemoved", testThatSingletonIsReleasedWhenDefinitionIsRemoved),
|
||||
("testThatSingletonIsReleasedWhenDefinitionIsOverridden", testThatSingletonIsReleasedWhenDefinitionIsOverridden),
|
||||
("testThatSingletonIsReleasedWhenContainerIsReset", testThatSingletonIsReleasedWhenContainerIsReset),
|
||||
("testThatItReusesInstanceInObjectGraphScopeDuringResolve", testThatItReusesInstanceInObjectGraphScopeDuringResolve),
|
||||
("testThatItDoesNotReuseInstanceInObjectGraphScopeInNextResolve", testThatItDoesNotReuseInstanceInObjectGraphScopeInNextResolve),
|
||||
("testThatItDoesNotReuseInstanceInObjectGraphScopeResolvedForNilTag", testThatItDoesNotReuseInstanceInObjectGraphScopeResolvedForNilTag)
|
||||
]
|
||||
}
|
||||
|
||||
func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#else
|
||||
override func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#endif
|
||||
|
||||
func testThatPrototypeIsDefaultScope() {
|
||||
let def = container.register { ServiceImp1() as Service }
|
||||
XCTAssertEqual(def.scope, ComponentScope.Prototype)
|
||||
}
|
||||
|
||||
func testThatScopeCanBeChanged() {
|
||||
let def = container.register(.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 === service2)
|
||||
}
|
||||
|
||||
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 === service2)
|
||||
}
|
||||
|
||||
func testThatSingletonIsNotReusedAcrossContainers() {
|
||||
//given
|
||||
let def = container.register(.Singleton) { ServiceImp1() as Service }
|
||||
let secondContainer = DependencyContainer()
|
||||
secondContainer.register(def, forTag: nil)
|
||||
|
||||
//when
|
||||
let service1 = try! container.resolve() as Service
|
||||
let service2 = try! secondContainer.resolve() as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service1 !== service2, "Singleton instances should not be reused across containers")
|
||||
}
|
||||
|
||||
func testThatSingletonIsReleasedWhenDefinitionIsRemoved() {
|
||||
//given
|
||||
let def = container.register(.Singleton) { ServiceImp1() as Service }
|
||||
let service1 = try! container.resolve() as Service
|
||||
|
||||
//when
|
||||
container.remove(def, forTag: nil)
|
||||
container.register(def, forTag: nil)
|
||||
|
||||
//then
|
||||
let service2 = try! container.resolve() as Service
|
||||
XCTAssertTrue(service1 !== service2, "Singleton instances should be released when definition is removed from the container")
|
||||
}
|
||||
|
||||
func testThatSingletonIsReleasedWhenDefinitionIsOverridden() {
|
||||
//given
|
||||
let def = container.register(.Singleton) { ServiceImp1() as Service }
|
||||
let service1 = try! container.resolve() as Service
|
||||
|
||||
//when
|
||||
container.register(def, forTag: nil)
|
||||
|
||||
//then
|
||||
let service2 = try! container.resolve() as Service
|
||||
XCTAssertTrue(service1 !== service2, "Singleton instances should be released when definition is overridden")
|
||||
}
|
||||
|
||||
func testThatSingletonIsReleasedWhenContainerIsReset() {
|
||||
//given
|
||||
let def = container.register(.Singleton) { ServiceImp1() as Service }
|
||||
let service1 = try! container.resolve() as Service
|
||||
|
||||
//when
|
||||
container.reset()
|
||||
container.register(def, forTag: nil)
|
||||
|
||||
//then
|
||||
let service2 = try! container.resolve() as Service
|
||||
XCTAssertTrue(service1 !== service2, "Singleton instances should be released when container is reset")
|
||||
}
|
||||
|
||||
func testThatItReusesInstanceInObjectGraphScopeDuringResolve() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { Client(server: try self.container.resolve()) as Client }
|
||||
|
||||
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
|
||||
|
||||
//then
|
||||
|
||||
//when service1 is resolved using this definition due to fallback to nil tag
|
||||
//we don't want every next resolve of service reuse it
|
||||
XCTAssertTrue(service2 is ServiceImp2)
|
||||
}
|
||||
container.register(tag: "service", .ObjectGraph) { ServiceImp2() as Service}
|
||||
|
||||
//when
|
||||
let service1 = try! container.resolve(tag: "tag") as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service1 is ServiceImp1)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
//
|
||||
// Dip
|
||||
//
|
||||
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import Dip
|
||||
|
||||
private protocol Service {}
|
||||
private class ServiceImp: Service {}
|
||||
|
||||
class DefinitionTests: XCTestCase {
|
||||
|
||||
private typealias F1 = () -> Service
|
||||
private typealias F2 = (String) -> Service
|
||||
|
||||
let tag1 = DependencyContainer.Tag.String("tag1")
|
||||
let tag2 = DependencyContainer.Tag.String("tag2")
|
||||
|
||||
#if os(Linux)
|
||||
var allTests: [(String, () throws -> Void)] {
|
||||
return [
|
||||
("testThatDefinitionKeyIsEqualBy_Type_Factory_Tag", testThatDefinitionKeyIsEqualBy_Type_Factory_Tag),
|
||||
("testThatDefinitionKeysWithDifferentTypesAreNotEqual", testThatDefinitionKeysWithDifferentTypesAreNotEqual),
|
||||
("testThatDefinitionKeysWithDifferentFactoriesAreNotEqual", testThatDefinitionKeysWithDifferentFactoriesAreNotEqual),
|
||||
("testThatDefinitionKeysWithDifferentTagsAreNotEqual", testThatDefinitionKeysWithDifferentTagsAreNotEqual),
|
||||
("testThatResolveDependenciesCallsResolveDependenciesBlock", testThatResolveDependenciesCallsResolveDependenciesBlock),
|
||||
("testThatResolveDependenciesBlockIsNotCalledWhenPassedWrongInstance", testThatResolveDependenciesBlockIsNotCalledWhenPassedWrongInstance)
|
||||
]
|
||||
}
|
||||
#endif
|
||||
|
||||
func testThatDefinitionKeyIsEqualBy_Type_Factory_Tag() {
|
||||
let equalKey1 = DefinitionKey(protocolType: Service.self, factoryType: F1.self, associatedTag: tag1)
|
||||
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)
|
||||
}
|
||||
|
||||
func testThatResolveDependenciesCallsResolveDependenciesBlock() {
|
||||
var blockCalled = false
|
||||
|
||||
//given
|
||||
let def = DefinitionOf<Service, () -> Service>(scope: .Prototype) { ServiceImp() as Service }.resolveDependencies { container, service in
|
||||
blockCalled = true
|
||||
}
|
||||
|
||||
//when
|
||||
try! def.resolveDependenciesOf(ServiceImp(), withContainer: DependencyContainer())
|
||||
|
||||
//then
|
||||
XCTAssertTrue(blockCalled)
|
||||
}
|
||||
|
||||
func testThatResolveDependenciesBlockIsNotCalledWhenPassedWrongInstance() {
|
||||
var blockCalled = false
|
||||
|
||||
//given
|
||||
let def = DefinitionOf<Service, () -> Service>(scope: .Prototype) { ServiceImp() as Service }.resolveDependencies { container, service in
|
||||
blockCalled = true
|
||||
}
|
||||
|
||||
//when
|
||||
try! def.resolveDependenciesOf(String(), withContainer: DependencyContainer())
|
||||
|
||||
//then
|
||||
XCTAssertFalse(blockCalled)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
//
|
||||
// Dip
|
||||
//
|
||||
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import Dip
|
||||
|
||||
private protocol Service: class { }
|
||||
private class ServiceImp1: Service { }
|
||||
private class ServiceImp2: Service { }
|
||||
|
||||
class DipTests: XCTestCase {
|
||||
|
||||
let container = DependencyContainer()
|
||||
|
||||
#if os(Linux)
|
||||
var allTests: [(String, () throws -> Void)] {
|
||||
return [
|
||||
("testThatItResolvesInstanceRegisteredWithoutTag", testThatItResolvesInstanceRegisteredWithoutTag),
|
||||
("testThatItResolvesInstanceRegisteredWithTag", testThatItResolvesInstanceRegisteredWithTag),
|
||||
("testThatItResolvesDifferentInstancesRegisteredForDifferentTags", testThatItResolvesDifferentInstancesRegisteredForDifferentTags),
|
||||
("testThatNewRegistrationOverridesPreviousRegistration", testThatNewRegistrationOverridesPreviousRegistration),
|
||||
("testThatItCallsResolveDependenciesOnDefinition", testThatItCallsResolveDependenciesOnDefinition),
|
||||
("testThatItThrowsErrorIfCanNotFindDefinitionForType", testThatItThrowsErrorIfCanNotFindDefinitionForType),
|
||||
("testThatItThrowsErrorIfCanNotFindDefinitionForTag", testThatItThrowsErrorIfCanNotFindDefinitionForTag),
|
||||
("testThatItThrowsErrorIfCanNotFindDefinitionForFactoryWithArguments", testThatItThrowsErrorIfCanNotFindDefinitionForFactoryWithArguments),
|
||||
("testThatItThrowsErrorIfConstructorThrows", testThatItThrowsErrorIfConstructorThrows),
|
||||
("testThatItThrowsErrorIfFailsToResolveDependency", testThatItThrowsErrorIfFailsToResolveDependency)
|
||||
]
|
||||
}
|
||||
|
||||
func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#else
|
||||
override func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#endif
|
||||
|
||||
func testThatItResolvesInstanceRegisteredWithoutTag() {
|
||||
//given
|
||||
container.register { ServiceImp1() as Service }
|
||||
|
||||
//when
|
||||
let serviceInstance = try! container.resolve() as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(serviceInstance is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItResolvesInstanceRegisteredWithTag() {
|
||||
//given
|
||||
container.register(tag: "service") { ServiceImp1() as Service }
|
||||
|
||||
//when
|
||||
let serviceInstance = try! container.resolve(tag: "service") as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(serviceInstance is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItResolvesDifferentInstancesRegisteredForDifferentTags() {
|
||||
//given
|
||||
container.register(tag: "service1") { ServiceImp1() as Service }
|
||||
container.register(tag: "service2") { ServiceImp2() as Service }
|
||||
|
||||
//when
|
||||
let service1Instance = try! container.resolve(tag: "service1") as Service
|
||||
let service2Instance = try! container.resolve(tag: "service2") as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service1Instance is ServiceImp1)
|
||||
XCTAssertTrue(service2Instance is ServiceImp2)
|
||||
}
|
||||
|
||||
func testThatNewRegistrationOverridesPreviousRegistration() {
|
||||
//given
|
||||
container.register { ServiceImp1() as Service }
|
||||
let service1 = try! container.resolve() as Service
|
||||
|
||||
//when
|
||||
container.register { ServiceImp2() as Service }
|
||||
let service2 = try! container.resolve() as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service1 is ServiceImp1)
|
||||
XCTAssertTrue(service2 is ServiceImp2)
|
||||
}
|
||||
|
||||
func testThatItCallsResolveDependenciesOnDefinition() {
|
||||
//given
|
||||
var resolveDependenciesCalled = false
|
||||
container.register { ServiceImp1() as Service }.resolveDependencies { (c, s) in
|
||||
resolveDependenciesCalled = true
|
||||
}
|
||||
|
||||
//when
|
||||
try! container.resolve() as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(resolveDependenciesCalled)
|
||||
}
|
||||
|
||||
func testThatItThrowsErrorIfCanNotFindDefinitionForType() {
|
||||
//given
|
||||
container.register { ServiceImp1() as ServiceImp1 }
|
||||
|
||||
//when
|
||||
AssertThrows(expression: try container.resolve() as Service) { error in
|
||||
guard case let DipError.DefinitionNotFound(key) = error else { return false }
|
||||
|
||||
//then
|
||||
typealias F = () throws -> Service
|
||||
let expectedKey = DefinitionKey(protocolType: Service.self, factoryType: F.self, associatedTag: nil)
|
||||
XCTAssertEqual(key, expectedKey)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func testThatItThrowsErrorIfCanNotFindDefinitionForTag() {
|
||||
//given
|
||||
container.register(tag: "some tag") { ServiceImp1() as Service }
|
||||
|
||||
//when
|
||||
AssertThrows(expression: try container.resolve(tag: "other tag") as Service) { error in
|
||||
guard case let DipError.DefinitionNotFound(key) = error else { return false }
|
||||
|
||||
//then
|
||||
typealias F = () throws -> Service
|
||||
let expectedKey = DefinitionKey(protocolType: Service.self, factoryType: F.self, associatedTag: "other tag")
|
||||
XCTAssertEqual(key, expectedKey)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func testThatItThrowsErrorIfCanNotFindDefinitionForFactoryWithArguments() {
|
||||
//given
|
||||
container.register { ServiceImp1() as Service }
|
||||
|
||||
//when
|
||||
AssertThrows(expression: try container.resolve(withArguments: "some string") as Service) { error in
|
||||
guard case let DipError.DefinitionNotFound(key) = error else { return false }
|
||||
|
||||
//then
|
||||
typealias F = (String) throws -> Service
|
||||
let expectedKey = DefinitionKey(protocolType: Service.self, factoryType: F.self, associatedTag: nil)
|
||||
XCTAssertEqual(key, expectedKey)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func testThatItThrowsErrorIfConstructorThrows() {
|
||||
//given
|
||||
let failedKey = DefinitionKey(protocolType: Any.self, factoryType: Any.self)
|
||||
let expectedError = DipError.DefinitionNotFound(key: failedKey)
|
||||
container.register { () throws -> Service in throw expectedError }
|
||||
|
||||
//when
|
||||
AssertThrows(expression: try container.resolve() as Service) { error in
|
||||
guard case let DipError.ResolutionFailed(key, error) = error else { return false }
|
||||
guard case let DipError.DefinitionNotFound(subKey) = error where subKey == failedKey else { return false }
|
||||
|
||||
//then
|
||||
typealias F = () throws -> Service
|
||||
let expectedKey = DefinitionKey(protocolType: Service.self, factoryType: F.self)
|
||||
XCTAssertEqual(key, expectedKey)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func testThatItThrowsErrorIfFailsToResolveDependency() {
|
||||
//given
|
||||
let failedKey = DefinitionKey(protocolType: Any.self, factoryType: Any.self)
|
||||
let expectedError = DipError.DefinitionNotFound(key: failedKey)
|
||||
container.register { ServiceImp1() as Service }
|
||||
.resolveDependencies { container, service in
|
||||
//simulate throwing error when resolving dependency
|
||||
throw expectedError
|
||||
}
|
||||
|
||||
//when
|
||||
AssertThrows(expression: try container.resolve() as Service) { error in
|
||||
guard case let DipError.ResolutionFailed(key, error) = error else { return false }
|
||||
guard case let DipError.DefinitionNotFound(subKey) = error where subKey == failedKey else { return false }
|
||||
|
||||
//then
|
||||
typealias F = () throws -> Service
|
||||
let expectedKey = DefinitionKey(protocolType: Service.self, factoryType: F.self)
|
||||
XCTAssertEqual(key, expectedKey)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
//
|
||||
// Dip
|
||||
//
|
||||
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import Dip
|
||||
|
||||
private protocol Service {
|
||||
var name: String { get }
|
||||
}
|
||||
|
||||
private class ServiceImp: Service {
|
||||
|
||||
let name: String
|
||||
|
||||
init(name: String, baseURL: String, port: Int) {
|
||||
self.name = name
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class ServiceImp1: Service {
|
||||
let name: String = "ServiceImp1"
|
||||
}
|
||||
|
||||
private class ServiceImp2: Service {
|
||||
let name: String = "ServiceImp2"
|
||||
}
|
||||
|
||||
class RuntimeArgumentsTests: XCTestCase {
|
||||
|
||||
let container = DependencyContainer()
|
||||
|
||||
#if os(Linux)
|
||||
var allTests: [(String, () throws -> Void)] {
|
||||
return [
|
||||
("testThatItResolvesInstanceWithOneArgument", testThatItResolvesInstanceWithOneArgument),
|
||||
("testThatItResolvesInstanceWithTwoArguments", testThatItResolvesInstanceWithTwoArguments),
|
||||
("testThatItResolvesInstanceWithThreeArguments", testThatItResolvesInstanceWithThreeArguments),
|
||||
("testThatItResolvesInstanceWithFourArguments", testThatItResolvesInstanceWithFourArguments),
|
||||
("testThatItResolvesInstanceWithFiveArguments", testThatItResolvesInstanceWithFiveArguments),
|
||||
("testThatItResolvesInstanceWithSixArguments", testThatItResolvesInstanceWithSixArguments),
|
||||
("testThatItRegistersDifferentFactoriesForDifferentNumberOfArguments", testThatItRegistersDifferentFactoriesForDifferentNumberOfArguments),
|
||||
("testThatItRegistersDifferentFactoriesForDifferentTypesOfArguments", testThatItRegistersDifferentFactoriesForDifferentTypesOfArguments),
|
||||
("testThatItRegistersDifferentFactoriesForDifferentOrderOfArguments", testThatItRegistersDifferentFactoriesForDifferentOrderOfArguments),
|
||||
("testThatNewRegistrationWithSameArgumentsOverridesPreviousRegistration", testThatNewRegistrationWithSameArgumentsOverridesPreviousRegistration),
|
||||
("testThatDifferentFactoriesRegisteredIfArgumentIsOptional", testThatDifferentFactoriesRegisteredIfArgumentIsOptional)
|
||||
]
|
||||
}
|
||||
|
||||
func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#else
|
||||
override func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#endif
|
||||
|
||||
func testThatItResolvesInstanceWithOneArgument() {
|
||||
//given
|
||||
let arg1 = 1
|
||||
container.register(factory: { (a1: Int) -> Service in
|
||||
XCTAssertEqual(a1, arg1)
|
||||
return ServiceImp1()
|
||||
})
|
||||
|
||||
//when
|
||||
let service = try! container.resolve(withArguments: arg1) as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItResolvesInstanceWithTwoArguments() {
|
||||
//given
|
||||
let arg1 = 1, arg2 = 2
|
||||
container.register { (a1: Int, a2: Int) -> Service in
|
||||
XCTAssertEqual(a1, arg1)
|
||||
XCTAssertEqual(a2, arg2)
|
||||
return ServiceImp1()
|
||||
}
|
||||
|
||||
//when
|
||||
let service = try! container.resolve(withArguments: arg1, arg2) as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItResolvesInstanceWithThreeArguments() {
|
||||
let arg1 = 1, arg2 = 2, arg3 = 3
|
||||
container.register { (a1: Int, a2: Int, a3: Int) -> Service in
|
||||
XCTAssertEqual(a1, arg1)
|
||||
XCTAssertEqual(a2, arg2)
|
||||
XCTAssertEqual(a3, arg3)
|
||||
return ServiceImp1()
|
||||
}
|
||||
|
||||
//when
|
||||
let service = try! container.resolve(withArguments: arg1, arg2, arg3) as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItResolvesInstanceWithFourArguments() {
|
||||
let arg1 = 1, arg2 = 2, arg3 = 3, arg4 = 4
|
||||
container.register { (a1: Int, a2: Int, a3: Int, a4: Int) -> Service in
|
||||
XCTAssertEqual(a1, arg1)
|
||||
XCTAssertEqual(a2, arg2)
|
||||
XCTAssertEqual(a3, arg3)
|
||||
XCTAssertEqual(a4, arg4)
|
||||
return ServiceImp1()
|
||||
}
|
||||
|
||||
//when
|
||||
let service = try! container.resolve(withArguments: arg1, arg2, arg3, arg4) as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItResolvesInstanceWithFiveArguments() {
|
||||
let arg1 = 1, arg2 = 2, arg3 = 3, arg4 = 4, arg5 = 5
|
||||
container.register { (a1: Int, a2: Int, a3: Int, a4: Int, a5: Int) -> Service in
|
||||
XCTAssertEqual(a1, arg1)
|
||||
XCTAssertEqual(a2, arg2)
|
||||
XCTAssertEqual(a3, arg3)
|
||||
XCTAssertEqual(a4, arg4)
|
||||
XCTAssertEqual(a5, arg5)
|
||||
return ServiceImp1()
|
||||
}
|
||||
|
||||
//when
|
||||
let service = try! container.resolve(withArguments: arg1, arg2, arg3, arg4, arg5) as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItResolvesInstanceWithSixArguments() {
|
||||
let arg1 = 1, arg2 = 2, arg3 = 3, arg4 = 4, arg5 = 5, arg6 = 6
|
||||
container.register { (a1: Int, a2: Int, a3: Int, a4: Int, a5: Int, a6: Int) -> Service in
|
||||
XCTAssertEqual(a1, arg1)
|
||||
XCTAssertEqual(a2, arg2)
|
||||
XCTAssertEqual(a3, arg3)
|
||||
XCTAssertEqual(a4, arg4)
|
||||
XCTAssertEqual(a5, arg5)
|
||||
XCTAssertEqual(a6, arg6)
|
||||
return ServiceImp1()
|
||||
}
|
||||
|
||||
//when
|
||||
let service = try! container.resolve(withArguments: arg1, arg2, arg3, arg4, arg5, arg6) as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItRegistersDifferentFactoriesForDifferentNumberOfArguments() {
|
||||
//given
|
||||
let arg1 = 1, arg2 = 2
|
||||
container.register { (a1: Int) in ServiceImp1() as Service }
|
||||
container.register { (a1: Int, a2: Int) in ServiceImp2() as Service }
|
||||
|
||||
//when
|
||||
let service1 = try! container.resolve(withArguments: arg1) as Service
|
||||
let service2 = try! container.resolve(withArguments: arg1, arg2) as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service1 is ServiceImp1)
|
||||
XCTAssertTrue(service2 is ServiceImp2)
|
||||
}
|
||||
|
||||
func testThatItRegistersDifferentFactoriesForDifferentTypesOfArguments() {
|
||||
//given
|
||||
let arg1 = 1, arg2 = "string"
|
||||
container.register(factory: { (a1: Int) in ServiceImp1() as Service })
|
||||
container.register(factory: { (a1: String) in ServiceImp2() as Service })
|
||||
|
||||
//when
|
||||
let service1 = try! container.resolve(withArguments: arg1) as Service
|
||||
let service2 = try! container.resolve(withArguments: arg2) as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service1 is ServiceImp1)
|
||||
XCTAssertTrue(service2 is ServiceImp2)
|
||||
}
|
||||
|
||||
func testThatItRegistersDifferentFactoriesForDifferentOrderOfArguments() {
|
||||
//given
|
||||
let arg1 = 1, arg2 = "string"
|
||||
container.register { (a1: Int, a2: String) in ServiceImp1() as Service }
|
||||
container.register { (a1: String, a2: Int) in ServiceImp2() as Service }
|
||||
|
||||
//when
|
||||
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)
|
||||
XCTAssertTrue(service2 is ServiceImp2)
|
||||
}
|
||||
|
||||
func testThatNewRegistrationWithSameArgumentsOverridesPreviousRegistration() {
|
||||
//given
|
||||
let arg1 = 1, arg2 = 2
|
||||
container.register { (a1: Int, a2: Int) in ServiceImp1() 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 = try! container.resolve(withArguments: arg1, arg2) as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service1 is ServiceImp1)
|
||||
XCTAssertTrue(service2 is ServiceImp2)
|
||||
}
|
||||
|
||||
func testThatDifferentFactoriesRegisteredIfArgumentIsOptional() {
|
||||
//given
|
||||
let name1 = "1", name2 = "2", name3 = "3"
|
||||
container.register { (port: Int, url: String) in ServiceImp(name: name1, baseURL: url, port: port) as Service }
|
||||
container.register { (port: Int, url: String?) in ServiceImp(name: name2, baseURL: url!, port: port) as Service }
|
||||
container.register { (port: Int, url: String!) in ServiceImp(name: name3, baseURL: url, port: port) as Service }
|
||||
|
||||
//when
|
||||
let service1 = try! container.resolve(withArguments: 80, "http://example.com") as Service
|
||||
let service2 = try! container.resolve(withArguments: 80, "http://example.com" as String?) as Service
|
||||
let service3 = try! container.resolve(withArguments: 80, "http://example.com" as String!) as Service
|
||||
|
||||
//then
|
||||
XCTAssertEqual(service1.name, name1)
|
||||
XCTAssertEqual(service2.name, name2)
|
||||
XCTAssertEqual(service3.name, name3)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
//
|
||||
// Dip
|
||||
//
|
||||
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import Dip
|
||||
|
||||
private protocol Server: class {
|
||||
var client: Client? { get set }
|
||||
}
|
||||
|
||||
private protocol Client: class {
|
||||
var server: Server { get }
|
||||
}
|
||||
|
||||
private class ClientImp: Client, Equatable {
|
||||
var server: Server
|
||||
init(server: Server) {
|
||||
self.server = server
|
||||
}
|
||||
}
|
||||
|
||||
private func ==<T: ClientImp>(lhs: T, rhs: T) -> Bool {
|
||||
return lhs === rhs
|
||||
}
|
||||
|
||||
private class ServerImp: Server, Hashable {
|
||||
weak var client: Client?
|
||||
init() {}
|
||||
|
||||
var hashValue: Int {
|
||||
return unsafeAddressOf(self).hashValue
|
||||
}
|
||||
}
|
||||
|
||||
private func ==<T: ServerImp>(lhs: T, rhs: T) -> Bool {
|
||||
return lhs === rhs
|
||||
}
|
||||
|
||||
private var resolvedServers = Set<ServerImp>()
|
||||
private var resolvedClients = Array<ClientImp>()
|
||||
|
||||
private var container: DependencyContainer!
|
||||
|
||||
#if os(Linux)
|
||||
import Glibc
|
||||
private var lock: pthread_spinlock_t = 0
|
||||
|
||||
private let resolveClientSync: () -> Client? = {
|
||||
var clientPointer: UnsafeMutablePointer<Void> = nil
|
||||
clientPointer = dispatch_sync { _ in
|
||||
let resolved = try! container.resolve() as Client
|
||||
return UnsafeMutablePointer(Unmanaged.passUnretained(resolved as! ClientImp).toOpaque())
|
||||
}
|
||||
return Unmanaged<ClientImp>.fromOpaque(COpaquePointer(clientPointer)).takeUnretainedValue()
|
||||
}
|
||||
|
||||
#else
|
||||
let queue = NSOperationQueue()
|
||||
let lock = NSRecursiveLock()
|
||||
|
||||
private let resolveClientSync: () -> Client? = {
|
||||
var client: Client?
|
||||
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
|
||||
client = try! container.resolve() as Client
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
let resolveServerAsync = {
|
||||
let service = try! container.resolve() as Server
|
||||
lock.lock()
|
||||
resolvedServers.insert(service as! ServerImp)
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
let resolveClientAsync = {
|
||||
let client = try! container.resolve() as Client
|
||||
lock.lock()
|
||||
resolvedClients.append(client as! ClientImp)
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
class ThreadSafetyTests: XCTestCase {
|
||||
|
||||
#if os(Linux)
|
||||
init() {
|
||||
pthread_spin_init(&lock, 0)
|
||||
}
|
||||
|
||||
var allTests: [(String, () throws -> Void)] {
|
||||
return [
|
||||
("testSingletonThreadSafety", testSingletonThreadSafety),
|
||||
("testFactoryThreadSafety", testFactoryThreadSafety),
|
||||
("testCircularReferenceThreadSafety", testCircularReferenceThreadSafety)
|
||||
]
|
||||
}
|
||||
|
||||
func setUp() {
|
||||
container = DependencyContainer()
|
||||
}
|
||||
|
||||
func tearDown() {
|
||||
resolvedServers.removeAll()
|
||||
resolvedClients.removeAll()
|
||||
}
|
||||
#else
|
||||
override func setUp() {
|
||||
container = DependencyContainer()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
resolvedServers.removeAll()
|
||||
resolvedClients.removeAll()
|
||||
}
|
||||
#endif
|
||||
|
||||
func testSingletonThreadSafety() {
|
||||
container.register(.Singleton) { ServerImp() as Server }
|
||||
|
||||
for _ in 0..<100 {
|
||||
#if os(Linux)
|
||||
dispatch_async({ _ in
|
||||
resolveServerAsync()
|
||||
return nil
|
||||
})
|
||||
#else
|
||||
queue.addOperationWithBlock(resolveServerAsync)
|
||||
#endif
|
||||
}
|
||||
|
||||
#if os(Linux)
|
||||
sleep(1)
|
||||
#else
|
||||
queue.waitUntilAllOperationsAreFinished()
|
||||
#endif
|
||||
|
||||
XCTAssertEqual(resolvedServers.count, 1, "Should create only one instance")
|
||||
}
|
||||
|
||||
|
||||
func testFactoryThreadSafety() {
|
||||
container.register { ServerImp() as Server }
|
||||
|
||||
for _ in 0..<100 {
|
||||
#if os(Linux)
|
||||
dispatch_async({ _ in
|
||||
resolveServerAsync()
|
||||
return nil
|
||||
})
|
||||
#else
|
||||
queue.addOperationWithBlock(resolveServerAsync)
|
||||
#endif
|
||||
}
|
||||
|
||||
#if os(Linux)
|
||||
sleep(1)
|
||||
#else
|
||||
queue.waitUntilAllOperationsAreFinished()
|
||||
#endif
|
||||
|
||||
XCTAssertEqual(resolvedServers.count, 100, "All instances should be different")
|
||||
}
|
||||
|
||||
|
||||
func testCircularReferenceThreadSafety() {
|
||||
container.register(.ObjectGraph) {
|
||||
ClientImp(server: try container.resolve()) as Client
|
||||
}
|
||||
|
||||
container.register(.ObjectGraph) { ServerImp() as Server }
|
||||
.resolveDependencies { container, server in
|
||||
server.client = resolveClientSync()
|
||||
}
|
||||
|
||||
for _ in 0..<100 {
|
||||
#if os(Linux)
|
||||
dispatch_async({ _ in
|
||||
resolveClientAsync()
|
||||
return nil
|
||||
})
|
||||
#else
|
||||
queue.addOperationWithBlock(resolveClientAsync)
|
||||
#endif
|
||||
}
|
||||
|
||||
#if os(Linux)
|
||||
sleep(1)
|
||||
#else
|
||||
queue.waitUntilAllOperationsAreFinished()
|
||||
#endif
|
||||
|
||||
XCTAssertEqual(resolvedClients.count, 100, "Instances should be not reused in different object graphs")
|
||||
for client in resolvedClients {
|
||||
let service = client.server as! ServerImp
|
||||
let serviceClient = service.client as! ClientImp
|
||||
XCTAssertEqual(serviceClient, client, "Instances should be reused when resolving single object graph")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// Dip
|
||||
//
|
||||
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
#if os(Linux)
|
||||
typealias FileString = StaticString
|
||||
#else
|
||||
typealias FileString = String
|
||||
#endif
|
||||
|
||||
|
||||
func AssertThrows<T>(file: FileString = __FILE__, line: UInt = __LINE__, @autoclosure expression: () throws -> T) {
|
||||
AssertThrows(file, line: line, expression: expression, "")
|
||||
}
|
||||
|
||||
func AssertThrows<T>(file: FileString = __FILE__, line: UInt = __LINE__, @autoclosure expression: () throws -> T, _ message: String) {
|
||||
AssertThrows(expression: expression, checkError: { _ in true }, message)
|
||||
}
|
||||
|
||||
func AssertThrows<T>(file: FileString = __FILE__, line: UInt = __LINE__, @autoclosure expression: () throws -> T, checkError: ErrorType -> Bool) {
|
||||
AssertThrows(file, line: line, expression: expression, checkError: checkError, "")
|
||||
}
|
||||
|
||||
func AssertThrows<T>(file: FileString = __FILE__, line: UInt = __LINE__, @autoclosure expression: () throws -> T, checkError: ErrorType -> Bool, _ message: String) {
|
||||
do {
|
||||
try expression()
|
||||
XCTFail(message, file: file, line: line)
|
||||
}
|
||||
catch {
|
||||
XCTAssertTrue(checkError(error), "Thrown unexpected error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func AssertNoThrow<T>(file: FileString = __FILE__, line: UInt = __LINE__, @autoclosure expression: () throws -> T) {
|
||||
AssertNoThrow(file, line: line, expression: expression, "")
|
||||
}
|
||||
|
||||
func AssertNoThrow<T>(file: FileString = __FILE__, line: UInt = __LINE__, @autoclosure expression: () throws -> T, _ message: String) {
|
||||
do {
|
||||
try expression()
|
||||
}
|
||||
catch {
|
||||
XCTFail(message, file: file, line: line)
|
||||
}
|
||||
}
|
||||
|
||||
#if os(Linux)
|
||||
import Glibc
|
||||
typealias TMain = @convention(c) (UnsafeMutablePointer<Void>) -> UnsafeMutablePointer<Void>
|
||||
|
||||
func dispatch_async(block: TMain) {
|
||||
var pid: pthread_t = 0
|
||||
pthread_create(&pid, nil, block, nil)
|
||||
}
|
||||
|
||||
func dispatch_sync(block: TMain) -> UnsafeMutablePointer<Void> {
|
||||
var pid: pthread_t = 0
|
||||
var result: UnsafeMutablePointer<Void> = nil
|
||||
pthread_create(&pid, nil, block, nil)
|
||||
pthread_join(pid, &result)
|
||||
return result
|
||||
}
|
||||
|
||||
extension pthread_spinlock_t {
|
||||
mutating func lock() {
|
||||
pthread_spin_lock(&self)
|
||||
}
|
||||
mutating func unlock() {
|
||||
pthread_spin_unlock(&self)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,10 @@
|
||||
import XCTest
|
||||
|
||||
XCTMain([
|
||||
DipTests(),
|
||||
DefinitionTests(),
|
||||
RuntimeArgumentsTests(),
|
||||
ComponentScopeTests(),
|
||||
AutoInjectionTests(),
|
||||
ThreadSafetyTests()
|
||||
])
|
||||
@@ -0,0 +1,208 @@
|
||||
//: [Previous: Shared Instances](@previous)
|
||||
|
||||
import UIKit
|
||||
import Dip
|
||||
|
||||
let container = DependencyContainer()
|
||||
/*:
|
||||
|
||||
### Auto-Injection
|
||||
|
||||
If you follow Single Responsibility Principle chances are very high that you will end up with more than two collaborating components in your system. Let's say you have a component that depends on few others. Using _Dip_ you can register all of the dependencies in a container as well as that component itself and register a factory that will create that component and feed it with the dependencies resolving them with a container:
|
||||
*/
|
||||
|
||||
protocol Service: class {
|
||||
var logger: Logger? { get }
|
||||
var tracker: Tracker? { get }
|
||||
}
|
||||
|
||||
class ServiceImp: Service {
|
||||
var logger: Logger?
|
||||
var tracker: Tracker?
|
||||
}
|
||||
|
||||
container.register() { TrackerImp() as Tracker }
|
||||
container.register() { LoggerImp() as Logger }
|
||||
|
||||
container.register() { ServiceImp() as Service }
|
||||
.resolveDependencies { container, service in
|
||||
(service as! ServiceImp).logger = try container.resolve() as Logger
|
||||
(service as! ServiceImp).tracker = try container.resolve() as Tracker
|
||||
}
|
||||
|
||||
let service = try! container.resolve() as Service
|
||||
service.logger
|
||||
service.tracker
|
||||
|
||||
/*:
|
||||
Not bad so far. Though that `resolveDependencies` block looks heavy. It would be cool if we can get rid of it. Alternatively you can use _constructor injection_ here, which is actually more prefereable by default but not always possible (see [circular dependencies](Circular%20dependencies)).
|
||||
Now let's say that you have a bunch of components in your app that require `Logger` or `Tracker` too. You will need to resolve them in a factory for each component again and again. That can be a lot of boilerplate code, simple but still duplicated.
|
||||
|
||||
That is one of the scenarios when auto-injection can be useful. It works with property injection and with it the previous code will transform to this:
|
||||
*/
|
||||
|
||||
class AutoInjectedServiceImp: Service {
|
||||
private var injectedLogger = Injected<Logger>()
|
||||
var logger: Logger? { return injectedLogger.value }
|
||||
|
||||
private var injectedTracker = Injected<Tracker>()
|
||||
var tracker: Tracker? { return injectedTracker.value }
|
||||
}
|
||||
|
||||
container.register() { AutoInjectedServiceImp() as Service }
|
||||
|
||||
let autoInjectedService = try! container.resolve() as Service
|
||||
autoInjectedService.logger
|
||||
autoInjectedService.tracker
|
||||
|
||||
/*:
|
||||
As you can see we added two private properties to our implementation of `Service` - `injectedLogger` and `injectedTracker`. Their types are `Injeceted<Logger>` and `Injected<Tracker>` respectively. Note that we've not just defined them as properties of those types, but defined them with some initial value. `Injected<T>` is a simple _wrapper class_ that wraps value of generic type and provides read-write access to it with `value` property. This property is defined as optional, so that when we create instance of `Injected<T>` it will have `nil` in its value. There is also another wrapper - `InjectedWeak<T>` - which in contrast to `Injected<T>` holds a week reference to its wrapped object, thus requiring it to be a _reference type_ (or `AnyObject`), when `Injected<T>` can also wrap value types (or `Any`).
|
||||
|
||||
What is happening under the hood is that after concrete instance of resolved type is created (`Service` in that case), container will iterate through its properties using `Mirror`. For each of the properties wrapped with `Injected<T>` or `InjectedWeak<T>` it will search a definition that can be used to create an instance of wrapped type and use it to create and inject a concrete instance in a `value` property of a wrapper. The fact that wrappers are _classes_ or _reference types_ makes it possible at runtime to inject dependency in instance of resolved type.
|
||||
|
||||
You can provide closure that will be called when the dependency will be injected in the property. It is similar to `didSet` property observer.
|
||||
|
||||
Auto-injected properties can be marked with tag. Then container will search for definition tagged by the same tag to resolve this property.
|
||||
|
||||
Auto-injected properties are required by default. That means that if container fails to resolve any of auto-injected properties of the instance (or any of its dependencies) it will fail resolution of the object graph in whole.
|
||||
*/
|
||||
|
||||
class ServerWithRequiredClient {
|
||||
var client = Injected<Client>()
|
||||
}
|
||||
|
||||
container.register { ServerWithRequiredClient() }
|
||||
|
||||
do {
|
||||
let serverWithClient = try container.resolve() as ServerWithRequiredClient
|
||||
}
|
||||
catch {
|
||||
print(error)
|
||||
}
|
||||
|
||||
/*:
|
||||
You can make auto-injected property optional by passing `false` to `required` parameter of `Injected<T>`/`InjectedWeak<T>` constructor. For such properties container will ignore any errors when it resolves this property (or any of its dependencies).
|
||||
*/
|
||||
|
||||
class ServerWithOptionalClient {
|
||||
var optionalClient = Injected<Client>(required: false)
|
||||
}
|
||||
|
||||
container.register { ServerWithOptionalClient() }
|
||||
let serverWithNoClient = try! container.resolve() as ServerWithOptionalClient
|
||||
serverWithNoClient.optionalClient.value
|
||||
|
||||
/*:
|
||||
Another example of using auto-injection is circular dependencies. Let's say you have a `Server` and a `ServerClient` both referencing each other.
|
||||
*/
|
||||
|
||||
protocol Server: class {
|
||||
weak var client: ServerClient? { get }
|
||||
}
|
||||
|
||||
protocol ServerClient: class {
|
||||
var server: Server? { get }
|
||||
}
|
||||
|
||||
class ServerImp: Server {
|
||||
weak var client: ServerClient?
|
||||
}
|
||||
|
||||
class ServerClientImp: ServerClient {
|
||||
var server: Server?
|
||||
|
||||
init(server: Server) {
|
||||
self.server = server
|
||||
}
|
||||
}
|
||||
|
||||
/*:
|
||||
The standard way to register such components in `DependencyContainer` will lead to such code:
|
||||
*/
|
||||
|
||||
container.register(.ObjectGraph) {
|
||||
ServerClientImp(server: try container.resolve()) as ServerClient
|
||||
}
|
||||
|
||||
container.register(.ObjectGraph) { ServerImp() as Server }
|
||||
.resolveDependencies { (container: DependencyContainer, server: Server) in
|
||||
(server as! ServerImp).client = try container.resolve() as ServerClient
|
||||
}
|
||||
|
||||
let client = try! container.resolve() as ServerClient
|
||||
client.server
|
||||
|
||||
/*:
|
||||
With auto-injection you will have the following code:
|
||||
*/
|
||||
|
||||
class InjectedServerImp: Server {
|
||||
private var injectedClient = InjectedWeak<ServerClient>()
|
||||
var client: ServerClient? { return injectedClient.value }
|
||||
}
|
||||
|
||||
class InjectedClientImp: ServerClient {
|
||||
private var injectedServer = Injected<Server>()
|
||||
var server: Server? { get { return injectedServer.value } }
|
||||
}
|
||||
|
||||
container.register(.ObjectGraph) { InjectedServerImp() as Server }
|
||||
container.register(.ObjectGraph) { InjectedClientImp() as ServerClient }
|
||||
|
||||
let injectedClient = try! container.resolve() as ServerClient
|
||||
injectedClient.server
|
||||
injectedClient.server?.client === injectedClient //circular dependencies were resolved correctly
|
||||
|
||||
/*:
|
||||
You can see that component registration looks much simpler now. But on the other side it requires some boilerplate code in implementations, and also tightly coupls your code with Dip.
|
||||
|
||||
Here is an example with higher number of dependencies.
|
||||
*/
|
||||
container.register() { RouterImp() as Router }
|
||||
container.register() { DataProviderImp() as DataProvider }
|
||||
|
||||
class ViewController: UIViewController {
|
||||
var logger: Logger?
|
||||
var tracker: Tracker?
|
||||
var dataProvider: DataProvider?
|
||||
var router: Router?
|
||||
}
|
||||
|
||||
container.register { ViewController() }
|
||||
.resolveDependencies { container, controller in
|
||||
controller.logger = try container.resolve() as Logger
|
||||
controller.tracker = try container.resolve() as Tracker
|
||||
controller.dataProvider = try container.resolve() as DataProvider
|
||||
controller.router = try container.resolve() as Router
|
||||
}
|
||||
|
||||
let viewController = try! container.resolve() as ViewController
|
||||
viewController.router
|
||||
|
||||
/*:
|
||||
With auto-injection you can replace that with something like this:
|
||||
*/
|
||||
|
||||
class AutoInjectedViewController: UIViewController {
|
||||
let logger = Injected<Logger>()
|
||||
let tracker = Injected<Tracker>()
|
||||
let dataProvider = Injected<DataProvider>()
|
||||
let router = Injected<Router>()
|
||||
}
|
||||
|
||||
container.register { AutoInjectedViewController() }
|
||||
|
||||
let autoViewController = try! container.resolve() as AutoInjectedViewController
|
||||
autoViewController.router.value
|
||||
|
||||
/*:
|
||||
In such scenario when view controller is created by storyboard you will need to use property injection anyway, so the overhead of adding additional properties for auto-injection is smaller. Also all the boilerplate code of unwrapping injected properties (if you need that) can be moved to extension, cleaning implementation a bit.
|
||||
|
||||
> **Note**: For such cases concider using [DipUI](https://github.com/AliSoftware/Dip-UI). It is a small extension for Dip that allows you to do exactly what we need in this example - inject dependencies in instances created by storyboards. It does not require to use auto-injection feature.
|
||||
|
||||
So as you can see there are certain advantages and disadvatages of using auto-injection. It makes your definitions simpler, especially if there are circular dependencies involved or the number of dependencies is high. But it requires additional properties and some boilerplate code in your implementations, makes your implementatios tightly coupled with Dip. It has also some limitations like that it requires factories for auto-injected types that accept no runtime arguments to be registered in a container.
|
||||
|
||||
So you should decide for yourself whether you prefer to use auto-injection or "the standard" way. At the end they let you achieve the same result.
|
||||
*/
|
||||
|
||||
//: [Next: Testing](@next)
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Timeline
|
||||
version = "3.0">
|
||||
<TimelineItems>
|
||||
</TimelineItems>
|
||||
</Timeline>
|
||||
@@ -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)
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Timeline
|
||||
version = "3.0">
|
||||
<TimelineItems>
|
||||
</TimelineItems>
|
||||
</Timeline>
|
||||
@@ -29,7 +29,7 @@ Both syntaxes are equivalent. The one using the configuration block is simply a
|
||||
|
||||
### When/where to create container?
|
||||
|
||||
While there is an option to use container as a global variable we advise instead to create and configure container in your app delegate and pass it between your objects.
|
||||
While there is an option to use container as a global variable we advise instead to create and configure container in your app delegate and pass it between your objects (see [Shared Instances](Shared%20Instances)).
|
||||
|
||||
*/
|
||||
//: [Next: Registering Components](@next)
|
||||
|
||||
@@ -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(withArguments: url, 80) as Service
|
||||
let service2 = container.resolve(withArguments: 80, url) as Service
|
||||
let service3 = container.resolve(withArguments: 80, NSURL(string: "http://example.com")) as Service
|
||||
let service4 = container.resolve(withArguments: 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, Arg7>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory)
|
||||
}
|
||||
|
||||
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, Arg7>(tag tag: Tag? = nil, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7) throws -> T {
|
||||
return try resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7) throws -> T) in try factory(arg1, arg2, arg3, arg4, arg5, arg6, arg7) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,10 +152,12 @@ 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 (more precisely - a set of patterns) as well as a singleton. And any pattern can be abused. DI can be used 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/). You should carefully decide when to use DI, you should not inject everything and everywhere and define a protocol for every single class you use. 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.
|
||||
|
||||
If you want to know more about Dependency Injection in general we recomend you to read ["Dependency Injection in .Net" by Mark Seemann](https://www.manning.com/books/dependency-injection-in-dot-net). Dip was inspired by implementations of IoC container for .Net platform and shares core principles described in that book.
|
||||
|
||||
*/
|
||||
|
||||
//: [Next: Testing](@next)
|
||||
//: [Next: Auto-Injection](@next)
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -16,6 +16,9 @@ If you follow [Protocol-Oriented programming](https://developer.apple.com/videos
|
||||
But still there should be some point in your program where concrete instances are created. The thing is that it's better to have one well defined point for that than to scatter setup logic all over the place with different factories and lazy properties. IoC containers like _Dip_ play the role of that point.
|
||||
|
||||
The following pages in this Playground demonstrates how to use _Dip_ to adopt all those concepts in practice.
|
||||
|
||||
If you want to know more about Dependency Injection in general we recomend you to read ["Dependency Injection in .Net" by Mark Seemann](https://www.manning.com/books/dependency-injection-in-dot-net). Dip was inspired by implementations of IoC container for .Net platform and shares core principles described in that book.
|
||||
|
||||
*/
|
||||
//: [Next: Creating a DependencyContainer](@next)
|
||||
|
||||
|
||||
@@ -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,31 @@ public class ServiceFactory {
|
||||
}
|
||||
}
|
||||
|
||||
public class ClientServiceImp: Service {
|
||||
public weak var client: Client?
|
||||
public init() {}
|
||||
}
|
||||
|
||||
public protocol Logger {}
|
||||
public protocol Tracker {}
|
||||
public protocol DataProvider {}
|
||||
public protocol Router {}
|
||||
|
||||
public class LoggerImp: Logger {
|
||||
public init() {}
|
||||
}
|
||||
|
||||
public class TrackerImp: Tracker {
|
||||
public init() {}
|
||||
}
|
||||
|
||||
public class RouterImp: Router {
|
||||
public init() {}
|
||||
}
|
||||
|
||||
public class DataProviderImp: DataProvider {
|
||||
public init() {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
<page name='Resolving components'/>
|
||||
<page name='Runtime arguments'/>
|
||||
<page name='Scopes'/>
|
||||
<page name='Circular dependencies'/>
|
||||
<page name='Shared Instances'/>
|
||||
<page name='Auto-injection'/>
|
||||
<page name='Testing'/>
|
||||
</pages>
|
||||
</playground>
|
||||
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// 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 PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Dip"
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
[](https://travis-ci.org/AliSoftware/Dip)
|
||||
[](http://cocoapods.org/pods/Dip)
|
||||
[](https://github.com/Carthage/Carthage)
|
||||
[](http://cocoapods.org/pods/Dip)
|
||||
[](http://cocoapods.org/pods/Dip)
|
||||
|
||||
@@ -12,20 +13,25 @@ _Photo courtesy of [www.kevinandamanda.com](http://www.kevinandamanda.com/recipe
|
||||
|
||||
`Dip` is a simple **Dependency Injection Container**.
|
||||
|
||||
It's not true Dependency Injection, but it's damn close, and aimed to be as simple as possible.
|
||||
It's inspired by `.NET`'s [Unity Container](https://msdn.microsoft.com/library/ff647202.aspx).
|
||||
It's aimed to be as simple as possible yet provide rich functionality usual for DI containers on other platforms. It's inspired by `.NET`'s [Unity Container](https://msdn.microsoft.com/library/ff647202.aspx) and other DI containers.
|
||||
|
||||
* You start by creating `let dc = DependencyContainer()` and **register all your dependencies, by associating a `protocol` to an `instance` or a `factory`**.
|
||||
* Then anywhere in your application, you can call `dc.resolve()` to **resolve a `protocol` into an instance of a concrete type** using that `DependencyContainer`.
|
||||
* You start by creating `let dc = DependencyContainer()` and **register all your dependencies, by associating a `protocol` to a `factory`**.
|
||||
* Then you can call `dc.resolve()` to **resolve a `protocol` into an instance of a concrete type** using that `DependencyContainer`.
|
||||
|
||||
This allows you to define the real, concrete types only in one place ([e.g. like this in your app](https://github.com/AliSoftware/Dip/blob/master/Example/DipSampleApp/DependencyContainers.swift#L22-L27), and [resetting it in your `setUp` for each Unit Tests](https://github.com/AliSoftware/Dip/blob/master/Example/Tests/SWAPIPersonProviderTests.swift#L17-L21)) and then [only work with `protocols` in your code](https://github.com/AliSoftware/Dip/blob/master/Example/DipSampleApp/Providers/SWAPIStarshipProvider.swift#L12) (which only define an API contract), without worrying about the real implementation.
|
||||
This allows you to define the real, concrete types only in one place ([e.g. like this in your app](SampleApp/DipSampleApp/DependencyContainers.swift#L22-L27), and [resetting it in your `setUp` for each Unit Tests](SampleApp/Tests/SWAPIPersonProviderTests.swift#L17-L21)) and then [only work with `protocols` in your code](SampleApp/DipSampleApp/Providers/SWAPIStarshipProvider.swift#L12) (which only define an API contract), without worrying about the real implementation.
|
||||
|
||||
> You can easily use Dip along with Storyboards and Nibs - checkout [Dip-UI](https://github.com/AliSoftware/Dip-UI) extensions.
|
||||
|
||||
## Advantages of DI and loose coupling
|
||||
|
||||
* Define clear API contracts before even thinking about implementation, and make your code loosly coupled with the real implementation.
|
||||
* Easily switch between implementations — as long as they respect the same API contact (the `protocol`
|
||||
* Easily switch between implementations — as long as they respect the same API contact (the `protocol`), making your app modular and scalable.
|
||||
* Greatly improve testability, as you can register a real instance in your app but a fake instance in your tests dedicated for testing / mocking the fonctionnality
|
||||
* Get rid of those `sharedInstances` and avoid the singleton pattern at all costs
|
||||
* Enable parallel development in your team. You and your teammates can work independently on different parts of the app after you agree on the interfaces.
|
||||
* As a bonus get rid of those `sharedInstances` and avoid the singleton pattern at all costs.
|
||||
|
||||
|
||||
If you want to know more about Dependency Injection in general we recomend you to read ["Dependency Injection in .Net"](https://www.manning.com/books/dependency-injection-in-dot-net) by Mark Seemann. Dip was inspired particularly by implementations of some DI containers for .Net platform and shares core principles described in that book (even if you are not familiar with .Net platform the prenciples described in that book are platform agnostic).
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -42,6 +48,27 @@ If you use _Carthage_ add this line to your Cartfile:
|
||||
github "AliSoftware/Dip"
|
||||
```
|
||||
|
||||
If you use [_Swift Package Manager_](https://swift.org/package-manager/) add Dip as dependency to you `Package.swift`:
|
||||
|
||||
```
|
||||
let package = Package(
|
||||
name: "MyPackage",
|
||||
dependencies: [
|
||||
.Package(url: "https://github.com/AliSoftware/Dip.git", "4.2.0")
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
## Running tests
|
||||
|
||||
On OSX you can run tests from Xcode. On Linux you need to have Swift Package Manager installed and use it to build test executable:
|
||||
|
||||
```
|
||||
cd Dip/DipTests
|
||||
swift build
|
||||
./.build/debug/DipTests
|
||||
```
|
||||
|
||||
## Playground
|
||||
|
||||
Dip comes with a **Playground** to introduce you to Inversion of Control, Dependency Injection, and how to use Dip in practice.
|
||||
@@ -54,41 +81,56 @@ The next paragraphs give you an overview of the Usage of _Dip_ directly, but if
|
||||
|
||||
## Usage
|
||||
|
||||
*See [CHANGELOG.md](https://github.com/AliSoftware/Dip/blob/master/CHANGELOG.md) for instructions to migrate from 2.0.0 to 3.0.0*
|
||||
|
||||
### Register instance factories
|
||||
|
||||
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 just `register { … }`) 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 }`).
|
||||
* `register() { … as SomeType }` will register provided factory with a given type.
|
||||
* if you want to register an concrete implementation for some abstraction (protocol) you need **cast the instance to that protocol type** (e.g. `register { PlistUsersProvider() as UsersListProviderType }`).
|
||||
* if you want just to register concrete type in container you may not need a type cast
|
||||
|
||||
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.
|
||||
Typically, to register your dependencies as early as possible in your app life-cycle, you will declare a `let dip: DependencyContainer = { … }()` somewhere, most likely in your `AppDelegate`. In unit tests you may configure container in each test method specifically and then reset it in `tearDown()`.
|
||||
|
||||
### Resolve dependencies
|
||||
|
||||
* `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.
|
||||
* `try resolve() as SomeType` will return a new instance matching the requested type (protocol or concrete type).
|
||||
* `resolve()` is a generic method so you need to explicitly specify the return type (using `as` or explicitly providing type of a variable that will hold the resulting value) so that Swift's type inference knows which type you're trying to resolve.
|
||||
|
||||
```swift
|
||||
container.register { ServiceImp() as Service }
|
||||
let service = container.resolve() as Service
|
||||
let service = try! container.resolve() as Service
|
||||
```
|
||||
|
||||
Ususally you will use _abstractions_ for your dependencies, but container can also resolve concrete types, if you register them. You can use that in cases where abstraction is not really required.
|
||||
|
||||
```swift
|
||||
container.register { ServiceImp() }
|
||||
let service: ServiceImp = try! container.resolve()
|
||||
```
|
||||
|
||||
|
||||
### Scopes
|
||||
|
||||
Dip provides two _scopes_ that you can use to register dependencies:
|
||||
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 _must_ be used to properly 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.
|
||||
|
||||
You specify scope when you register dependency like that:
|
||||
|
||||
```swift
|
||||
container.register() { ServiceImp() as Service } //.Prototype is a default
|
||||
container.register(.ObjectGraph) { ServiceImp() as Service }
|
||||
container.register(.Singleton) { ServiceImp() as Service }
|
||||
```
|
||||
|
||||
|
||||
### Using block-based initialization
|
||||
|
||||
When calling the initializer of `DependencyContainer()`, you can pass a block that will be called right after the initialization. This allows you to have a nice syntax to do all your `register(…)` calls in there, instead of having to do them separately.
|
||||
|
||||
It may not seem to provide much, but given the fact that `DependencyContainers` are typically declared as global constants using a top-level `let`, it gets very useful, because instead of having to do it like this:
|
||||
It may not seem to provide much, but it gets very useful, because instead of having to do setup the container like this:
|
||||
|
||||
```swift
|
||||
let dip: DependencyContainer = {
|
||||
@@ -101,7 +143,7 @@ let dip: DependencyContainer = {
|
||||
}()
|
||||
```
|
||||
|
||||
You can instead write this exact equivalent code, which is more compact, and indent better in Xcode (as the final closing brack is properly aligned):
|
||||
you can instead write this exact equivalent code, which is more compact, and indent better in Xcode (as the final closing brack is properly aligned):
|
||||
|
||||
```swift
|
||||
let dip = DependencyContainer { dip in
|
||||
@@ -110,105 +152,191 @@ let dip = DependencyContainer { dip in
|
||||
}
|
||||
```
|
||||
|
||||
### Using tags to associate various factories to one protocol
|
||||
### Using tags to associate various factories to one type
|
||||
|
||||
* If you give a `tag` in the parameter to `register()`, it will associate that instance or factory with this tag, which can be used later during `resolve` (see below).
|
||||
* `resolve(tag: tag)` will try to find an instance (or instance factory) that match both the requested protocol _and_ the tag. If it doesn't find any, it will fallback to an instance (or instance factory) that only match the requested protocol.
|
||||
* The tags can be StringLiteralType or IntegerLiteralType. That said you can use plain strings or integers as tags.
|
||||
* `resolve(tag: tag)` will try to find a factory that match both the requested protocol _and_ the tag. If it doesn't find any, it will fallback to the factory that only match the requested type.
|
||||
* The tags can be `StringLiteralType` or `IntegerLiteralType`. That said you can use plain strings or integers as tags.
|
||||
|
||||
|
||||
```swift
|
||||
enum WebService: String {
|
||||
case PersonWS
|
||||
case StarshipWS
|
||||
var tag: Tag { return Tag.String(self.rawValue) }
|
||||
case Production
|
||||
case Development
|
||||
|
||||
var tag: DependencyContainer.Tag { return DependencyContainer.Tag.String(self.rawValue) }
|
||||
}
|
||||
|
||||
let wsDependencies = DependencyContainer() { dip in
|
||||
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 }
|
||||
dip.register(tag: WebService.Production.tag) { URLSessionNetworkLayer(baseURL: "http://prod.myapi.com/api/")! as NetworkLayer }
|
||||
dip.register(tag: WebService.Development.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
|
||||
|
||||
You can register factories that accept up to six arguments. When you resolve dependency you can pass those arguments to `resolve()` method and they will be passed to the factory. Note that _number_, _types_ and _order_ of parameters matters. Also use of optional parameter and not optional parameter will result in two factories registered in container.
|
||||
You can register factories that accept up to six arguments. When you resolve dependency you can pass those arguments to `resolve()` method and they will be passed to the factory. Note that _number_, _types_ and _order_ of parameters matters (see _Runtime arguments_ page of the Playground).
|
||||
|
||||
```swift
|
||||
let webServices = DependencyContainer() { webServices in
|
||||
webServices.register { (url: NSURL, port: Int) in WebServiceImp1(url, port: port) as WebServiceAPI }
|
||||
webServices.register { (port: Int, url: NSURL) in WebServiceImp2(url, port: port) as WebServiceAPI }
|
||||
webServices.register { (port: Int, url: NSURL?) in WebServiceImp3(url!, port: port) as WebServiceAPI }
|
||||
}
|
||||
|
||||
let service1 = webServices.resolve(withArguments: NSURL(string: "http://example.url")!, 80) as WebServiceAPI // service1 is WebServiceImp1
|
||||
let service2 = webServices.resolve(withArguments: 80, NSURL(string: "http://example.url")!) as WebServiceAPI // service2 is WebServiceImp2
|
||||
let service3 = webServices.resolve(withArguments: 80, NSURL(string: "http://example.url")) as WebServiceAPI // service3 is WebServiceImp3
|
||||
let service = try! webServices.resolve(withArguments: NSURL(string: "http://example.url")!, 80) as WebServiceAPI
|
||||
|
||||
```
|
||||
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:
|
||||
Though Dip provides support for up to six runtime arguments out of the box you can extend that.
|
||||
|
||||
```
|
||||
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, withArguments 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) }
|
||||
### Circular dependencies
|
||||
|
||||
_Dip_ supports circular dependencies. For that you need to register your components with `ObjectGraph` scope and use `resolveDependencies` method of `DefinitionOf` returned by `register` method like this:
|
||||
|
||||
```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 information about circular dependencies you can find in the Playground.
|
||||
|
||||
### Auto-Injection
|
||||
|
||||
Auto-injection lets your resolve all the dependencies of the instance resolved by container with just one call, also allowing a simpler syntax to register circular dependencies.
|
||||
|
||||
```swift
|
||||
protocol Server {
|
||||
weak var client: Client? { get }
|
||||
}
|
||||
|
||||
protocol Client: class {
|
||||
var server: Server? { get }
|
||||
}
|
||||
|
||||
class ServerImp: Server {
|
||||
private var injectedClient = InjectedWeak<Client>()
|
||||
var client: Client? { return injectedClient.value }
|
||||
}
|
||||
|
||||
class ClientImp: Client {
|
||||
private var injectedServer = Injected<Server>()
|
||||
var server: Server? { get { return injectedServer.value} }
|
||||
}
|
||||
|
||||
container.register(.ObjectGraph) { ServerImp() as Server }
|
||||
container.register(.ObjectGraph) { ClientImp() as Client }
|
||||
|
||||
let client = try! container.resolve() as Client
|
||||
|
||||
```
|
||||
You can find more use cases for auto-injection in the Playground available in this repository.
|
||||
|
||||
> Tip: You can use either `Injected<T>` and `InjectedWeak<T>` wrappers provided by Dip, or your own wrappers (even plain `Box<T>`) that conform to `AutoInjectedPropertyBox` protocol.
|
||||
|
||||
### Thread safety
|
||||
|
||||
`DependencyContainer` is thread safe, you can register and resolve components from different threads.
|
||||
Still we encourage you to register components in the main thread early in the application lifecycle to prevent race conditions
|
||||
when you try to resolve component from one thread while it was not yet registered in container by another thread.
|
||||
|
||||
### Errors
|
||||
|
||||
The resolve operation has a potential to fail because you can use the wrong type, factory or a wrong tag. For that reason Dip throws a `DipError` if it fails to resolve a type. Thus when calling `resolve` you need to use a `try` operator.
|
||||
There are very rare use cases when your application can recover from this kind of error. In most of the cases you can use `try!` to cause an exception at runtime if error was thrown or `try?` if a dependency is optional. This way `try!` serves as an additional mark for developers that resolution can fail.
|
||||
Dip also provides helpful descriptions for errors that can occur when you call `resolve`. See the source code documentation to know more about that.
|
||||
|
||||
### Concrete Example
|
||||
|
||||
Somewhere in your App target, register the dependencies:
|
||||
|
||||
```swift
|
||||
let dip: DependencyContainer = {
|
||||
let dip = DependencyContainer()
|
||||
let env = ProductionEnvironment(analytics: true)
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
> Do the same in your Unit Tests target & test cases, but obviously with different Dependencies registered, depending on what you want to test and what instances you need to inject to provide dummy implementations for your tests.
|
||||
|
||||
|
||||
Then to use dependencies throughout your app, use `dip.resolve()`, like this:
|
||||
Let's say you have some view model that depends on some data provider and web service:
|
||||
|
||||
```swift
|
||||
struct WebService {
|
||||
let env: EnvironmentType = dip.resolve()
|
||||
let env: EnvironmentType
|
||||
|
||||
init(env: EnvironmentType) {
|
||||
self.env = env
|
||||
}
|
||||
|
||||
func sendRequest(path: String, …) {
|
||||
// ... use stuff like env.baseURL here
|
||||
// … use stuff like env.baseURL here
|
||||
}
|
||||
}
|
||||
|
||||
struct SomeViewModel {
|
||||
let ws: WebServiceType = dip.resolve()
|
||||
var friendsProvider: FriendsProviderType
|
||||
init(userName: String) {
|
||||
friendsProvider = dip.resolve(tag: userName, userName)
|
||||
let ws: WebServiceType
|
||||
let friendsProvider: FriendsProviderType
|
||||
|
||||
init(friendsProvider: FriendsProviderType, webService: WebServiceType) {
|
||||
self.friendsProvider = friendsProvider
|
||||
self.ws = webService
|
||||
}
|
||||
|
||||
func foo() {
|
||||
ws.someMethodDeclaredOnWebServiceType()
|
||||
let friends = friendsProvider.someFriendsProviderTypeMethod()
|
||||
print("friends: \(friends)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This way, when running your app target:
|
||||
As you can see we have few layers of dependencies here. All of them together represent _dependency graph_.
|
||||
To be able to resolve this graph with Dip we need to make it aware of those types.
|
||||
For that we register the dependencies somewhere early in app life cycle (most likely in AppDelegate):
|
||||
|
||||
* `ws` will be resolved as your singleton instance `WebService` registered before.
|
||||
* `friendsProvider` will be resolved as a new instance each time, which will be an instance created via `PlistFriendsProvider(plist: "myfriends")` if `userName` is `me` and created via `DummyFriendsProvider(userName)` for any other `userName` value (because `resolve(tag: userName, userName)` will fallback to `resolve(tag: nil, userName)` in that case, using the instance factory which was registered without a tag, but will pass `userName` as argument).
|
||||
```swift
|
||||
let dip: DependencyContainer = {
|
||||
let dip = DependencyContainer()
|
||||
let enableAnalytics = … //i.e. read the setting from plist
|
||||
dip.register(.Singleton) { ProductionEnvironment(analytics: enableAnalytics) as EnvironmentType }
|
||||
dip.register(.Singleton) { WebService(env: try dip.resolve()) as WebServiceType }
|
||||
|
||||
But when running your Unit tests target, it will probably resolve to other instances, depending on how you registered your dependencies in your Test Case.
|
||||
dip.register() { userName in DummyFriendsProvider(user: name) as FriendsProviderType }
|
||||
dip.register(tag: "me") { (_: String) in PlistFriendsProvider(plist: "myfriends") as FriendsProviderType }
|
||||
|
||||
dip.register() { userName in
|
||||
let webService = try dip.resolve() as WebServiceType
|
||||
let friendsProvider = try dip.resolve(tag: userName, withArguments: userName) as
|
||||
return SomeViewModel(friendsProvider: freindsProvider, webService: webService)
|
||||
}
|
||||
return dip
|
||||
}
|
||||
```
|
||||
|
||||
> Do the same in your Unit Tests target & test cases, but obviously with different _implementations_ (test doubles) registered.
|
||||
|
||||
Then to resolve the graph use `dip.resolve()`, like this:
|
||||
|
||||
```swift
|
||||
let viewModel = try! dip.resolve(withArguments: userName) as SomeViewModel
|
||||
//now you can use view model or pass it to it's consumer
|
||||
|
||||
```
|
||||
|
||||
This way with just one call to `resolve()` you will have the whole graph of your dependencies resolved and ready to use:
|
||||
|
||||
* environmet will be resolved as a singleton instance of `ProductionEnvironment` with enabled analitycs;
|
||||
* `ws` will be resolved as a singleton instance of `WebService` and will have it's `env` property set to `ProductionEnvironment`, already resolved (and reatined) by container.
|
||||
* `friendsProvider` will be resolved as a new instance each time you create a view model, which will be an instance created via
|
||||
`PlistFriendsProvider(plist: "myfriends")` if `userName` is `me` and created via `DummyFriendsProvider(userName)` for any other
|
||||
`userName` value (because `resolve(tag: userName, withArguments: userName)` will fallback to `resolve(tag: nil, withArguments: userName)` in that case, using
|
||||
the instance factory which was registered without a tag, but will pass `userName` as an argument).
|
||||
* view model will be created using `init(friendsProvider:webService:)` with `friendsProvider` and `webService` that have been
|
||||
already resolved by container.
|
||||
|
||||
When running your Unit tests target, it may be resolved with other instances, depending on how you registered your dependencies in your Test Case.
|
||||
|
||||
> Try to constrain calls to `resolve()` method to one place and try to use one call to `resolve()` to instantiate the whole graph of the dependencies.
|
||||
The same should be applied to dependencies registration - it should be performed with one call and should be done in one place.
|
||||
Don't scatter calls to container all around your code. Using `resolve` inside your implementations will be equal to creating dependencies directly and is actually against DI. Moreover it will drag the dependency on Dip everywhere and will make requirements of your types implicit instead of explicit.
|
||||
Instead you should combine use of container with DI patterns like _Constructor Injection_ and _Property Injection_. Any DI container is just a tool, not a goal.
|
||||
You should aplly DI patterns in your code first and only then think about using DI container as a tool to make dependencies management easier.
|
||||
You will find some other advices on how to use the container in the Playground.
|
||||
We hope that after reading this README and going through the Playground you will admit the benifits of DI and loose coupling that it enables whether you use Dip or not.
|
||||
|
||||
### Complete Example Project
|
||||
|
||||
|
||||
@@ -10,14 +10,12 @@
|
||||
090012291BC6FECA0079C600 /* BaseCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090012161BC6FECA0079C600 /* BaseCell.swift */; };
|
||||
0900122A1BC6FECA0079C600 /* PersonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090012171BC6FECA0079C600 /* PersonCell.swift */; };
|
||||
0900122C1BC6FECA0079C600 /* PersonProviderAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0900121B1BC6FECA0079C600 /* PersonProviderAPI.swift */; };
|
||||
0900122D1BC6FECA0079C600 /* DummyPilotProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0900121C1BC6FECA0079C600 /* DummyPilotProvider.swift */; };
|
||||
0900122E1BC6FECA0079C600 /* HardCodedStarshipProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0900121D1BC6FECA0079C600 /* HardCodedStarshipProvider.swift */; };
|
||||
0900122F1BC6FECA0079C600 /* PlistPersonProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0900121E1BC6FECA0079C600 /* PlistPersonProvider.swift */; };
|
||||
0900123B1BC6FF4D0079C600 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0900123A1BC6FF4D0079C600 /* Main.storyboard */; };
|
||||
0900123D1BC7012A0079C600 /* StarshipProviderAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0900123C1BC7012A0079C600 /* StarshipProviderAPI.swift */; };
|
||||
090012401BC704C60079C600 /* Person.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0900123F1BC704C60079C600 /* Person.swift */; };
|
||||
090012421BC7059E0079C600 /* Starship.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090012411BC7059E0079C600 /* Starship.swift */; };
|
||||
090012441BC708A00079C600 /* DummyStarshipProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090012431BC708A00079C600 /* DummyStarshipProvider.swift */; };
|
||||
0982AF081C503EEE00B62463 /* FakePersonsProviders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0982AF071C503EEE00B62463 /* FakePersonsProviders.swift */; };
|
||||
0982AF0A1C50401800B62463 /* FakeStarshipProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0982AF091C50401800B62463 /* FakeStarshipProvider.swift */; };
|
||||
099022621BC123C000E76F43 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099022611BC123C000E76F43 /* AppDelegate.swift */; };
|
||||
099022691BC123C000E76F43 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 099022681BC123C000E76F43 /* Assets.xcassets */; };
|
||||
0990226C1BC123C000E76F43 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0990226A1BC123C000E76F43 /* LaunchScreen.storyboard */; };
|
||||
@@ -52,14 +50,12 @@
|
||||
090012161BC6FECA0079C600 /* BaseCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseCell.swift; sourceTree = "<group>"; };
|
||||
090012171BC6FECA0079C600 /* PersonCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersonCell.swift; sourceTree = "<group>"; };
|
||||
0900121B1BC6FECA0079C600 /* PersonProviderAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersonProviderAPI.swift; sourceTree = "<group>"; };
|
||||
0900121C1BC6FECA0079C600 /* DummyPilotProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DummyPilotProvider.swift; sourceTree = "<group>"; };
|
||||
0900121D1BC6FECA0079C600 /* HardCodedStarshipProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HardCodedStarshipProvider.swift; sourceTree = "<group>"; };
|
||||
0900121E1BC6FECA0079C600 /* PlistPersonProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlistPersonProvider.swift; sourceTree = "<group>"; };
|
||||
0900123A1BC6FF4D0079C600 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
|
||||
0900123C1BC7012A0079C600 /* StarshipProviderAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StarshipProviderAPI.swift; sourceTree = "<group>"; };
|
||||
0900123F1BC704C60079C600 /* Person.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Person.swift; sourceTree = "<group>"; };
|
||||
090012411BC7059E0079C600 /* Starship.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Starship.swift; sourceTree = "<group>"; };
|
||||
090012431BC708A00079C600 /* DummyStarshipProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DummyStarshipProvider.swift; sourceTree = "<group>"; };
|
||||
0982AF071C503EEE00B62463 /* FakePersonsProviders.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FakePersonsProviders.swift; sourceTree = "<group>"; };
|
||||
0982AF091C50401800B62463 /* FakeStarshipProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FakeStarshipProvider.swift; sourceTree = "<group>"; };
|
||||
0990225F1BC123C000E76F43 /* DipSampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DipSampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
099022611BC123C000E76F43 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
099022681BC123C000E76F43 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
@@ -150,8 +146,7 @@
|
||||
090012381BC6FEFD0079C600 /* PersonProviders */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0900121C1BC6FECA0079C600 /* DummyPilotProvider.swift */,
|
||||
0900121E1BC6FECA0079C600 /* PlistPersonProvider.swift */,
|
||||
0982AF071C503EEE00B62463 /* FakePersonsProviders.swift */,
|
||||
09D796121BC9A5BC003C68EB /* SWAPIPersonProvider.swift */,
|
||||
);
|
||||
name = PersonProviders;
|
||||
@@ -160,8 +155,7 @@
|
||||
090012391BC6FF080079C600 /* StarshipProviders */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
090012431BC708A00079C600 /* DummyStarshipProvider.swift */,
|
||||
0900121D1BC6FECA0079C600 /* HardCodedStarshipProvider.swift */,
|
||||
0982AF091C50401800B62463 /* FakeStarshipProvider.swift */,
|
||||
09D7961A1BC9BE65003C68EB /* SWAPIStarshipProvider.swift */,
|
||||
);
|
||||
name = StarshipProviders;
|
||||
@@ -342,25 +336,23 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
09D796151BC9A5FC003C68EB /* NetworkLayer.swift in Sources */,
|
||||
0982AF081C503EEE00B62463 /* FakePersonsProviders.swift in Sources */,
|
||||
09D795FF1BC71F5A003C68EB /* PersonListViewController.swift in Sources */,
|
||||
0900122A1BC6FECA0079C600 /* PersonCell.swift in Sources */,
|
||||
09D796011BC722C0003C68EB /* StarshipListViewController.swift in Sources */,
|
||||
0900122C1BC6FECA0079C600 /* PersonProviderAPI.swift in Sources */,
|
||||
09D7961D1BC9C62E003C68EB /* SWAPICommon.swift in Sources */,
|
||||
090012291BC6FECA0079C600 /* BaseCell.swift in Sources */,
|
||||
0900122D1BC6FECA0079C600 /* DummyPilotProvider.swift in Sources */,
|
||||
099022621BC123C000E76F43 /* AppDelegate.swift in Sources */,
|
||||
09D796131BC9A5BC003C68EB /* SWAPIPersonProvider.swift in Sources */,
|
||||
090012421BC7059E0079C600 /* Starship.swift in Sources */,
|
||||
0900123D1BC7012A0079C600 /* StarshipProviderAPI.swift in Sources */,
|
||||
0982AF0A1C50401800B62463 /* FakeStarshipProvider.swift in Sources */,
|
||||
09D7961B1BC9BE65003C68EB /* SWAPIStarshipProvider.swift in Sources */,
|
||||
0900122E1BC6FECA0079C600 /* HardCodedStarshipProvider.swift in Sources */,
|
||||
09D796071BC73E8B003C68EB /* StoryboardConstants.swift in Sources */,
|
||||
09D796031BC72691003C68EB /* StarshipCell.swift in Sources */,
|
||||
090012401BC704C60079C600 /* Person.swift in Sources */,
|
||||
090012441BC708A00079C600 /* DummyStarshipProvider.swift in Sources */,
|
||||
09D7960D1BC7431C003C68EB /* FetchableTrait.swift in Sources */,
|
||||
0900122F1BC6FECA0079C600 /* PlistPersonProvider.swift in Sources */,
|
||||
09D796191BC9BA49003C68EB /* DependencyContainers.swift in Sources */,
|
||||
09D796171BC9B53D003C68EB /* URLSessionNetworkLayer.swift in Sources */,
|
||||
);
|
||||
|
||||
@@ -7,21 +7,34 @@
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Dip
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
private let container = DependencyContainer()
|
||||
|
||||
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
|
||||
// Override point for customization after application launch.
|
||||
|
||||
//This is a composition root where container is configured and all dependencies are resolved
|
||||
configureContainer(container)
|
||||
|
||||
let personProvider = try! container.resolve() as PersonProviderAPI
|
||||
let starshipProvider = try! container.resolve() as StarshipProviderAPI
|
||||
|
||||
if let tabBarVC = self.window?.rootViewController as? UITabBarController,
|
||||
let vcs = tabBarVC.viewControllers as? [UINavigationController] {
|
||||
if let personListVC = vcs[0].topViewController as? PersonListViewController {
|
||||
personListVC.personProvider = personProvider
|
||||
personListVC.starshipProvider = starshipProvider
|
||||
personListVC.loadFirstPage()
|
||||
}
|
||||
if let starshipListVC = vcs[1].topViewController as? StarshipListViewController {
|
||||
starshipListVC.starshipProvider = starshipProvider
|
||||
starshipListVC.personProvider = personProvider
|
||||
starshipListVC.loadFirstPage()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,42 +17,53 @@ private let FAKE_STARSHIPS = false
|
||||
/* ---- */
|
||||
|
||||
|
||||
|
||||
// MARK: Dependency Container for WebServices & NetworkLayer
|
||||
let wsDependencies = DependencyContainer() { dip in
|
||||
// MARK: Dependency Container for Providers
|
||||
func configureContainer(dip: DependencyContainer) {
|
||||
|
||||
// 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(.Singleton) { URLSessionNetworkLayer(baseURL: "http://swapi.co/api/")! as NetworkLayer }
|
||||
|
||||
}
|
||||
|
||||
|
||||
// MARK: Dependency Container for Providers
|
||||
let providerDependencies = DependencyContainer() { dip in
|
||||
|
||||
if FAKE_PERSONS {
|
||||
|
||||
// 1) Register the PersonProviderAPI singleton, one generic and one specific for a specific personID
|
||||
dip.register(.Singleton) { DummyPilotProvider() as PersonProviderAPI }
|
||||
dip.register(tag: 0, .Singleton) { PlistPersonProvider(plist: "mainPilot") as PersonProviderAPI }
|
||||
// 1) Register fake persons provider
|
||||
//Here we use constructor injection for one of the dependencies property injection for another, and we provide dependencies manually
|
||||
dip.register() { FakePersonsProvider(dummyProvider: DummyPilotProvider()) as PersonProviderAPI }
|
||||
.resolveDependencies { (_, resolved: PersonProviderAPI) in
|
||||
//here we resolve optional dependencies
|
||||
//see what happens when you comment this out
|
||||
(resolved as! FakePersonsProvider).plistProvider = PlistPersonProvider(plist: "mainPilot")
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// 1) Register the SWAPIPersonProvider (that hits the real swapi.co WebService)
|
||||
dip.register(.Singleton) { SWAPIPersonProvider() as PersonProviderAPI }
|
||||
// Here we use constructor injection again, but let the container to resolve dependency for us
|
||||
dip.register() { SWAPIPersonProvider(webService: try dip.resolve()) as PersonProviderAPI }
|
||||
|
||||
}
|
||||
|
||||
if FAKE_STARSHIPS {
|
||||
|
||||
// 2) Register the StarshipProviderAPI factories, one generic and one specific for a specific starshipID
|
||||
dip.register() { HardCodedStarshipProvider() as StarshipProviderAPI }
|
||||
dip.register(tag: 0) { DummyStarshipProvider(pilotName: "Main Pilot") as StarshipProviderAPI }
|
||||
// 2) Register fake starships provider
|
||||
|
||||
//Here we register different implementations for the same protocol using tags
|
||||
dip.register(tag: "hardcoded") { HardCodedStarshipProvider() as StarshipProviderAPI }
|
||||
|
||||
//Here we register factory that will require a runtime argument
|
||||
dip.register(tag: "dummy") { DummyStarshipProvider(pilotName: $0) as StarshipProviderAPI }
|
||||
|
||||
//Here we use constructor injection, but instead of providing dependencies manually container resolves them for us
|
||||
dip.register() {
|
||||
FakeStarshipProvider(
|
||||
dummyProvider: try dip.resolve(tag: "dummy", withArguments: "Main Pilot"),
|
||||
hardCodedProvider: try dip.resolve(tag: "hardcoded")) as StarshipProviderAPI
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// 2) Register the SWAPIStarshipProvider (that hits the real swapi.co WebService)
|
||||
dip.register(.Singleton) { SWAPIStarshipProvider() as StarshipProviderAPI }
|
||||
// Here we use constructor injection again, but let the container to resolve dependency for us
|
||||
dip.register() { SWAPIStarshipProvider(webService: try dip.resolve()) as StarshipProviderAPI }
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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="9531" systemVersion="15C50" 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="9529"/>
|
||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
@@ -13,7 +13,6 @@
|
||||
<tabBar key="tabBar" contentMode="scaleToFill" id="hlX-Lx-tB1">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="49"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<animations/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
</tabBar>
|
||||
<connections>
|
||||
@@ -32,96 +31,83 @@
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="95" sectionHeaderHeight="28" sectionFooterHeight="28" id="EYu-RQ-JO9">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<animations/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="PersonCell" rowHeight="95" id="Sba-Wm-z4c" customClass="PersonCell" customModule="DipSampleApp" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="28" width="600" height="95"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="Sba-Wm-z4c" id="UE9-zV-NsG">
|
||||
<rect key="frame" x="0.0" y="0.0" width="567" height="94.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="567" height="94"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" text="Name:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gVy-Vx-i2h">
|
||||
<rect key="frame" x="8" y="8" width="49" height="19.5"/>
|
||||
<animations/>
|
||||
<rect key="frame" x="8" y="8" width="49" height="20"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" text="Height (cm):" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ymL-8F-Mpa">
|
||||
<rect key="frame" x="8" y="35" width="94" height="19.5"/>
|
||||
<animations/>
|
||||
<rect key="frame" x="8" y="36" width="94" height="20"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" text="Mass (kg):" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9sd-2A-Ixx">
|
||||
<rect key="frame" x="8" y="63" width="79" height="19.5"/>
|
||||
<animations/>
|
||||
<rect key="frame" x="8" y="64" width="79" height="20"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" text="Eyes:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="YPu-dh-Fhf">
|
||||
<rect key="frame" x="378" y="63" width="41" height="19.5"/>
|
||||
<animations/>
|
||||
<rect key="frame" x="378" y="64" width="41" height="20"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="1CQ-rV-Y7c">
|
||||
<rect key="frame" x="539" y="8" width="20" height="20"/>
|
||||
<animations/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="20" id="PK0-f1-YTg"/>
|
||||
<constraint firstAttribute="height" constant="20" id="TOR-23-CAr"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="-name-" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GhU-yA-Nkj">
|
||||
<rect key="frame" x="110" y="7" width="421" height="20.5"/>
|
||||
<animations/>
|
||||
<rect key="frame" x="110" y="7" width="421" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="-height-" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VLd-vh-c8m">
|
||||
<rect key="frame" x="110" y="34" width="260.5" height="20.5"/>
|
||||
<animations/>
|
||||
<rect key="frame" x="110" y="35" width="260" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="-mass-" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="zgG-Ij-dCY">
|
||||
<rect key="frame" x="110" y="62" width="260" height="20.5"/>
|
||||
<animations/>
|
||||
<rect key="frame" x="110" y="63" width="260" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="-hair-" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7TM-Aw-IPT">
|
||||
<rect key="frame" x="427" y="34" width="120" height="20.5"/>
|
||||
<animations/>
|
||||
<rect key="frame" x="427" y="35" width="120" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="-eyes-" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Hv7-qr-iAY">
|
||||
<rect key="frame" x="427" y="62" width="120" height="20.5"/>
|
||||
<animations/>
|
||||
<rect key="frame" x="427" y="63" width="120" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" text="Hair:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rDV-iQ-Ugd">
|
||||
<rect key="frame" x="378" y="35" width="36.5" height="19.5"/>
|
||||
<animations/>
|
||||
<rect key="frame" x="378" y="36" width="37" height="20"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<animations/>
|
||||
<constraints>
|
||||
<constraint firstItem="Hv7-qr-iAY" firstAttribute="baseline" secondItem="YPu-dh-Fhf" secondAttribute="baseline" id="590-8v-aT3"/>
|
||||
<constraint firstItem="zgG-Ij-dCY" firstAttribute="baseline" secondItem="9sd-2A-Ixx" secondAttribute="baseline" id="62H-PJ-mjV"/>
|
||||
@@ -152,7 +138,6 @@
|
||||
<constraint firstItem="rDV-iQ-Ugd" firstAttribute="leading" secondItem="VLd-vh-c8m" secondAttribute="trailing" constant="8" symbolic="YES" id="xL3-7N-f8f"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<animations/>
|
||||
<connections>
|
||||
<outlet property="eyesLabel" destination="Hv7-qr-iAY" id="bhd-4V-chX"/>
|
||||
<outlet property="genderImageView" destination="1CQ-rV-Y7c" id="8rr-1s-qr4"/>
|
||||
@@ -182,75 +167,65 @@
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="102" sectionHeaderHeight="28" sectionFooterHeight="28" id="uPj-BZ-JVx">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<animations/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="StarshipCell" rowHeight="102" id="rfe-RG-ql1" customClass="StarshipCell" customModule="DipSampleApp" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="28" width="600" height="102"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="rfe-RG-ql1" id="RHz-uO-ANq">
|
||||
<rect key="frame" x="0.0" y="0.0" width="567" height="101.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="567" height="101"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" text="Name:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pR4-Aq-S9H">
|
||||
<rect key="frame" x="8" y="8" width="49" height="19.5"/>
|
||||
<animations/>
|
||||
<rect key="frame" x="8" y="8" width="49" height="20"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" text="Model:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="V8k-Fh-teX">
|
||||
<rect key="frame" x="8" y="33" width="52" height="19.5"/>
|
||||
<animations/>
|
||||
<rect key="frame" x="8" y="34" width="52" height="20"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" text="Manufacturer:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="iWU-h6-HNF">
|
||||
<rect key="frame" x="8" y="61" width="108" height="19.5"/>
|
||||
<animations/>
|
||||
<rect key="frame" x="8" y="62" width="108" height="20"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" horizontalCompressionResistancePriority="1000" text="Crew:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JoH-bT-HcN">
|
||||
<rect key="frame" x="460" y="33" width="44.5" height="19.5"/>
|
||||
<animations/>
|
||||
<rect key="frame" x="459" y="34" width="45" height="20"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" horizontalCompressionResistancePriority="1000" text="Pass:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="arT-4h-pLt">
|
||||
<rect key="frame" x="463" y="61" width="41" height="19.5"/>
|
||||
<animations/>
|
||||
<rect key="frame" x="463" y="62" width="41" height="20"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="-name-" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Lwe-Ix-EQE">
|
||||
<rect key="frame" x="124" y="7" width="423" height="20.5"/>
|
||||
<animations/>
|
||||
<rect key="frame" x="124" y="7" width="423" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="500" text="-model-" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kIz-wz-fGn">
|
||||
<rect key="frame" x="124" y="32" width="328" height="20.5"/>
|
||||
<animations/>
|
||||
<rect key="frame" x="124" y="33" width="327" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="500" text="-manufacturer-" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fVY-W3-78a">
|
||||
<rect key="frame" x="124" y="60" width="331" height="20.5"/>
|
||||
<animations/>
|
||||
<rect key="frame" x="124" y="61" width="331" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="-crew-" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WGV-Ar-PMC">
|
||||
<rect key="frame" x="512" y="32" width="35" height="20.5"/>
|
||||
<animations/>
|
||||
<rect key="frame" x="512" y="33" width="35" height="21"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="35" id="tsk-nr-zwr"/>
|
||||
</constraints>
|
||||
@@ -259,14 +234,12 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="-pass-" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="YnA-qH-8AA">
|
||||
<rect key="frame" x="512" y="60" width="35" height="20.5"/>
|
||||
<animations/>
|
||||
<rect key="frame" x="512" y="61" width="35" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<animations/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="YnA-qH-8AA" secondAttribute="trailing" constant="20" symbolic="YES" id="0iR-Ax-Tba"/>
|
||||
<constraint firstItem="pR4-Aq-S9H" firstAttribute="leading" secondItem="iWU-h6-HNF" secondAttribute="leading" id="1dW-gb-Qyn"/>
|
||||
@@ -294,7 +267,6 @@
|
||||
<constraint firstItem="WGV-Ar-PMC" firstAttribute="leading" secondItem="YnA-qH-8AA" secondAttribute="leading" id="ycM-ic-t05"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<animations/>
|
||||
<connections>
|
||||
<outlet property="crewLabel" destination="WGV-Ar-PMC" id="f6d-9q-59z"/>
|
||||
<outlet property="manufacturerLabel" destination="fVY-W3-78a" id="ABE-NG-bZY"/>
|
||||
@@ -324,7 +296,6 @@
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" id="VfI-ho-mqu">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<animations/>
|
||||
</navigationBar>
|
||||
<connections>
|
||||
<segue destination="nuL-fZ-hCQ" kind="relationship" relationship="rootViewController" id="l50-ri-Dfy"/>
|
||||
@@ -342,7 +313,6 @@
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" id="YPS-Al-CkA">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<animations/>
|
||||
</navigationBar>
|
||||
<connections>
|
||||
<segue destination="I87-Zh-w4A" kind="relationship" relationship="rootViewController" id="07W-Xm-zjJ"/>
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
//
|
||||
// DummyPilotProvider.swift
|
||||
// Dip
|
||||
//
|
||||
// Created by Olivier Halligon on 12/09/2015.
|
||||
// Copyright © 2015 AliSoftware. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct DummyPilotProvider : PersonProviderAPI {
|
||||
|
||||
func fetchIDs(completion: [Int] -> Void) {
|
||||
completion(Array(0..<5))
|
||||
}
|
||||
|
||||
func fetch(id: Int, completion: Person? -> Void) {
|
||||
completion(dummyPerson(id))
|
||||
}
|
||||
|
||||
private func dummyPerson(idx: Int) -> Person {
|
||||
let colors = ["blue", "brown", "yellow", "orange", "red", "dark"]
|
||||
let genders: [Gender?] = [Gender.Male, Gender.Female, nil]
|
||||
return Person(
|
||||
name: "John Dummy Doe #\(idx)",
|
||||
height: 150 + (idx*27%40),
|
||||
mass: 50 + (idx*7%30),
|
||||
hairColor: colors[idx*3%colors.count],
|
||||
eyeColor: colors[idx*2%colors.count],
|
||||
gender: genders[idx%3],
|
||||
starshipIDs: [idx % 3, 2*idx % 4]
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
//
|
||||
// DummyStarshipProvider.swift
|
||||
// Dip
|
||||
//
|
||||
// Created by Olivier Halligon on 08/10/2015.
|
||||
// Copyright © 2015 AliSoftware. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct DummyStarshipProvider : StarshipProviderAPI {
|
||||
var pilotName: String
|
||||
|
||||
func fetchIDs(completion: [Int] -> Void) {
|
||||
let nbShips = pilotName.characters.count
|
||||
completion(Array(0..<nbShips))
|
||||
}
|
||||
|
||||
func fetch(id: Int, completion: Starship? -> Void) {
|
||||
completion(dummyStarship(id))
|
||||
}
|
||||
|
||||
private func dummyStarship(idx: Int) -> Starship {
|
||||
return Starship(
|
||||
name: "\(pilotName)'s awesome starship #\(idx)",
|
||||
model: "\(pilotName)Ship",
|
||||
manufacturer: "Dummy Industries",
|
||||
crew: 1 + (idx%3),
|
||||
passengers: 10 + (idx*7 % 40),
|
||||
pilotIDs: [idx]
|
||||
)
|
||||
}
|
||||
}
|
||||
+54
-1
@@ -2,12 +2,39 @@
|
||||
// PlistPersonProvider.swift
|
||||
// Dip
|
||||
//
|
||||
// Created by Olivier Halligon on 12/09/2015.
|
||||
// Created by Ilya Puchka on 12/09/2015.
|
||||
// Copyright © 2015 AliSoftware. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
///Provides some dummy Person entities
|
||||
struct DummyPilotProvider : PersonProviderAPI {
|
||||
|
||||
func fetchIDs(completion: [Int] -> Void) {
|
||||
completion(Array(0..<5))
|
||||
}
|
||||
|
||||
func fetch(id: Int, completion: Person? -> Void) {
|
||||
completion(dummyPerson(id))
|
||||
}
|
||||
|
||||
private func dummyPerson(idx: Int) -> Person {
|
||||
let colors = ["blue", "brown", "yellow", "orange", "red", "dark"]
|
||||
let genders: [Gender?] = [Gender.Male, Gender.Female, nil]
|
||||
return Person(
|
||||
name: "John Dummy Doe #\(idx)",
|
||||
height: 150 + (idx*27%40),
|
||||
mass: 50 + (idx*7%30),
|
||||
hairColor: colors[idx*3%colors.count],
|
||||
eyeColor: colors[idx*2%colors.count],
|
||||
gender: genders[idx%3],
|
||||
starshipIDs: [idx % 3, 2*idx % 4]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
///Provides Person entities reading then from plist file
|
||||
class PlistPersonProvider : PersonProviderAPI {
|
||||
let people: [Person]
|
||||
|
||||
@@ -59,3 +86,29 @@ class PlistPersonProvider : PersonProviderAPI {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class FakePersonsProvider: PersonProviderAPI {
|
||||
|
||||
let dummyProvider: PersonProviderAPI
|
||||
var plistProvider: PersonProviderAPI!
|
||||
|
||||
//In this class we use both constructor injection and property injection,
|
||||
//nil is a valid local default
|
||||
init(dummyProvider: PersonProviderAPI) {
|
||||
self.dummyProvider = dummyProvider
|
||||
}
|
||||
|
||||
func fetchIDs(completion: [Int] -> Void) {
|
||||
dummyProvider.fetchIDs(completion)
|
||||
}
|
||||
|
||||
func fetch(id: Int, completion: Person? -> Void) {
|
||||
if let plistProvider = plistProvider where id == 0 {
|
||||
plistProvider.fetch(id, completion: completion)
|
||||
}
|
||||
else {
|
||||
dummyProvider.fetch(id, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// FakeStarshipProvider.swift
|
||||
// DipSampleApp
|
||||
//
|
||||
// Created by Ilya Puchka on 20.01.16.
|
||||
// Copyright © 2016 AliSoftware. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
///Provides some dummy Starship entities
|
||||
struct DummyStarshipProvider : StarshipProviderAPI {
|
||||
var pilotName: String
|
||||
|
||||
func fetchIDs(completion: [Int] -> Void) {
|
||||
let nbShips = pilotName.characters.count
|
||||
completion(Array(0..<nbShips))
|
||||
}
|
||||
|
||||
func fetch(id: Int, completion: Starship? -> Void) {
|
||||
completion(dummyStarship(id))
|
||||
}
|
||||
|
||||
private func dummyStarship(idx: Int) -> Starship {
|
||||
return Starship(
|
||||
name: "\(pilotName)'s awesome starship #\(idx)",
|
||||
model: "\(pilotName)Ship",
|
||||
manufacturer: "Dummy Industries",
|
||||
crew: 1 + (idx%3),
|
||||
passengers: 10 + (idx*7 % 40),
|
||||
pilotIDs: [idx]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
///Provides hardcoded Starship entities stored in memory
|
||||
class HardCodedStarshipProvider : StarshipProviderAPI {
|
||||
|
||||
let starships = [
|
||||
Starship(name: "First Ship", model: "AwesomeShip", manufacturer: "HardCoded Inc.", crew: 3, passengers: 20, pilotIDs: [1,2]),
|
||||
Starship(name: "Second Ship", model: "AwesomeShip Express", manufacturer: "HardCoded Inc.", crew: 4, passengers: 10, pilotIDs: [1]),
|
||||
Starship(name: "Third Ship", model: "AwesomeShip Cargo", manufacturer: "HardCoded Inc.", crew: 12, passengers: 150, pilotIDs: [2]),
|
||||
] + Array(4..<75).map { Starship(name: "Ship #\($0)", model: "AwesomeShip Fighter", manufacturer: "HardCoded Inc.", crew: 1, passengers: 2, pilotIDs: [1]) }
|
||||
|
||||
func fetchIDs(completion: [Int] -> Void) {
|
||||
completion(Array(0..<starships.count))
|
||||
}
|
||||
|
||||
func fetch(id: Int, completion: Starship? -> Void) {
|
||||
guard id < starships.count else {
|
||||
completion(nil)
|
||||
return
|
||||
}
|
||||
completion(starships[id])
|
||||
}
|
||||
}
|
||||
|
||||
class FakeStarshipProvider: StarshipProviderAPI {
|
||||
|
||||
let dummyProvider: StarshipProviderAPI
|
||||
let hardCodedProvider: StarshipProviderAPI
|
||||
|
||||
//Constructor injection again here
|
||||
init(dummyProvider: StarshipProviderAPI, hardCodedProvider: StarshipProviderAPI) {
|
||||
self.dummyProvider = dummyProvider
|
||||
self.hardCodedProvider = hardCodedProvider
|
||||
}
|
||||
|
||||
func fetchIDs(completion: [Int] -> Void) {
|
||||
hardCodedProvider.fetchIDs(completion)
|
||||
}
|
||||
|
||||
func fetch(id: Int, completion: Starship? -> Void) {
|
||||
if id == 0 {
|
||||
dummyProvider.fetch(id, completion: completion)
|
||||
}
|
||||
else {
|
||||
hardCodedProvider.fetch(id, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
//
|
||||
// HardCodedStarshipProvider.swift
|
||||
// Dip
|
||||
//
|
||||
// Created by Olivier Halligon on 11/09/2015.
|
||||
// Copyright © 2015 AliSoftware. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class HardCodedStarshipProvider : StarshipProviderAPI {
|
||||
|
||||
let starships = [
|
||||
Starship(name: "First Ship", model: "AwesomeShip", manufacturer: "HardCoded Inc.", crew: 3, passengers: 20, pilotIDs: [1,2]),
|
||||
Starship(name: "Second Ship", model: "AwesomeShip Express", manufacturer: "HardCoded Inc.", crew: 4, passengers: 10, pilotIDs: [1]),
|
||||
Starship(name: "Third Ship", model: "AwesomeShip Cargo", manufacturer: "HardCoded Inc.", crew: 12, passengers: 150, pilotIDs: [2]),
|
||||
] + Array(4..<75).map { Starship(name: "Ship #\($0)", model: "AwesomeShip Fighter", manufacturer: "HardCoded Inc.", crew: 1, passengers: 2, pilotIDs: [1]) }
|
||||
|
||||
func fetchIDs(completion: [Int] -> Void) {
|
||||
completion(Array(0..<starships.count))
|
||||
}
|
||||
|
||||
func fetch(id: Int, completion: Starship? -> Void) {
|
||||
guard id < starships.count else {
|
||||
completion(nil)
|
||||
return
|
||||
}
|
||||
completion(starships[id])
|
||||
}
|
||||
}
|
||||
@@ -13,12 +13,6 @@ enum SWAPIError: ErrorType {
|
||||
case InvalidJSON
|
||||
}
|
||||
|
||||
enum WebService: String {
|
||||
case PersonWS
|
||||
case StarshipWS
|
||||
var tag: DependencyContainer.Tag { return DependencyContainer.Tag.String(self.rawValue) }
|
||||
}
|
||||
|
||||
func idFromURLString(urlString: String) -> Int? {
|
||||
let url = NSURL(string: urlString)
|
||||
let idString = url.flatMap { $0.lastPathComponent }
|
||||
|
||||
@@ -8,8 +8,17 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
///Provides Person entitis fetching them with web service
|
||||
struct SWAPIPersonProvider : PersonProviderAPI {
|
||||
let ws = wsDependencies.resolve(tag: WebService.PersonWS.tag) as NetworkLayer
|
||||
let ws: NetworkLayer
|
||||
|
||||
//Here we inject dependency using _constructor injection_ pattern.
|
||||
//The alternative way is a _property injection_
|
||||
//but it should be used only for optional dependencies
|
||||
//where there is a good local default implementation
|
||||
init(webService: NetworkLayer) {
|
||||
self.ws = webService
|
||||
}
|
||||
|
||||
func fetchIDs(completion: [Int] -> Void) {
|
||||
ws.request("people") { response in
|
||||
|
||||
@@ -8,8 +8,17 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
///Provides Starship entities fetching them using web service
|
||||
struct SWAPIStarshipProvider : StarshipProviderAPI {
|
||||
let ws = wsDependencies.resolve(tag: WebService.StarshipWS.tag) as NetworkLayer
|
||||
let ws: NetworkLayer
|
||||
|
||||
//Here we inject dependency using _constructor injection_ pattern.
|
||||
//The alternative way is a _property injection_
|
||||
//but it should be used only for optional dependencies
|
||||
//where there is a good local default implementation
|
||||
init(webService: NetworkLayer) {
|
||||
self.ws = webService
|
||||
}
|
||||
|
||||
func fetchIDs(completion: [Int] -> Void) {
|
||||
ws.request("starships") { response in
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
///NetworkLayer implementation on top of NSURLSession
|
||||
struct URLSessionNetworkLayer : NetworkLayer {
|
||||
let baseURL: NSURL
|
||||
let session: NSURLSession
|
||||
|
||||
@@ -11,14 +11,16 @@ import UIKit
|
||||
class PersonListViewController: UITableViewController, FetchableTrait {
|
||||
var objects: [Person]?
|
||||
var batchRequestID = 0
|
||||
|
||||
var personProvider: PersonProviderAPI!
|
||||
var starshipProvider: StarshipProviderAPI!
|
||||
|
||||
func fetchIDs(completion: [Int] -> Void) {
|
||||
let provider = providerDependencies.resolve() as PersonProviderAPI
|
||||
return provider.fetchIDs(completion)
|
||||
return personProvider.fetchIDs(completion)
|
||||
}
|
||||
|
||||
func fetchOne(personID: Int, completion: Person? -> Void) {
|
||||
let provider = providerDependencies.resolve(tag: .Int(personID)) as PersonProviderAPI
|
||||
return provider.fetch(personID, completion: completion)
|
||||
return personProvider.fetch(personID, completion: completion)
|
||||
}
|
||||
|
||||
var fetchProgress: (current: Int, total: Int?) = (0, nil) {
|
||||
@@ -37,7 +39,7 @@ class PersonListViewController: UITableViewController, FetchableTrait {
|
||||
else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
destVC.starshipProvider = starshipProvider
|
||||
destVC.loadObjects(person.starshipIDs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,15 +13,14 @@ class StarshipListViewController : UITableViewController, FetchableTrait {
|
||||
var objects: [Starship]?
|
||||
var batchRequestID = 0
|
||||
|
||||
private func provider(tag:Int?) -> StarshipProviderAPI {
|
||||
return providerDependencies.resolve(tag: tag.flatMap { .Int($0) })
|
||||
}
|
||||
var starshipProvider: StarshipProviderAPI!
|
||||
var personProvider: PersonProviderAPI!
|
||||
|
||||
func fetchIDs(completion: [Int] -> Void) {
|
||||
provider(nil).fetchIDs(completion)
|
||||
starshipProvider.fetchIDs(completion)
|
||||
}
|
||||
func fetchOne(shipID:Int, completion: Starship? -> Void) {
|
||||
provider(shipID).fetch(shipID, completion: completion)
|
||||
starshipProvider.fetch(shipID, completion: completion)
|
||||
}
|
||||
|
||||
var fetchProgress: (current: Int, total: Int?) = (0, nil) {
|
||||
@@ -41,6 +40,7 @@ class StarshipListViewController : UITableViewController, FetchableTrait {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
destVC.personProvider = personProvider
|
||||
destVC.loadObjects(starship.pilotIDs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,9 +23,9 @@ class SWAPIPersonProviderTests: XCTestCase {
|
||||
|
||||
func testFetchPersonIDs() {
|
||||
let mock = NetworkMock(json: ["results": [fakePerson1, fakePerson2]])
|
||||
wsDependencies.register(tag: WebService.PersonWS.tag, .Singleton) { mock as NetworkLayer }
|
||||
wsDependencies.register(.Singleton) { mock as NetworkLayer }
|
||||
|
||||
let provider = SWAPIPersonProvider()
|
||||
let provider = SWAPIPersonProvider(webService: try! wsDependencies.resolve())
|
||||
provider.fetchIDs { personIDs in
|
||||
XCTAssertNotNil(personIDs)
|
||||
XCTAssertEqual(personIDs.count, 2)
|
||||
@@ -36,11 +36,10 @@ class SWAPIPersonProviderTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testFetchOnePerson() {
|
||||
|
||||
let mock = NetworkMock(json: fakePerson1)
|
||||
wsDependencies.register(tag: WebService.PersonWS.tag, .Singleton) { mock as NetworkLayer }
|
||||
wsDependencies.register(.Singleton) { mock as NetworkLayer }
|
||||
|
||||
let provider = SWAPIPersonProvider()
|
||||
let provider = SWAPIPersonProvider(webService: try! wsDependencies.resolve())
|
||||
provider.fetch(1) { person in
|
||||
XCTAssertNotNil(person)
|
||||
XCTAssertEqual(person?.name, "John Doe")
|
||||
@@ -58,9 +57,9 @@ class SWAPIPersonProviderTests: XCTestCase {
|
||||
func testFetchInvalidPerson() {
|
||||
let json = ["error":"whoops"]
|
||||
let mock = NetworkMock(json: json)
|
||||
wsDependencies.register(tag: WebService.PersonWS.tag, .Singleton) { mock as NetworkLayer }
|
||||
wsDependencies.register(.Singleton) { mock as NetworkLayer }
|
||||
|
||||
let provider = SWAPIPersonProvider()
|
||||
let provider = SWAPIPersonProvider(webService: try! wsDependencies.resolve())
|
||||
provider.fetch(12) { person in
|
||||
XCTAssertNil(person)
|
||||
}
|
||||
|
||||
@@ -23,9 +23,9 @@ class SWAPIStarshipProviderTests: XCTestCase {
|
||||
|
||||
func testFetchStarshipIDs() {
|
||||
let mock = NetworkMock(json: ["results": [fakeShip1, fakeShip2]])
|
||||
wsDependencies.register(tag: WebService.StarshipWS.tag, .Singleton) { mock as NetworkLayer }
|
||||
wsDependencies.register(.Singleton) { mock as NetworkLayer }
|
||||
|
||||
let provider = SWAPIStarshipProvider()
|
||||
let provider = SWAPIStarshipProvider(webService: try! wsDependencies.resolve())
|
||||
provider.fetchIDs { shipIDs in
|
||||
XCTAssertNotNil(shipIDs)
|
||||
XCTAssertEqual(shipIDs.count, 2)
|
||||
@@ -38,9 +38,9 @@ class SWAPIStarshipProviderTests: XCTestCase {
|
||||
func testFetchOneStarship() {
|
||||
|
||||
let mock = NetworkMock(json: fakeShip1)
|
||||
wsDependencies.register(tag: WebService.StarshipWS.tag, .Singleton) { mock as NetworkLayer }
|
||||
wsDependencies.register(.Singleton) { mock as NetworkLayer }
|
||||
|
||||
let provider = SWAPIStarshipProvider()
|
||||
let provider = SWAPIStarshipProvider(webService: try! wsDependencies.resolve())
|
||||
provider.fetch(1) { starship in
|
||||
XCTAssertNotNil(starship)
|
||||
XCTAssertEqual(starship?.name, "Falcon")
|
||||
@@ -57,9 +57,9 @@ class SWAPIStarshipProviderTests: XCTestCase {
|
||||
func testFetchInvalidStarship() {
|
||||
let json = ["error":"whoops"]
|
||||
let mock = NetworkMock(json: json)
|
||||
wsDependencies.register(tag: WebService.StarshipWS.tag, .Singleton) { mock as NetworkLayer }
|
||||
wsDependencies.register(.Singleton) { mock as NetworkLayer }
|
||||
|
||||
let provider = SWAPIStarshipProvider()
|
||||
let provider = SWAPIStarshipProvider(webService: try! wsDependencies.resolve())
|
||||
provider.fetch(12) { starship in
|
||||
XCTAssertNil(starship)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
//
|
||||
// Dip
|
||||
//
|
||||
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
extension DependencyContainer {
|
||||
|
||||
/**
|
||||
Resolves properties of passed object wrapped with `Injected<T>` or `InjectedWeak<T>`
|
||||
*/
|
||||
func autoInjectProperties(instance: Any) throws {
|
||||
try Mirror(reflecting: instance).children.forEach(_resolveChild)
|
||||
}
|
||||
|
||||
private func _resolveChild(child: Mirror.Child) throws {
|
||||
guard let injectedPropertyBox = child.value as? AutoInjectedPropertyBox else { return }
|
||||
|
||||
do {
|
||||
try injectedPropertyBox.resolve(self)
|
||||
}
|
||||
catch {
|
||||
throw DipError.AutoInjectionFailed(label: child.label, type: injectedPropertyBox.dynamicType.wrappedType, underlyingError: error)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
Implement this protocol if you want to use your own type to wrap auto-injected properties
|
||||
instead of using `Injected<T>` or `InjectedWeak<T>` types.
|
||||
|
||||
**Example**:
|
||||
```swift
|
||||
class MyCustomBox<T> {
|
||||
private(set) var value: T?
|
||||
init() {}
|
||||
}
|
||||
|
||||
extension MyCustomBox: AutoInjectedPropertyBox {
|
||||
static var wrappedType: Any.Type { return T.self }
|
||||
|
||||
func resolve(container: DependencyContainer) throws {
|
||||
value = try container.resolve() as T
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
*/
|
||||
public protocol AutoInjectedPropertyBox: class {
|
||||
///The type of wrapped property.
|
||||
static var wrappedType: Any.Type { get }
|
||||
|
||||
/**
|
||||
This method will be called by `DependencyContainer` during processing resolved instance properties.
|
||||
In this method you should resolve an instance for wrapped property and store a reference to it.
|
||||
|
||||
- parameter container: A container to be used to resolve an instance
|
||||
|
||||
-note: This method is not intended to be called manually, `DependencyContainer` will call it by itself.
|
||||
*/
|
||||
func resolve(container: DependencyContainer) throws
|
||||
}
|
||||
|
||||
/**
|
||||
Use this wrapper to identify _strong_ properties of the instance that should be
|
||||
auto-injected by `DependencyContainer`. Type T can be any type.
|
||||
|
||||
- warning: Do not define this property as optional or container will not be able to inject it.
|
||||
Instead define it with initial value of `Injected<T>()`.
|
||||
|
||||
**Example**:
|
||||
|
||||
```swift
|
||||
class ClientImp: Client {
|
||||
var service = Injected<Service>()
|
||||
}
|
||||
|
||||
```
|
||||
- seealso: `InjectedWeak`
|
||||
|
||||
*/
|
||||
public final class Injected<T>: _InjectedPropertyBox<T>, AutoInjectedPropertyBox {
|
||||
|
||||
public static var wrappedType: Any.Type {
|
||||
return T.self
|
||||
}
|
||||
|
||||
///Wrapped value.
|
||||
public private(set) var value: T? {
|
||||
didSet {
|
||||
if let value = value { didInject(value) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a new wrapper for auto-injected property.
|
||||
|
||||
- parameters:
|
||||
- required: Defines if the property is required or not.
|
||||
If container fails to inject required property it will als fail to resolve
|
||||
the instance that defines that property. Default is `true`.
|
||||
- tag: An optional tag to use to lookup definitions when injecting this property. Default is `nil`.
|
||||
- didInject: block that will be called when concrete instance is injected in this property.
|
||||
Similar to `didSet` property observer. Default value does nothing.
|
||||
*/
|
||||
public override init(required: Bool = true, tag: DependencyContainer.Tag? = nil, didInject: T -> () = { _ in }) {
|
||||
super.init(required: required, tag: tag, didInject: didInject)
|
||||
}
|
||||
|
||||
public func resolve(container: DependencyContainer) throws {
|
||||
let resolved: T? = try super.resolve(container)
|
||||
value = resolved
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
Use this wrapper to identify _weak_ properties of the instance that should be
|
||||
auto-injected by `DependencyContainer`. Type T should be a **class** type.
|
||||
Otherwise it will cause runtime exception when container will try to resolve the property.
|
||||
Use this wrapper to define one of two circular dependencies to avoid retain cycle.
|
||||
|
||||
- note: The only difference between `InjectedWeak` and `Injected` is that `InjectedWeak` uses
|
||||
_weak_ reference to store underlying value, when `Injected` uses _strong_ reference.
|
||||
For that reason if you resolve instance that has a _weak_ auto-injected property this property
|
||||
will be released when `resolve` will complete.
|
||||
|
||||
Use `InjectedWeak<T>` to define one of two circular dependecies if another dependency is defined as `Injected<U>`.
|
||||
This will prevent a retain cycle between resolved instances.
|
||||
|
||||
- warning: Do not define this property as optional or container will not be able to inject it.
|
||||
Instead define it with initial value of `InjectedWeak<T>()`.
|
||||
|
||||
**Example**:
|
||||
|
||||
```swift
|
||||
class ServiceImp: Service {
|
||||
var client = InjectedWeak<Client>()
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
- seealso: `Injected`
|
||||
|
||||
*/
|
||||
public final class InjectedWeak<T>: _InjectedPropertyBox<T>, AutoInjectedPropertyBox {
|
||||
|
||||
//Only classes (means AnyObject) can be used as `weak` properties
|
||||
//but we can not make <T: AnyObject> because that will prevent using protocol as generic type
|
||||
//so we just rely on user reading documentation and passing AnyObject in runtime
|
||||
//also we will throw fatal error if type can not be casted to AnyObject during resolution.
|
||||
|
||||
public static var wrappedType: Any.Type {
|
||||
return T.self
|
||||
}
|
||||
|
||||
weak var _value: AnyObject? = nil {
|
||||
didSet {
|
||||
if let value = value { didInject(value) }
|
||||
}
|
||||
}
|
||||
|
||||
///Wrapped value.
|
||||
public var value: T? {
|
||||
return _value as? T
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a new wrapper for weak auto-injected property.
|
||||
|
||||
- parameters:
|
||||
- required: Defines if the property is required or not.
|
||||
If container fails to inject required property it will als fail to resolve
|
||||
the instance that defines that property. Default is `true`.
|
||||
- tag: An optional tag to use to lookup definitions when injecting this property. Default is `nil`.
|
||||
- didInject: block that will be called when concrete instance is injected in this property.
|
||||
Similar to `didSet` property observer. Default value does nothing.
|
||||
*/
|
||||
public override init(required: Bool = true, tag: DependencyContainer.Tag? = nil, didInject: T -> () = { _ in }) {
|
||||
super.init(required: required, tag: tag, didInject: didInject)
|
||||
}
|
||||
|
||||
public func resolve(container: DependencyContainer) throws {
|
||||
let resolved: T? = try super.resolve(container)
|
||||
if required && !(resolved is AnyObject) {
|
||||
fatalError("\(T.self) can not be casted to AnyObject. InjectedWeak wrapper should be used to wrap only classes.")
|
||||
}
|
||||
_value = resolved as? AnyObject
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class _InjectedPropertyBox<T> {
|
||||
|
||||
let required: Bool
|
||||
let didInject: T -> ()
|
||||
let tag: DependencyContainer.Tag?
|
||||
|
||||
init(required: Bool = true, tag: DependencyContainer.Tag?, didInject: T -> () = { _ in }) {
|
||||
self.required = required
|
||||
self.tag = tag
|
||||
self.didInject = didInject
|
||||
}
|
||||
|
||||
private func resolve(container: DependencyContainer) throws -> T? {
|
||||
let resolved: T?
|
||||
if required {
|
||||
resolved = try container.resolve(tag: tag) as T
|
||||
}
|
||||
else {
|
||||
resolved = try? container.resolve(tag: tag) as T
|
||||
}
|
||||
return resolved
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
///A key used to store definitons in a container.
|
||||
public struct DefinitionKey : Hashable, CustomStringConvertible {
|
||||
public let protocolType: Any.Type
|
||||
public let factoryType: Any.Type
|
||||
public let associatedTag: DependencyContainer.Tag?
|
||||
|
||||
init(protocolType: Any.Type, factoryType: Any.Type, associatedTag: DependencyContainer.Tag? = nil) {
|
||||
self.protocolType = protocolType
|
||||
self.factoryType = factoryType
|
||||
self.associatedTag = associatedTag
|
||||
}
|
||||
|
||||
public var hashValue: Int {
|
||||
return "\(protocolType)-\(factoryType)-\(associatedTag)".hashValue
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
return "type: \(protocolType), factory: \(factoryType), tag: \(associatedTag.desc)"
|
||||
}
|
||||
}
|
||||
|
||||
/// Check two definition keys on equality by comparing their `protocolType`, `factoryType` and `associatedTag` properties.
|
||||
public func ==(lhs: DefinitionKey, rhs: DefinitionKey) -> Bool {
|
||||
return
|
||||
lhs.protocolType == rhs.protocolType &&
|
||||
lhs.factoryType == rhs.factoryType &&
|
||||
lhs.associatedTag == rhs.associatedTag
|
||||
}
|
||||
|
||||
///Component scope defines a strategy used by the `DependencyContainer` to manage resolved instances life cycle.
|
||||
public enum ComponentScope {
|
||||
/**
|
||||
A new instance will be created every time it's resolved.
|
||||
This is a default strategy. Use this strategy when you don't want instances to be shared
|
||||
between different consumers (i.e. if it is not thread safe).
|
||||
|
||||
**Example**:
|
||||
|
||||
```
|
||||
container.register { ServiceImp() as Service }
|
||||
container.register {
|
||||
ServiceConsumerImp(
|
||||
service1: try container.resolve() as Service
|
||||
service2: try container.resolve() as Service
|
||||
) as ServiceConsumer
|
||||
}
|
||||
let consumer = container.resolve() as ServiceConsumer
|
||||
consumer.service1 !== consumer.service2 //true
|
||||
|
||||
```
|
||||
*/
|
||||
case Prototype
|
||||
|
||||
/**
|
||||
Instance resolved with the same definition will be reused until topmost `resolve(tag:)` method returns.
|
||||
When you resolve the same object graph again the container will create new instances.
|
||||
Use this strategy if you want different object in objects graph to share the same instance.
|
||||
|
||||
- warning: Make sure this component is thread safe or accessed always from the same thread.
|
||||
|
||||
**Example**:
|
||||
|
||||
```
|
||||
container.register(.ObjectGraph) { ServiceImp() as Service }
|
||||
container.register {
|
||||
ServiceConsumerImp(
|
||||
service1: try container.resolve() as Service
|
||||
service2: try container.resolve() as Service
|
||||
) as ServiceConsumer
|
||||
}
|
||||
let consumer1 = container.resolve() as ServiceConsumer
|
||||
let consumer2 = container.resolve() as ServiceConsumer
|
||||
consumer1.service1 === consumer1.service2 //true
|
||||
consumer2.service1 === consumer2.service2 //true
|
||||
consumer1.service1 !== consumer2.service1 //true
|
||||
```
|
||||
*/
|
||||
case ObjectGraph
|
||||
|
||||
/**
|
||||
Resolved instance will be retained by the container and always reused.
|
||||
Do not mix this life cycle with _singleton pattern_.
|
||||
Instance will be not shared between different containers.
|
||||
|
||||
- warning: Make sure this component is thread safe or accessed always from the same thread.
|
||||
|
||||
- note: When you override or remove definition from the container an instance
|
||||
that was resolved with this definition will be released. When you reset
|
||||
the container it will release all singleton instances.
|
||||
|
||||
**Example**:
|
||||
|
||||
```
|
||||
container.register(.Singleton) { ServiceImp() as Service }
|
||||
container.register {
|
||||
ServiceConsumerImp(
|
||||
service1: try container.resolve() as Service
|
||||
service2: try container.resolve() as Service
|
||||
) as ServiceConsumer
|
||||
}
|
||||
let consumer1 = container.resolve() as ServiceConsumer
|
||||
let consumer2 = container.resolve() as ServiceConsumer
|
||||
consumer1.service1 === consumer1.service2 //true
|
||||
consumer2.service1 === consumer2.service2 //true
|
||||
consumer1.service1 === consumer2.service1 //true
|
||||
```
|
||||
*/
|
||||
case Singleton
|
||||
}
|
||||
|
||||
/**
|
||||
`DefinitionOf<T, F>` describes how instances of type `T` should be created when this type is resolved by the `DependencyContainer`.
|
||||
|
||||
- `T` is the type of the instance to resolve
|
||||
- `F` is the type of the factory that will create an instance of T.
|
||||
|
||||
For example `DefinitionOf<Service, (String) -> Service>` is the type of definition that will create an instance of type `Service` using factory that accepts `String` argument.
|
||||
*/
|
||||
public final class DefinitionOf<T, F>: Definition {
|
||||
|
||||
/**
|
||||
Set the block that will be used to resolve dependencies of the instance.
|
||||
This block will be called before `resolve(tag:)` returns. It can be set only once.
|
||||
|
||||
- parameter block: The block to use to resolve dependencies of the instance.
|
||||
|
||||
- returns: modified definition
|
||||
|
||||
- note: To resolve circular dependencies at least one of them should use this block
|
||||
to resolve its dependencies. Otherwise the application will enter an infinite loop and crash.
|
||||
|
||||
**Example**
|
||||
|
||||
```swift
|
||||
container.register { ClientImp(service: try container.resolve() as Service) as Client }
|
||||
|
||||
container.register { ServiceImp() as Service }
|
||||
.resolveDependencies { container, service in
|
||||
service.client = try container.resolve() as Client
|
||||
}
|
||||
```
|
||||
|
||||
*/
|
||||
public func resolveDependencies(block: (DependencyContainer, T) throws -> ()) -> DefinitionOf<T, F> {
|
||||
guard resolveDependenciesBlock == nil else {
|
||||
fatalError("You can not change resolveDependencies block after it was set.")
|
||||
}
|
||||
self.resolveDependenciesBlock = block
|
||||
return self
|
||||
}
|
||||
|
||||
/// Calls `resolveDependencies` block if it was set.
|
||||
func resolveDependenciesOf(resolvedInstance: Any, withContainer container: DependencyContainer) throws {
|
||||
guard let resolvedInstance = resolvedInstance as? T else { return }
|
||||
try self.resolveDependenciesBlock?(container, resolvedInstance)
|
||||
}
|
||||
|
||||
let factory: F
|
||||
private(set) var scope: ComponentScope = .Prototype
|
||||
|
||||
private(set) var resolveDependenciesBlock: ((DependencyContainer, T) throws -> ())?
|
||||
|
||||
public init(scope: ComponentScope, factory: F) {
|
||||
self.factory = factory
|
||||
self.scope = scope
|
||||
}
|
||||
|
||||
private var _resolvedInstance: T?
|
||||
|
||||
}
|
||||
|
||||
///Dummy protocol to store definitions for different types in collection
|
||||
public protocol Definition: class { }
|
||||
|
||||
protocol _Definition: Definition {
|
||||
var scope: ComponentScope { get }
|
||||
}
|
||||
|
||||
extension DefinitionOf: _Definition { }
|
||||
|
||||
extension DefinitionOf: CustomStringConvertible {
|
||||
public var description: String {
|
||||
return "type: \(T.self), factory: \(F.self), scope: \(scope)"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,429 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
// MARK: - DependencyContainer
|
||||
|
||||
/**
|
||||
`DependencyContainer` allows you to do _Dependency Injection_
|
||||
by associating abstractions to concrete implementations.
|
||||
*/
|
||||
public final class DependencyContainer {
|
||||
|
||||
/**
|
||||
Use a tag in case you need to register multiple factories fo the same type,
|
||||
to differentiate them. Tags can be either String or Int, to your convenience.
|
||||
*/
|
||||
public enum Tag: Equatable {
|
||||
case String(StringLiteralType)
|
||||
case Int(IntegerLiteralType)
|
||||
}
|
||||
|
||||
var definitions = [DefinitionKey : Definition]()
|
||||
let resolvedInstances = ResolvedInstances()
|
||||
let lock = RecursiveLock()
|
||||
|
||||
/**
|
||||
Designated initializer for a DependencyContainer
|
||||
|
||||
- parameter configBlock: A configuration block in which you typically put all you `register` calls.
|
||||
|
||||
- note: The `configBlock` is simply called at the end of the `init` to let you configure everything.
|
||||
It is only present for convenience to have a cleaner syntax when declaring and initializing
|
||||
your `DependencyContainer` instances.
|
||||
|
||||
- returns: A new DependencyContainer.
|
||||
*/
|
||||
public init(@noescape configBlock: (DependencyContainer->()) = { _ in }) {
|
||||
configBlock(self)
|
||||
}
|
||||
|
||||
private func threadSafe<T>(@noescape closure: () throws -> T) rethrows -> T {
|
||||
lock.lock()
|
||||
defer {
|
||||
lock.unlock()
|
||||
}
|
||||
return try closure()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Registering definitions
|
||||
|
||||
extension DependencyContainer {
|
||||
|
||||
/**
|
||||
Register factory for type `T` and associate it with an optional tag.
|
||||
|
||||
- parameters:
|
||||
- tag: The arbitrary tag to associate this factory with. Pass `nil` to associate with any tag. Default value is `nil`.
|
||||
- scope: The scope to use for instance created by the factory.
|
||||
- factory: The factory to register.
|
||||
|
||||
- returns: A registered definition.
|
||||
|
||||
- note: You should cast the factory return type to the protocol you want to register it for
|
||||
(unless you want to register concrete type).
|
||||
|
||||
**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, _ scope: ComponentScope = .Prototype, factory: () throws -> T) -> DefinitionOf<T, () throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory)
|
||||
}
|
||||
|
||||
/**
|
||||
Register generic factory associated with an optional tag.
|
||||
|
||||
- parameters:
|
||||
- tag: The arbitrary tag to associate this factory with. Pass `nil` to associate with any tag. Default value is `nil`.
|
||||
- scope: The scope to use for instance created by the factory.
|
||||
- factory: The factory to register.
|
||||
|
||||
- returns: A registered definition.
|
||||
|
||||
- note: You _should not_ call this method directly, instead call any of other `register` methods.
|
||||
You _should_ use this method only to register dependency with more runtime arguments
|
||||
than _Dip_ supports (currently it's up to six) like in the following example:
|
||||
|
||||
```swift
|
||||
public func register<T, Arg1, Arg2, Arg3, ...>(tag: Tag? = nil, scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, ...) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, ...) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory) as DefinitionOf<T, (Arg1, Arg2, Arg3, ...) throws -> T>
|
||||
}
|
||||
```
|
||||
|
||||
Though before you do so you should probably review your design and try to reduce number of depnedencies.
|
||||
*/
|
||||
public func registerFactory<T, F>(tag tag: Tag? = nil, scope: ComponentScope, factory: F) -> DefinitionOf<T, F> {
|
||||
let definition = DefinitionOf<T, F>(scope: scope, factory: factory)
|
||||
register(definition, forTag: tag)
|
||||
return definition
|
||||
}
|
||||
|
||||
/**
|
||||
Register definiton in the container and associate it with an optional tag.
|
||||
Will override already registered definition for the same type and factory, associated with the same tag.
|
||||
|
||||
- parameters:
|
||||
- tag: The arbitrary tag to associate this definition with. Pass `nil` to associate with any tag. Default value is `nil`.
|
||||
- definition: The definition to register in the container.
|
||||
|
||||
*/
|
||||
public func register<T, F>(definition: DefinitionOf<T, F>, forTag tag: Tag? = nil) {
|
||||
let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag)
|
||||
register(definition, forKey: key)
|
||||
}
|
||||
|
||||
func register(definition: Definition, forKey key: DefinitionKey) {
|
||||
threadSafe {
|
||||
definitions[key] = definition
|
||||
resolvedInstances.singletons[key] = nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Resolve dependencies
|
||||
|
||||
extension DependencyContainer {
|
||||
|
||||
/**
|
||||
Resolve a an instance of type `T`.
|
||||
|
||||
If no matching definition was registered with provided `tag`,
|
||||
container will lookup definition associated with `nil` tag.
|
||||
|
||||
- parameter tag: The arbitrary tag to use to lookup definition.
|
||||
|
||||
- throws: An error of type `DipError`:
|
||||
`ResolutionFailed` - if some error was thrown during resolution;
|
||||
`DefinitionNotFound` - if no matching definition was registered in that container.
|
||||
`AutoInjectionFailed` - if failed to auto-inject required property
|
||||
|
||||
- returns: An instance of type `T`.
|
||||
|
||||
- seealso: `register(tag:_:factory:)`
|
||||
|
||||
**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) throws -> T {
|
||||
return try resolve(tag: tag) { (factory: () throws -> T) in try factory() }
|
||||
}
|
||||
|
||||
/**
|
||||
Resolve an instance of type `T` using generic builder closure that accepts generic factory and returns created instance.
|
||||
|
||||
- parameters:
|
||||
- tag: The arbitrary tag to use to lookup definition.
|
||||
- builder: Generic closure that accepts generic factory and returns inctance created by that factory.
|
||||
|
||||
- throws: An error of type `DipError`:
|
||||
`ResolutionFailed` - if some error was thrown during resolution;
|
||||
`DefinitionNotFound` - if no matching definition was registered in that container.
|
||||
`AutoInjectionFailed` - if failed to auto-inject required property
|
||||
|
||||
- returns: An instance of type `T`.
|
||||
|
||||
- note: You _should not_ call this method directly, instead call any of other
|
||||
`resolve(tag:)` or `resolve(tag:withArguments:)` methods.
|
||||
You _should_ use this method only to resolve dependency with more runtime arguments than
|
||||
_Dip_ supports (currently it's up to six) like in the following example:
|
||||
|
||||
```swift
|
||||
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 so you should probably review your design and try to reduce the number of dependencies.
|
||||
*/
|
||||
public func resolve<T, F>(tag tag: Tag? = nil, builder: F throws -> T) throws -> T {
|
||||
let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag)
|
||||
|
||||
do {
|
||||
return try _resolveKey(key, builder: builder)
|
||||
}
|
||||
catch {
|
||||
switch error {
|
||||
case let DipError.DefinitionNotFound(errorKey) where key == errorKey:
|
||||
throw error
|
||||
default:
|
||||
throw DipError.ResolutionFailed(key: key, underlyingError: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Lookup definition by the key and use it to resolve instance. Fallback to the key with `nil` tag.
|
||||
func _resolveKey<T, F>(key: DefinitionKey, builder: F throws -> T) throws -> T {
|
||||
return try threadSafe {
|
||||
let nilTagKey = key.associatedTag.map { _ in DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: nil) }
|
||||
|
||||
guard let definition = (self.definitions[key] ?? self.definitions[nilTagKey]) as? DefinitionOf<T, F> else {
|
||||
throw DipError.DefinitionNotFound(key: key)
|
||||
}
|
||||
return try self._resolveDefinition(definition, key: key, builder: builder)
|
||||
}
|
||||
}
|
||||
|
||||
/// Actually resolve dependency.
|
||||
private func _resolveDefinition<T, F>(definition: DefinitionOf<T, F>, key: DefinitionKey, builder: F throws -> T) rethrows -> T {
|
||||
return try resolvedInstances.resolve {
|
||||
if let previouslyResolved: T = resolvedInstances.previouslyResolvedInstance(forKey: key, inScope: definition.scope) {
|
||||
return previouslyResolved
|
||||
}
|
||||
else {
|
||||
let resolvedInstance = try 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.previouslyResolvedInstance(forKey: key, inScope: definition.scope) {
|
||||
return previouslyResolved
|
||||
}
|
||||
|
||||
resolvedInstances.storeResolvedInstance(resolvedInstance, forKey: key, inScope: definition.scope)
|
||||
|
||||
try definition.resolveDependenciesOf(resolvedInstance, withContainer: self)
|
||||
|
||||
//we perform auto-injection as the last step to be able to reuse instances
|
||||
//stored when manually resolving dependencies in resolveDependencies block
|
||||
try autoInjectProperties(resolvedInstance)
|
||||
|
||||
return resolvedInstance
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///Pool to hold instances, created during call to `resolve()`.
|
||||
///Before `resolve()` returns pool is drained.
|
||||
class ResolvedInstances {
|
||||
var resolvedInstances = [DefinitionKey: Any]()
|
||||
var singletons = [DefinitionKey: Any]()
|
||||
|
||||
func storeResolvedInstance<T>(instance: T, forKey key: DefinitionKey, inScope scope: ComponentScope) {
|
||||
switch scope {
|
||||
case .Singleton: singletons[key] = instance
|
||||
case .ObjectGraph: resolvedInstances[key] = instance
|
||||
case .Prototype: break
|
||||
}
|
||||
}
|
||||
|
||||
func previouslyResolvedInstance<T>(forKey key: DefinitionKey, inScope scope: ComponentScope) -> T? {
|
||||
switch scope {
|
||||
case .Singleton: return singletons[key] as? T
|
||||
case .ObjectGraph: return resolvedInstances[key] as? T
|
||||
case .Prototype: return nil
|
||||
}
|
||||
}
|
||||
|
||||
private var depth: Int = 0
|
||||
|
||||
func resolve<T>(@noescape block: () throws ->T) rethrows -> T {
|
||||
depth = depth + 1
|
||||
|
||||
defer {
|
||||
depth = depth - 1
|
||||
if depth == 0 {
|
||||
resolvedInstances.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
let resolved = try block()
|
||||
return resolved
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Removing definitions
|
||||
|
||||
extension DependencyContainer {
|
||||
|
||||
/**
|
||||
Removes definition registered in the container.
|
||||
|
||||
- parameters:
|
||||
- tag: The tag used to register definition.
|
||||
- definition: The definition to remove
|
||||
*/
|
||||
public func remove<T, F>(definition: DefinitionOf<T, F>, forTag tag: Tag? = nil) {
|
||||
let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag)
|
||||
remove(definitionForKey: key)
|
||||
}
|
||||
|
||||
func remove(definitionForKey key: DefinitionKey) {
|
||||
threadSafe {
|
||||
definitions[key] = nil
|
||||
resolvedInstances.singletons[key] = nil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Removes all definitions registered in the container.
|
||||
*/
|
||||
public func reset() {
|
||||
threadSafe {
|
||||
definitions.removeAll()
|
||||
resolvedInstances.singletons.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension DependencyContainer: CustomStringConvertible {
|
||||
|
||||
public var description: String {
|
||||
return "Definitions: \(definitions.count)\n" + definitions.map({ "\($0.0)" }).joinWithSeparator("\n")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension DependencyContainer.Tag: IntegerLiteralConvertible {
|
||||
|
||||
public init(integerLiteral value: IntegerLiteralType) {
|
||||
self = .Int(value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension DependencyContainer.Tag: StringLiteralConvertible {
|
||||
|
||||
public init(stringLiteral value: StringLiteralType) {
|
||||
self = .String(value)
|
||||
}
|
||||
|
||||
public init(unicodeScalarLiteral value: StringLiteralType) {
|
||||
self.init(stringLiteral: value)
|
||||
}
|
||||
|
||||
public init(extendedGraphemeClusterLiteral value: StringLiteralType) {
|
||||
self.init(stringLiteral: value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public func ==(lhs: DependencyContainer.Tag, rhs: DependencyContainer.Tag) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (.String(lhsString), .String(rhsString)):
|
||||
return lhsString == rhsString
|
||||
case let (.Int(lhsInt), .Int(rhsInt)):
|
||||
return lhsInt == rhsInt
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Errors thrown by `DependencyContainer`'s methods.
|
||||
|
||||
- seealso: `resolve(tag:)`
|
||||
*/
|
||||
public enum DipError: ErrorType, CustomStringConvertible {
|
||||
/**
|
||||
Thrown by `resolve(tag:)` if some error was thrown during resolution.
|
||||
|
||||
- parameters:
|
||||
- key: The key, which is associated with definition used to resolve instance
|
||||
- underlyingError: The error that caused resolution to fail
|
||||
*/
|
||||
case ResolutionFailed(key: DefinitionKey, underlyingError: ErrorType)
|
||||
|
||||
/**
|
||||
Thrown by `resolve(tag:)` if no matching definition was registered in container.
|
||||
|
||||
- parameter key: definition key used to lookup matching definition
|
||||
*/
|
||||
case DefinitionNotFound(key: DefinitionKey)
|
||||
|
||||
/**
|
||||
Thrown by `resolve(tag:)` if failed to auto-inject required property.
|
||||
|
||||
- parameters:
|
||||
- label: The name of the property
|
||||
- type: The type of the property
|
||||
- underlyingError: The error that caused auto-injection to fail
|
||||
*/
|
||||
case AutoInjectionFailed(label: String?, type: Any.Type, underlyingError: ErrorType)
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case let .ResolutionFailed(key, error):
|
||||
return "Failed to resolve type \(key.protocolType). \(error)"
|
||||
case let .DefinitionNotFound(key):
|
||||
return "No definition registered for \(key).\nCheck the tag, type you try to resolve, number, order and types of runtime arguments passed to `resolve()` and match them with registered factories for type \(key.protocolType)."
|
||||
case let .AutoInjectionFailed(label, type, error):
|
||||
return "Failed to auto-inject property \"\(label.desc)\" of type \(type). \(error)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
// MARK: - Register/resolve dependencies with runtime arguments
|
||||
|
||||
extension DependencyContainer {
|
||||
|
||||
// MARK: 1 Runtime Argument
|
||||
|
||||
/**
|
||||
Register factory that accepts one runtime argumentof type `Arg1`. You can use up to six runtime arguments.
|
||||
|
||||
- note: You can have several factories with different number or types of arguments registered for same type,
|
||||
optionally associated with some tags. When container resolves that type it matches the type,
|
||||
__number__, __types__ and __order__ of runtime arguments and optional tag that you pass to `resolve(tag:withArguments:)` method.
|
||||
|
||||
- parameters:
|
||||
- tag: The arbitrary tag to associate this factory with. Pass `nil` to associate with any tag. Default value is `nil`.
|
||||
- scope: The scope to use for this component. Default value is `.Prototype`.
|
||||
- factory: The factory to register.
|
||||
|
||||
- seealso: `registerFactory(tag:scope:factory:)`
|
||||
*/
|
||||
public func register<T, Arg1>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1) throws -> T) -> DefinitionOf<T, (Arg1) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory)
|
||||
}
|
||||
|
||||
/**
|
||||
Resolve a dependency using one runtime argument.
|
||||
|
||||
- parameters:
|
||||
- tag: The arbitrary tag to lookup registered definition.
|
||||
- arg1: The first argument to pass to the definition's factory.
|
||||
|
||||
- throws: An error of type `DipError`:
|
||||
`ResolutionFailed` - if some error was thrown during resolution;
|
||||
`DefinitionNotFound` - if no matching definition was registered in that container.
|
||||
|
||||
- returns: An instance of type `T`.
|
||||
|
||||
- seealso: `register(tag:_:factory:)`, `resolve(tag:builder:)`
|
||||
*/
|
||||
public func resolve<T, Arg1>(tag tag: Tag? = nil, withArguments arg1: Arg1) throws -> T {
|
||||
return try resolve(tag: tag) { (factory: (Arg1) throws -> T) in try factory(arg1) }
|
||||
}
|
||||
|
||||
// MARK: 2 Runtime Arguments
|
||||
|
||||
/// - seealso: `register(tag:scope:factory:)`
|
||||
public func register<T, Arg1, Arg2>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2) throws -> T) -> DefinitionOf<T, (Arg1, Arg2) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory)
|
||||
}
|
||||
|
||||
/// - seealso: `resolve(tag:_:)`
|
||||
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) throws -> T) in try factory(arg1, arg2) }
|
||||
}
|
||||
|
||||
// MARK: 3 Runtime Arguments
|
||||
|
||||
/// - seealso: `register(tag:scope:factory:)`
|
||||
public func register<T, Arg1, Arg2, Arg3>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory)
|
||||
}
|
||||
|
||||
/// - 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) throws -> T) in try factory(arg1, arg2, arg3) }
|
||||
}
|
||||
|
||||
// MARK: 4 Runtime Arguments
|
||||
|
||||
/// - seealso: `register(tag:scope:factory:)`
|
||||
public func register<T, Arg1, Arg2, Arg3, Arg4>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory)
|
||||
}
|
||||
|
||||
/// - 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) throws -> T) in try factory(arg1, arg2, arg3, arg4) }
|
||||
}
|
||||
|
||||
// MARK: 5 Runtime Arguments
|
||||
|
||||
/// - seealso: `register(tag:scope:factory:)`
|
||||
public func register<T, Arg1, Arg2, Arg3, Arg4, Arg5>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4, Arg5) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4, Arg5) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory)
|
||||
}
|
||||
|
||||
/// - 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) throws -> T) in try factory(arg1, arg2, arg3, arg4, arg5) }
|
||||
}
|
||||
|
||||
// MARK: 6 Runtime Arguments
|
||||
|
||||
/// - seealso: `register(tag:scope:factory:)`
|
||||
public func register<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory)
|
||||
}
|
||||
|
||||
/// - 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) throws -> T) in try factory(arg1, arg2, arg3, arg4, arg5, arg6) }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// Dip
|
||||
//
|
||||
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
extension Dictionary {
|
||||
subscript(key: Key?) -> Value? {
|
||||
get {
|
||||
guard let key = key else { return nil }
|
||||
return self[key]
|
||||
}
|
||||
set {
|
||||
guard let key = key else { return }
|
||||
self[key] = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional {
|
||||
var desc: String {
|
||||
return self.map { "\($0)" } ?? "nil"
|
||||
}
|
||||
}
|
||||
|
||||
#if os(Linux)
|
||||
import Glibc
|
||||
class RecursiveLock {
|
||||
private var _lock = _initializeRecursiveMutex()
|
||||
|
||||
func lock() {
|
||||
_lock.lock()
|
||||
}
|
||||
|
||||
func unlock() {
|
||||
_lock.unlock()
|
||||
}
|
||||
|
||||
deinit {
|
||||
pthread_mutex_destroy(&_lock)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func _initializeRecursiveMutex() -> pthread_mutex_t {
|
||||
var mutex: pthread_mutex_t = pthread_mutex_t()
|
||||
var mta: pthread_mutexattr_t = pthread_mutexattr_t()
|
||||
pthread_mutexattr_init(&mta)
|
||||
pthread_mutexattr_settype(&mta, Int32(PTHREAD_MUTEX_RECURSIVE))
|
||||
pthread_mutex_init(&mutex, &mta)
|
||||
return mutex
|
||||
}
|
||||
|
||||
extension pthread_mutex_t {
|
||||
mutating func lock() {
|
||||
pthread_mutex_trylock(&self)
|
||||
}
|
||||
mutating func unlock() {
|
||||
pthread_mutex_unlock(&self)
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
import Foundation
|
||||
typealias RecursiveLock = NSRecursiveLock
|
||||
#endif
|
||||
Reference in New Issue
Block a user