Compare commits
80 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eadd87a5bb | |||
| 4c4b9ef57a | |||
| ee99cf4fdc | |||
| 40dfd6419f | |||
| 88fa94ed30 | |||
| 58d1813681 | |||
| 7277b940ca | |||
| 581c241fd1 | |||
| 3c9d2a6db8 | |||
| 10309c1d5d | |||
| 2a27152fa1 | |||
| e04a741479 | |||
| dbaba50c88 | |||
| 032db2d2b1 | |||
| b2456e0430 | |||
| fd17075a8a | |||
| 2d9be9acc0 | |||
| efd279b26b | |||
| b5cb796f5f | |||
| e1fd904842 | |||
| c65ae6cedd | |||
| 21aedcb655 | |||
| 37d0e4750f | |||
| 95157a99f6 | |||
| 1ad3aa2c18 | |||
| 1c916cbafc | |||
| 86b6afe5d9 | |||
| a1e7a1f958 | |||
| c414882c6a | |||
| f8cd1c4dc3 | |||
| 3e68acd37b | |||
| 9b047e4145 | |||
| f31236688d | |||
| af61fea49e | |||
| 8c9172aac9 | |||
| 5495bd2b97 | |||
| b3e6c3c61a | |||
| be86b30da3 | |||
| 26ba11dcbf | |||
| 1356a8056f | |||
| 0ddb37bea3 | |||
| 537cad5923 | |||
| 0c4ce2213b | |||
| a91dacb29c | |||
| 895a6f2583 | |||
| 73f71a99b2 | |||
| 41664914f4 | |||
| 53bc97ba63 | |||
| c321189b66 | |||
| 317d67ca90 | |||
| cbcef835f7 | |||
| 47a1870de1 | |||
| da2197c909 | |||
| 3ee8b04118 | |||
| 29c1a3805f | |||
| 4dc11a6e2d | |||
| 7a4d9c554c | |||
| cf4bb0352a | |||
| 8c6a822eca | |||
| 4c6718efb3 | |||
| 1306f12804 | |||
| 764cb98a59 | |||
| 130d71ce3c | |||
| 490c2a4d56 | |||
| 6e98f270bf | |||
| b3d090c3fd | |||
| 4e5cf238c9 | |||
| f4f660d81d | |||
| 211b2fce97 | |||
| 3aa79d1c69 | |||
| 4d085d1329 | |||
| 84845c9b17 | |||
| f4c38f281e | |||
| 99315e32eb | |||
| b48fe9840a | |||
| 6a6fbf906b | |||
| 7e5a0c8ada | |||
| 1f7ce50035 | |||
| 3700f687a2 | |||
| c817980dd8 |
+39
-17
@@ -1,19 +1,41 @@
|
||||
language: objective-c
|
||||
osx_image: xcode7.2
|
||||
env:
|
||||
global:
|
||||
- MODULE_NAME=Dip
|
||||
|
||||
# cache: cocoapods
|
||||
# before_install:
|
||||
# - gem install cocoapods # Since Travis is not always on latest version
|
||||
matrix:
|
||||
allow_failures:
|
||||
- os: linux
|
||||
include:
|
||||
- script:
|
||||
- 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
|
||||
os: osx
|
||||
osx_image: xcode7.3
|
||||
language: objective-c
|
||||
- script:
|
||||
- swift build
|
||||
os: linux
|
||||
dist: trusty
|
||||
sudo: required
|
||||
language: generic
|
||||
before_install:
|
||||
- wget -q -O - https://swift.org/keys/all-keys.asc | gpg --import -
|
||||
- cd ..
|
||||
- export SWIFT_VERSION=swift-DEVELOPMENT-SNAPSHOT-2016-05-09-a
|
||||
- wget https://swift.org/builds/development/ubuntu1404/$SWIFT_VERSION/$SWIFT_VERSION-ubuntu14.04.tar.gz
|
||||
- tar xzf $SWIFT_VERSION-ubuntu14.04.tar.gz
|
||||
- export PATH="${PWD}/${SWIFT_VERSION}-ubuntu14.04/usr/bin:${PATH}"
|
||||
- export SWIFT_RELEASE_VERSION=2.2.1
|
||||
- export SWIFT_RELEASE_NAME="${SWIFT_RELEASE_VERSION}-RELEASE"
|
||||
- wget https://swift.org/builds/swift-$SWIFT_RELEASE_VERSION-release/ubuntu1404/swift-$SWIFT_RELEASE_NAME/swift-$SWIFT_RELEASE_NAME-ubuntu14.04.tar.gz
|
||||
- tar xzf swift-$SWIFT_RELEASE_NAME-ubuntu14.04.tar.gz
|
||||
- export SWIFT_EXEC="${PWD}/swift-${SWIFT_RELEASE_NAME}-ubuntu14.04/usr/bin/swiftc"
|
||||
- cd $MODULE_NAME
|
||||
|
||||
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-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
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
@@ -1,5 +1,47 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 4.5.0
|
||||
|
||||
* Added weakly-typed API to resolve components when exact type is unknown during compile time.
|
||||
[#79](https://github.com/AliSoftware/Dip/pull/79), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
* Added type forwarding feature. You can register the same factory to resolve different types.
|
||||
[#89](https://github.com/AliSoftware/Dip/pull/89), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
* Container now can resolve optional types :tada:
|
||||
[#84](https://github.com/AliSoftware/Dip/pull/84), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
* Added container context that provides contextual information during graph resolution process.
|
||||
[#83](https://github.com/AliSoftware/Dip/pull/83), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
* Added method to validate container configuration.
|
||||
[#87](https://github.com/AliSoftware/Dip/pull/87), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
* Added method to manually set value wrapped by auto-injection wrappers.
|
||||
[#81](https://github.com/AliSoftware/Dip/pull/81), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
* Added separate error type for failures during auto-wiring.
|
||||
[#85](https://github.com/AliSoftware/Dip/pull/85), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
|
||||
|
||||
## 4.4.0
|
||||
|
||||
* Added `.EagerSingleton` scope for objectes requiring early instantiation and `bootstrap()` method on `DepenencyContainer`.
|
||||
[#65](https://github.com/AliSoftware/Dip/pull/65), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
* Reverted order of `Resolvable` callbacks.
|
||||
[#67](https://github.com/AliSoftware/Dip/pull/67), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
|
||||
## 4.3.1
|
||||
|
||||
* Fix Swift 2.2 compile errors in tests.
|
||||
[#62](https://github.com/AliSoftware/Dip/pull/62), [@mwoollard](https://github.com/mwoollard)
|
||||
|
||||
## 4.3.0
|
||||
|
||||
* Added `DependencyTagConvertible` protocol for better typed tags.
|
||||
[#50](https://github.com/AliSoftware/Dip/pull/50), [@gavrix](https://github.com/gavrix)
|
||||
* Auto-wiring. `DependencyContainer` resolves constructor arguments automatically.
|
||||
[#55](https://github.com/AliSoftware/Dip/pull/55), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
* Added `Resolvable` protocol to get a callback when dependencies graph is complete.
|
||||
[#57](https://github.com/AliSoftware/Dip/pull/57), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
* Removed `DipError.ResolutionFailed` error for better consistency.
|
||||
[#58](https://github.com/AliSoftware/Dip/pull/58), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
|
||||
|
||||
## 4.2.0
|
||||
|
||||
* Added support for Swift Package Manager.
|
||||
|
||||
+6
-14
@@ -1,21 +1,13 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "Dip"
|
||||
s.version = "4.2.0"
|
||||
s.summary = "A simple Dependency Resolver: Dependency Injection using Protocol resolution."
|
||||
s.version = "4.5.0"
|
||||
s.summary = "Dependency Injection for Swift made easy."
|
||||
|
||||
s.description = <<-DESC
|
||||
Dip is a Swift framework to manage your Dependencies between your classes
|
||||
in your app using Dependency Injection.
|
||||
|
||||
It's aimed to be very simple to use while improving testability
|
||||
of your app by allowing you to get rid of those sharedInstances and instead
|
||||
inject values based on protocol resolution.
|
||||
|
||||
Define your API using a protocol, then ask Dip to resolve this protocol into
|
||||
an instance dynamically in your classes. Then your App and your Tests can be
|
||||
configured to resolve the protocol using a different instance or class so this
|
||||
improve testability by decoupling the API and the concrete class used to implement it.
|
||||
DESC
|
||||
Dip is a Swift Dependency Injection Container.
|
||||
It provides reusable functionality for managing dependencies of your types
|
||||
and will help you to wire up different parts of your app.
|
||||
DESC
|
||||
|
||||
s.homepage = "https://github.com/AliSoftware/Dip"
|
||||
s.license = 'MIT'
|
||||
|
||||
@@ -38,6 +38,13 @@
|
||||
0919F4EC1C16419500DC3B10 /* DefinitionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919F4CF1C16417000DC3B10 /* DefinitionTests.swift */; };
|
||||
0919F4ED1C16419500DC3B10 /* RuntimeArgumentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919F4D21C16417000DC3B10 /* RuntimeArgumentsTests.swift */; };
|
||||
0919F4EE1C16419500DC3B10 /* ComponentScopeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919F4CE1C16417000DC3B10 /* ComponentScopeTests.swift */; };
|
||||
095A51CF1CEA1664006B957C /* TypeForwardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095A51CE1CEA1664006B957C /* TypeForwardingTests.swift */; };
|
||||
095A51D01CEA1664006B957C /* TypeForwardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095A51CE1CEA1664006B957C /* TypeForwardingTests.swift */; };
|
||||
095A51D11CEA1664006B957C /* TypeForwardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095A51CE1CEA1664006B957C /* TypeForwardingTests.swift */; };
|
||||
095F829C1D043B41008CD706 /* TypeForwarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095F829B1D043B41008CD706 /* TypeForwarding.swift */; };
|
||||
095F829D1D043BAA008CD706 /* TypeForwarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095F829B1D043B41008CD706 /* TypeForwarding.swift */; };
|
||||
095F829E1D043BAA008CD706 /* TypeForwarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095F829B1D043B41008CD706 /* TypeForwarding.swift */; };
|
||||
095F829F1D043BAB008CD706 /* TypeForwarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095F829B1D043B41008CD706 /* TypeForwarding.swift */; };
|
||||
0982AF0C1C5183A000B62463 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0982AF0B1C5183A000B62463 /* Utils.swift */; };
|
||||
0982AF0D1C5183A000B62463 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0982AF0B1C5183A000B62463 /* Utils.swift */; };
|
||||
0982AF0E1C5183A000B62463 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0982AF0B1C5183A000B62463 /* Utils.swift */; };
|
||||
@@ -49,12 +56,22 @@
|
||||
09873F7A1C1E0252000C02F6 /* AutoInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09873F551C1E0237000C02F6 /* AutoInjection.swift */; };
|
||||
09873F7B1C1E0253000C02F6 /* AutoInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09873F551C1E0237000C02F6 /* AutoInjection.swift */; };
|
||||
09873F7C1C1E0254000C02F6 /* AutoInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09873F551C1E0237000C02F6 /* AutoInjection.swift */; };
|
||||
09B035FC1C5D2AD6001EA5B7 /* AutoWiringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B035FB1C5D2AD6001EA5B7 /* AutoWiringTests.swift */; };
|
||||
09B035FD1C5D2AD6001EA5B7 /* AutoWiringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B035FB1C5D2AD6001EA5B7 /* AutoWiringTests.swift */; };
|
||||
09B035FE1C5D2AD6001EA5B7 /* AutoWiringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B035FB1C5D2AD6001EA5B7 /* AutoWiringTests.swift */; };
|
||||
09B036001C5D2B83001EA5B7 /* AutoWiring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B035FF1C5D2B83001EA5B7 /* AutoWiring.swift */; };
|
||||
09B036011C5D2B83001EA5B7 /* AutoWiring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B035FF1C5D2B83001EA5B7 /* AutoWiring.swift */; };
|
||||
09B036021C5D2B83001EA5B7 /* AutoWiring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B035FF1C5D2B83001EA5B7 /* AutoWiring.swift */; };
|
||||
09B036031C5D2B83001EA5B7 /* AutoWiring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B035FF1C5D2B83001EA5B7 /* AutoWiring.swift */; };
|
||||
09C20EC11C8B3BFD009A082B /* ThreadSafetyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C20EBF1C8B3BC3009A082B /* ThreadSafetyTests.swift */; };
|
||||
09C20EC21C8B3BFE009A082B /* ThreadSafetyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C20EBF1C8B3BC3009A082B /* ThreadSafetyTests.swift */; };
|
||||
09C20EC31C8B3BFF009A082B /* ThreadSafetyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C20EBF1C8B3BC3009A082B /* ThreadSafetyTests.swift */; };
|
||||
09D598331C6F9EC100F24D49 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09D598321C6F9EC100F24D49 /* Utils.swift */; };
|
||||
09D598341C6F9EC100F24D49 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09D598321C6F9EC100F24D49 /* Utils.swift */; };
|
||||
09D598351C6F9EC100F24D49 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09D598321C6F9EC100F24D49 /* Utils.swift */; };
|
||||
2C15B9511C25F01200EA3486 /* ThreadSafetyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C15B94F1C25EF7800EA3486 /* ThreadSafetyTests.swift */; };
|
||||
2C15B9521C25F01300EA3486 /* ThreadSafetyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C15B94F1C25EF7800EA3486 /* ThreadSafetyTests.swift */; };
|
||||
2C15B9531C25F01500EA3486 /* ThreadSafetyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C15B94F1C25EF7800EA3486 /* ThreadSafetyTests.swift */; };
|
||||
64EC9B511CD0CB4000FF6A4C /* ContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EC9B501CD0CB4000FF6A4C /* ContextTests.swift */; };
|
||||
64EC9B521CD0CBB400FF6A4C /* ContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EC9B501CD0CB4000FF6A4C /* ContextTests.swift */; };
|
||||
64EC9B531CD0CBB500FF6A4C /* ContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64EC9B501CD0CB4000FF6A4C /* ContextTests.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -94,16 +111,21 @@
|
||||
0919F4CA1C16417000DC3B10 /* Dip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Dip.swift; path = ../../Sources/Dip.swift; sourceTree = "<group>"; };
|
||||
0919F4CB1C16417000DC3B10 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
0919F4CC1C16417000DC3B10 /* RuntimeArguments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RuntimeArguments.swift; path = ../../Sources/RuntimeArguments.swift; sourceTree = "<group>"; };
|
||||
0919F4CE1C16417000DC3B10 /* ComponentScopeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ComponentScopeTests.swift; path = Sources/ComponentScopeTests.swift; sourceTree = "<group>"; };
|
||||
0919F4CF1C16417000DC3B10 /* DefinitionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DefinitionTests.swift; path = Sources/DefinitionTests.swift; sourceTree = "<group>"; };
|
||||
0919F4D01C16417000DC3B10 /* DipTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DipTests.swift; path = Sources/DipTests.swift; sourceTree = "<group>"; };
|
||||
0919F4CE1C16417000DC3B10 /* ComponentScopeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ComponentScopeTests.swift; path = ../../Tests/Dip/ComponentScopeTests.swift; sourceTree = "<group>"; };
|
||||
0919F4CF1C16417000DC3B10 /* DefinitionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DefinitionTests.swift; path = ../../Tests/Dip/DefinitionTests.swift; sourceTree = "<group>"; };
|
||||
0919F4D01C16417000DC3B10 /* DipTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DipTests.swift; path = ../../Tests/Dip/DipTests.swift; sourceTree = "<group>"; };
|
||||
0919F4D11C16417000DC3B10 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
0919F4D21C16417000DC3B10 /* RuntimeArgumentsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RuntimeArgumentsTests.swift; path = Sources/RuntimeArgumentsTests.swift; sourceTree = "<group>"; };
|
||||
0919F4D21C16417000DC3B10 /* RuntimeArgumentsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RuntimeArgumentsTests.swift; path = ../../Tests/Dip/RuntimeArgumentsTests.swift; sourceTree = "<group>"; };
|
||||
095A51CE1CEA1664006B957C /* TypeForwardingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypeForwardingTests.swift; sourceTree = "<group>"; };
|
||||
095F829B1D043B41008CD706 /* TypeForwarding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TypeForwarding.swift; path = ../../Sources/TypeForwarding.swift; sourceTree = "<group>"; };
|
||||
0982AF0B1C5183A000B62463 /* Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Utils.swift; path = ../../Sources/Utils.swift; sourceTree = "<group>"; };
|
||||
09873F551C1E0237000C02F6 /* AutoInjection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AutoInjection.swift; path = ../../Sources/AutoInjection.swift; sourceTree = "<group>"; };
|
||||
09873F751C1E0249000C02F6 /* AutoInjectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AutoInjectionTests.swift; path = Sources/AutoInjectionTests.swift; sourceTree = "<group>"; };
|
||||
09D598321C6F9EC100F24D49 /* Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Utils.swift; path = Sources/Utils.swift; sourceTree = "<group>"; };
|
||||
2C15B94F1C25EF7800EA3486 /* ThreadSafetyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ThreadSafetyTests.swift; path = Sources/ThreadSafetyTests.swift; sourceTree = "<group>"; };
|
||||
09873F751C1E0249000C02F6 /* AutoInjectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AutoInjectionTests.swift; path = ../../Tests/Dip/AutoInjectionTests.swift; sourceTree = "<group>"; };
|
||||
09B035FB1C5D2AD6001EA5B7 /* AutoWiringTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AutoWiringTests.swift; path = ../../Tests/Dip/AutoWiringTests.swift; sourceTree = "<group>"; };
|
||||
09B035FF1C5D2B83001EA5B7 /* AutoWiring.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AutoWiring.swift; path = ../../Sources/AutoWiring.swift; sourceTree = "<group>"; };
|
||||
09C20EBF1C8B3BC3009A082B /* ThreadSafetyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ThreadSafetyTests.swift; path = ../../Tests/Dip/ThreadSafetyTests.swift; sourceTree = "<group>"; };
|
||||
09D598321C6F9EC100F24D49 /* Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Utils.swift; path = ../../Tests/Dip/Utils.swift; sourceTree = "<group>"; };
|
||||
64EC9B501CD0CB4000FF6A4C /* ContextTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ContextTests.swift; path = ../../Tests/Dip/ContextTests.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -170,6 +192,8 @@
|
||||
0919F4C81C16417000DC3B10 /* Definition.swift */,
|
||||
0919F4CC1C16417000DC3B10 /* RuntimeArguments.swift */,
|
||||
09873F551C1E0237000C02F6 /* AutoInjection.swift */,
|
||||
09B035FF1C5D2B83001EA5B7 /* AutoWiring.swift */,
|
||||
095F829B1D043B41008CD706 /* TypeForwarding.swift */,
|
||||
0982AF0B1C5183A000B62463 /* Utils.swift */,
|
||||
0919F4CB1C16417000DC3B10 /* Info.plist */,
|
||||
);
|
||||
@@ -180,11 +204,14 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0919F4D01C16417000DC3B10 /* DipTests.swift */,
|
||||
64EC9B501CD0CB4000FF6A4C /* ContextTests.swift */,
|
||||
0919F4CF1C16417000DC3B10 /* DefinitionTests.swift */,
|
||||
0919F4D21C16417000DC3B10 /* RuntimeArgumentsTests.swift */,
|
||||
0919F4CE1C16417000DC3B10 /* ComponentScopeTests.swift */,
|
||||
09873F751C1E0249000C02F6 /* AutoInjectionTests.swift */,
|
||||
2C15B94F1C25EF7800EA3486 /* ThreadSafetyTests.swift */,
|
||||
09C20EBF1C8B3BC3009A082B /* ThreadSafetyTests.swift */,
|
||||
09B035FB1C5D2AD6001EA5B7 /* AutoWiringTests.swift */,
|
||||
095A51CE1CEA1664006B957C /* TypeForwardingTests.swift */,
|
||||
09D598321C6F9EC100F24D49 /* Utils.swift */,
|
||||
0919F4D11C16417000DC3B10 /* Info.plist */,
|
||||
);
|
||||
@@ -495,7 +522,9 @@
|
||||
files = (
|
||||
0982AF0C1C5183A000B62463 /* Utils.swift in Sources */,
|
||||
0919F4D51C16417B00DC3B10 /* Definition.swift in Sources */,
|
||||
09B036001C5D2B83001EA5B7 /* AutoWiring.swift in Sources */,
|
||||
0919F4D41C16417B00DC3B10 /* Dip.swift in Sources */,
|
||||
095F829C1D043B41008CD706 /* TypeForwarding.swift in Sources */,
|
||||
09873F561C1E0237000C02F6 /* AutoInjection.swift in Sources */,
|
||||
0919F4D61C16417B00DC3B10 /* RuntimeArguments.swift in Sources */,
|
||||
);
|
||||
@@ -506,12 +535,15 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0919F4E61C16419300DC3B10 /* ComponentScopeTests.swift in Sources */,
|
||||
2C15B9511C25F01200EA3486 /* ThreadSafetyTests.swift in Sources */,
|
||||
09C20EC11C8B3BFD009A082B /* ThreadSafetyTests.swift in Sources */,
|
||||
0919F4E41C16419300DC3B10 /* DefinitionTests.swift in Sources */,
|
||||
09D598331C6F9EC100F24D49 /* Utils.swift in Sources */,
|
||||
09B035FC1C5D2AD6001EA5B7 /* AutoWiringTests.swift in Sources */,
|
||||
0919F4E31C16419300DC3B10 /* DipTests.swift in Sources */,
|
||||
0919F4E51C16419300DC3B10 /* RuntimeArgumentsTests.swift in Sources */,
|
||||
09873F771C1E024E000C02F6 /* AutoInjectionTests.swift in Sources */,
|
||||
095A51CF1CEA1664006B957C /* TypeForwardingTests.swift in Sources */,
|
||||
64EC9B511CD0CB4000FF6A4C /* ContextTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -521,7 +553,9 @@
|
||||
files = (
|
||||
0982AF0D1C5183A000B62463 /* Utils.swift in Sources */,
|
||||
0919F4D91C16417C00DC3B10 /* Definition.swift in Sources */,
|
||||
09B036011C5D2B83001EA5B7 /* AutoWiring.swift in Sources */,
|
||||
0919F4D81C16417C00DC3B10 /* Dip.swift in Sources */,
|
||||
095F829D1D043BAA008CD706 /* TypeForwarding.swift in Sources */,
|
||||
09873F7A1C1E0252000C02F6 /* AutoInjection.swift in Sources */,
|
||||
0919F4DA1C16417C00DC3B10 /* RuntimeArguments.swift in Sources */,
|
||||
);
|
||||
@@ -532,12 +566,15 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0919F4EA1C16419400DC3B10 /* ComponentScopeTests.swift in Sources */,
|
||||
2C15B9521C25F01300EA3486 /* ThreadSafetyTests.swift in Sources */,
|
||||
09C20EC21C8B3BFE009A082B /* ThreadSafetyTests.swift in Sources */,
|
||||
0919F4E81C16419400DC3B10 /* DefinitionTests.swift in Sources */,
|
||||
09D598341C6F9EC100F24D49 /* Utils.swift in Sources */,
|
||||
09B035FD1C5D2AD6001EA5B7 /* AutoWiringTests.swift in Sources */,
|
||||
0919F4E71C16419400DC3B10 /* DipTests.swift in Sources */,
|
||||
0919F4E91C16419400DC3B10 /* RuntimeArgumentsTests.swift in Sources */,
|
||||
09873F781C1E024E000C02F6 /* AutoInjectionTests.swift in Sources */,
|
||||
095A51D01CEA1664006B957C /* TypeForwardingTests.swift in Sources */,
|
||||
64EC9B521CD0CBB400FF6A4C /* ContextTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -547,7 +584,9 @@
|
||||
files = (
|
||||
0982AF0E1C5183A000B62463 /* Utils.swift in Sources */,
|
||||
0919F4DD1C16417D00DC3B10 /* Definition.swift in Sources */,
|
||||
09B036021C5D2B83001EA5B7 /* AutoWiring.swift in Sources */,
|
||||
0919F4DC1C16417D00DC3B10 /* Dip.swift in Sources */,
|
||||
095F829E1D043BAA008CD706 /* TypeForwarding.swift in Sources */,
|
||||
09873F7B1C1E0253000C02F6 /* AutoInjection.swift in Sources */,
|
||||
0919F4DE1C16417D00DC3B10 /* RuntimeArguments.swift in Sources */,
|
||||
);
|
||||
@@ -558,12 +597,15 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0919F4EE1C16419500DC3B10 /* ComponentScopeTests.swift in Sources */,
|
||||
2C15B9531C25F01500EA3486 /* ThreadSafetyTests.swift in Sources */,
|
||||
09C20EC31C8B3BFF009A082B /* ThreadSafetyTests.swift in Sources */,
|
||||
0919F4EC1C16419500DC3B10 /* DefinitionTests.swift in Sources */,
|
||||
09D598351C6F9EC100F24D49 /* Utils.swift in Sources */,
|
||||
09B035FE1C5D2AD6001EA5B7 /* AutoWiringTests.swift in Sources */,
|
||||
0919F4EB1C16419500DC3B10 /* DipTests.swift in Sources */,
|
||||
0919F4ED1C16419500DC3B10 /* RuntimeArgumentsTests.swift in Sources */,
|
||||
09873F791C1E024F000C02F6 /* AutoInjectionTests.swift in Sources */,
|
||||
095A51D11CEA1664006B957C /* TypeForwardingTests.swift in Sources */,
|
||||
64EC9B531CD0CBB500FF6A4C /* ContextTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -573,7 +615,9 @@
|
||||
files = (
|
||||
0982AF0F1C5183A000B62463 /* Utils.swift in Sources */,
|
||||
0919F4E11C16417E00DC3B10 /* Definition.swift in Sources */,
|
||||
09B036031C5D2B83001EA5B7 /* AutoWiring.swift in Sources */,
|
||||
0919F4E01C16417E00DC3B10 /* Dip.swift in Sources */,
|
||||
095F829F1D043BAB008CD706 /* TypeForwarding.swift in Sources */,
|
||||
09873F7C1C1E0254000C02F6 /* AutoInjection.swift in Sources */,
|
||||
0919F4E21C16417E00DC3B10 /* RuntimeArguments.swift in Sources */,
|
||||
);
|
||||
@@ -848,7 +892,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 4.2.0;
|
||||
CURRENT_PROJECT_VERSION = 4.5.0;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
@@ -897,7 +941,7 @@
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 4.2.0;
|
||||
CURRENT_PROJECT_VERSION = 4.5.0;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "DipTests",
|
||||
dependencies: [
|
||||
.Package(url: "../../../Dip", majorVersion: 4, minor: 2),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
//
|
||||
// Dip
|
||||
//
|
||||
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import Dip
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import XCTest
|
||||
|
||||
XCTMain([
|
||||
DipTests(),
|
||||
DefinitionTests(),
|
||||
RuntimeArgumentsTests(),
|
||||
ComponentScopeTests(),
|
||||
AutoInjectionTests(),
|
||||
ThreadSafetyTests()
|
||||
])
|
||||
@@ -0,0 +1,252 @@
|
||||
//
|
||||
// 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 protocol ForwardedType: class { }
|
||||
private class ServiceImp1: NSObject, Service, ForwardedType { }
|
||||
private class ServiceImp2: NSObject, Service, ForwardedType { }
|
||||
|
||||
class TypeForwardingTests: XCTestCase {
|
||||
|
||||
let container = DependencyContainer()
|
||||
|
||||
#if os(Linux)
|
||||
static var allTests: [(String, TypeForwardingTests -> () throws -> Void)] {
|
||||
return [
|
||||
("testThatItResolvesInstanceByTypeForwarding", testThatItResolvesInstanceByTypeForwarding),
|
||||
("testThatItReusesInstanceResolvedByTypeForwarding", testThatItReusesInstanceResolvedByTypeForwarding),
|
||||
("testThatItDoesNotResolveByTypeForwardingIfRegisteredForAnotherTag", testThatItDoesNotResolveByTypeForwardingIfRegisteredForAnotherTag),
|
||||
("testThatItDoesNotReuseInstanceResolvedByTypeForwardingRegisteredForAnotherTag", testThatItDoesNotReuseInstanceResolvedByTypeForwardingRegisteredForAnotherTag),
|
||||
("testThatItCallsResolvedDependenciesBlockWhenResolvingByTypeForwarding", testThatItCallsResolvedDependenciesBlockWhenResolvingByTypeForwarding),
|
||||
("testThatItFallbackToDefinitionWithNoTagWhenResolvingInstanceByTypeForwarding", testThatItFallbackToDefinitionWithNoTagWhenResolvingInstanceByTypeForwarding),
|
||||
("testThatItThrowsErrorWhenResolvingNotImplementedTypeWithTypeForwarding", testThatItThrowsErrorWhenResolvingNotImplementedTypeWithTypeForwarding),
|
||||
("testThatItOverridesIfSeveralDefinitionsWithTheSameTagForwardTheSameType", testThatItOverridesIfSeveralDefinitionsWithTheSameTagForwardTheSameType)
|
||||
]
|
||||
}
|
||||
|
||||
func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#else
|
||||
override func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#endif
|
||||
|
||||
func testThatItResolvesInstanceByTypeForwarding() {
|
||||
//given
|
||||
let def = container.register { ServiceImp1() as Service }
|
||||
container.register(def, type: ForwardedType.self)
|
||||
container.register(def, type: NSObject.self)
|
||||
|
||||
//when
|
||||
let anotherService = try! container.resolve() as ForwardedType
|
||||
let anyOtherService = try! container.resolve(ForwardedType.self)
|
||||
let object = try! container.resolve() as NSObject
|
||||
let anyObject = try! container.resolve(NSObject.self)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(anotherService is ServiceImp1)
|
||||
XCTAssertTrue(object is ServiceImp1)
|
||||
XCTAssertTrue(anyOtherService is ServiceImp1)
|
||||
XCTAssertTrue(anyObject is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItReusesInstanceResolvedByTypeForwarding() {
|
||||
//given
|
||||
let def = container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
.resolveDependencies { container, resolved in
|
||||
//when
|
||||
let forwardType = try container.resolve() as ForwardedType
|
||||
let anyForwardType = try container.resolve(ForwardedType.self) as! ForwardedType
|
||||
let object = try container.resolve() as NSObject
|
||||
let anyObject = try container.resolve(NSObject.self) as! NSObject
|
||||
let service = try container.resolve() as Service
|
||||
let anyService = try container.resolve(Service.self) as! Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(forwardType === resolved as! ForwardedType)
|
||||
XCTAssertTrue(anyForwardType === resolved as! ForwardedType)
|
||||
XCTAssertTrue(object === resolved as! NSObject)
|
||||
XCTAssertTrue(anyObject === resolved as! NSObject)
|
||||
XCTAssertTrue(service === resolved)
|
||||
XCTAssertTrue(anyService === resolved)
|
||||
}
|
||||
container.register(def, type: ForwardedType.self)
|
||||
container.register(def, type: NSObject.self)
|
||||
|
||||
let _ = try! container.resolve() as Service
|
||||
let _ = try! container.resolve() as ForwardedType
|
||||
let _ = try! container.resolve() as NSObject
|
||||
}
|
||||
|
||||
func testThatItDoesNotResolveByTypeForwardingIfRegisteredForAnotherTag() {
|
||||
//given
|
||||
let def = container.register(tag: "tag") { ServiceImp1() as Service }
|
||||
container.register(def, type: ForwardedType.self, tag: "otherTag")
|
||||
|
||||
//then
|
||||
AssertThrows(expression: try container.resolve(tag: "tag") as ForwardedType)
|
||||
AssertThrows(expression: try container.resolve(ForwardedType.self, tag: "tag"))
|
||||
|
||||
//and given
|
||||
container.register(def, type: ForwardedType.self, tag: "tag")
|
||||
|
||||
//then
|
||||
AssertNoThrow(expression: try container.resolve(tag: "tag") as ForwardedType)
|
||||
AssertNoThrow(expression: try container.resolve(ForwardedType.self, tag: "tag"))
|
||||
}
|
||||
|
||||
func testThatItDoesNotReuseInstanceResolvedByTypeForwardingRegisteredForAnotherTag() {
|
||||
var resolveDependenciesCalled = false
|
||||
//given
|
||||
let def = container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
.resolveDependencies { container, service in
|
||||
guard resolveDependenciesCalled == false else { return }
|
||||
resolveDependenciesCalled = true
|
||||
|
||||
let forwardType = try container.resolve(tag: "tag") as ForwardedType
|
||||
let anyForwardType = try container.resolve(ForwardedType.self, tag: "tag") as! ForwardedType
|
||||
|
||||
let object = try container.resolve() as NSObject
|
||||
let anyObject = try container.resolve(NSObject.self) as! NSObject
|
||||
|
||||
//then
|
||||
XCTAssertFalse(forwardType === service as! ForwardedType)
|
||||
XCTAssertFalse(anyForwardType === service as! ForwardedType)
|
||||
XCTAssertTrue(object === service as! NSObject)
|
||||
XCTAssertTrue(anyObject === service as! NSObject)
|
||||
}
|
||||
container.register(def, type: ForwardedType.self, tag: "tag")
|
||||
container.register(def, type: NSObject.self)
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve() as Service
|
||||
}
|
||||
|
||||
func testThatItCallsResolvedDependenciesBlockWhenResolvingByTypeForwarding() {
|
||||
//given
|
||||
var originalResolveDependenciesCalled = false
|
||||
var resolveDependenciesCalled = false
|
||||
let def = container.register { ServiceImp1() }
|
||||
.resolveDependencies { container, service in
|
||||
originalResolveDependenciesCalled = true
|
||||
}
|
||||
|
||||
container.register(def, type: Service.self)
|
||||
.resolveDependencies { container, object in
|
||||
resolveDependenciesCalled = true
|
||||
}
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve() as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(resolveDependenciesCalled)
|
||||
XCTAssertTrue(originalResolveDependenciesCalled)
|
||||
|
||||
//and when
|
||||
resolveDependenciesCalled = false
|
||||
originalResolveDependenciesCalled = false
|
||||
let _ = try! container.resolve(Service.self)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(resolveDependenciesCalled)
|
||||
XCTAssertTrue(originalResolveDependenciesCalled)
|
||||
}
|
||||
|
||||
func testThatItFallbackToDefinitionWithNoTagWhenResolvingInstanceByTypeForwarding() {
|
||||
let def = container.register { ServiceImp1() as Service }
|
||||
container.register { ServiceImp2() as Service }
|
||||
container.register(def, type: NSObject.self)
|
||||
|
||||
//when
|
||||
let service = try! container.resolve(tag: "tag") as NSObject
|
||||
let anyService = try! container.resolve(NSObject.self, tag: "tag")
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service is ServiceImp1)
|
||||
XCTAssertTrue(anyService is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItFirstUsesTaggedDefinitionWhenResolvingOptional() {
|
||||
let expectedTag: DependencyContainer.Tag = .String("tag")
|
||||
container.register(tag: expectedTag) { ServiceImp1() as Service }
|
||||
.resolveDependencies { container, resolved in
|
||||
XCTAssertEqual(container.context.tag, expectedTag)
|
||||
}
|
||||
container.register { ServiceImp2() as Service }
|
||||
|
||||
//when
|
||||
let service = try! container.resolve(tag: "tag") as Service?
|
||||
let anyService = try! container.resolve((Service?).self, tag: "tag")
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service is ServiceImp1)
|
||||
XCTAssertTrue(anyService is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItThrowsErrorWhenResolvingNotImplementedTypeWithTypeForwarding() {
|
||||
//given
|
||||
let def = container.register { ServiceImp1() as Service }
|
||||
container.register(def, type: NSCoder.self)
|
||||
|
||||
//then
|
||||
AssertThrows(expression: try container.resolve() as NSCoder)
|
||||
AssertThrows(expression: try container.resolve(NSCoder.self))
|
||||
}
|
||||
|
||||
func testThatItOverridesIfSeveralDefinitionsWithTheSameTagForwardTheSameType() {
|
||||
let def1 = container.register { ServiceImp1() as Service }
|
||||
let def2 = container.register { ServiceImp2() as Service }
|
||||
container.register(def1, type: NSObject.self)
|
||||
|
||||
XCTAssertTrue(try! container.resolve() as NSObject is ServiceImp1)
|
||||
XCTAssertTrue(try! container.resolve(NSObject.self) is ServiceImp1)
|
||||
|
||||
//when
|
||||
container.register(def2, type: NSObject.self)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(try! container.resolve() as NSObject is ServiceImp2)
|
||||
XCTAssertTrue(try! container.resolve(NSObject.self) is ServiceImp2)
|
||||
|
||||
//and given
|
||||
container.register(def2, type: NSObject.self, tag: "tag")
|
||||
|
||||
XCTAssertTrue(try! container.resolve(tag: "tag") as NSObject is ServiceImp2)
|
||||
XCTAssertTrue(try! container.resolve(NSObject.self, tag: "tag") is ServiceImp2)
|
||||
|
||||
//when
|
||||
container.register(def1, type: NSObject.self, tag: "tag")
|
||||
|
||||
//then
|
||||
XCTAssertTrue(try! container.resolve(tag: "tag") as NSObject is ServiceImp1)
|
||||
XCTAssertTrue(try! container.resolve(NSObject.self, tag: "tag") is ServiceImp1)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,7 +8,9 @@ let container = DependencyContainer()
|
||||
|
||||
### Auto-Injection
|
||||
|
||||
If you follow Single Responsibility Principle chances are very high that you will end up with more than two collaborating components in your system. Let's say you have a component that depends on few others. Using _Dip_ you can register all of the dependencies in a container as well as that component itself and register a factory that will create that component and feed it with the dependencies resolving them with a container:
|
||||
On the previous page you saw how auto-wiring helps us get rid of boilerplate code when registering and resolving components with consturctor injection. Auto-injection solves the same problem for property injection.
|
||||
|
||||
Let's say you have following related components:
|
||||
*/
|
||||
|
||||
protocol Service: class {
|
||||
@@ -21,13 +23,18 @@ class ServiceImp: Service {
|
||||
var tracker: Tracker?
|
||||
}
|
||||
|
||||
/*:
|
||||
When you register them in a container you will end up with something like this:
|
||||
*/
|
||||
|
||||
container.register() { TrackerImp() as Tracker }
|
||||
container.register() { LoggerImp() as Logger }
|
||||
|
||||
container.register() { ServiceImp() as Service }
|
||||
.resolveDependencies { container, service in
|
||||
(service as! ServiceImp).logger = try container.resolve() as Logger
|
||||
(service as! ServiceImp).tracker = try container.resolve() as Tracker
|
||||
let service = service as! ServiceImp
|
||||
service.logger = try container.resolve() as Logger
|
||||
service.tracker = try container.resolve() as Tracker
|
||||
}
|
||||
|
||||
let service = try! container.resolve() as Service
|
||||
@@ -35,10 +42,8 @@ service.logger
|
||||
service.tracker
|
||||
|
||||
/*:
|
||||
Not bad so far. Though that `resolveDependencies` block looks heavy. It would be cool if we can get rid of it. Alternatively you can use _constructor injection_ here, which is actually more prefereable by default but not always possible (see [circular dependencies](Circular%20dependencies)).
|
||||
Now let's say that you have a bunch of components in your app that require `Logger` or `Tracker` too. You will need to resolve them in a factory for each component again and again. That can be a lot of boilerplate code, simple but still duplicated.
|
||||
|
||||
That is one of the scenarios when auto-injection can be useful. It works with property injection and with it the previous code will transform to this:
|
||||
Notice that the same boilerplate code that we saw in constructor injection now moved to `resolveDepedencies` block.
|
||||
With auto-injection your code transforms to this:
|
||||
*/
|
||||
|
||||
class AutoInjectedServiceImp: Service {
|
||||
@@ -60,10 +65,12 @@ As you can see we added two private properties to our implementation of `Service
|
||||
|
||||
What is happening under the hood is that after concrete instance of resolved type is created (`Service` in that case), container will iterate through its properties using `Mirror`. For each of the properties wrapped with `Injected<T>` or `InjectedWeak<T>` it will search a definition that can be used to create an instance of wrapped type and use it to create and inject a concrete instance in a `value` property of a wrapper. The fact that wrappers are _classes_ or _reference types_ makes it possible at runtime to inject dependency in instance of resolved type.
|
||||
|
||||
You can provide closure that will be called when the dependency will be injected in the property. It is similar to `didSet` property observer.
|
||||
The requirement for auto-injection is that types injected types should be registered in a container and should use factories with no runtime arguments.
|
||||
|
||||
Auto-injected properties can be marked with tag. Then container will search for definition tagged by the same tag to resolve this property.
|
||||
|
||||
You can provide closure that will be called when the dependency will be injected in the property. It is similar to `didSet` property observer.
|
||||
|
||||
Auto-injected properties are required by default. That means that if container fails to resolve any of auto-injected properties of the instance (or any of its dependencies) it will fail resolution of the object graph in whole.
|
||||
*/
|
||||
|
||||
@@ -198,11 +205,9 @@ autoViewController.router.value
|
||||
/*:
|
||||
In such scenario when view controller is created by storyboard you will need to use property injection anyway, so the overhead of adding additional properties for auto-injection is smaller. Also all the boilerplate code of unwrapping injected properties (if you need that) can be moved to extension, cleaning implementation a bit.
|
||||
|
||||
> **Note**: For such cases concider using [DipUI](https://github.com/AliSoftware/Dip-UI). It is a small extension for Dip that allows you to do exactly what we need in this example - inject dependencies in instances created by storyboards. It does not require to use auto-injection feature.
|
||||
> **Note**: For such cases concider using [DipUI](https://github.com/AliSoftware/Dip-UI). It is a small extension for Dip that allows you to do exactly what we need in this example - inject dependencies in instances created by storyboards. It does not require to use auto-injection feature but plays nice with it.
|
||||
|
||||
So as you can see there are certain advantages and disadvatages of using auto-injection. It makes your definitions simpler, especially if there are circular dependencies involved or the number of dependencies is high. But it requires additional properties and some boilerplate code in your implementations, makes your implementatios tightly coupled with Dip. It has also some limitations like that it requires factories for auto-injected types that accept no runtime arguments to be registered in a container.
|
||||
|
||||
So you should decide for yourself whether you prefer to use auto-injection or "the standard" way. At the end they let you achieve the same result.
|
||||
So as you can see there are certain advantages and disadvatages of using auto-injection. It makes your definitions simpler, especially if there are circular dependencies involved or the number of dependencies is high, removing boilerplate calls to `resolve` method in `resolveDependencies` block of your definitions. But it requires additional properties and some boilerplate code in your implementations, makes your implementatios tightly coupled with Dip. You can avoid tight coupoling by using your own boxing classes instead of `Injected<T>` and `InjectedWeak<T>` (see `AutoInjectedPropertyBox`).
|
||||
*/
|
||||
|
||||
//: [Next: Testing](@next)
|
||||
//: [Next: Type Forwarding](@next)
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
//: [Previous: Shared Instances](@previous)
|
||||
|
||||
import Dip
|
||||
import UIKit
|
||||
|
||||
/*:
|
||||
|
||||
### Auto-wiring
|
||||
|
||||
Among three main DI patterns - _constructor_, _property_ and _method_ injection - construction injection should be your choise by default. Dip makes use of this pattern very simple.
|
||||
|
||||
Let's say you have some VIPER module with following components:
|
||||
*/
|
||||
protocol Service {}
|
||||
protocol Interactor {
|
||||
var service: Service { get }
|
||||
}
|
||||
protocol Router {}
|
||||
protocol ViewOutput {}
|
||||
protocol Presenter {
|
||||
var router: Router { get }
|
||||
var interactor: Interactor { get }
|
||||
var view: ViewOutput { get }
|
||||
}
|
||||
|
||||
class RouterImp: Router {}
|
||||
class View: UIView, ViewOutput {}
|
||||
class ServiceImp: Service {}
|
||||
|
||||
/*:
|
||||
VIPER module by its nature consists of a lot of components, wired up using protocols. Using construction injection you can end up with following constructors for presenter and interactor:
|
||||
*/
|
||||
|
||||
class InteractorImp: Interactor {
|
||||
var service: Service
|
||||
|
||||
init(service: Service) {
|
||||
self.service = service
|
||||
}
|
||||
}
|
||||
|
||||
class PresenterImp: Presenter {
|
||||
let router: Router
|
||||
let interactor: Interactor
|
||||
let view: ViewOutput
|
||||
|
||||
init(view: ViewOutput, interactor: Interactor, router: Router) {
|
||||
self.view = view
|
||||
self.interactor = interactor
|
||||
self.router = router
|
||||
}
|
||||
}
|
||||
|
||||
/*:
|
||||
If you register these components in a container you will end up with rather boilerplate code:
|
||||
*/
|
||||
|
||||
let container = DependencyContainer()
|
||||
container.register { ServiceImp() as Service }
|
||||
container.register { RouterImp() as Router }
|
||||
container.register { View() as ViewOutput }
|
||||
|
||||
container.register { try InteractorImp(service: container.resolve()) as Interactor }
|
||||
container.register {
|
||||
try PresenterImp(
|
||||
view: container.resolve(),
|
||||
interactor: container.resolve(),
|
||||
router: container.resolve()) as Presenter
|
||||
}
|
||||
|
||||
|
||||
var presenter = try! container.resolve() as Presenter
|
||||
presenter.interactor.service
|
||||
|
||||
/*:
|
||||
While definition for `Interactor` looks fine, `Presenter`'s definition is overloaded with the same `resolve` calls to container.
|
||||
|
||||
The other option you have is to register factory with runtime arguments:
|
||||
*/
|
||||
|
||||
container.register { InteractorImp(service: $0) as Interactor }
|
||||
container.register { PresenterImp(view: $0, interactor: $1, router: $2) as Presenter }
|
||||
|
||||
/*:
|
||||
But then to resolve presenter or interactor you will first need to resolve their dependencies and pass them as arguments to `resolve` method:
|
||||
*/
|
||||
|
||||
let service = try! container.resolve() as Service
|
||||
let interactor = try! container.resolve(withArguments: service) as Interactor
|
||||
let view = try! container.resolve() as ViewOutput
|
||||
let router = try! container.resolve() as Router
|
||||
presenter = try! container.resolve(withArguments: view, interactor, router) as Presenter
|
||||
presenter.interactor.service
|
||||
|
||||
/*:
|
||||
Again to much of boilerplate code. Also it's easy to make a mistake in the order of arguments.
|
||||
|
||||
Auto-wiring solves this problem by combining these two approaches - you register factories with runtime arguments, but resolve components with just a call to `resolve()`. Container will resolve all consturctor arguments for you.
|
||||
*/
|
||||
|
||||
container.register { InteractorImp(service: $0) as Interactor }
|
||||
container.register { PresenterImp(view: $0, interactor: $1, router: $2) as Presenter }
|
||||
|
||||
presenter = try! container.resolve() as Presenter
|
||||
presenter.interactor.service
|
||||
|
||||
/*:
|
||||
You don't need to call `resolve` in a factory and care about order of arguments any more.
|
||||
|
||||
The only requirement is that all constructor arguments should be registered in the container and there should be no several factories with the same _number_ of arguments registered for the same components.
|
||||
|
||||
In very rare case when you have several different factories with different set of runtime arguments registered for the same component, when you try to resolve it container will try to use these factories one by one until one of them succeeds starting with a factory with most numbers of arguments. If it finds two factories with the same number of arguments it will throw an error.
|
||||
|
||||
You can use auto-wiring with tags. The tag that you pass to `resolve` method will be used to resolve each of the constructor arguments.
|
||||
*/
|
||||
|
||||
//: [Next: Auto-injection](@next)
|
||||
+9
-2
@@ -31,11 +31,17 @@ container.register(factory: factory.someService)
|
||||
|
||||
/*:
|
||||
Optionally you can associate definitions with Integer or String tags. This way you can register different implementations for the same protocol.
|
||||
You can use String or Integer literals, or the `DependencyContainer.Tag` enum.
|
||||
You can use `DependencyContainer.Tag` enum, String or Integer literals, or instances of types that conform to `DependencyTagConvertible` protocol.
|
||||
*/
|
||||
|
||||
container.register(tag: "tag") { ServiceImp1() as Service }
|
||||
container.register(tag: DependencyContainer.Tag.Int(0)) { ServiceImp1() as Service }
|
||||
container.register(tag: 0) { ServiceImp1() as Service }
|
||||
|
||||
enum MyCustomTag: String, DependencyTagConvertible {
|
||||
case SomeTag
|
||||
}
|
||||
|
||||
container.register(tag: MyCustomTag.SomeTag) { ServiceImp1() as Service }
|
||||
|
||||
/*:
|
||||
We recommand you to use constants for the tags, to make the intent clear and avoid magic numbers and typos.
|
||||
@@ -44,6 +50,7 @@ You can remove all registered definitions or register and remove them one by one
|
||||
*/
|
||||
|
||||
let serviceDefinition = container.register { ServiceImp1() as Service }
|
||||
container
|
||||
container.remove(serviceDefinition)
|
||||
|
||||
container.reset()
|
||||
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Timeline
|
||||
version = "3.0">
|
||||
<TimelineItems>
|
||||
</TimelineItems>
|
||||
</Timeline>
|
||||
@@ -35,12 +35,13 @@ _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, 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 register<T, A, B, C, D, E, F, G>(tag tag: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: (A, B, C, D, E, F, G) throws -> T) -> DefinitionOf<T, (A, B, C, D, E, F, G) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: 7) { container, tag in try factory(container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag))
|
||||
}
|
||||
}
|
||||
|
||||
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) }
|
||||
public func resolve<T, A, B, C, D, E, F, G>(tag tag: DependencyTagConvertible? = nil, _ arg1: A, _ arg2: B, _ arg3: C, _ arg4: D, _ arg5: E, _ arg6: F, _ arg7: G) throws -> T {
|
||||
return try resolve(tag: tag) { factory in try factory(arg1, arg2, arg3, arg4, arg5, arg6, arg7) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Timeline
|
||||
version = "3.0">
|
||||
<TimelineItems>
|
||||
</TimelineItems>
|
||||
</Timeline>
|
||||
@@ -13,6 +13,7 @@ Dip supports three different scopes of objects: _Prototype_, _ObjectGraph_ and _
|
||||
* 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 `.EagerSingleton` scope is the same as `.Singleton` scope but instances with this cope will be created when you call `bootstrap()` method on the container.
|
||||
|
||||
The `.Prototype` scope is the default. To set a scope you pass it as an argument to `register` method.
|
||||
*/
|
||||
@@ -43,5 +44,23 @@ let sameSharedService = try! container.resolve(tag: "shared instance") as Servic
|
||||
// same instances, the singleton scope keep and reuse instances during the lifetime of the container
|
||||
sharedService as! ServiceImp3 === sameSharedService as! ServiceImp3
|
||||
|
||||
/*:
|
||||
### Bootstrapping
|
||||
|
||||
You can use `bootstrap()` method to fix your container setup and initialise components registered with `EagerSingleton` scope.
|
||||
After bootstrapping if you try to add or remove any definition it will cause runtime exception. Call `boostrap` when you registered all the components, for example at the end of initialization block if you use `init(configBlock:)`.
|
||||
*/
|
||||
|
||||
var resolvedEagerSingleton = false
|
||||
let definition = container.register(tag: "eager shared instance", .EagerSingleton) { ServiceImp1() as Service }
|
||||
.resolveDependencies { _ in resolvedEagerSingleton = true }
|
||||
|
||||
try! container.bootstrap()
|
||||
resolvedEagerSingleton
|
||||
|
||||
let eagerSharedService = try! container.resolve(tag: "eager shared instance") as Service
|
||||
|
||||
container.remove(definition)
|
||||
|
||||
//: [Next: Circular Dependencies](@next)
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Timeline
|
||||
version = "3.0">
|
||||
<TimelineItems>
|
||||
</TimelineItems>
|
||||
</Timeline>
|
||||
@@ -127,37 +127,14 @@ class DipViewController: UIViewController {
|
||||
var dipController = DipViewController(dependencies: container)
|
||||
|
||||
/*:
|
||||
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.
|
||||
*/
|
||||
Of cource `DependencyContainer` should not be a singleton too. There is just no need for that because you never should call `DependencyContainer` from inside of your components. That will make it a [service locator antipatter]((http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/)). You may only call `DependencyContainer` from the _Composition root_ - the place where all the components are configured and wired together.
|
||||
|
||||
protocol ApiClientProvider {
|
||||
func apiClient() -> ApiClientProtocol
|
||||
}
|
||||
|
||||
extension DependencyContainer: ApiClientProvider {
|
||||
func apiClient() -> ApiClientProtocol {
|
||||
return try! self.resolve() as ApiClientProtocol
|
||||
}
|
||||
}
|
||||
|
||||
extension DipViewController {
|
||||
convenience init(apiClientProvider: ApiClientProvider) {
|
||||
self.init()
|
||||
self.apiClient = apiClientProvider.apiClient()
|
||||
}
|
||||
}
|
||||
|
||||
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 (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.
|
||||
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. 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: Auto-Injection](@next)
|
||||
//: [Next: Auto-wiring](@next)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Timeline
|
||||
version = "3.0">
|
||||
<TimelineItems>
|
||||
</TimelineItems>
|
||||
</Timeline>
|
||||
@@ -1,37 +1,49 @@
|
||||
//: [Previous: Shared Instances](@previous)
|
||||
|
||||
//import XCTest
|
||||
import Dip
|
||||
|
||||
let container = DependencyContainer()
|
||||
|
||||
/*:
|
||||
|
||||
### Testing
|
||||
|
||||
If you use Dependency Injection patterns like contructor and property injection it will be much easier
|
||||
to unit test your components. When it comes to integration tests you may want to mock some real services.
|
||||
In these tests you can register mock implementation in the container and it will be injected instead of the real implementation.
|
||||
|
||||
Dip is convenient to use for testing. Here is s simple example of how you can write tests with Dip.
|
||||
|
||||
__Note__: That's a very simple example just to demostrate use of Dip in tests, not how you should or should not tests your code in general.
|
||||
> That's a very simple example just to demonstrate use of Dip in tests, not how you should or should not test your code in general.
|
||||
You can learn more about testing based on state verification vs behavior verification [here](http://martinfowler.com/articles/mocksArentStubs.html).
|
||||
|
||||
|
||||
> XCTest is not supported by playgrounds so to be able to compile this page we commented out XCTest specific code.
|
||||
*/
|
||||
|
||||
protocol Service {
|
||||
protocol ServiceType {
|
||||
func doSomething()
|
||||
}
|
||||
|
||||
class RealService: ServiceType {
|
||||
func doSomething() {
|
||||
//do something real
|
||||
}
|
||||
}
|
||||
|
||||
class Client {
|
||||
var service: Service!
|
||||
var service: ServiceType!
|
||||
|
||||
func callService() {
|
||||
service.doSomething()
|
||||
}
|
||||
}
|
||||
|
||||
import XCTest
|
||||
import Dip
|
||||
|
||||
/*:
|
||||
Instead of the real `Service` implementation, provide a _fake_ implementation with test hooks that you need:
|
||||
*/
|
||||
|
||||
class FakeService: Service {
|
||||
class FakeService: ServiceType {
|
||||
var doSomethingCalled = false
|
||||
|
||||
func doSomething() {
|
||||
@@ -41,30 +53,60 @@ class FakeService: Service {
|
||||
init() {}
|
||||
}
|
||||
|
||||
class MyTests: XCTestCase {
|
||||
var container: DependencyContainer!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
/*:
|
||||
Register fake implementation as `Service`:
|
||||
Somewhere in your production code you register real implementations:
|
||||
*/
|
||||
func configure(container: DependencyContainer) {
|
||||
container.register { RealService() as ServiceType }
|
||||
container.register { Client() }
|
||||
.resolveDependencies { container, client in
|
||||
client.service = try container.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
class MyTests/*: XCTestCase*/ {
|
||||
|
||||
/*override*/ func setUp() {
|
||||
//super.setUp()
|
||||
|
||||
/*:
|
||||
Reset container configuration to normal state:
|
||||
*/
|
||||
container = DependencyContainer { container in
|
||||
container.register { FakeService() as Service }
|
||||
}
|
||||
container.reset()
|
||||
configure(container)
|
||||
}
|
||||
|
||||
func testThatDoSomethingIsCalled() {
|
||||
let sut = Client()
|
||||
sut.service = try! container.resolve() as Service
|
||||
|
||||
/*:
|
||||
Register fake implementation as `Service`:
|
||||
*/
|
||||
container.register { FakeService() as ServiceType }
|
||||
|
||||
let sut = try! container.resolve() as Client
|
||||
sut.callService()
|
||||
|
||||
/*:
|
||||
And finally you test it was called:
|
||||
*/
|
||||
XCTAssertTrue((sut.service as! FakeService).doSomethingCalled)
|
||||
let service = sut.service as! FakeService
|
||||
//XCTAssertTrue(service.doSomethingCalled)
|
||||
}
|
||||
}
|
||||
|
||||
/*:
|
||||
You can also validate your container configuration. You can do that either in a separate test suit or when runnging application in `DEBUG` mode.
|
||||
|
||||
During validation container will try to resolve all the definitions registered in it. If some of definitions requires runtime arguments you can provide them as arguments to `validate` method. They should exactly match types of arguments required by factories. Multiple arguments for the single factory should be grouped in a tuple. If you don't provide arguments validation will fail.
|
||||
*/
|
||||
container.register { (url: NSURL, port: Int) in ServiceImp4(name: "1", baseURL: url, port: port) as Service }
|
||||
try! container.validate((NSURL(string: "https://github.com/AliSoftware/Dip")!, 80))
|
||||
|
||||
do {
|
||||
try container.validate()
|
||||
}
|
||||
catch {
|
||||
print(error)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Timeline
|
||||
version = "3.0">
|
||||
<TimelineItems>
|
||||
</TimelineItems>
|
||||
</Timeline>
|
||||
@@ -0,0 +1,109 @@
|
||||
//: [Previous: Auto-injection](@previous)
|
||||
|
||||
import Foundation
|
||||
import Dip
|
||||
|
||||
let container = DependencyContainer()
|
||||
|
||||
/*:
|
||||
### Type Forwarding
|
||||
|
||||
Very often we end up with single class that implements several protocols. This is normal even in [VIPER architecture](https://github.com/mutualmobile/VIPER-SWIFT/blob/master/VIPER-SWIFT/Classes/Modules/List/User%20Interface/Presenter/ListPresenter.swift#L12) that constantly strives for Single Responsibility Principle.
|
||||
|
||||
Let's look at example of VIPER architecture:
|
||||
*/
|
||||
|
||||
extension ListPresenter: ListInteractorOutput, ListModuleInterface, AddModuleDelegate {}
|
||||
extension ListInteractor: ListInteractorInput {}
|
||||
extension AddPresenter: AddModuleInterface {}
|
||||
|
||||
/*:
|
||||
In VIPER we need to create several objects (presenters, wireframes, interactors) which should be accessed thorugh different interfaces. We need to wire them all together so that we have the same instances in place for different types.
|
||||
|
||||
- `ListInteractor` referenced by `ListPresenter` in its `listInteractor` property (via `ListInteractorInput` protocol) should hold a backward reference to the same presenter in its `output` property
|
||||
- `ListWireframe` referenced by `ListPresenter` should also hold a backward reference to the same presenter in its `listPresenter` property
|
||||
- `AddWireframe` should hold a reference to `AddPresenter` that should hold reference to the same `ListPresenter` in its `addModuleDelegate` property (via `AddModuleDelegate` protocol).
|
||||
|
||||
We can achieve this result by explicitly rosolving concrete types:
|
||||
*/
|
||||
|
||||
container.register(.ObjectGraph) { ListWireframe(addWireFrame: $0, listPresenter: $1) }
|
||||
container.register(.ObjectGraph) { AddWireframe(addPresenter: $0) }
|
||||
|
||||
var listInteractorDefinition = container.register(.ObjectGraph) { ListInteractor() }
|
||||
.resolveDependencies { container, interactor in
|
||||
interactor.output = try container.resolve() as ListPresenter
|
||||
}
|
||||
|
||||
var listPresenterDefinition = container.register(.ObjectGraph) { ListPresenter() }
|
||||
.resolveDependencies { container, presenter in
|
||||
presenter.listInteractor = try container.resolve() as ListInteractor
|
||||
presenter.listWireframe = try container.resolve()
|
||||
}
|
||||
|
||||
var addPresenterDefinition = container.register(.ObjectGraph) { AddPresenter() }
|
||||
.resolveDependencies { container, presenter in
|
||||
presenter.addModuleDelegate = try container.resolve() as ListPresenter
|
||||
}
|
||||
|
||||
var addPresenter = try! container.resolve() as AddPresenter
|
||||
var listPresenter = addPresenter.addModuleDelegate as! ListPresenter
|
||||
var listInteractor = listPresenter.listInteractor as! ListInteractor
|
||||
listInteractor.output === listPresenter
|
||||
var listWireframe = listPresenter.listWireframe
|
||||
listWireframe?.listPresenter === listPresenter
|
||||
|
||||
/*:
|
||||
Alternatively we can use type-forwarding. With type-forwarding we register definition for one (source) type and also for another (forwarded) type. When container will try to resolve forwarded type it will use the same definition as for source type, and (if registered in `ObjectGraph` scope or as a singleton) will reuse the same instance. With that you don't need to resolve concrete types in definitions:
|
||||
*/
|
||||
|
||||
listInteractorDefinition = container.register(.ObjectGraph) { ListInteractor() }
|
||||
.resolveDependencies { container, interactor in
|
||||
interactor.output = try container.resolve()
|
||||
}
|
||||
|
||||
listPresenterDefinition = container.register(.ObjectGraph) { ListPresenter() }
|
||||
.resolveDependencies { container, presenter in
|
||||
presenter.listInteractor = try container.resolve()
|
||||
presenter.listWireframe = try container.resolve()
|
||||
}
|
||||
|
||||
addPresenterDefinition = container.register(.ObjectGraph) { AddPresenter() }
|
||||
.resolveDependencies { container, presenter in
|
||||
presenter.addModuleDelegate = try container.resolve()
|
||||
}
|
||||
|
||||
/*:
|
||||
And now we register definitions for type-forwarding:
|
||||
*/
|
||||
|
||||
container.register(listInteractorDefinition, type: ListInteractorInput.self)
|
||||
container.register(listPresenterDefinition, type: ListInteractorOutput.self)
|
||||
container.register(listPresenterDefinition, type: ListModuleInterface.self)
|
||||
container.register(listPresenterDefinition, type: AddModuleDelegate.self)
|
||||
|
||||
addPresenter = try! container.resolve() as AddPresenter
|
||||
listPresenter = addPresenter.addModuleDelegate as! ListPresenter
|
||||
listInteractor = listPresenter.listInteractor as! ListInteractor
|
||||
listInteractor.output === listPresenter
|
||||
listWireframe = listPresenter.listWireframe
|
||||
listWireframe?.listPresenter === listPresenter
|
||||
|
||||
/*:
|
||||
Type forwarding will work the same way whenever your resolve dependencies with property injection using `resolveDependencies` block, or with auto-injected properties, or with constructor injection and auto-wiring.
|
||||
|
||||
Registering definition for type forwarding will effectively register another definition in the container, linked with original one. So the same overriding rool will be applied for such registrations - last wins. If you need to register different definitions for the same type you should register them with different tags.
|
||||
|
||||
You can also provide `resolveDependencies` block for forwarded definition. First container will call `resolveDependencies` block of the source definition, and then of forwarded definition:
|
||||
*/
|
||||
listInteractorDefinition
|
||||
.resolveDependencies { container, interactor in
|
||||
print("resolved ListInteractor")
|
||||
}
|
||||
container.register(listInteractorDefinition, type: ListInteractorInput.self)
|
||||
.resolveDependencies { container, interactor in
|
||||
print("resolved ListInteractorInput")
|
||||
}
|
||||
addPresenter = try! container.resolve() as AddPresenter
|
||||
|
||||
//: [Next: Testing](@next)
|
||||
@@ -74,5 +74,37 @@ public class DataProviderImp: DataProvider {
|
||||
public init() {}
|
||||
}
|
||||
|
||||
public protocol ListInteractorOutput: class {}
|
||||
public protocol ListModuleInterface: class {}
|
||||
public protocol ListInteractorInput: class {}
|
||||
public class ListPresenter: NSObject {
|
||||
public var listInteractor : ListInteractorInput?
|
||||
public var listWireframe : ListWireframe?
|
||||
public override init() {}
|
||||
}
|
||||
public class ListInteractor: NSObject {
|
||||
public var output : ListInteractorOutput?
|
||||
public override init() {}
|
||||
}
|
||||
|
||||
public class ListWireframe : NSObject {
|
||||
public let addWireframe: AddWireframe
|
||||
public let listPresenter: ListPresenter
|
||||
public init(addWireFrame: AddWireframe, listPresenter: ListPresenter) {
|
||||
self.addWireframe = addWireFrame
|
||||
self.listPresenter = listPresenter
|
||||
}
|
||||
}
|
||||
|
||||
public protocol AddModuleDelegate: class {}
|
||||
public protocol AddModuleInterface: class {}
|
||||
public class AddWireframe: NSObject {
|
||||
let addPresenter : AddPresenter
|
||||
public init(addPresenter: AddPresenter) {
|
||||
self.addPresenter = addPresenter
|
||||
}
|
||||
}
|
||||
public class AddPresenter: NSObject {
|
||||
public var addModuleDelegate : AddModuleDelegate?
|
||||
public override init() {}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<playground version='6.0' target-platform='ios' display-mode='rendered'>
|
||||
<playground version='6.0' target-platform='ios' display-mode='raw'>
|
||||
<pages>
|
||||
<page name='What is Dip?'/>
|
||||
<page name='Creating container'/>
|
||||
@@ -9,7 +9,9 @@
|
||||
<page name='Scopes'/>
|
||||
<page name='Circular dependencies'/>
|
||||
<page name='Shared Instances'/>
|
||||
<page name='Auto-wiring'/>
|
||||
<page name='Auto-injection'/>
|
||||
<page name='Type Forwarding'/>
|
||||
<page name='Testing'/>
|
||||
</pages>
|
||||
</playground>
|
||||
@@ -5,6 +5,8 @@
|
||||
[](https://github.com/Carthage/Carthage)
|
||||
[](http://cocoapods.org/pods/Dip)
|
||||
[](http://cocoapods.org/pods/Dip)
|
||||
[](https://developer.apple.com/swift)
|
||||
[](https://developer.apple.com/swift)
|
||||
|
||||

|
||||
_Photo courtesy of [www.kevinandamanda.com](http://www.kevinandamanda.com/recipes/appetizer/homemade-soft-cinnamon-sugar-pretzel-bites-with-salted-caramel-dipping-sauce.html)_
|
||||
@@ -15,26 +17,127 @@ _Photo courtesy of [www.kevinandamanda.com](http://www.kevinandamanda.com/recipe
|
||||
|
||||
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 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](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 start by creating `let container = DependencyContainer()` and **registering your dependencies, by associating a _protocol_ or _type_ to a `factory`**.
|
||||
* Then you can call `container.resolve()` to **resolve an instance of _protocol_ or _type_** using that `DependencyContainer`.
|
||||
|
||||
> 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
|
||||
## Documentation & Usage Examples
|
||||
|
||||
* 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`), 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
|
||||
* 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.
|
||||
Dip is completely [documented](http://cocoadocs.org/docsets/Dip/4.5.0/) and comes with a Playground that lets you try all its features and become familiar with API. You can find it in `Dip.xcworkspace`.
|
||||
|
||||
> Note: it may happen that you will need to build Dip framework before playground will be able to use it. For that select `Dip-iOS` scheme and build.
|
||||
|
||||
You can find bunch of usage examples in a [wiki](wiki).
|
||||
|
||||
There are also several blog posts that describe how to use Dip and some of its implementation details:
|
||||
|
||||
- [IoC container in Swift](http://ilya.puchka.me/ioc-container-in-swift/)
|
||||
- [IoC container in Swift. Circular dependencies and auto-injection](http://ilya.puchka.me/ioc-container-in-swift-circular-dependencies-and-auto-injection/)
|
||||
- [Dependency injection with Dip](http://ilya.puchka.me/dependency-injecinjection-with-dip/)
|
||||
|
||||
File an issue if you have any question.
|
||||
|
||||
|
||||
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).
|
||||
## Features
|
||||
|
||||
- **[Scopes](wiki/scopes)**. Dip supports 4 different scopes (or life cycle strategies): _Prototype_, _ObjectGraph_, _Singleton_, _EagerSingleton_;
|
||||
- **[Named definitions](wiki/named-definitions)**. You can register different factories for the same protocol or type by registering them with [tags]();
|
||||
- **[Runtime arguments](wiki/runtime-arguments)**. You can register factories that accept up to 6 runtime arguments;
|
||||
- **[Circular dependencies](wiki/circular-dependencies)**. Dip can resolve circular dependencies;
|
||||
- **[Auto-wiring](wiki/auto-wiring)** & **[Auto-injection](wiki/auto-injection)**. Dip can infer your components' dependencies injected in constructor and automatically resolve them as well as dependencies injected with properties.
|
||||
- **[Type forwarding](wiki/Type-forwarding)**. You can register the same factory to resolve different types.
|
||||
- **[Storyboards integration](wiki/storyboards-integration)**. You can easily use Dip along with storyboards and Xibs without ever referencing container in your view controller's code;
|
||||
- **Weakly typed components**. Dip can resolve weak types when they are unknown at compile time.
|
||||
- **Easy configuration**. No complex container hierarchy, no unneeded functionality;
|
||||
- **Thread safety**. Registering and resolving components is thread safe;
|
||||
- **Helpful error messages and configuration validation**. You can validate your container configuration. If something can not be resolved at runtime Dip throws an error that completely describes the issue;
|
||||
|
||||
## Basic usage
|
||||
|
||||
```swift
|
||||
import Dip
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
// Create the container
|
||||
private let container = DependencyContainer { container in
|
||||
|
||||
// Register some factory. ServiceImp here implements protocol Service
|
||||
container.register { ServiceImp() as Service }
|
||||
}
|
||||
|
||||
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
|
||||
|
||||
// Resolve a concrete instance. Container will instantiate new instance of ServiceImp
|
||||
let service = try! container.resolve() as Service
|
||||
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## More sophisticated example
|
||||
|
||||
```swift
|
||||
import Dip
|
||||
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
private let container = DependencyContainer.configure()
|
||||
...
|
||||
}
|
||||
|
||||
//CompositionRoot.swift
|
||||
import Dip
|
||||
import DipUI
|
||||
|
||||
extension DependencyContainer {
|
||||
|
||||
static func configure() -> DependencyContainer {
|
||||
return DependencyContainer { container in
|
||||
container.register(tag: "ViewController") { ViewController() }
|
||||
.resolveDependencies { container, controller in
|
||||
controller.animationsFactory = try container.resolve() as AnimatonsFactory
|
||||
}
|
||||
|
||||
container.register { AuthFormBehaviourImp(apiClient: $0) as AuthFormBehaviour }
|
||||
container.register { container as AnimationsFactory }
|
||||
container.register { view in ShakeAnimationImp(view: view) as ShakeAnimation }
|
||||
container.register { APIClient(baseURL: NSURL(string: "http://localhost:2368")!) as ApiClient }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension DependencyContainer: AnimationsFactory {
|
||||
func shakeAnimation(view: UIView) -> ShakeAnimation {
|
||||
return try! self.resolve(withArguments: view)
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewController: StoryboardInstantiatable {}
|
||||
|
||||
//ViewController.swift
|
||||
|
||||
class ViewController {
|
||||
var animationsFactory: AnimationsFactory?
|
||||
|
||||
private let _formBehaviour = Injected<AuthFormBehaviour>()
|
||||
|
||||
var formBehaviour: AuthFormBehaviour? {
|
||||
return _formBehaviour.value
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
Since version 4.3.1 Dip is built with Swift 2.2. The latest version built with Swift 2.1 is 4.3.0.
|
||||
|
||||
Dip is available through [CocoaPods](http://cocoapods.org). To install
|
||||
it, simply add the following line to your Podfile:
|
||||
|
||||
@@ -42,312 +145,33 @@ it, simply add the following line to your Podfile:
|
||||
pod "Dip"
|
||||
```
|
||||
|
||||
If you use _Carthage_ add this line to your Cartfile:
|
||||
If you use [Carthage](https://github.com/Carthage/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`:
|
||||
If you use [Swift Package Manager](https://swift.org/package-manager/) add Dip as dependency to you `Package.swift`:
|
||||
|
||||
```
|
||||
```swift
|
||||
let package = Package(
|
||||
name: "MyPackage",
|
||||
dependencies: [
|
||||
.Package(url: "https://github.com/AliSoftware/Dip.git", "4.2.0")
|
||||
.Package(url: "https://github.com/AliSoftware/Dip.git", "4.5.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:
|
||||
On OSX you can run tests from Xcode. On Linux you need to have Swift Package Manager installed and use it to build and run tests:
|
||||
|
||||
```
|
||||
cd Dip/DipTests
|
||||
swift build
|
||||
./.build/debug/DipTests
|
||||
cd Dip
|
||||
swift build && swift test
|
||||
```
|
||||
|
||||
## Playground
|
||||
|
||||
Dip comes with a **Playground** to introduce you to Inversion of Control, Dependency Injection, and how to use Dip in practice.
|
||||
|
||||
To play with it, [open `Dip.xcworkspace`](Dip/Dip.xcworkspace), then click on the `DipPlayground` entry in Xcode's Project Navigator and let it be your guide.
|
||||
|
||||
_Note: Do not open the `DipPlayground.playground` file directly, as it needs to be part of the workspace to access the Dip framework so that the demo code it contains can work._
|
||||
|
||||
The next paragraphs give you an overview of the Usage of _Dip_ directly, but if you're new to Dependency Injection, the Playground is probably a better start.
|
||||
|
||||
## Usage
|
||||
|
||||
### Register instance factories
|
||||
|
||||
First, create a `DependencyContainer` and use it to register instance factories with protocols, using those methods:
|
||||
|
||||
* `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, 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
|
||||
|
||||
* `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 = 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 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 it gets very useful, because instead of having to do setup the container like this:
|
||||
|
||||
```swift
|
||||
let dip: DependencyContainer = {
|
||||
let dip = DependencyContainer()
|
||||
|
||||
dip.register { ProductionEnvironment(analytics: true) as EnvironmentType }
|
||||
dip.register { WebService() as WebServiceAPI }
|
||||
|
||||
return dip
|
||||
}()
|
||||
```
|
||||
|
||||
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
|
||||
dip.register { ProductionEnvironment(analytics: true) as EnvironmentType }
|
||||
dip.register { WebService() as WebServiceAPI }
|
||||
}
|
||||
```
|
||||
|
||||
### 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 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 Production
|
||||
case Development
|
||||
|
||||
var tag: DependencyContainer.Tag { return DependencyContainer.Tag.String(self.rawValue) }
|
||||
}
|
||||
|
||||
let wsDependencies = DependencyContainer() { dip in
|
||||
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 = 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 (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 }
|
||||
}
|
||||
|
||||
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 that.
|
||||
|
||||
### 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
|
||||
|
||||
Let's say you have some view model that depends on some data provider and web service:
|
||||
|
||||
```swift
|
||||
struct WebService {
|
||||
let env: EnvironmentType
|
||||
|
||||
init(env: EnvironmentType) {
|
||||
self.env = env
|
||||
}
|
||||
|
||||
func sendRequest(path: String, …) {
|
||||
// … use stuff like env.baseURL here
|
||||
}
|
||||
}
|
||||
|
||||
struct SomeViewModel {
|
||||
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)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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):
|
||||
|
||||
```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 }
|
||||
|
||||
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
|
||||
|
||||
In addition to this Usage overview and to the aforementioned playground, you can also find a complete example in the `SampleApp/DipSampleApp` project provided in this repository.
|
||||
|
||||
This sample project is a bit more complex, but closer to real-world applications (even if this sample is all about StarWars!),
|
||||
by declaring protocols like `NetworkLayer` which can be resolved to a `URLSessionNetworkLayer` in the real app, but to a dummy
|
||||
network layer returning fixture data during the Unit Tests.
|
||||
|
||||
This sample uses the Star Wars API provided by swapi.co to fetch Star Wars characters and starships info and display them in TableViews.
|
||||
|
||||
> Note: Swift Package Manager is destributed with Swift development snapshots only, so it builds packages using Swift 3. To build Dip you will need to build it with Swift 2.2, for that you need to set [`$SWIFT_EXEC`](https://github.com/apple/swift-package-manager#choosing-swift-version) environment variable.
|
||||
|
||||
## Credits
|
||||
|
||||
|
||||
@@ -36,6 +36,6 @@ extension BaseCell where Self : UITableViewCell {
|
||||
}
|
||||
|
||||
protocol FillableCell: BaseCell {
|
||||
typealias ObjectType
|
||||
associatedtype ObjectType
|
||||
func fillWithObject(object: ObjectType)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,11 @@ private let FAKE_STARSHIPS = false
|
||||
/* ---- */
|
||||
|
||||
|
||||
enum DependencyTags: Int, DependencyTagConvertible {
|
||||
case Hardcoded
|
||||
case Dummy
|
||||
}
|
||||
|
||||
// MARK: Dependency Container for Providers
|
||||
func configureContainer(dip: DependencyContainer) {
|
||||
|
||||
@@ -47,16 +52,16 @@ func configureContainer(dip: DependencyContainer) {
|
||||
// 2) Register fake starships provider
|
||||
|
||||
//Here we register different implementations for the same protocol using tags
|
||||
dip.register(tag: "hardcoded") { HardCodedStarshipProvider() as StarshipProviderAPI }
|
||||
dip.register(tag: DependencyTags.Hardcoded) { HardCodedStarshipProvider() as StarshipProviderAPI }
|
||||
|
||||
//Here we register factory that will require a runtime argument
|
||||
dip.register(tag: "dummy") { DummyStarshipProvider(pilotName: $0) as StarshipProviderAPI }
|
||||
dip.register(tag: DependencyTags.Dummy) { DummyStarshipProvider(pilotName: $0) as StarshipProviderAPI }
|
||||
|
||||
//Here we use constructor injection, but instead of providing dependencies manually container resolves them for us
|
||||
dip.register() {
|
||||
FakeStarshipProvider(
|
||||
dummyProvider: try dip.resolve(tag: "dummy", withArguments: "Main Pilot"),
|
||||
hardCodedProvider: try dip.resolve(tag: "hardcoded")) as StarshipProviderAPI
|
||||
dummyProvider: try dip.resolve(tag: DependencyTags.Dummy, withArguments: "Main Pilot"),
|
||||
hardCodedProvider: try dip.resolve(tag: DependencyTags.Hardcoded)) as StarshipProviderAPI
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import UIKit
|
||||
|
||||
protocol FetchableTrait: class {
|
||||
typealias ObjectType
|
||||
associatedtype ObjectType
|
||||
var objects: [ObjectType]? { get set }
|
||||
var batchRequestID: Int { get set }
|
||||
var tableView: UITableView! { get }
|
||||
|
||||
+67
-16
@@ -34,11 +34,17 @@ extension DependencyContainer {
|
||||
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)
|
||||
try inContext(
|
||||
context.tag,
|
||||
resolvingType: injectedPropertyBox.dynamicType.wrappedType,
|
||||
injectedInProperty: child.label)
|
||||
{
|
||||
do {
|
||||
try injectedPropertyBox.resolve(self)
|
||||
}
|
||||
catch {
|
||||
throw DipError.AutoInjectionFailed(label: child.label, type: injectedPropertyBox.dynamicType.wrappedType, underlyingError: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +55,7 @@ extension DependencyContainer {
|
||||
instead of using `Injected<T>` or `InjectedWeak<T>` types.
|
||||
|
||||
**Example**:
|
||||
|
||||
```swift
|
||||
class MyCustomBox<T> {
|
||||
private(set) var value: T?
|
||||
@@ -75,7 +82,7 @@ public protocol AutoInjectedPropertyBox: class {
|
||||
|
||||
- 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.
|
||||
- note: This method is not intended to be called manually, `DependencyContainer` will call it by itself.
|
||||
*/
|
||||
func resolve(container: DependencyContainer) throws
|
||||
}
|
||||
@@ -93,13 +100,13 @@ public protocol AutoInjectedPropertyBox: class {
|
||||
class ClientImp: Client {
|
||||
var service = Injected<Service>()
|
||||
}
|
||||
|
||||
```
|
||||
- seealso: `InjectedWeak`
|
||||
|
||||
*/
|
||||
public final class Injected<T>: _InjectedPropertyBox<T>, AutoInjectedPropertyBox {
|
||||
|
||||
///The type of wrapped property.
|
||||
public static var wrappedType: Any.Type {
|
||||
return T.self
|
||||
}
|
||||
@@ -122,15 +129,33 @@ public final class Injected<T>: _InjectedPropertyBox<T>, AutoInjectedPropertyBox
|
||||
- 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 convenience init(required: Bool = true, didInject: T -> () = { _ in }) {
|
||||
self.init(value: nil, required: required, tag: nil, overrideTag: false, didInject: didInject)
|
||||
}
|
||||
|
||||
|
||||
public convenience init(required: Bool = true, tag: DependencyTagConvertible?, didInject: T -> () = { _ in }) {
|
||||
self.init(value: nil, required: required, tag: tag, overrideTag: true, didInject: didInject)
|
||||
}
|
||||
|
||||
private init(value: T?, required: Bool = true, tag: DependencyTagConvertible?, overrideTag: Bool, didInject: T -> ()) {
|
||||
self.value = value
|
||||
super.init(required: required, tag: tag, overrideTag: overrideTag, didInject: didInject)
|
||||
}
|
||||
|
||||
public func resolve(container: DependencyContainer) throws {
|
||||
let resolved: T? = try super.resolve(container)
|
||||
value = resolved
|
||||
}
|
||||
|
||||
/// Returns a new wrapper with provided value.
|
||||
public func setValue(value: T?) -> Injected {
|
||||
guard (required && value != nil) || !required else {
|
||||
fatalError("Can not set required property to nil.")
|
||||
}
|
||||
|
||||
return Injected(value: value, required: required, tag: tag, overrideTag: overrideTag, didInject: didInject)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,11 +194,12 @@ public final class InjectedWeak<T>: _InjectedPropertyBox<T>, AutoInjectedPropert
|
||||
//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.
|
||||
|
||||
///The type of wrapped property.
|
||||
public static var wrappedType: Any.Type {
|
||||
return T.self
|
||||
}
|
||||
|
||||
weak var _value: AnyObject? = nil {
|
||||
private weak var _value: AnyObject? = nil {
|
||||
didSet {
|
||||
if let value = value { didInject(value) }
|
||||
}
|
||||
@@ -195,10 +221,19 @@ public final class InjectedWeak<T>: _InjectedPropertyBox<T>, AutoInjectedPropert
|
||||
- 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 convenience init(required: Bool = true, didInject: T -> () = { _ in }) {
|
||||
self.init(value: nil, required: required, tag: nil, overrideTag: false, didInject: didInject)
|
||||
}
|
||||
|
||||
|
||||
public convenience init(required: Bool = true, tag: DependencyTagConvertible?, didInject: T -> () = { _ in }) {
|
||||
self.init(value: nil, required: required, tag: tag, overrideTag: true, didInject: didInject)
|
||||
}
|
||||
|
||||
private init(value: T?, required: Bool = true, tag: DependencyTagConvertible?, overrideTag: Bool, didInject: T -> ()) {
|
||||
self._value = value as? AnyObject
|
||||
super.init(required: required, tag: tag, overrideTag: overrideTag, didInject: didInject)
|
||||
}
|
||||
|
||||
public func resolve(container: DependencyContainer) throws {
|
||||
let resolved: T? = try super.resolve(container)
|
||||
if required && !(resolved is AnyObject) {
|
||||
@@ -207,6 +242,19 @@ public final class InjectedWeak<T>: _InjectedPropertyBox<T>, AutoInjectedPropert
|
||||
_value = resolved as? AnyObject
|
||||
}
|
||||
|
||||
/// Returns a new wrapper with provided value.
|
||||
public func setValue(value: T?) -> InjectedWeak {
|
||||
let _value = value as? AnyObject
|
||||
if value != nil && _value == nil {
|
||||
fatalError("\(T.self) can not be casted to AnyObject. InjectedWeak wrapper should be used to wrap only classes.")
|
||||
}
|
||||
guard (required && _value != nil) || !required else {
|
||||
fatalError("Can not set required property to nil.")
|
||||
}
|
||||
|
||||
return InjectedWeak(value: value, required: required, tag: tag, overrideTag: overrideTag, didInject: didInject)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class _InjectedPropertyBox<T> {
|
||||
@@ -214,15 +262,18 @@ private class _InjectedPropertyBox<T> {
|
||||
let required: Bool
|
||||
let didInject: T -> ()
|
||||
let tag: DependencyContainer.Tag?
|
||||
let overrideTag: Bool
|
||||
|
||||
init(required: Bool = true, tag: DependencyContainer.Tag?, didInject: T -> () = { _ in }) {
|
||||
init(required: Bool = true, tag: DependencyTagConvertible?, overrideTag: Bool, didInject: T -> () = { _ in }) {
|
||||
self.required = required
|
||||
self.tag = tag
|
||||
self.tag = tag?.dependencyTag
|
||||
self.overrideTag = overrideTag
|
||||
self.didInject = didInject
|
||||
}
|
||||
|
||||
private func resolve(container: DependencyContainer) throws -> T? {
|
||||
let resolved: T?
|
||||
let tag = overrideTag ? self.tag : container.context.tag
|
||||
if required {
|
||||
resolved = try container.resolve(tag: tag) as T
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
protocol AutoWiringDefinition: Definition {
|
||||
var numberOfArguments: Int? { get }
|
||||
var autoWiringFactory: ((DependencyContainer, DependencyContainer.Tag?) throws -> Any)? { get }
|
||||
}
|
||||
|
||||
extension AutoWiringDefinition {
|
||||
func supportsAutoWiring() -> Bool {
|
||||
return autoWiringFactory != nil && numberOfArguments > 0
|
||||
}
|
||||
}
|
||||
|
||||
extension DependencyContainer {
|
||||
|
||||
/// Tries to resolve instance using auto-wire factories
|
||||
func _autowire<T>(key: DefinitionKey) throws -> T {
|
||||
guard key.argumentsType == Void.self else {
|
||||
throw DipError.DefinitionNotFound(key: key)
|
||||
}
|
||||
|
||||
let tag = key.associatedTag
|
||||
let type = key.protocolType
|
||||
let resolved: Any?
|
||||
do {
|
||||
let definitions = autoWiringDefinitions(forType: type, tag: tag)
|
||||
resolved = try _resolve(enumerating: definitions) { try _resolveKey($0, tag: tag, type: type) }
|
||||
}
|
||||
catch {
|
||||
throw DipError.AutoWiringFailed(type: type, underlyingError: error)
|
||||
}
|
||||
|
||||
if let resolved = resolved as? T {
|
||||
return resolved
|
||||
}
|
||||
else {
|
||||
throw DipError.DefinitionNotFound(key: key)
|
||||
}
|
||||
}
|
||||
|
||||
private func autoWiringDefinitions(forType type: Any.Type, tag: DependencyContainer.Tag?) -> [KeyDefinitionPair] {
|
||||
var definitions = self.definitions.map({ (key: $0.0, definition: $0.1) })
|
||||
|
||||
//filter definitions
|
||||
definitions = definitions
|
||||
.filter({ $0.definition.supportsAutoWiring() })
|
||||
.sort({ $0.definition.numberOfArguments > $1.definition.numberOfArguments })
|
||||
|
||||
definitions = filter(definitions, type: type, tag: tag)
|
||||
definitions = order(definitions, byTag: tag)
|
||||
|
||||
return definitions
|
||||
}
|
||||
|
||||
/// Enumerates definitions one by one until one of them succeeds, otherwise returns nil
|
||||
private func _resolve(enumerating keyDefinitionPairs: [KeyDefinitionPair], @noescape block: (DefinitionKey) throws -> Any?) throws -> Any? {
|
||||
for (index, keyDefinitionPair) in keyDefinitionPairs.enumerate() {
|
||||
//If the next definition matches current definition then they are ambigous
|
||||
if let nextPair = keyDefinitionPairs[next: index], case keyDefinitionPair = nextPair {
|
||||
throw DipError.AmbiguousDefinitions(
|
||||
type: keyDefinitionPair.key.protocolType,
|
||||
definitions: [keyDefinitionPair.definition, nextPair.definition]
|
||||
)
|
||||
}
|
||||
|
||||
if let resolved = try block(keyDefinitionPair.key) {
|
||||
return resolved
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func _resolveKey(key: DefinitionKey, tag: DependencyContainer.Tag?, type: Any.Type) throws -> Any {
|
||||
let key = key.tagged(tag ?? context.tag)
|
||||
return try _resolveKey(key, builder: { definition in
|
||||
try definition.autoWiringFactory!(self, tag)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension CollectionType where Self.Index: Comparable {
|
||||
subscript(safe index: Index) -> Generator.Element? {
|
||||
guard indices ~= index else { return nil }
|
||||
return self[index]
|
||||
}
|
||||
subscript(next index: Index) -> Generator.Element? {
|
||||
return self[safe: index.advancedBy(1)]
|
||||
}
|
||||
}
|
||||
|
||||
typealias KeyDefinitionPair = (key: DefinitionKey, definition: _Definition)
|
||||
|
||||
/// Definitions are matched if they are registered for the same tag and thier factories accept the same number of runtime arguments.
|
||||
private func ~=(lhs: KeyDefinitionPair, rhs: KeyDefinitionPair) -> Bool {
|
||||
guard lhs.key.protocolType == rhs.key.protocolType else { return false }
|
||||
guard lhs.key.associatedTag == rhs.key.associatedTag else { return false }
|
||||
guard lhs.definition.numberOfArguments == rhs.definition.numberOfArguments else { return false }
|
||||
return true
|
||||
}
|
||||
|
||||
func filter(definitions: [KeyDefinitionPair], type: Any.Type, tag: DependencyContainer.Tag?) -> [KeyDefinitionPair] {
|
||||
return definitions
|
||||
.filter({ $0.key.protocolType == type || $0.definition.doesImplements(type) })
|
||||
.filter({ $0.key.associatedTag == tag || $0.key.associatedTag == nil })
|
||||
}
|
||||
|
||||
func order(definitions: [KeyDefinitionPair], byTag tag: DependencyContainer.Tag?) -> [KeyDefinitionPair] {
|
||||
return
|
||||
//first will try to use tagged definitions
|
||||
definitions.filter({ $0.key.associatedTag == tag }) +
|
||||
//then will use not tagged definitions
|
||||
definitions.filter({ $0.key.associatedTag != tag })
|
||||
}
|
||||
+148
-27
@@ -25,29 +25,36 @@
|
||||
///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?
|
||||
public let argumentsType: Any.Type
|
||||
public private(set) var associatedTag: DependencyContainer.Tag?
|
||||
|
||||
init(protocolType: Any.Type, factoryType: Any.Type, associatedTag: DependencyContainer.Tag? = nil) {
|
||||
init(protocolType: Any.Type, argumentsType: Any.Type, associatedTag: DependencyContainer.Tag? = nil) {
|
||||
self.protocolType = protocolType
|
||||
self.factoryType = factoryType
|
||||
self.argumentsType = argumentsType
|
||||
self.associatedTag = associatedTag
|
||||
}
|
||||
|
||||
public var hashValue: Int {
|
||||
return "\(protocolType)-\(factoryType)-\(associatedTag)".hashValue
|
||||
return "\(protocolType)-\(argumentsType)-\(associatedTag)".hashValue
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
return "type: \(protocolType), factory: \(factoryType), tag: \(associatedTag.desc)"
|
||||
return "type: \(protocolType), arguments: \(argumentsType), tag: \(associatedTag.desc)"
|
||||
}
|
||||
|
||||
func tagged(tag: DependencyContainer.Tag?) -> DefinitionKey {
|
||||
var tagged = self
|
||||
tagged.associatedTag = tag
|
||||
return tagged
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// 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.argumentsType == rhs.argumentsType &&
|
||||
lhs.associatedTag == rhs.associatedTag
|
||||
}
|
||||
|
||||
@@ -130,8 +137,18 @@ public enum ComponentScope {
|
||||
```
|
||||
*/
|
||||
case Singleton
|
||||
|
||||
/**
|
||||
The same scope as `Singleton`, but instance will be created when container is bootstrapped.
|
||||
|
||||
- seealso: `bootstrap()`
|
||||
*/
|
||||
case EagerSingleton
|
||||
}
|
||||
|
||||
///Dummy protocol to store definitions for different types in collection
|
||||
public protocol Definition: class { }
|
||||
|
||||
/**
|
||||
`DefinitionOf<T, F>` describes how instances of type `T` should be created when this type is resolved by the `DependencyContainer`.
|
||||
|
||||
@@ -142,65 +159,133 @@ public enum ComponentScope {
|
||||
*/
|
||||
public final class DefinitionOf<T, F>: Definition {
|
||||
|
||||
init(scope: ComponentScope, factory: F) {
|
||||
self.factory = factory
|
||||
self.scope = scope
|
||||
}
|
||||
|
||||
//MARK: - _Definition
|
||||
|
||||
let factory: F
|
||||
let scope: ComponentScope
|
||||
private(set) var weakFactory: (Any throws -> Any)!
|
||||
private(set) var resolveDependenciesBlock: ((DependencyContainer, Any) throws -> ())?
|
||||
|
||||
/**
|
||||
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.
|
||||
This block will be called before `resolve(tag:)` returns.
|
||||
|
||||
- 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.
|
||||
to resolve its dependencies. Otherwise the application will enter an infinite loop and crash.
|
||||
|
||||
- note: You can call this method several times on the same definition.
|
||||
Container will call all provided blocks in the same order.
|
||||
|
||||
**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.")
|
||||
public func resolveDependencies(block: (DependencyContainer, T) throws -> ()) -> DefinitionOf {
|
||||
let oldBlock = self.resolveDependenciesBlock
|
||||
self.resolveDependenciesBlock = {
|
||||
try oldBlock?($0, $1 as! T)
|
||||
try block($0, $1 as! T)
|
||||
}
|
||||
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)
|
||||
if let resolveDependenciesBlock = self.resolveDependenciesBlock {
|
||||
try resolveDependenciesBlock(container, resolvedInstance)
|
||||
}
|
||||
}
|
||||
|
||||
let factory: F
|
||||
private(set) var scope: ComponentScope = .Prototype
|
||||
//MARK: - AutoWiringDefinition
|
||||
|
||||
private(set) var resolveDependenciesBlock: ((DependencyContainer, T) throws -> ())?
|
||||
private(set) var autoWiringFactory: ((DependencyContainer, DependencyContainer.Tag?) throws -> Any)?
|
||||
private(set) var numberOfArguments: Int?
|
||||
|
||||
public init(scope: ComponentScope, factory: F) {
|
||||
self.factory = factory
|
||||
self.scope = scope
|
||||
//MARK: - TypeForwardingDefinition
|
||||
|
||||
/// Types that can be resolved using this definition.
|
||||
private(set) var implementingTypes: [Any.Type] = [(T?).self, (T!).self]
|
||||
|
||||
/// Return `true` if type can be resolved using this definition
|
||||
func doesImplements(type: Any.Type) -> Bool {
|
||||
return implementingTypes.contains({ $0 == type })
|
||||
}
|
||||
|
||||
private var _resolvedInstance: T?
|
||||
//MARK: - _TypeForwardingDefinition
|
||||
|
||||
/// Adds type as being able to be resolved using this definition
|
||||
private func implements(type: Any.Type) {
|
||||
implements([type])
|
||||
}
|
||||
|
||||
/// Adds types as being able to be resolved using this definition
|
||||
private func implements(types: [Any.Type]) {
|
||||
implementingTypes.appendContentsOf(types.filter({ !doesImplements($0) }))
|
||||
}
|
||||
|
||||
/// Definition to which resolution will be forwarded to
|
||||
private weak var forwardsToDefinition: _TypeForwardingDefinition? {
|
||||
didSet {
|
||||
if let forwardsToDefinition = forwardsToDefinition {
|
||||
implements(forwardsToDefinition.type)
|
||||
implements(forwardsToDefinition.implementingTypes)
|
||||
|
||||
for definition in [forwardsToDefinition] + forwardsToDefinition.forwardsFromDefinitions {
|
||||
definition.implements(type)
|
||||
definition.implements(implementingTypes)
|
||||
}
|
||||
forwardsToDefinition.forwardsFromDefinitions.append(self)
|
||||
resolveDependencies({ try forwardsToDefinition.resolveDependenciesOf($1, withContainer: $0) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Definitions that will forward resolution to this definition
|
||||
private var forwardsFromDefinitions: [_TypeForwardingDefinition] = []
|
||||
|
||||
}
|
||||
|
||||
///Dummy protocol to store definitions for different types in collection
|
||||
public protocol Definition: class { }
|
||||
//MARK: - _Definition
|
||||
|
||||
protocol _Definition: Definition {
|
||||
protocol _Definition: Definition, AutoWiringDefinition, TypeForwardingDefinition {
|
||||
var type: Any.Type { get }
|
||||
var scope: ComponentScope { get }
|
||||
var weakFactory: (Any throws -> Any)! { get }
|
||||
func resolveDependenciesOf(resolvedInstance: Any, withContainer container: DependencyContainer) throws
|
||||
}
|
||||
|
||||
extension DefinitionOf: _Definition { }
|
||||
//MARK: - Type Forwarding
|
||||
|
||||
private protocol _TypeForwardingDefinition: TypeForwardingDefinition, _Definition {
|
||||
weak var forwardsToDefinition: _TypeForwardingDefinition? { get set }
|
||||
var forwardsFromDefinitions: [_TypeForwardingDefinition] { get set }
|
||||
func implements(type: Any.Type)
|
||||
func implements(type: [Any.Type])
|
||||
}
|
||||
|
||||
extension DefinitionOf: _TypeForwardingDefinition {
|
||||
var type: Any.Type {
|
||||
return T.self
|
||||
}
|
||||
}
|
||||
|
||||
extension DefinitionOf: CustomStringConvertible {
|
||||
public var description: String {
|
||||
@@ -208,3 +293,39 @@ extension DefinitionOf: CustomStringConvertible {
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Definition Builder
|
||||
|
||||
/// Internal class used to build definition
|
||||
/// Need this builder as alternative to changing to DefinitionOf<T, U> where U - type of arguments
|
||||
class DefinitionBuilder<T, U> {
|
||||
typealias F = U throws -> T
|
||||
|
||||
var scope: ComponentScope!
|
||||
var factory: F!
|
||||
|
||||
var numberOfArguments: Int?
|
||||
var autoWiringFactory: ((DependencyContainer, DependencyContainer.Tag?) throws -> T)?
|
||||
|
||||
var forwardsDefinition: _Definition?
|
||||
|
||||
init(@noescape configure: (DefinitionBuilder -> ())) {
|
||||
configure(self)
|
||||
}
|
||||
|
||||
func build() -> DefinitionOf<T, F> {
|
||||
let factory = self.factory
|
||||
let definition = DefinitionOf<T, F>(scope: scope, factory: factory)
|
||||
definition.numberOfArguments = numberOfArguments
|
||||
definition.autoWiringFactory = autoWiringFactory
|
||||
definition.weakFactory = {
|
||||
guard let args = $0 as? U else {
|
||||
let key = DefinitionKey(protocolType: T.self, argumentsType: U.self)
|
||||
throw DipError.DefinitionNotFound(key: key)
|
||||
}
|
||||
return try factory(args)
|
||||
}
|
||||
definition.forwardsToDefinition = forwardsDefinition as? _TypeForwardingDefinition
|
||||
return definition
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+498
-127
@@ -22,8 +22,6 @@
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
// MARK: - DependencyContainer
|
||||
|
||||
/**
|
||||
`DependencyContainer` allows you to do _Dependency Injection_
|
||||
by associating abstractions to concrete implementations.
|
||||
@@ -33,16 +31,22 @@ public final class DependencyContainer {
|
||||
/**
|
||||
Use a tag in case you need to register multiple factories fo the same type,
|
||||
to differentiate them. Tags can be either String or Int, to your convenience.
|
||||
|
||||
- seealso: `DependencyTagConvertible`
|
||||
*/
|
||||
public enum Tag: Equatable {
|
||||
case String(StringLiteralType)
|
||||
case Int(IntegerLiteralType)
|
||||
}
|
||||
|
||||
var definitions = [DefinitionKey : Definition]()
|
||||
let resolvedInstances = ResolvedInstances()
|
||||
let lock = RecursiveLock()
|
||||
private(set) public var context: Context!
|
||||
var definitions = [DefinitionKey : _Definition]()
|
||||
private let resolvedInstances = ResolvedInstances()
|
||||
private let lock = RecursiveLock()
|
||||
|
||||
private(set) var bootstrapped = false
|
||||
private var bootstrapQueue: [() throws -> ()] = []
|
||||
|
||||
/**
|
||||
Designated initializer for a DependencyContainer
|
||||
|
||||
@@ -58,6 +62,22 @@ public final class DependencyContainer {
|
||||
configBlock(self)
|
||||
}
|
||||
|
||||
/**
|
||||
Call this method to complete container setup. After container is bootstrapped
|
||||
you can not add or remove definitions. Trying to do so will cause runtime exception.
|
||||
You can completely reset container, after reset you can bootstrap it again.
|
||||
During bootsrap container will instantiate components registered with `EagerSingleton` scope.
|
||||
|
||||
- throws: `DipError` if failed to instantiate any component
|
||||
*/
|
||||
public func bootstrap() throws {
|
||||
try threadSafe {
|
||||
bootstrapped = true
|
||||
try bootstrapQueue.forEach({ try $0() })
|
||||
bootstrapQueue.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
private func threadSafe<T>(@noescape closure: () throws -> T) rethrows -> T {
|
||||
lock.lock()
|
||||
defer {
|
||||
@@ -68,6 +88,110 @@ public final class DependencyContainer {
|
||||
|
||||
}
|
||||
|
||||
extension DependencyContainer {
|
||||
|
||||
/**
|
||||
Context provides contextual information about resolution process.
|
||||
|
||||
You can use the context for debugging or to pass through tag when you explicitly resolve dependencies.
|
||||
When auto-wiring or auto-injecting tag will be implicitly passed through by the container.
|
||||
For auto-injected properties you can disable that by providing tag (some value or `nil`) when defining property.
|
||||
|
||||
**Example**:
|
||||
|
||||
```swift
|
||||
class SomeServiceImp: SomeService {
|
||||
//container will pass through the tag ("tag") used to resolve containing instance to resolve this property
|
||||
let injected = Injected<SomeDependency>()
|
||||
|
||||
//container will use "someTag" tag to resolve this property
|
||||
let injectedTagged = Injected<SomeDependency>(tag: "someTag")
|
||||
|
||||
//container will use `nil` tag to resolve this property
|
||||
let injectedNilTag = Injected<SomeDependency>(tag: nil)
|
||||
}
|
||||
|
||||
container.register {
|
||||
//container will pass through the tag ("tag") used to resolve SomeService to resolve $0
|
||||
SomeServiceImp(dependency: $0) as SomeService
|
||||
}.resolveDependencies { container, service in
|
||||
//container will use `nil` tag to resolve this dependency
|
||||
self.dependency = try container.resolve() as SomeDependency
|
||||
|
||||
//container will use current context tag ("tag") to resolve this dependency
|
||||
self.taggedDependency = try container.resolve(tag: container.context.tag) as SomeDependency
|
||||
}
|
||||
|
||||
//container will use "tag" to resolve this instance
|
||||
let service = try! container.resolve(tag: "tag") as SomeService
|
||||
|
||||
```
|
||||
*/
|
||||
public struct Context {
|
||||
|
||||
/// The tag used to resolve currently resolving type.
|
||||
private(set) public var tag: Tag?
|
||||
|
||||
/// The type that caused currently resolving type to be resolved.
|
||||
/// `nil` for root object in a dependencies graph.
|
||||
private(set) public var injectedInType: Any.Type?
|
||||
|
||||
/// The label of the property where resolved instance will be auto-injected.
|
||||
private(set) public var injectedInProperty: String?
|
||||
|
||||
/// Currently resolving type.
|
||||
private(set) public var resolvingType: Any.Type
|
||||
|
||||
private var depth: Int = 0
|
||||
|
||||
init(tag: Tag?, injectedInType: Any.Type?, injectedInProperty: String?, resolvingType: Any.Type) {
|
||||
self.tag = tag
|
||||
self.injectedInType = injectedInType
|
||||
self.injectedInProperty = injectedInProperty
|
||||
self.resolvingType = resolvingType
|
||||
}
|
||||
}
|
||||
|
||||
/// Pushes new context created with provided values and calls block. When block returns previous context is restored.
|
||||
/// For `nil` values (except tag) new context will use values from the current context.
|
||||
/// Will releas resolved instances and call `Resolvable` callbacks when popped to initial context.
|
||||
func inContext<T>(tag: Tag?, resolvingType: Any.Type? = nil, injectedInProperty: String? = nil, @noescape block: () throws -> T) throws -> T {
|
||||
return try threadSafe {
|
||||
let currentContext = self.context
|
||||
|
||||
defer {
|
||||
self.context = currentContext
|
||||
|
||||
if self.context == nil {
|
||||
// We call didResolveDependencies only at this point
|
||||
// because this is a point when dependencies graph is complete.
|
||||
for resolvedInstance in self.resolvedInstances.resolvableInstances.reverse() {
|
||||
resolvedInstance.didResolveDependencies()
|
||||
}
|
||||
self.resolvedInstances.resolvedInstances.removeAll()
|
||||
self.resolvedInstances.resolvableInstances.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
if currentContext == nil {
|
||||
self.context = Context(tag: tag, injectedInType: nil, injectedInProperty: nil, resolvingType: resolvingType ?? T.self)
|
||||
}
|
||||
else {
|
||||
self.context = Context(
|
||||
tag: tag,
|
||||
injectedInType: currentContext.injectedInType ?? currentContext.resolvingType,
|
||||
injectedInProperty: injectedInProperty ?? currentContext.injectedInProperty,
|
||||
resolvingType: resolvingType ?? T.self
|
||||
)
|
||||
self.context.depth = currentContext.depth + 1
|
||||
}
|
||||
|
||||
return try block()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Registering definitions
|
||||
|
||||
extension DependencyContainer {
|
||||
@@ -90,41 +214,55 @@ extension DependencyContainer {
|
||||
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 }
|
||||
container.register { try ClientImp(service: 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)
|
||||
public func register<T>(tag tag: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: () throws -> T) -> DefinitionOf<T, () throws -> T> {
|
||||
let definition = DefinitionBuilder<T, ()> {
|
||||
$0.scope = scope
|
||||
$0.factory = factory
|
||||
}.build()
|
||||
register(definition, forTag: tag)
|
||||
return definition
|
||||
}
|
||||
|
||||
/**
|
||||
Register generic factory associated with an optional tag.
|
||||
Register generic factory and auto-wiring factory and associate it with an optional tag.
|
||||
|
||||
- parameters:
|
||||
- tag: The arbitrary tag to associate this factory with. Pass `nil` to associate with any tag. Default value is `nil`.
|
||||
- scope: The scope to use for instance created by the factory.
|
||||
- factory: The factory to register.
|
||||
- numberOfArguments: The number of factory arguments. Will be used on auto-wiring to sort definitions.
|
||||
- autoWiringFactory: The factory to be used on auto-wiring to resolve component.
|
||||
|
||||
- returns: A registered definition.
|
||||
|
||||
|
||||
- note: You _should not_ call this method directly, instead call any of other `register` methods.
|
||||
You _should_ use this method only to register dependency with more runtime arguments
|
||||
than _Dip_ supports (currently it's up to six) like in the following example:
|
||||
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>
|
||||
public func register<T, A, B, C, ...>(tag: Tag? = nil, scope: ComponentScope = .Prototype, factory: (A, B, C, ...) throws -> T) -> DefinitionOf<T, (A, B, C, ...) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: ...) { container, tag in
|
||||
try factory(container.resolve(tag: tag), ...)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Though before you do so you should probably review your design and try to reduce number of depnedencies.
|
||||
*/
|
||||
public func registerFactory<T, F>(tag tag: Tag? = nil, scope: ComponentScope, factory: F) -> DefinitionOf<T, F> {
|
||||
let definition = DefinitionOf<T, F>(scope: scope, factory: factory)
|
||||
public func registerFactory<T, U>(tag tag: DependencyTagConvertible? = nil, scope: ComponentScope, factory: U throws -> T, numberOfArguments: Int, autoWiringFactory: (DependencyContainer, Tag?) throws -> T) -> DefinitionOf<T, U throws -> T> {
|
||||
let definition = DefinitionBuilder<T, U> {
|
||||
$0.scope = scope
|
||||
$0.factory = factory
|
||||
$0.numberOfArguments = numberOfArguments
|
||||
$0.autoWiringFactory = autoWiringFactory
|
||||
}.build()
|
||||
register(definition, forTag: tag)
|
||||
return definition
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Register definiton in the container and associate it with an optional tag.
|
||||
Will override already registered definition for the same type and factory, associated with the same tag.
|
||||
@@ -134,18 +272,25 @@ extension DependencyContainer {
|
||||
- definition: The definition to register in the container.
|
||||
|
||||
*/
|
||||
public func register<T, F>(definition: DefinitionOf<T, F>, forTag tag: Tag? = nil) {
|
||||
let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag)
|
||||
public func register<T, U>(definition: DefinitionOf<T, U throws -> T>, forTag tag: DependencyTagConvertible? = nil) {
|
||||
let key = DefinitionKey(protocolType: T.self, argumentsType: U.self, associatedTag: tag?.dependencyTag)
|
||||
register(definition, forKey: key)
|
||||
|
||||
if case .EagerSingleton = definition.scope {
|
||||
bootstrapQueue.append({ let _ = try self.resolve(tag: tag) as T })
|
||||
}
|
||||
}
|
||||
|
||||
func register(definition: Definition, forKey key: DefinitionKey) {
|
||||
/// Actually register definition
|
||||
func register(definition: _Definition, forKey key: DefinitionKey) {
|
||||
precondition(!bootstrapped, "You can not modify container's definitions after it was bootstrapped.")
|
||||
|
||||
threadSafe {
|
||||
definitions[key] = definition
|
||||
resolvedInstances.singletons[key] = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Resolve dependencies
|
||||
@@ -160,10 +305,7 @@ extension DependencyContainer {
|
||||
|
||||
- parameter tag: The arbitrary tag to use to lookup definition.
|
||||
|
||||
- throws: An error of type `DipError`:
|
||||
`ResolutionFailed` - if some error was thrown during resolution;
|
||||
`DefinitionNotFound` - if no matching definition was registered in that container.
|
||||
`AutoInjectionFailed` - if failed to auto-inject required property
|
||||
- throws: `DipError.DefinitionNotFound`, `DipError.AutoInjectionFailed`, `DipError.AmbiguousDefinitions`
|
||||
|
||||
- returns: An instance of type `T`.
|
||||
|
||||
@@ -177,10 +319,29 @@ extension DependencyContainer {
|
||||
```
|
||||
|
||||
*/
|
||||
public func resolve<T>(tag tag: Tag? = nil) throws -> T {
|
||||
public func resolve<T>(tag tag: DependencyTagConvertible? = nil) throws -> T {
|
||||
return try resolve(tag: tag) { (factory: () throws -> T) in try factory() }
|
||||
}
|
||||
|
||||
/**
|
||||
Resolve a an instance of provided type. Weakly-typed alternative of `resolve(tag:)`
|
||||
|
||||
- warning: This method does not make any type checks, so there is no guaranty that
|
||||
resulting instance is actually an instance of requrested type.
|
||||
That can happen if you register forwarded type that is not implemented by resolved instance.
|
||||
|
||||
**Example**:
|
||||
```swift
|
||||
let service = try! container.resolve(Service.self) as! Service
|
||||
let service = try! container.resolve(Service.self, tag: "service") as! Service
|
||||
```
|
||||
|
||||
- seealso: `resolve(tag:)`, `register(tag:_:factory:)`, `implements(_:)`
|
||||
*/
|
||||
public func resolve(type: Any.Type, tag: DependencyTagConvertible? = nil) throws -> Any {
|
||||
return try self.resolve(type, tag: tag) { factory in try factory(())}
|
||||
}
|
||||
|
||||
/**
|
||||
Resolve an instance of type `T` using generic builder closure that accepts generic factory and returns created instance.
|
||||
|
||||
@@ -188,11 +349,8 @@ extension DependencyContainer {
|
||||
- tag: The arbitrary tag to use to lookup definition.
|
||||
- builder: Generic closure that accepts generic factory and returns inctance created by that factory.
|
||||
|
||||
- throws: An error of type `DipError`:
|
||||
`ResolutionFailed` - if some error was thrown during resolution;
|
||||
`DefinitionNotFound` - if no matching definition was registered in that container.
|
||||
`AutoInjectionFailed` - if failed to auto-inject required property
|
||||
|
||||
- throws: `DipError.DefinitionNotFound`, `DipError.AutoInjectionFailed`, `DipError.AmbiguousDefinitions`
|
||||
|
||||
- returns: An instance of type `T`.
|
||||
|
||||
- note: You _should not_ call this method directly, instead call any of other
|
||||
@@ -201,107 +359,123 @@ extension DependencyContainer {
|
||||
_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, ...) }
|
||||
public func resolve<T, A, B, C, ...>(tag tag: Tag? = nil, _ arg1: A, _ arg2: B, _ arg3: C, ...) throws -> T {
|
||||
return try resolve(tag: tag) { factory 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)
|
||||
}
|
||||
public func resolve<T, U>(tag tag: DependencyTagConvertible? = nil, builder: (U throws -> T) throws -> T) throws -> T {
|
||||
let resolved = try resolve(T.self, tag: tag, builder: { (factory: (U throws -> Any)) in
|
||||
try builder({
|
||||
guard let resolved = try factory($0) as? T else {
|
||||
let key = DefinitionKey(protocolType: T.self, argumentsType: U.self, associatedTag: tag?.dependencyTag)
|
||||
throw DipError.DefinitionNotFound(key: key)
|
||||
}
|
||||
return resolved
|
||||
})
|
||||
})
|
||||
|
||||
return resolved as! T
|
||||
}
|
||||
|
||||
/**
|
||||
Resolve an instance of provided type using builder closure. Weakly-typed alternative of `resolve(tag:builder:)`
|
||||
|
||||
- seealso: `resolve(tag:builder:)`
|
||||
*/
|
||||
public func resolve<U>(type: Any.Type, tag: DependencyTagConvertible? = nil, builder: (U throws -> Any) throws -> Any) throws -> Any {
|
||||
return try inContext(tag?.dependencyTag, resolvingType: type) {
|
||||
let key = DefinitionKey(protocolType: type, argumentsType: U.self, associatedTag: tag?.dependencyTag)
|
||||
|
||||
return try _resolveKey(key, builder: { definition throws -> Any in
|
||||
try builder(definition.weakFactory)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)
|
||||
func _resolveKey<T>(key: DefinitionKey, builder: _Definition throws -> T) throws -> T {
|
||||
guard let matching = definition(matching: key) else {
|
||||
//if no definition found - auto-wire
|
||||
return try _autowire(key)
|
||||
}
|
||||
|
||||
do {
|
||||
return try _resolveDefinition(matching.definition, forKey: matching.key, builder: builder)
|
||||
}
|
||||
//if failed to resolve type for matching key - try auto-wiring
|
||||
//(usually happens when inferring optional type)
|
||||
catch let DipError.DefinitionNotFound(errorKey) where errorKey.protocolType == matching.key.protocolType {
|
||||
return try _autowire(key)
|
||||
}
|
||||
}
|
||||
|
||||
/// Actually resolve dependency.
|
||||
private func _resolveDefinition<T, F>(definition: DefinitionOf<T, F>, key: DefinitionKey, builder: F throws -> T) rethrows -> T {
|
||||
return try resolvedInstances.resolve {
|
||||
private func _resolveDefinition<T>(definition: _Definition, forKey key: DefinitionKey, builder: _Definition throws -> T) throws -> T {
|
||||
|
||||
//first search for already resolved instance for this type or any of forwarding types
|
||||
if let previouslyResolved: T = previouslyResolved(definition, key: key) {
|
||||
return previouslyResolved
|
||||
}
|
||||
|
||||
var resolvedInstance = try builder(definition)
|
||||
|
||||
/*
|
||||
Strongly-typed `resolve(tag:builder:)` calls weakly-typed `resolve(_:tag:builder:)`,
|
||||
so `T` will be `Any` at runtime, erasing type information when this method returns.
|
||||
When we try to cast result of `Any` to generic type T Swift fails to cast it.
|
||||
The same happens in the following code snippet:
|
||||
|
||||
let optService: Service? = ServiceImp()
|
||||
let anyService: Any = optService
|
||||
let service: Service = anyService as! Service
|
||||
|
||||
As a workaround we detect boxing here and unwrap it so that we return not a box, but wrapped instance.
|
||||
*/
|
||||
if let box = resolvedInstance as? BoxType, unboxed = box.unboxed as? T {
|
||||
resolvedInstance = unboxed
|
||||
}
|
||||
|
||||
//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 = previouslyResolved(definition, key: key) {
|
||||
return previouslyResolved
|
||||
}
|
||||
|
||||
resolvedInstances.storeResolvedInstance(resolvedInstance, forKey: key, inScope: definition.scope)
|
||||
|
||||
try definition.resolveDependenciesOf(resolvedInstance, withContainer: self)
|
||||
try autoInjectProperties(resolvedInstance)
|
||||
|
||||
return resolvedInstance
|
||||
}
|
||||
|
||||
private func previouslyResolved<T>(definition: _Definition, key: DefinitionKey) -> T? {
|
||||
let keys = definition.implementingTypes.map({
|
||||
DefinitionKey(protocolType: $0, argumentsType: key.argumentsType, associatedTag: key.associatedTag)
|
||||
})
|
||||
for key in [key] + keys {
|
||||
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
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
///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
|
||||
}
|
||||
/// Searches for definition that matches provided key
|
||||
private func definition(matching key: DefinitionKey) -> KeyDefinitionPair? {
|
||||
let typeDefinitions = definitions.filter({ $0.0.protocolType == key.protocolType })
|
||||
guard !typeDefinitions.isEmpty else {
|
||||
return typeForwardingDefinition(key.protocolType, tag: key.associatedTag)
|
||||
}
|
||||
|
||||
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
|
||||
if let definition = (self.definitions[key] ?? self.definitions[key.tagged(nil)]) {
|
||||
return (key, definition)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
@@ -317,12 +491,14 @@ extension DependencyContainer {
|
||||
- tag: The tag used to register definition.
|
||||
- definition: The definition to remove
|
||||
*/
|
||||
public func remove<T, F>(definition: DefinitionOf<T, F>, forTag tag: Tag? = nil) {
|
||||
let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag)
|
||||
public func remove<T, U>(definition: DefinitionOf<T, U>, forTag tag: DependencyTagConvertible? = nil) {
|
||||
let key = DefinitionKey(protocolType: T.self, argumentsType: U.self, associatedTag: tag?.dependencyTag)
|
||||
remove(definitionForKey: key)
|
||||
}
|
||||
|
||||
func remove(definitionForKey key: DefinitionKey) {
|
||||
private func remove(definitionForKey key: DefinitionKey) {
|
||||
precondition(!bootstrapped, "You can not modify container's definitions after it was bootstrapped.")
|
||||
|
||||
threadSafe {
|
||||
definitions[key] = nil
|
||||
resolvedInstances.singletons[key] = nil
|
||||
@@ -336,11 +512,107 @@ extension DependencyContainer {
|
||||
threadSafe {
|
||||
definitions.removeAll()
|
||||
resolvedInstances.singletons.removeAll()
|
||||
bootstrapped = false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension DependencyContainer {
|
||||
|
||||
/**
|
||||
Validates container configuration trying to resolve each registered definition one by one.
|
||||
If definition fails to be resolved without arguments will search provided arguments array
|
||||
for arguments matched by type and try to resolve this definition using these arguments.
|
||||
If there are no matching arguments will rethrow original error.
|
||||
|
||||
- parameter arguments: set of arguments to use to resolve registered definitions.
|
||||
Use a tuple for registered factories that accept several runtime arguments.
|
||||
*/
|
||||
public func validate(arguments: Any...) throws {
|
||||
validateNextDefinition: for (key, _) in definitions {
|
||||
do {
|
||||
//try to resolve key using provided arguments
|
||||
for argumentsSet in arguments where argumentsSet.dynamicType == key.argumentsType {
|
||||
do {
|
||||
try inContext(key.associatedTag, resolvingType: key.protocolType) {
|
||||
try _resolveKey(key, builder: { definition throws -> Any in
|
||||
try definition.weakFactory(argumentsSet)
|
||||
})
|
||||
}
|
||||
continue validateNextDefinition
|
||||
}
|
||||
catch let error as DipError {
|
||||
throw error
|
||||
}
|
||||
//ignore other errors
|
||||
catch { print(error) }
|
||||
}
|
||||
|
||||
//try to resolve key using auto-wiring
|
||||
do {
|
||||
try self.resolve(key.protocolType, tag: key.associatedTag)
|
||||
}
|
||||
catch let error as DipError {
|
||||
throw error
|
||||
}
|
||||
//ignore other errors
|
||||
catch { print(error) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///Pool to hold instances, created during call to `resolve()`.
|
||||
///Before `resolve()` returns pool is drained.
|
||||
private class ResolvedInstances {
|
||||
var resolvedInstances = [DefinitionKey: Any]()
|
||||
var singletons = [DefinitionKey: Any]()
|
||||
var resolvableInstances = [Resolvable]()
|
||||
|
||||
func storeResolvedInstance<T>(instance: T, forKey key: DefinitionKey, inScope scope: ComponentScope) {
|
||||
switch scope {
|
||||
case .Singleton, .EagerSingleton: singletons[key] = instance
|
||||
case .ObjectGraph: resolvedInstances[key] = instance
|
||||
case .Prototype: break
|
||||
}
|
||||
|
||||
if let resolvable = instance as? Resolvable {
|
||||
resolvableInstances.append(resolvable)
|
||||
}
|
||||
}
|
||||
|
||||
func previouslyResolvedInstance<T>(forKey key: DefinitionKey, inScope scope: ComponentScope) -> T? {
|
||||
switch scope {
|
||||
case .Singleton, .EagerSingleton: 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 {
|
||||
// We call didResolveDependencies only at this point
|
||||
// because this is a point when dependencies graph is complete.
|
||||
for resolvedInstance in resolvableInstances.reverse() {
|
||||
resolvedInstance.didResolveDependencies()
|
||||
}
|
||||
resolvedInstances.removeAll()
|
||||
resolvableInstances.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
let resolved = try block()
|
||||
return resolved
|
||||
}
|
||||
}
|
||||
|
||||
extension DependencyContainer: CustomStringConvertible {
|
||||
|
||||
public var description: String {
|
||||
@@ -349,16 +621,54 @@ extension DependencyContainer: CustomStringConvertible {
|
||||
|
||||
}
|
||||
|
||||
extension DependencyContainer.Tag: IntegerLiteralConvertible {
|
||||
//MARK: - Resolvable
|
||||
|
||||
public init(integerLiteral value: IntegerLiteralType) {
|
||||
self = .Int(value)
|
||||
/// Conform to this protocol when you need to have a callback when all the dependencies are injected.
|
||||
public protocol Resolvable {
|
||||
/// This method is called by the container when all dependencies of the instance are resolved.
|
||||
func didResolveDependencies()
|
||||
}
|
||||
|
||||
//MARK: - DependencyTagConvertible
|
||||
|
||||
/// Implement this protocol of your type if you want to use its instances as `DependencyContainer`'s tags.
|
||||
/// `DependencyContainer.Tag`, `String`, `Int` and any `RawRepresentable` with `RawType` of `String` or `Int` by default confrom to this protocol.
|
||||
public protocol DependencyTagConvertible {
|
||||
var dependencyTag: DependencyContainer.Tag { get }
|
||||
}
|
||||
|
||||
extension DependencyContainer.Tag: DependencyTagConvertible {
|
||||
public var dependencyTag: DependencyContainer.Tag {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
extension String: DependencyTagConvertible {
|
||||
public var dependencyTag: DependencyContainer.Tag {
|
||||
return .String(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Int: DependencyTagConvertible {
|
||||
public var dependencyTag: DependencyContainer.Tag {
|
||||
return .Int(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension DependencyTagConvertible where Self: RawRepresentable, Self.RawValue == Int {
|
||||
public var dependencyTag: DependencyContainer.Tag {
|
||||
return .Int(rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
extension DependencyTagConvertible where Self: RawRepresentable, Self.RawValue == String {
|
||||
public var dependencyTag: DependencyContainer.Tag {
|
||||
return .String(rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
extension DependencyContainer.Tag: StringLiteralConvertible {
|
||||
|
||||
|
||||
public init(stringLiteral value: StringLiteralType) {
|
||||
self = .String(value)
|
||||
}
|
||||
@@ -373,6 +683,14 @@ extension DependencyContainer.Tag: StringLiteralConvertible {
|
||||
|
||||
}
|
||||
|
||||
extension DependencyContainer.Tag: IntegerLiteralConvertible {
|
||||
|
||||
public init(integerLiteral value: IntegerLiteralType) {
|
||||
self = .Int(value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public func ==(lhs: DependencyContainer.Tag, rhs: DependencyContainer.Tag) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (.String(lhsString), .String(rhsString)):
|
||||
@@ -384,20 +702,14 @@ public func ==(lhs: DependencyContainer.Tag, rhs: DependencyContainer.Tag) -> Bo
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - DipError
|
||||
|
||||
/**
|
||||
Errors thrown by `DependencyContainer`'s methods.
|
||||
|
||||
- seealso: `resolve(tag:)`
|
||||
*/
|
||||
public enum DipError: ErrorType, CustomStringConvertible {
|
||||
/**
|
||||
Thrown by `resolve(tag:)` if some error was thrown during resolution.
|
||||
|
||||
- parameters:
|
||||
- key: The key, which is associated with definition used to resolve instance
|
||||
- underlyingError: The error that caused resolution to fail
|
||||
*/
|
||||
case ResolutionFailed(key: DefinitionKey, underlyingError: ErrorType)
|
||||
|
||||
/**
|
||||
Thrown by `resolve(tag:)` if no matching definition was registered in container.
|
||||
@@ -416,14 +728,73 @@ public enum DipError: ErrorType, CustomStringConvertible {
|
||||
*/
|
||||
case AutoInjectionFailed(label: String?, type: Any.Type, underlyingError: ErrorType)
|
||||
|
||||
/**
|
||||
Thrown by `resolve(tag:)` if failed to auto-wire a type.
|
||||
|
||||
- parameters:
|
||||
- type: The type that failed to be resolved by auto-wiring
|
||||
- underlyingError: The error that cause auto-wiring to fail
|
||||
*/
|
||||
case AutoWiringFailed(type: Any.Type, underlyingError: ErrorType)
|
||||
|
||||
/**
|
||||
Thrown when auto-wiring type if several definitions with the same number of runtime arguments
|
||||
are registered for that type.
|
||||
|
||||
- parameters:
|
||||
- type: The type that failed to be resolved by auto-wiring
|
||||
- definitions: Ambiguous definitions
|
||||
*/
|
||||
case AmbiguousDefinitions(type: Any.Type, definitions: [Definition])
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case let .ResolutionFailed(key, error):
|
||||
return "Failed to resolve type \(key.protocolType). \(error)"
|
||||
case let .DefinitionNotFound(key):
|
||||
return "No definition registered for \(key).\nCheck the tag, type you try to resolve, number, order and types of runtime arguments passed to `resolve()` and match them with registered factories for type \(key.protocolType)."
|
||||
case let .AutoInjectionFailed(label, type, error):
|
||||
return "Failed to auto-inject property \"\(label.desc)\" of type \(type). \(error)"
|
||||
case let .AutoWiringFailed(type, error):
|
||||
return "Failed to auto-wire type \"\(type)\". \(error)"
|
||||
case let .AmbiguousDefinitions(type, definitions):
|
||||
return "Ambiguous definitions for \(type):\n" +
|
||||
definitions.map({ "\($0)" }).joinWithSeparator(";\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///Internal protocol used to unwrap optional values.
|
||||
private protocol BoxType {
|
||||
var unboxed: Any? { get }
|
||||
}
|
||||
|
||||
extension Optional: BoxType {
|
||||
private var unboxed: Any? {
|
||||
switch self {
|
||||
case let .Some(value): return value
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ImplicitlyUnwrappedOptional: BoxType {
|
||||
private var unboxed: Any? {
|
||||
switch self {
|
||||
case let .Some(value): return value
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Deprecated methods
|
||||
|
||||
extension DependencyContainer {
|
||||
@available(*, deprecated=4.3.0, message="Use registerFactory(tag:scope:factory:numberOfArguments:autoWiringFactory:) instead.")
|
||||
public func registerFactory<T, U>(tag tag: DependencyTagConvertible? = nil, scope: ComponentScope, factory: U throws -> T) -> DefinitionOf<T, U throws -> T> {
|
||||
let definition = DefinitionBuilder<T, U> {
|
||||
$0.scope = scope
|
||||
$0.factory = factory
|
||||
}.build()
|
||||
register(definition, forTag: tag)
|
||||
return definition
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ extension DependencyContainer {
|
||||
// MARK: 1 Runtime Argument
|
||||
|
||||
/**
|
||||
Register factory that accepts one runtime argumentof type `Arg1`. You can use up to six runtime arguments.
|
||||
Register factory that accepts one runtime argument of type `A`. 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,
|
||||
@@ -42,87 +42,127 @@ extension DependencyContainer {
|
||||
|
||||
- seealso: `registerFactory(tag:scope:factory:)`
|
||||
*/
|
||||
public func register<T, Arg1>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1) throws -> T) -> DefinitionOf<T, (Arg1) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory)
|
||||
public func register<T, A>(tag tag: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: (A) throws -> T) -> DefinitionOf<T, (A) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: 1) { container, tag in try factory(container.resolve(tag: tag)) }
|
||||
}
|
||||
|
||||
/**
|
||||
Resolve a dependency using one runtime argument.
|
||||
Resolve type `T` using one runtime argument.
|
||||
|
||||
- note: When resolving a type container will first try to use definition
|
||||
that exactly matches types of arguments that you pass to resolve method.
|
||||
If it fails or no such definition is found container will try to _auto-wire_ component.
|
||||
For that it will iterate through all the definitions registered for that type
|
||||
which factories accept any number of runtime arguments and are tagged with the same tag,
|
||||
passed to `resolve` method, or with no tag. Container will try to use these definitions
|
||||
to resolve a component one by one until one of them succeeds, starting with tagged definitions
|
||||
in order of decreasing their's factories number of arguments. If none of them succeds it will
|
||||
throw an error. If it finds two definitions with the same number of arguments - it will throw
|
||||
an error.
|
||||
|
||||
- parameters:
|
||||
- tag: The arbitrary tag to lookup registered definition.
|
||||
- arg1: The first argument to pass to the definition's factory.
|
||||
|
||||
- throws: An error of type `DipError`:
|
||||
`ResolutionFailed` - if some error was thrown during resolution;
|
||||
`DefinitionNotFound` - if no matching definition was registered in that container.
|
||||
- throws: `DipError.DefinitionNotFound`, `DipError.AutoInjectionFailed`, `DipError.AmbiguousDefinitions`
|
||||
|
||||
- returns: An instance of type `T`.
|
||||
|
||||
- seealso: `register(tag:_:factory:)`, `resolve(tag:builder:)`
|
||||
*/
|
||||
public func resolve<T, Arg1>(tag tag: Tag? = nil, withArguments arg1: Arg1) throws -> T {
|
||||
return try resolve(tag: tag) { (factory: (Arg1) throws -> T) in try factory(arg1) }
|
||||
public func resolve<T, A>(tag tag: DependencyTagConvertible? = nil, withArguments arg1: A) throws -> T {
|
||||
return try resolve(tag: tag) { factory in try factory(arg1) }
|
||||
}
|
||||
|
||||
|
||||
///- seealso: `resolve(_:tag:)`, `resolve(tag:withArguments:)`
|
||||
public func resolve<A>(type: Any.Type, tag: DependencyTagConvertible? = nil, withArguments arg1: A) throws -> Any {
|
||||
return try resolve(type, tag: tag) { factory in try factory(arg1) }
|
||||
}
|
||||
|
||||
// MARK: 2 Runtime Arguments
|
||||
|
||||
/// - seealso: `register(tag:scope:factory:)`
|
||||
public func register<T, Arg1, Arg2>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2) throws -> T) -> DefinitionOf<T, (Arg1, Arg2) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory)
|
||||
public func register<T, A, B>(tag tag: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: (A, B) throws -> T) -> DefinitionOf<T, (A, B) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: 2) { container, tag in try factory(container.resolve(tag: tag), container.resolve(tag: tag)) }
|
||||
}
|
||||
|
||||
/// - 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) }
|
||||
/// - seealso: `resolve(tag:withArguments:)`
|
||||
public func resolve<T, A, B>(tag tag: DependencyTagConvertible? = nil, withArguments arg1: A, _ arg2: B) throws -> T {
|
||||
return try resolve(tag: tag) { factory in try factory(arg1, arg2) }
|
||||
}
|
||||
|
||||
///- seealso: `resolve(_:tag:)`, `resolve(tag:withArguments:)`
|
||||
public func resolve<A, B>(type: Any.Type, tag: DependencyTagConvertible? = nil, withArguments arg1: A, _ arg2: B) throws -> Any {
|
||||
return try resolve(type, tag: tag) { factory in try factory((arg1, arg2)) }
|
||||
}
|
||||
|
||||
// MARK: 3 Runtime Arguments
|
||||
|
||||
/// - seealso: `register(tag:scope:factory:)`
|
||||
public func register<T, Arg1, Arg2, Arg3>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory)
|
||||
public func register<T, A, B, C>(tag tag: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: (A, B, C) throws -> T) -> DefinitionOf<T, (A, B, C) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: 3) { container, tag in try factory(container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag)) }
|
||||
}
|
||||
|
||||
/// - seealso: `resolve(tag:withArguments:)`
|
||||
public func resolve<T, Arg1, Arg2, Arg3>(tag tag: Tag? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3) throws -> T {
|
||||
return try resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3) throws -> T) in try factory(arg1, arg2, arg3) }
|
||||
public func resolve<T, A, B, C>(tag tag: DependencyTagConvertible? = nil, withArguments arg1: A, _ arg2: B, _ arg3: C) throws -> T {
|
||||
return try resolve(tag: tag) { factory in try factory(arg1, arg2, arg3) }
|
||||
}
|
||||
|
||||
///- seealso: `resolve(_:tag:)`, `resolve(tag:withArguments:)`
|
||||
public func resolve<A, B, C>(type: Any.Type, tag: DependencyTagConvertible? = nil, withArguments arg1: A, _ arg2: B, _ arg3: C) throws -> Any {
|
||||
return try resolve(type, tag: tag) { factory in try factory((arg1, arg2, arg3)) }
|
||||
}
|
||||
|
||||
// MARK: 4 Runtime Arguments
|
||||
|
||||
/// - seealso: `register(tag:scope:factory:)`
|
||||
public func register<T, Arg1, Arg2, Arg3, Arg4>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory)
|
||||
public func register<T, A, B, C, D>(tag tag: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: (A, B, C, D) throws -> T) -> DefinitionOf<T, (A, B, C, D) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: 4) { container, tag in try factory(container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag)) }
|
||||
}
|
||||
|
||||
/// - seealso: `resolve(tag:withArguments:)`
|
||||
public func resolve<T, Arg1, Arg2, Arg3, Arg4>(tag tag: Tag? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4) throws -> T {
|
||||
return try resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4) throws -> T) in try factory(arg1, arg2, arg3, arg4) }
|
||||
public func resolve<T, A, B, C, D>(tag tag: DependencyTagConvertible? = nil, withArguments arg1: A, _ arg2: B, _ arg3: C, _ arg4: D) throws -> T {
|
||||
return try resolve(tag: tag) { factory in try factory(arg1, arg2, arg3, arg4) }
|
||||
}
|
||||
|
||||
/// - seealso: `resolve(_:tag:)`, `resolve(tag:withArguments:)`
|
||||
public func resolve<A, B, C, D>(type: Any.Type, tag: DependencyTagConvertible? = nil, withArguments arg1: A, _ arg2: B, _ arg3: C, _ arg4: D) throws -> Any {
|
||||
return try resolve(type, tag: tag) { factory in try factory((arg1, arg2, arg3, arg4)) }
|
||||
}
|
||||
|
||||
// MARK: 5 Runtime Arguments
|
||||
|
||||
/// - seealso: `register(tag:scope:factory:)`
|
||||
public func register<T, Arg1, Arg2, Arg3, Arg4, Arg5>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4, Arg5) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4, Arg5) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory)
|
||||
public func register<T, A, B, C, D, E>(tag tag: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: (A, B, C, D, E) throws -> T) -> DefinitionOf<T, (A, B, C, D, E) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: 5) { container, tag in try factory(container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag)) }
|
||||
}
|
||||
|
||||
/// - seealso: `resolve(tag:withArguments:)`
|
||||
public func resolve<T, Arg1, Arg2, Arg3, Arg4, Arg5>(tag tag: Tag? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5) throws -> T {
|
||||
return try resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5) throws -> T) in try factory(arg1, arg2, arg3, arg4, arg5) }
|
||||
public func resolve<T, A, B, C, D, E>(tag tag: DependencyTagConvertible? = nil, withArguments arg1: A, _ arg2: B, _ arg3: C, _ arg4: D, _ arg5: E) throws -> T {
|
||||
return try resolve(tag: tag) { factory in try factory(arg1, arg2, arg3, arg4, arg5) }
|
||||
}
|
||||
|
||||
///- seealso: `resolve(_:tag:)`, `resolve(tag:withArguments:)`
|
||||
public func resolve<A, B, C, D, E>(type: Any.Type, tag: DependencyTagConvertible? = nil, withArguments arg1: A, _ arg2: B, _ arg3: C, _ arg4: D, _ arg5: E) throws -> Any {
|
||||
return try resolve(type, tag: tag) { factory in try factory((arg1, arg2, arg3, arg4, arg5)) }
|
||||
}
|
||||
|
||||
// MARK: 6 Runtime Arguments
|
||||
|
||||
/// - seealso: `register(tag:scope:factory:)`
|
||||
public func register<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory)
|
||||
public func register<T, A, B, C, D, E, F>(tag tag: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: (A, B, C, D, E, F) throws -> T) -> DefinitionOf<T, (A, B, C, D, E, F) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: 6) { container, tag in try factory(container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag)) }
|
||||
}
|
||||
|
||||
/// - seealso: `resolve(tag:withArguments:)`
|
||||
public func resolve<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6>(tag tag: Tag? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6) throws -> T {
|
||||
return try resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) throws -> T) in try factory(arg1, arg2, arg3, arg4, arg5, arg6) }
|
||||
public func resolve<T, A, B, C, D, E, F>(tag tag: DependencyTagConvertible? = nil, withArguments arg1: A, _ arg2: B, _ arg3: C, _ arg4: D, _ arg5: E, _ arg6: F) throws -> T {
|
||||
return try resolve(tag: tag) { factory in try factory(arg1, arg2, arg3, arg4, arg5, arg6) }
|
||||
}
|
||||
|
||||
/// - seealso: `resolve(_:tag:)`, `resolve(tag:withArguments:)`
|
||||
public func resolve<A, B, C, D, E, F>(type: Any.Type, tag: DependencyTagConvertible? = nil, withArguments arg1: A, _ arg2: B, _ arg3: C, _ arg4: D, _ arg5: E, _ arg6: F) throws -> Any {
|
||||
return try resolve(type, tag: tag) { factory in try factory((arg1, arg2, arg3, arg4, arg5, arg6)) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
protocol TypeForwardingDefinition: Definition {
|
||||
var implementingTypes: [Any.Type] { get }
|
||||
func doesImplements(type: Any.Type) -> Bool
|
||||
}
|
||||
|
||||
extension DependencyContainer {
|
||||
|
||||
/**
|
||||
Registers definition for passed type.
|
||||
|
||||
If instance created by definition factory does not implement registered type
|
||||
container will throw `DipError.DefinitionNotFound` error when trying to resolve that type.
|
||||
|
||||
- parameters:
|
||||
- definition: Definition to register
|
||||
- type: Type to register definition for
|
||||
- tag: Optional tag to associate definition with. Default is `nil`.
|
||||
|
||||
- returns: New definition for passed type.
|
||||
*/
|
||||
public func register<T, U, F>(definition: DefinitionOf<T, U throws -> T>, type: F.Type, tag: DependencyTagConvertible? = nil) -> DefinitionOf<F, U throws -> F> {
|
||||
let key = DefinitionKey(protocolType: F.self, argumentsType: U.self)
|
||||
|
||||
let forwardDefinition = DefinitionBuilder<F, U> {
|
||||
$0.scope = definition.scope
|
||||
|
||||
let factory = definition.factory
|
||||
$0.factory = { [unowned self] in
|
||||
guard let resolved = try factory($0) as? F else {
|
||||
throw DipError.DefinitionNotFound(key: key.tagged(self.context.tag))
|
||||
}
|
||||
return resolved
|
||||
}
|
||||
|
||||
$0.numberOfArguments = definition.numberOfArguments
|
||||
$0.autoWiringFactory = definition.autoWiringFactory.map({ autoWiringFactory in
|
||||
{ [unowned self] in
|
||||
guard let resolved = try autoWiringFactory($0, $1) as? F else {
|
||||
throw DipError.DefinitionNotFound(key: key.tagged(self.context.tag))
|
||||
}
|
||||
return resolved
|
||||
}
|
||||
})
|
||||
|
||||
$0.forwardsDefinition = definition
|
||||
}.build()
|
||||
|
||||
register(forwardDefinition, forTag: tag)
|
||||
return forwardDefinition
|
||||
}
|
||||
|
||||
/// Searches for definition that forwards requested type
|
||||
func typeForwardingDefinition(type: Any.Type, tag: DependencyContainer.Tag?) -> KeyDefinitionPair? {
|
||||
var forwardingDefinitions = self.definitions.map({ (key: $0.0, definition: $0.1) })
|
||||
|
||||
forwardingDefinitions = filter(forwardingDefinitions, type: type, tag: tag)
|
||||
forwardingDefinitions = order(forwardingDefinitions, byTag: tag)
|
||||
|
||||
//we need to carry on original tag
|
||||
return forwardingDefinitions.first.map({ ($0.key.tagged(tag), $0.definition) })
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,13 +28,11 @@ import XCTest
|
||||
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 {
|
||||
@@ -49,7 +47,6 @@ private class ServerImp: Server {
|
||||
weak var anotherClient: Client?
|
||||
|
||||
weak var _optionalProperty = InjectedWeak<AnyObject>(required: false)
|
||||
var optionalProperty: AnyObject? { return _optionalProperty?.value }
|
||||
}
|
||||
|
||||
private class ClientImp: Client {
|
||||
@@ -64,9 +61,9 @@ private class ClientImp: Client {
|
||||
var anotherServer: Server?
|
||||
|
||||
var _optionalProperty = Injected<AnyObject>(required: false)
|
||||
var optionalProperty: AnyObject? { return _optionalProperty.value }
|
||||
|
||||
var taggedServer = Injected<Server>(tag: "tagged")
|
||||
var nilTaggedServer = Injected<Server>(tag: nil)
|
||||
}
|
||||
|
||||
private class Obj1 {
|
||||
@@ -95,9 +92,10 @@ class AutoInjectionTests: XCTestCase {
|
||||
let container = DependencyContainer()
|
||||
|
||||
#if os(Linux)
|
||||
var allTests: [(String, () throws -> Void)] {
|
||||
static var allTests: [(String, AutoInjectionTests -> () throws -> Void)] {
|
||||
return [
|
||||
("testThatItResolvesAutoInjectedDependencies", testThatItResolvesAutoInjectedDependencies),
|
||||
("testThatItCanSetInjectedProperty", testThatItCanSetInjectedProperty),
|
||||
("testThatItThrowsErrorIfFailsToAutoInjectDependency", testThatItThrowsErrorIfFailsToAutoInjectDependency),
|
||||
("testThatItResolvesAutoInjectedSingletons", testThatItResolvesAutoInjectedSingletons),
|
||||
("testThatItCallsResolveDependencyBlockWhenAutoInjecting", testThatItCallsResolveDependencyBlockWhenAutoInjecting),
|
||||
@@ -106,12 +104,14 @@ class AutoInjectionTests: XCTestCase {
|
||||
("testThatThereIsNoRetainCycleBetweenAutoInjectedCircularDependencies", testThatThereIsNoRetainCycleBetweenAutoInjectedCircularDependencies),
|
||||
("testThatItCallsDidInjectOnAutoInjectedProperty", testThatItCallsDidInjectOnAutoInjectedProperty),
|
||||
("testThatNoErrorThrownWhenOptionalPropertiesAreNotAutoInjected", testThatNoErrorThrownWhenOptionalPropertiesAreNotAutoInjected),
|
||||
("testThatItResolvesTaggedAutoInjectedProperties", testThatItResolvesTaggedAutoInjectedProperties)
|
||||
("testThatItResolvesTaggedAutoInjectedProperties", testThatItResolvesTaggedAutoInjectedProperties),
|
||||
("testThatItPassesTagToAutoInjectedProperty", testThatItPassesTagToAutoInjectedProperty),
|
||||
("testThatItDoesNotPassTagToAutoInjectedPropertyWithExplicitTag", testThatItDoesNotPassTagToAutoInjectedPropertyWithExplicitTag)
|
||||
]
|
||||
}
|
||||
|
||||
func setUp() {
|
||||
container.reset()
|
||||
container.reset()
|
||||
}
|
||||
#else
|
||||
override func setUp() {
|
||||
@@ -128,6 +128,22 @@ class AutoInjectionTests: XCTestCase {
|
||||
XCTAssertTrue(client === server?.client)
|
||||
}
|
||||
|
||||
func testThatItCanSetInjectedProperty() {
|
||||
container.register(.ObjectGraph) { ServerImp() as Server }
|
||||
container.register(.ObjectGraph) { ClientImp() as Client }
|
||||
|
||||
let client = (try! container.resolve() as Client) as! ClientImp
|
||||
let server = client.server as! ServerImp
|
||||
|
||||
let newServer = ServerImp()
|
||||
let newClient = ClientImp()
|
||||
client._server = client._server.setValue(newServer)
|
||||
server._client = server._client.setValue(newClient)
|
||||
|
||||
XCTAssertTrue(client.server === newServer)
|
||||
XCTAssertTrue(server.client === newClient)
|
||||
}
|
||||
|
||||
func testThatItThrowsErrorIfFailsToAutoInjectDependency() {
|
||||
container.register(.ObjectGraph) { ClientImp() as Client }
|
||||
|
||||
@@ -280,6 +296,52 @@ class AutoInjectionTests: XCTestCase {
|
||||
|
||||
//server and tagged server should be resolved as different instances
|
||||
XCTAssertTrue(server !== taggedServer)
|
||||
XCTAssertNotNil(server)
|
||||
XCTAssertNotNil(taggedServer)
|
||||
}
|
||||
|
||||
func testThatItPassesTagToAutoInjectedProperty() {
|
||||
//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(tag: "tagged") as Client
|
||||
|
||||
//then
|
||||
let taggedServer = (client as! ClientImp).taggedServer.value!
|
||||
let server = client.server!
|
||||
|
||||
//server and tagged server should be resolved as the same instance
|
||||
XCTAssertTrue(server === taggedServer)
|
||||
}
|
||||
|
||||
func testThatItDoesNotPassTagToAutoInjectedPropertyWithExplicitTag() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServerImp() as Server }
|
||||
container.register(tag: "tagged", .ObjectGraph) { ServerImp() as Server }
|
||||
|
||||
container.register(.ObjectGraph) { ClientImp() as Client }
|
||||
.resolveDependencies { (container, client) -> () in
|
||||
client.anotherServer = try! container.resolve() as Server
|
||||
}
|
||||
|
||||
//when
|
||||
let client = try! container.resolve(tag: "otherTag") as Client
|
||||
|
||||
//then
|
||||
let taggedServer = (client as! ClientImp).taggedServer.value!
|
||||
let nilTaggedServer = (client as! ClientImp).nilTaggedServer.value!
|
||||
let server = client.server!
|
||||
|
||||
//server and tagged server should be resolved as different instances
|
||||
XCTAssertTrue(server !== taggedServer)
|
||||
XCTAssertTrue((client.anotherServer as! ServerImp) === nilTaggedServer)
|
||||
|
||||
XCTAssertNotNil(server)
|
||||
XCTAssertNotNil(taggedServer)
|
||||
XCTAssertNotNil(nilTaggedServer)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
//
|
||||
// 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 ServiceImp3 {}
|
||||
|
||||
private protocol AutoWiredClient: class {
|
||||
var service1: Service! { get set }
|
||||
var service2: Service! { get set }
|
||||
}
|
||||
|
||||
private class AutoWiredClientImp: AutoWiredClient {
|
||||
var service1: Service!
|
||||
var service2: Service!
|
||||
|
||||
init(service1: Service?, service2: ServiceImp2) {
|
||||
self.service1 = service1
|
||||
self.service2 = service2
|
||||
}
|
||||
init() {}
|
||||
}
|
||||
|
||||
class AutoWiringTests: XCTestCase {
|
||||
|
||||
let container = DependencyContainer()
|
||||
|
||||
#if os(Linux)
|
||||
static var allTests: [(String, AutoWiringTests -> () throws -> Void)] {
|
||||
return [
|
||||
("testThatItCanResolveWithAutoWiring", testThatItCanResolveWithAutoWiring),
|
||||
("testThatItUsesAutoWireFactoryWithMostNumberOfArguments", testThatItUsesAutoWireFactoryWithMostNumberOfArguments),
|
||||
("testThatItThrowsAmbiguityErrorWhenUsingAutoWire", testThatItThrowsAmbiguityErrorWhenUsingAutoWire),
|
||||
("testThatItFirstTriesToUseTaggedFactoriesWhenUsingAutoWire", testThatItFirstTriesToUseTaggedFactoriesWhenUsingAutoWire),
|
||||
("testThatItFallbackToNotTaggedFactoryWhenUsingAutoWire", testThatItFallbackToNotTaggedFactoryWhenUsingAutoWire),
|
||||
("testThatItDoesNotTryToUseAutoWiringWhenCallingResolveWithArguments", testThatItDoesNotTryToUseAutoWiringWhenCallingResolveWithArguments),
|
||||
("testThatItDoesNotUseAutoWiringWhenFailedToResolveLowLevelDependency", testThatItDoesNotUseAutoWiringWhenFailedToResolveLowLevelDependency),
|
||||
("testThatItReusesInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgain", testThatItReusesInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgain),
|
||||
("testThatItReusesInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgainWithTheSameTag", testThatItReusesInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgainWithTheSameTag),
|
||||
("testThatItDoesNotReuseInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgainWithNoTag", testThatItDoesNotReuseInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgainWithNoTag),
|
||||
("testThatItUsesTagToResolveDependenciesWithAutoWiringWith1Argument", testThatItUsesTagToResolveDependenciesWithAutoWiringWith1Argument),
|
||||
("testThatItUsesTagToResolveDependenciesWithAutoWiringWith2Arguments", testThatItUsesTagToResolveDependenciesWithAutoWiringWith2Arguments),
|
||||
("testThatItUsesTagToResolveDependenciesWithAutoWiringWith3Arguments", testThatItUsesTagToResolveDependenciesWithAutoWiringWith3Arguments),
|
||||
("testThatItUsesTagToResolveDependenciesWithAutoWiringWith4Arguments", testThatItUsesTagToResolveDependenciesWithAutoWiringWith4Arguments),
|
||||
("testThatItUsesTagToResolveDependenciesWithAutoWiringWith5Arguments", testThatItUsesTagToResolveDependenciesWithAutoWiringWith5Arguments),
|
||||
("testThatItUsesTagToResolveDependenciesWithAutoWiringWith6Arguments", testThatItUsesTagToResolveDependenciesWithAutoWiringWith6Arguments),
|
||||
("testThatItCanAutoWireOptional", testThatItCanAutoWireOptional)
|
||||
]
|
||||
}
|
||||
|
||||
func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#else
|
||||
override func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#endif
|
||||
|
||||
func testThatItCanResolveWithAutoWiring() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
|
||||
//when
|
||||
let client = try! container.resolve() as AutoWiredClient
|
||||
|
||||
//then
|
||||
let service1 = client.service1
|
||||
XCTAssertTrue(service1 is ServiceImp1)
|
||||
let service2 = client.service2
|
||||
XCTAssertTrue(service2 is ServiceImp2)
|
||||
|
||||
//when
|
||||
let anyClient = try! container.resolve(AutoWiredClient.self)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(anyClient is AutoWiredClientImp)
|
||||
}
|
||||
|
||||
func testThatItUsesAutoWireFactoryWithMostNumberOfArguments() {
|
||||
//given
|
||||
|
||||
//1 arg
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
|
||||
//1 arg
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: try self.container.resolve(), service2: $0) as AutoWiredClient }
|
||||
|
||||
//2 args
|
||||
var factoryWithMostNumberOfArgumentsCalled = false
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
.resolveDependencies { _ in
|
||||
factoryWithMostNumberOfArgumentsCalled = true
|
||||
}
|
||||
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve() as AutoWiredClient
|
||||
|
||||
//then
|
||||
XCTAssertTrue(factoryWithMostNumberOfArgumentsCalled)
|
||||
}
|
||||
|
||||
func testThatItThrowsAmbiguityErrorWhenUsingAutoWire() {
|
||||
//given
|
||||
|
||||
//1 arg
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
|
||||
//1 arg
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: try self.container.resolve(), service2: $0) as AutoWiredClient }
|
||||
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
//when
|
||||
AssertThrows(expression: try container.resolve() as AutoWiredClient) { error -> Bool in
|
||||
switch error {
|
||||
case let DipError.AutoWiringFailed(_, error):
|
||||
if case DipError.AmbiguousDefinitions = error { return true }
|
||||
else { return false }
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testThatItFirstTriesToUseTaggedFactoriesWhenUsingAutoWire() {
|
||||
//given
|
||||
|
||||
//1 arg
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
|
||||
//1 arg
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: try self.container.resolve(), service2: $0) as AutoWiredClient }
|
||||
|
||||
//2 args
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
|
||||
//1 arg tagged
|
||||
var taggedFactoryWithMostNumberOfArgumentsCalled = false
|
||||
container.register(tag: "tag", .ObjectGraph) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
|
||||
|
||||
//2 arg tagged
|
||||
container.register(tag: "tag", .ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }.resolveDependencies { _ in
|
||||
taggedFactoryWithMostNumberOfArgumentsCalled = true
|
||||
}
|
||||
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve(tag: "tag") as AutoWiredClient
|
||||
|
||||
//then
|
||||
XCTAssertTrue(taggedFactoryWithMostNumberOfArgumentsCalled)
|
||||
}
|
||||
|
||||
func testThatItFallbackToNotTaggedFactoryWhenUsingAutoWire() {
|
||||
//given
|
||||
|
||||
//1 arg
|
||||
var notTaggedFactoryWithMostNumberOfArgumentsCalled = false
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }.resolveDependencies {_ in
|
||||
notTaggedFactoryWithMostNumberOfArgumentsCalled = true
|
||||
}
|
||||
|
||||
//1 arg tagged
|
||||
container.register(tag: "tag", .ObjectGraph) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
|
||||
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve(tag: "other tag") as AutoWiredClient
|
||||
|
||||
//then
|
||||
XCTAssertTrue(notTaggedFactoryWithMostNumberOfArgumentsCalled)
|
||||
}
|
||||
|
||||
func testThatItDoesNotTryToUseAutoWiringWhenCallingResolveWithArguments() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
//when
|
||||
let service = try! container.resolve() as Service
|
||||
AssertThrows(expression: try container.resolve(withArguments: service) as AutoWiredClient,
|
||||
"Container should not use auto-wiring when resolving with runtime arguments")
|
||||
}
|
||||
|
||||
func testThatItDoesNotUseAutoWiringWhenFailedToResolveLowLevelDependency() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp() as AutoWiredClient }
|
||||
.resolveDependencies { container, resolved in
|
||||
resolved.service1 = try container.resolve() as Service
|
||||
resolved.service2 = try container.resolve() as ServiceImp2
|
||||
|
||||
//simulate that something goes wrong on the way
|
||||
throw DipError.DefinitionNotFound(key: DefinitionKey(protocolType: ServiceImp1.self, argumentsType: Any.self))
|
||||
}
|
||||
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
.resolveDependencies { container, resolved in
|
||||
//auto-wiring should be performed only when definition for type to resolve is not found
|
||||
//but not for any other type along the way in the graph
|
||||
XCTFail("Auto-wiring should not be performed if instance was actually resolved.")
|
||||
}
|
||||
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
//then
|
||||
AssertThrows(expression: try container.resolve() as AutoWiredClient,
|
||||
"Container should not use auto-wiring when definition for resolved type is registered.")
|
||||
}
|
||||
|
||||
func testThatItReusesInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgain() {
|
||||
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
var anotherInstance: AutoWiredClient?
|
||||
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
.resolveDependencies { container, _ in
|
||||
if anotherInstance == nil {
|
||||
anotherInstance = try! container.resolve() as AutoWiredClient
|
||||
}
|
||||
}
|
||||
|
||||
//when
|
||||
let resolved = try! container.resolve() as AutoWiredClient
|
||||
|
||||
//then
|
||||
//when doing another auto-wiring during resolve we should reuse instance
|
||||
XCTAssertTrue((resolved as! AutoWiredClientImp) === (anotherInstance as! AutoWiredClientImp))
|
||||
}
|
||||
|
||||
func testThatItReusesInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgainWithTheSameTag() {
|
||||
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
var anotherInstance: AutoWiredClient?
|
||||
|
||||
container.register(tag: "tag", .ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
.resolveDependencies { container, _ in
|
||||
if anotherInstance == nil {
|
||||
anotherInstance = try! container.resolve(tag: "tag") as AutoWiredClient
|
||||
}
|
||||
}
|
||||
|
||||
//when
|
||||
let resolved = try! container.resolve(tag: "tag") as AutoWiredClient
|
||||
|
||||
//then
|
||||
//when doing another auto-wiring during resolve we should reuse instance
|
||||
XCTAssertTrue((resolved as! AutoWiredClientImp) === (anotherInstance as! AutoWiredClientImp))
|
||||
}
|
||||
|
||||
func testThatItDoesNotReuseInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgainWithNoTag() {
|
||||
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
var anotherInstance: AutoWiredClient?
|
||||
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
.resolveDependencies { container, _ in
|
||||
if anotherInstance == nil {
|
||||
anotherInstance = try! container.resolve() as AutoWiredClient
|
||||
}
|
||||
}
|
||||
|
||||
//when
|
||||
let resolved = try! container.resolve(tag: "tag") as AutoWiredClient
|
||||
|
||||
//then
|
||||
//when doing another auto-wiring during resolve we should reuse instance
|
||||
XCTAssertTrue((resolved as! AutoWiredClientImp) !== (anotherInstance as! AutoWiredClientImp))
|
||||
}
|
||||
|
||||
func testThatItUsesTagToResolveDependenciesWithAutoWiringWith1Argument() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(tag: "tag", .ObjectGraph) { ServiceImp2() as Service }
|
||||
|
||||
container.register(.ObjectGraph) { (dep1: Service) -> ServiceImp3 in
|
||||
XCTAssertTrue(dep1 is ServiceImp2)
|
||||
return ServiceImp3()
|
||||
}
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve(tag: "tag") as ServiceImp3
|
||||
}
|
||||
|
||||
func testThatItUsesTagToResolveDependenciesWithAutoWiringWith2Arguments() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(tag: "tag", .ObjectGraph) { ServiceImp2() as Service }
|
||||
|
||||
container.register(.ObjectGraph) { (dep1: Service, dep2: Service) -> ServiceImp3 in
|
||||
XCTAssertTrue(dep1 is ServiceImp2)
|
||||
XCTAssertTrue(dep2 is ServiceImp2)
|
||||
return ServiceImp3()
|
||||
}
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve(tag: "tag") as ServiceImp3
|
||||
}
|
||||
|
||||
func testThatItUsesTagToResolveDependenciesWithAutoWiringWith3Arguments() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(tag: "tag", .ObjectGraph) { ServiceImp2() as Service }
|
||||
|
||||
container.register(.ObjectGraph) { (dep1: Service, dep2: Service, dep3: Service) -> ServiceImp3 in
|
||||
XCTAssertTrue(dep1 is ServiceImp2)
|
||||
XCTAssertTrue(dep2 is ServiceImp2)
|
||||
XCTAssertTrue(dep3 is ServiceImp2)
|
||||
return ServiceImp3()
|
||||
}
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve(tag: "tag") as ServiceImp3
|
||||
}
|
||||
|
||||
func testThatItUsesTagToResolveDependenciesWithAutoWiringWith4Arguments() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(tag: "tag", .ObjectGraph) { ServiceImp2() as Service }
|
||||
|
||||
container.register(.ObjectGraph) { (dep1: Service, dep2: Service, dep3: Service, dep4: Service) -> ServiceImp3 in
|
||||
XCTAssertTrue(dep1 is ServiceImp2)
|
||||
XCTAssertTrue(dep2 is ServiceImp2)
|
||||
XCTAssertTrue(dep3 is ServiceImp2)
|
||||
XCTAssertTrue(dep4 is ServiceImp2)
|
||||
return ServiceImp3()
|
||||
}
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve(tag: "tag") as ServiceImp3
|
||||
}
|
||||
|
||||
func testThatItUsesTagToResolveDependenciesWithAutoWiringWith5Arguments() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(tag: "tag", .ObjectGraph) { ServiceImp2() as Service }
|
||||
|
||||
container.register(.ObjectGraph) { (dep1: Service, dep2: Service, dep3: Service, dep4: Service, dep5: Service) -> ServiceImp3 in
|
||||
XCTAssertTrue(dep1 is ServiceImp2)
|
||||
XCTAssertTrue(dep2 is ServiceImp2)
|
||||
XCTAssertTrue(dep3 is ServiceImp2)
|
||||
XCTAssertTrue(dep4 is ServiceImp2)
|
||||
XCTAssertTrue(dep5 is ServiceImp2)
|
||||
return ServiceImp3()
|
||||
}
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve(tag: "tag") as ServiceImp3
|
||||
}
|
||||
|
||||
func testThatItUsesTagToResolveDependenciesWithAutoWiringWith6Arguments() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(tag: "tag", .ObjectGraph) { ServiceImp2() as Service }
|
||||
|
||||
container.register(.ObjectGraph) { (dep1: Service, dep2: Service, dep3: Service, dep4: Service, dep5: Service, dep6: Service) -> ServiceImp3 in
|
||||
XCTAssertTrue(dep1 is ServiceImp2)
|
||||
XCTAssertTrue(dep2 is ServiceImp2)
|
||||
XCTAssertTrue(dep3 is ServiceImp2)
|
||||
XCTAssertTrue(dep4 is ServiceImp2)
|
||||
XCTAssertTrue(dep5 is ServiceImp2)
|
||||
XCTAssertTrue(dep6 is ServiceImp2)
|
||||
return ServiceImp3()
|
||||
}
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve(tag: "tag") as ServiceImp3
|
||||
}
|
||||
|
||||
func testThatItCanAutoWireOptional() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
|
||||
var resolved: AutoWiredClient?
|
||||
//when
|
||||
AssertNoThrow(expression: resolved = try container.resolve() as AutoWiredClient?)
|
||||
XCTAssertNotNil(resolved)
|
||||
|
||||
//when
|
||||
AssertNoThrow(expression: resolved = try container.resolve() as AutoWiredClient!)
|
||||
XCTAssertNotNil(resolved)
|
||||
|
||||
//when
|
||||
AssertNoThrow(expression: resolved = try container.resolve(tag: "tag") as AutoWiredClient?)
|
||||
XCTAssertNotNil(resolved)
|
||||
|
||||
//when
|
||||
AssertNoThrow(expression: resolved = try container.resolve(tag: "tag") as AutoWiredClient!)
|
||||
XCTAssertNotNil(resolved)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+140
-61
@@ -48,7 +48,7 @@ class ComponentScopeTests: XCTestCase {
|
||||
let container = DependencyContainer()
|
||||
|
||||
#if os(Linux)
|
||||
var allTests: [(String, () throws -> Void)] {
|
||||
static var allTests: [(String, ComponentScopeTests -> () throws -> Void)] {
|
||||
return [
|
||||
("testThatPrototypeIsDefaultScope", testThatPrototypeIsDefaultScope),
|
||||
("testThatScopeCanBeChanged", testThatScopeCanBeChanged),
|
||||
@@ -60,7 +60,8 @@ class ComponentScopeTests: XCTestCase {
|
||||
("testThatSingletonIsReleasedWhenContainerIsReset", testThatSingletonIsReleasedWhenContainerIsReset),
|
||||
("testThatItReusesInstanceInObjectGraphScopeDuringResolve", testThatItReusesInstanceInObjectGraphScopeDuringResolve),
|
||||
("testThatItDoesNotReuseInstanceInObjectGraphScopeInNextResolve", testThatItDoesNotReuseInstanceInObjectGraphScopeInNextResolve),
|
||||
("testThatItDoesNotReuseInstanceInObjectGraphScopeResolvedForNilTag", testThatItDoesNotReuseInstanceInObjectGraphScopeResolvedForNilTag)
|
||||
("testThatItDoesNotReuseInstanceInObjectGraphScopeResolvedForNilTag", testThatItDoesNotReuseInstanceInObjectGraphScopeResolvedForNilTag),
|
||||
("testThatItReusesResolvedInstanceWhenResolvingOptional", testThatItReusesResolvedInstanceWhenResolvingOptional)
|
||||
]
|
||||
}
|
||||
|
||||
@@ -96,78 +97,104 @@ class ComponentScopeTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testThatItReusesInstanceForSingletonScope() {
|
||||
//given
|
||||
container.register(.Singleton) { ServiceImp1() as Service }
|
||||
func test(scope: ComponentScope) {
|
||||
//given
|
||||
container.register(scope) { ServiceImp1() as Service }
|
||||
|
||||
//when
|
||||
let service1 = try! container.resolve() as Service
|
||||
let service2 = try! container.resolve() as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service1 === service2)
|
||||
}
|
||||
|
||||
//when
|
||||
let service1 = try! container.resolve() as Service
|
||||
let service2 = try! container.resolve() as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service1 === service2)
|
||||
test(.Singleton)
|
||||
test(.EagerSingleton)
|
||||
}
|
||||
|
||||
func testThatSingletonIsNotReusedAcrossContainers() {
|
||||
//given
|
||||
let def = container.register(.Singleton) { ServiceImp1() as Service }
|
||||
let secondContainer = DependencyContainer()
|
||||
secondContainer.register(def, forTag: nil)
|
||||
func test(scope: ComponentScope) {
|
||||
//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")
|
||||
}
|
||||
|
||||
//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")
|
||||
test(.Singleton)
|
||||
test(.EagerSingleton)
|
||||
}
|
||||
|
||||
func testThatSingletonIsReleasedWhenDefinitionIsRemoved() {
|
||||
//given
|
||||
let def = container.register(.Singleton) { ServiceImp1() as Service }
|
||||
let service1 = try! container.resolve() as Service
|
||||
func test(scope: ComponentScope) {
|
||||
//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")
|
||||
}
|
||||
|
||||
//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")
|
||||
test(.Singleton)
|
||||
test(.EagerSingleton)
|
||||
}
|
||||
|
||||
func testThatSingletonIsReleasedWhenDefinitionIsOverridden() {
|
||||
//given
|
||||
let def = container.register(.Singleton) { ServiceImp1() as Service }
|
||||
let service1 = try! container.resolve() as Service
|
||||
func test(scope: ComponentScope) {
|
||||
//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")
|
||||
}
|
||||
|
||||
//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")
|
||||
test(.Singleton)
|
||||
test(.EagerSingleton)
|
||||
}
|
||||
|
||||
func testThatSingletonIsReleasedWhenContainerIsReset() {
|
||||
//given
|
||||
let def = container.register(.Singleton) { ServiceImp1() as Service }
|
||||
let service1 = try! container.resolve() as Service
|
||||
func test(scope: ComponentScope) {
|
||||
//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")
|
||||
}
|
||||
|
||||
//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")
|
||||
test(.Singleton)
|
||||
test(.EagerSingleton)
|
||||
}
|
||||
|
||||
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
|
||||
container.register(.ObjectGraph) { Server() as Server }
|
||||
.resolveDependencies { container, server in
|
||||
server.client = try container.resolve() as Client
|
||||
}
|
||||
|
||||
//when
|
||||
@@ -181,8 +208,9 @@ class ComponentScopeTests: XCTestCase {
|
||||
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
|
||||
container.register(.ObjectGraph) { Server() as Server }
|
||||
.resolveDependencies { container, server in
|
||||
server.client = try container.resolve() as Client
|
||||
}
|
||||
|
||||
//when
|
||||
@@ -200,14 +228,15 @@ class ComponentScopeTests: XCTestCase {
|
||||
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(.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}
|
||||
|
||||
@@ -217,6 +246,56 @@ class ComponentScopeTests: XCTestCase {
|
||||
//then
|
||||
XCTAssertTrue(service1 is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatOnlyEagerSingletonIsCreatedWhenContainerIsBootsrapped() {
|
||||
//given
|
||||
var eagerSingletonResolved = false
|
||||
|
||||
container.register(tag: "eager", .EagerSingleton) { ServiceImp1() as Service }
|
||||
.resolveDependencies { container, service in eagerSingletonResolved = true }
|
||||
|
||||
container.register(tag: "singleton", .Singleton) { ServiceImp1() as Service }
|
||||
.resolveDependencies { container, service in XCTFail() }
|
||||
|
||||
container.register(tag: "prototype", .Prototype) { ServiceImp1() as Service }
|
||||
.resolveDependencies { container, service in XCTFail() }
|
||||
|
||||
container.register(tag: "graph", .ObjectGraph) { ServiceImp1() as Service }
|
||||
.resolveDependencies { container, service in XCTFail() }
|
||||
|
||||
//when
|
||||
try! container.bootstrap()
|
||||
XCTAssertTrue(eagerSingletonResolved)
|
||||
}
|
||||
|
||||
func testThatContainerCanBeBootstrappedAgainAfterReset() {
|
||||
try! container.bootstrap()
|
||||
XCTAssertTrue(container.bootstrapped)
|
||||
|
||||
container.reset()
|
||||
XCTAssertFalse(container.bootstrapped)
|
||||
}
|
||||
|
||||
func testThatItReusesResolvedInstanceWhenResolvingOptional() {
|
||||
var otherService: Service!
|
||||
var impOtherService: Service!
|
||||
var anyOtherService: Any!
|
||||
var anyImpOtherService: Any!
|
||||
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
.resolveDependencies { container, service in
|
||||
otherService = try! container.resolve() as Service?
|
||||
impOtherService = try! container.resolve() as Service!
|
||||
anyOtherService = try! container.resolve((Service?).self)
|
||||
anyImpOtherService = try! container.resolve((Service!).self)
|
||||
}
|
||||
|
||||
let service = try! container.resolve() as Service
|
||||
XCTAssertTrue(otherService as! ServiceImp1 === service as! ServiceImp1)
|
||||
XCTAssertTrue(impOtherService as! ServiceImp1 === service as! ServiceImp1)
|
||||
XCTAssertTrue(anyOtherService as! ServiceImp1 === service as! ServiceImp1)
|
||||
XCTAssertTrue(anyImpOtherService as! ServiceImp1 === service as! ServiceImp1)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {}
|
||||
private class ServiceImp1: Service {
|
||||
let injected = Injected<ServiceImp2>()
|
||||
let injectedWeak = InjectedWeak<ServiceImp2>()
|
||||
let taggedInjected = Injected<ServiceImp2>(tag: "injectedTag")
|
||||
let taggedInjectedWeak = InjectedWeak<ServiceImp2>(tag: "injectedTag")
|
||||
let injectedNilTag = Injected<ServiceImp2>(tag: nil)
|
||||
}
|
||||
private class ServiceImp2: Service {}
|
||||
|
||||
class ContextTests: XCTestCase {
|
||||
|
||||
let container = DependencyContainer()
|
||||
|
||||
#if os(Linux)
|
||||
static var allTests: [(String, ContextTests -> () throws -> Void)] {
|
||||
return [
|
||||
("testThatContextStoresCurrentlyResolvedType", testThatContextStoresCurrentlyResolvedType),
|
||||
("testThatContextStoresInjectedInType", testThatContextStoresInjectedInType),
|
||||
("testThatContextStoresTheTagPassedToResolve", testThatContextStoresTheTagPassedToResolve),
|
||||
("testThatContextStoresTheTagPassedToResolveWhenAutoInjecting", testThatContextStoresTheTagPassedToResolveWhenAutoInjecting),
|
||||
("testThatContextStoresTheTagPassedToResolveWhenAutoWiring", testThatContextStoresTheTagPassedToResolveWhenAutoWiring),
|
||||
("testThatContextDoesNotOverrideNilTagPassedToResolve", testThatContextDoesNotOverrideNilTagPassedToResolve),
|
||||
("testThatContextStoresNameOfAutoInjectedProperty", testThatContextStoresNameOfAutoInjectedProperty)
|
||||
]
|
||||
}
|
||||
|
||||
func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#else
|
||||
override func setUp() {
|
||||
container.reset()
|
||||
container.register { ServiceImp2() }
|
||||
}
|
||||
#endif
|
||||
|
||||
func testThatContextStoresCurrentlyResolvedType() {
|
||||
container.register { () -> Service in
|
||||
XCTAssertTrue(self.container.context.resolvingType == Service.self)
|
||||
let _ = try self.container.resolve() as ServiceImp1
|
||||
return ServiceImp1() as Service
|
||||
}.resolveDependencies { _ in
|
||||
XCTAssertTrue(self.container.context.resolvingType == Service.self)
|
||||
let _ = try self.container.resolve() as ServiceImp1
|
||||
}
|
||||
|
||||
container.register { () -> ServiceImp1 in
|
||||
XCTAssertTrue(self.container.context.resolvingType == ServiceImp1.self)
|
||||
return ServiceImp1()
|
||||
}.resolveDependencies { _ in
|
||||
XCTAssertTrue(self.container.context.resolvingType == ServiceImp1.self)
|
||||
}
|
||||
|
||||
let _ = try! container.resolve() as Service
|
||||
}
|
||||
|
||||
func testThatContextStoresInjectedInType() {
|
||||
container.register { () -> Service in
|
||||
XCTAssertNil(self.container.context.injectedInType)
|
||||
let _ = try self.container.resolve() as ServiceImp1
|
||||
return ServiceImp1() as Service
|
||||
}.resolveDependencies { _ in
|
||||
XCTAssertNil(self.container.context.injectedInType)
|
||||
let _ = try self.container.resolve() as ServiceImp1
|
||||
}
|
||||
|
||||
container.register { () -> ServiceImp1 in
|
||||
XCTAssertTrue(self.container.context.injectedInType == Service.self)
|
||||
return ServiceImp1()
|
||||
}.resolveDependencies { _ in
|
||||
XCTAssertTrue(self.container.context.injectedInType == Service.self)
|
||||
}
|
||||
|
||||
let _ = try! container.resolve() as Service
|
||||
}
|
||||
|
||||
func testThatContextStoresTheTagPassedToResolve() {
|
||||
container.register { () -> Service in
|
||||
XCTAssertNotNil(self.container.context.tag)
|
||||
XCTAssertTrue(DependencyContainer.Tag.String("tag") ~= self.container.context.tag!)
|
||||
let _ = try self.container.resolve(tag: "otherTag") as ServiceImp1
|
||||
return ServiceImp1() as Service
|
||||
}.resolveDependencies { _ in
|
||||
XCTAssertNotNil(self.container.context.tag)
|
||||
XCTAssertTrue(DependencyContainer.Tag.String("tag") ~= self.container.context.tag!)
|
||||
let _ = try self.container.resolve(tag: "otherTag") as ServiceImp1
|
||||
}
|
||||
|
||||
container.register { () -> ServiceImp1 in
|
||||
XCTAssertNotNil(self.container.context.tag)
|
||||
XCTAssertTrue(DependencyContainer.Tag.String("otherTag") ~= self.container.context.tag!)
|
||||
return ServiceImp1()
|
||||
}.resolveDependencies { _ in
|
||||
XCTAssertNotNil(self.container.context.tag)
|
||||
XCTAssertTrue(DependencyContainer.Tag.String("otherTag") ~= self.container.context.tag!)
|
||||
}
|
||||
|
||||
let _ = try! container.resolve(tag: "tag") as Service
|
||||
}
|
||||
|
||||
func testThatContextStoresTheTagPassedToResolveWhenAutoInjecting() {
|
||||
container.register { ServiceImp1() as Service }
|
||||
container.register { ServiceImp1() }
|
||||
|
||||
container.register() { () -> ServiceImp2 in
|
||||
if self.container.context.injectedInProperty == "injectedNilTag" {
|
||||
XCTAssertNil(self.container.context.tag)
|
||||
}
|
||||
else {
|
||||
XCTAssertNotNil(self.container.context.tag)
|
||||
XCTAssertTrue(DependencyContainer.Tag.String("injectedTag") ~= self.container.context.tag!)
|
||||
}
|
||||
return ServiceImp2()
|
||||
}.resolveDependencies { _ in
|
||||
if self.container.context.injectedInProperty == "injectedNilTag" {
|
||||
XCTAssertNil(self.container.context.tag)
|
||||
}
|
||||
else {
|
||||
XCTAssertNotNil(self.container.context.tag)
|
||||
XCTAssertTrue(DependencyContainer.Tag.String("injectedTag") ~= self.container.context.tag!)
|
||||
}
|
||||
}
|
||||
|
||||
container.register(tag: "tag") { () -> ServiceImp2 in
|
||||
XCTAssertNotNil(self.container.context.tag)
|
||||
XCTAssertTrue(DependencyContainer.Tag.String("tag") ~= self.container.context.tag!)
|
||||
return ServiceImp2()
|
||||
}.resolveDependencies { _ in
|
||||
XCTAssertNotNil(self.container.context.tag)
|
||||
XCTAssertTrue(DependencyContainer.Tag.String("tag") ~= self.container.context.tag!)
|
||||
}
|
||||
|
||||
let _ = try! container.resolve(tag: "tag") as Service
|
||||
}
|
||||
|
||||
func testThatContextStoresTheTagPassedToResolveWhenAutoWiring() {
|
||||
container.register { (_: ServiceImp1) -> Service in
|
||||
return ServiceImp1() as Service
|
||||
}.resolveDependencies { _ in
|
||||
}
|
||||
|
||||
container.register { () -> ServiceImp1 in
|
||||
XCTAssertNotNil(self.container.context.tag)
|
||||
XCTAssertTrue(DependencyContainer.Tag.String("tag") ~= self.container.context.tag!)
|
||||
return ServiceImp1()
|
||||
}.resolveDependencies { _ in
|
||||
XCTAssertNotNil(self.container.context.tag)
|
||||
XCTAssertTrue(DependencyContainer.Tag.String("tag") ~= self.container.context.tag!)
|
||||
}
|
||||
|
||||
let _ = try! container.resolve(tag: "tag") as Service
|
||||
}
|
||||
|
||||
func testThatContextDoesNotOverrideNilTagPassedToResolve() {
|
||||
container.register { () -> Service in
|
||||
XCTAssertNotNil(self.container.context.tag)
|
||||
XCTAssertTrue(DependencyContainer.Tag.String("tag") ~= self.container.context.tag!)
|
||||
let _ = try self.container.resolve() as ServiceImp1
|
||||
return ServiceImp1() as Service
|
||||
}.resolveDependencies { _ in
|
||||
XCTAssertNotNil(self.container.context.tag)
|
||||
XCTAssertTrue(DependencyContainer.Tag.String("tag") ~= self.container.context.tag!)
|
||||
let _ = try self.container.resolve() as ServiceImp1
|
||||
}
|
||||
|
||||
container.register { () -> ServiceImp1 in
|
||||
XCTAssertNil(self.container.context.tag)
|
||||
return ServiceImp1()
|
||||
}.resolveDependencies { _ in
|
||||
XCTAssertNil(self.container.context.tag)
|
||||
}
|
||||
|
||||
let _ = try! container.resolve(tag: "tag") as Service
|
||||
}
|
||||
|
||||
func testThatContextStoresNameOfAutoInjectedProperty() {
|
||||
container.register { ServiceImp1() as Service }
|
||||
container.register { ServiceImp1() }
|
||||
|
||||
let names = ["injected", "injectedWeak", "taggedInjected", "taggedInjectedWeak", "injectedNilTag"]
|
||||
|
||||
container.register() { () -> ServiceImp2 in
|
||||
XCTAssertNotNil(self.container.context.injectedInProperty)
|
||||
XCTAssertTrue(names.contains(self.container.context.injectedInProperty!))
|
||||
return ServiceImp2()
|
||||
}.resolveDependencies { _ in
|
||||
XCTAssertNotNil(self.container.context.injectedInProperty)
|
||||
XCTAssertTrue(names.contains(self.container.context.injectedInProperty!))
|
||||
}
|
||||
|
||||
let _ = try! container.resolve() as Service
|
||||
}
|
||||
|
||||
}
|
||||
@@ -37,45 +37,46 @@ class DefinitionTests: XCTestCase {
|
||||
let tag2 = DependencyContainer.Tag.String("tag2")
|
||||
|
||||
#if os(Linux)
|
||||
var allTests: [(String, () throws -> Void)] {
|
||||
static var allTests: [(String, DefinitionTests -> () throws -> Void)] {
|
||||
return [
|
||||
("testThatDefinitionKeyIsEqualBy_Type_Factory_Tag", testThatDefinitionKeyIsEqualBy_Type_Factory_Tag),
|
||||
("testThatDefinitionKeysWithDifferentTypesAreNotEqual", testThatDefinitionKeysWithDifferentTypesAreNotEqual),
|
||||
("testThatDefinitionKeysWithDifferentFactoriesAreNotEqual", testThatDefinitionKeysWithDifferentFactoriesAreNotEqual),
|
||||
("testThatDefinitionKeysWithDifferentTagsAreNotEqual", testThatDefinitionKeysWithDifferentTagsAreNotEqual),
|
||||
("testThatResolveDependenciesCallsResolveDependenciesBlock", testThatResolveDependenciesCallsResolveDependenciesBlock),
|
||||
("testThatResolveDependenciesBlockIsNotCalledWhenPassedWrongInstance", testThatResolveDependenciesBlockIsNotCalledWhenPassedWrongInstance)
|
||||
("testThatResolveDependenciesBlockIsNotCalledWhenPassedWrongInstance", testThatResolveDependenciesBlockIsNotCalledWhenPassedWrongInstance),
|
||||
("testThatItRegisteresOptionalTypesAsForwardedTypes", testThatItRegisteresOptionalTypesAsForwardedTypes)
|
||||
]
|
||||
}
|
||||
#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)
|
||||
let equalKey1 = DefinitionKey(protocolType: Service.self, argumentsType: F1.self, associatedTag: tag1)
|
||||
let equalKey2 = DefinitionKey(protocolType: Service.self, argumentsType: 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)
|
||||
let keyWithDifferentType1 = DefinitionKey(protocolType: Service.self, argumentsType: F1.self, associatedTag: nil)
|
||||
let keyWithDifferentType2 = DefinitionKey(protocolType: AnyObject.self, argumentsType: 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)
|
||||
let keyWithDifferentFactory1 = DefinitionKey(protocolType: Service.self, argumentsType: F1.self, associatedTag: nil)
|
||||
let keyWithDifferentFactory2 = DefinitionKey(protocolType: Service.self, argumentsType: 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)
|
||||
let keyWithDifferentTag1 = DefinitionKey(protocolType: Service.self, argumentsType: F1.self, associatedTag: tag1)
|
||||
let keyWithDifferentTag2 = DefinitionKey(protocolType: Service.self, argumentsType: F1.self, associatedTag: tag2)
|
||||
|
||||
XCTAssertNotEqual(keyWithDifferentTag1, keyWithDifferentTag2)
|
||||
XCTAssertNotEqual(keyWithDifferentTag1.hashValue, keyWithDifferentTag2.hashValue)
|
||||
@@ -85,8 +86,9 @@ class DefinitionTests: XCTestCase {
|
||||
var blockCalled = false
|
||||
|
||||
//given
|
||||
let def = DefinitionOf<Service, () -> Service>(scope: .Prototype) { ServiceImp() as Service }.resolveDependencies { container, service in
|
||||
blockCalled = true
|
||||
let def = DefinitionOf<Service, () -> Service>(scope: .Prototype) { ServiceImp() as Service }
|
||||
.resolveDependencies { container, service in
|
||||
blockCalled = true
|
||||
}
|
||||
|
||||
//when
|
||||
@@ -100,8 +102,9 @@ class DefinitionTests: XCTestCase {
|
||||
var blockCalled = false
|
||||
|
||||
//given
|
||||
let def = DefinitionOf<Service, () -> Service>(scope: .Prototype) { ServiceImp() as Service }.resolveDependencies { container, service in
|
||||
blockCalled = true
|
||||
let def = DefinitionOf<Service, () -> Service>(scope: .Prototype) { ServiceImp() as Service }
|
||||
.resolveDependencies { container, service in
|
||||
blockCalled = true
|
||||
}
|
||||
|
||||
//when
|
||||
@@ -110,5 +113,13 @@ class DefinitionTests: XCTestCase {
|
||||
//then
|
||||
XCTAssertFalse(blockCalled)
|
||||
}
|
||||
|
||||
func testThatItRegisteresOptionalTypesAsForwardedTypes() {
|
||||
let def = DefinitionOf<Service, () -> Service>(scope: .Prototype) { ServiceImp() as Service }
|
||||
|
||||
XCTAssertTrue(def.implementingTypes.contains({ $0 == Service?.self }))
|
||||
XCTAssertTrue(def.implementingTypes.contains({ $0 == Service!.self }))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,600 @@
|
||||
//
|
||||
// Dip
|
||||
//
|
||||
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import Dip
|
||||
|
||||
private protocol Service: class { }
|
||||
private class ServiceImp1: Service { }
|
||||
private class ServiceImp2: Service { }
|
||||
|
||||
private protocol Server: class {
|
||||
weak var client: Client? { get }
|
||||
}
|
||||
private protocol Client: class {
|
||||
var server: Server? { get }
|
||||
}
|
||||
|
||||
class DipTests: XCTestCase {
|
||||
|
||||
let container = DependencyContainer()
|
||||
|
||||
#if os(Linux)
|
||||
static var allTests: [(String, DipTests -> () throws -> Void)] {
|
||||
return [
|
||||
("testThatItResolvesInstanceRegisteredWithoutTag", testThatItResolvesInstanceRegisteredWithoutTag),
|
||||
("testThatItResolvesInstanceRegisteredWithTag", testThatItResolvesInstanceRegisteredWithTag),
|
||||
("testThatItResolvesDifferentInstancesRegisteredForDifferentTags", testThatItResolvesDifferentInstancesRegisteredForDifferentTags),
|
||||
("testThatNewRegistrationOverridesPreviousRegistration", testThatNewRegistrationOverridesPreviousRegistration),
|
||||
("testThatItCallsResolveDependenciesOnDefinition", testThatItCallsResolveDependenciesOnDefinition),
|
||||
("testThatItThrowsErrorIfCanNotFindDefinitionForType", testThatItThrowsErrorIfCanNotFindDefinitionForType),
|
||||
("testThatItThrowsErrorIfCanNotFindDefinitionForTag", testThatItThrowsErrorIfCanNotFindDefinitionForTag),
|
||||
("testThatItThrowsErrorIfCanNotFindDefinitionForFactoryWithArguments", testThatItThrowsErrorIfCanNotFindDefinitionForFactoryWithArguments),
|
||||
("testThatItThrowsErrorIfConstructorThrows", testThatItThrowsErrorIfConstructorThrows),
|
||||
("testThatItThrowsErrorIfFailsToResolveDependency", testThatItThrowsErrorIfFailsToResolveDependency),
|
||||
("testThatItCallsDidResolveDependenciesOnResolvableIntance", testThatItCallsDidResolveDependenciesOnResolvableIntance),
|
||||
("testThatItCallsDidResolveDependenciesInReverseOrder", testThatItCallsDidResolveDependenciesInReverseOrder),
|
||||
("testThatItResolvesCircularDependencies", testThatItResolvesCircularDependencies)
|
||||
]
|
||||
}
|
||||
|
||||
func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#else
|
||||
override func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#endif
|
||||
|
||||
func testThatItResolvesInstanceRegisteredWithoutTag() {
|
||||
//given
|
||||
container.register { ServiceImp1() as Service }
|
||||
|
||||
//when
|
||||
let serviceInstance = try! container.resolve() as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(serviceInstance is ServiceImp1)
|
||||
|
||||
//and when
|
||||
let anyService = try! container.resolve(Service.self)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(anyService is ServiceImp1)
|
||||
|
||||
//and when
|
||||
let optService = try! container.resolve((Service?).self)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(optService is ServiceImp1)
|
||||
|
||||
//and when
|
||||
let impService = try! container.resolve((Service!).self)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(impService 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)
|
||||
|
||||
//and when
|
||||
let anyService = try! container.resolve(Service.self, tag: "service")
|
||||
|
||||
//then
|
||||
XCTAssertTrue(anyService is ServiceImp1)
|
||||
|
||||
//and when
|
||||
let optService = try! container.resolve((Service?).self, tag: "service")
|
||||
|
||||
//then
|
||||
XCTAssertTrue(optService is ServiceImp1)
|
||||
|
||||
//and when
|
||||
let impService = try! container.resolve((Service!).self, tag: "service")
|
||||
|
||||
//then
|
||||
XCTAssertTrue(impService 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)
|
||||
|
||||
//and when
|
||||
let anyService1 = try! container.resolve(Service.self, tag: "service1")
|
||||
let anyService2 = try! container.resolve(Service.self, tag: "service2")
|
||||
|
||||
//then
|
||||
XCTAssertTrue(anyService1 is ServiceImp1)
|
||||
XCTAssertTrue(anyService2 is ServiceImp2)
|
||||
|
||||
//and when
|
||||
let optService1 = try! container.resolve((Service?).self, tag: "service1")
|
||||
let optService2 = try! container.resolve((Service?).self, tag: "service2")
|
||||
|
||||
//then
|
||||
XCTAssertTrue(optService1 is ServiceImp1)
|
||||
XCTAssertTrue(optService2 is ServiceImp2)
|
||||
|
||||
//and when
|
||||
let impService1 = try! container.resolve((Service!).self, tag: "service1")
|
||||
let impService2 = try! container.resolve((Service!).self, tag: "service2")
|
||||
|
||||
//then
|
||||
XCTAssertTrue(impService1 is ServiceImp1)
|
||||
XCTAssertTrue(impService2 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)
|
||||
resolveDependenciesCalled = false
|
||||
|
||||
//and when
|
||||
try! container.resolve(Service.self)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(resolveDependenciesCalled)
|
||||
|
||||
resolveDependenciesCalled = false
|
||||
|
||||
//and when
|
||||
try! container.resolve((Service?).self)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(resolveDependenciesCalled)
|
||||
|
||||
resolveDependenciesCalled = false
|
||||
|
||||
//and when
|
||||
try! container.resolve((Service!).self)
|
||||
|
||||
//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
|
||||
let expectedKey = DefinitionKey(protocolType: Service.self, argumentsType: Void.self, associatedTag: nil)
|
||||
XCTAssertEqual(key, expectedKey)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
//and when
|
||||
AssertThrows(expression: try container.resolve(Service.self)) { error in
|
||||
guard case let DipError.DefinitionNotFound(key) = error else { return false }
|
||||
|
||||
//then
|
||||
let expectedKey = DefinitionKey(protocolType: Service.self, argumentsType: Void.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
|
||||
let expectedKey = DefinitionKey(protocolType: Service.self, argumentsType: Void.self, associatedTag: "other tag")
|
||||
XCTAssertEqual(key, expectedKey)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
//and when
|
||||
AssertThrows(expression: try container.resolve(Service.self, tag: "other tag")) { error in
|
||||
guard case let DipError.DefinitionNotFound(key) = error else { return false }
|
||||
|
||||
//then
|
||||
let expectedKey = DefinitionKey(protocolType: Service.self, argumentsType: Void.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
|
||||
let expectedKey = DefinitionKey(protocolType: Service.self, argumentsType: String.self, associatedTag: nil)
|
||||
XCTAssertEqual(key, expectedKey)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
//and when
|
||||
AssertThrows(expression: try container.resolve(Service.self, withArguments: "some string")) { error in
|
||||
guard case let DipError.DefinitionNotFound(key) = error else { return false }
|
||||
|
||||
//then
|
||||
let expectedKey = DefinitionKey(protocolType: Service.self, argumentsType: String.self, associatedTag: nil)
|
||||
XCTAssertEqual(key, expectedKey)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func testThatItThrowsErrorIfConstructorThrows() {
|
||||
//given
|
||||
let failedKey = DefinitionKey(protocolType: Any.self, argumentsType: Any.self)
|
||||
let expectedError = DipError.DefinitionNotFound(key: failedKey)
|
||||
container.register { () throws -> Service in throw expectedError }
|
||||
|
||||
//when
|
||||
AssertThrows(expression: try container.resolve() as Service) { error in
|
||||
switch error {
|
||||
case let DipError.DefinitionNotFound(key) where key == failedKey: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
//and when
|
||||
AssertThrows(expression: try container.resolve(Service.self)) { error in
|
||||
switch error {
|
||||
case let DipError.DefinitionNotFound(key) where key == failedKey: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testThatItThrowsErrorIfFailsToResolveDependency() {
|
||||
//given
|
||||
let failedKey = DefinitionKey(protocolType: Any.self, argumentsType: Any.self)
|
||||
let expectedError = DipError.DefinitionNotFound(key: failedKey)
|
||||
container.register { ServiceImp1() as Service }
|
||||
.resolveDependencies { container, service in
|
||||
//simulate throwing error when resolving dependency
|
||||
throw expectedError
|
||||
}
|
||||
|
||||
//when
|
||||
AssertThrows(expression: try container.resolve() as Service) { error in
|
||||
switch error {
|
||||
case let DipError.DefinitionNotFound(key) where key == failedKey: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
//and when
|
||||
AssertThrows(expression: try container.resolve(Service.self)) { error in
|
||||
switch error {
|
||||
case let DipError.DefinitionNotFound(key) where key == failedKey: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testThatItCallsDidResolveDependenciesOnResolvableIntance() {
|
||||
|
||||
class ResolvableService: Service, Resolvable {
|
||||
var didResolveDependenciesCalled = false
|
||||
|
||||
func didResolveDependencies() {
|
||||
XCTAssertFalse(didResolveDependenciesCalled, "didResolveDependencies should be called only once per instance")
|
||||
didResolveDependenciesCalled = true
|
||||
}
|
||||
}
|
||||
|
||||
//given
|
||||
container.register { ResolvableService() as Service }
|
||||
.resolveDependencies { _, service in
|
||||
XCTAssertFalse((service as! ResolvableService).didResolveDependenciesCalled, "didResolveDependencies should not be called yet")
|
||||
return
|
||||
}
|
||||
|
||||
container.register(tag: "graph", .ObjectGraph) { ResolvableService() as Service }
|
||||
.resolveDependencies { _, service in
|
||||
XCTAssertFalse((service as! ResolvableService).didResolveDependenciesCalled, "didResolveDependencies should not be called yet")
|
||||
return
|
||||
}
|
||||
|
||||
container.register(tag: "singleton", .Singleton) { ResolvableService() as Service }
|
||||
.resolveDependencies { _, service in
|
||||
XCTAssertFalse((service as! ResolvableService).didResolveDependenciesCalled, "didResolveDependencies should not be called yet")
|
||||
return
|
||||
}
|
||||
|
||||
//when
|
||||
let service = try! container.resolve() as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue((service as! ResolvableService).didResolveDependenciesCalled)
|
||||
|
||||
//and when
|
||||
let graphService = try! container.resolve(tag: "graph") as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue((graphService as! ResolvableService).didResolveDependenciesCalled)
|
||||
|
||||
//and when
|
||||
let singletonService = try! container.resolve(tag: "singleton") as Service
|
||||
let _ = try! container.resolve(tag: "singleton") as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue((singletonService as! ResolvableService).didResolveDependenciesCalled)
|
||||
}
|
||||
|
||||
func testThatItCallsDidResolveDependenciesInReverseOrder() {
|
||||
|
||||
class ResolvableService: Service, Resolvable {
|
||||
static var resolved: [Service] = []
|
||||
|
||||
func didResolveDependencies() {
|
||||
ResolvableService.resolved.append(self)
|
||||
}
|
||||
}
|
||||
|
||||
//given
|
||||
var resolveDependenciesCalled = false
|
||||
var service2: Service!
|
||||
container.register { ResolvableService() as Service }
|
||||
.resolveDependencies { _, service in
|
||||
if !resolveDependenciesCalled {
|
||||
resolveDependenciesCalled = true
|
||||
service2 = try self.container.resolve() as Service
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//when
|
||||
let service1 = try! container.resolve() as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(ResolvableService.resolved.first === service2)
|
||||
XCTAssertTrue(ResolvableService.resolved.last === service1)
|
||||
}
|
||||
|
||||
func testThatItResolvesCircularDependencies() {
|
||||
|
||||
class ResolvableServer: Server, Resolvable {
|
||||
weak var client: Client?
|
||||
weak var secondClient: Client?
|
||||
|
||||
init(client: Client) {
|
||||
self.client = client
|
||||
}
|
||||
|
||||
var didResolveDependenciesCalled = false
|
||||
|
||||
func didResolveDependencies() {
|
||||
XCTAssertFalse(didResolveDependenciesCalled, "didResolveDependencies should be called only once per instance")
|
||||
didResolveDependenciesCalled = true
|
||||
|
||||
XCTAssertNotNil(self.client)
|
||||
XCTAssertNotNil(self.secondClient)
|
||||
XCTAssertNotNil(self.client?.server)
|
||||
XCTAssertNotNil(self.secondClient)
|
||||
XCTAssertNotNil(self.secondClient?.server)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ResolvableClient: Client, Resolvable {
|
||||
var server: Server?
|
||||
var secondServer: Server?
|
||||
|
||||
init() {}
|
||||
|
||||
var didResolveDependenciesCalled = false
|
||||
|
||||
func didResolveDependencies() {
|
||||
XCTAssertFalse(didResolveDependenciesCalled, "didResolveDependencies should be called only once per instance")
|
||||
didResolveDependenciesCalled = true
|
||||
|
||||
XCTAssertNotNil(self.server)
|
||||
XCTAssertNotNil(self.secondServer)
|
||||
XCTAssertNotNil(self.server?.client)
|
||||
XCTAssertNotNil(self.secondServer?.client)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//given
|
||||
container.register(.ObjectGraph) { try ResolvableServer(client: self.container.resolve()) as Server }
|
||||
.resolveDependencies { (container: DependencyContainer, server: Server) in
|
||||
let server = server as! ResolvableServer
|
||||
server.secondClient = try container.resolve() as Client
|
||||
}
|
||||
|
||||
container.register(.ObjectGraph) { ResolvableClient() as Client }
|
||||
.resolveDependencies { (container: DependencyContainer, client: Client) in
|
||||
let client = client as! ResolvableClient
|
||||
client.server = try container.resolve() as Server
|
||||
client.secondServer = try container.resolve() as Server
|
||||
}
|
||||
|
||||
//when
|
||||
let client = (try! container.resolve() as Client) as! ResolvableClient
|
||||
let server = client.server as! ResolvableServer
|
||||
let secondServer = client.secondServer as! ResolvableServer
|
||||
let secondClient = server.secondClient as! ResolvableClient
|
||||
|
||||
//then
|
||||
XCTAssertTrue(client === server.client)
|
||||
XCTAssertTrue(client === server.secondClient)
|
||||
XCTAssertTrue(client === secondServer.client)
|
||||
XCTAssertTrue(client === secondServer.secondClient)
|
||||
XCTAssertTrue(client === secondClient)
|
||||
XCTAssertTrue(server === secondServer)
|
||||
|
||||
XCTAssertTrue(client.didResolveDependenciesCalled)
|
||||
XCTAssertTrue(server.didResolveDependenciesCalled)
|
||||
}
|
||||
|
||||
func testThatItValidatesConfiguration() {
|
||||
//given
|
||||
var createdService1 = false
|
||||
var createdService2 = false
|
||||
var createdService3 = false
|
||||
var createdService = false
|
||||
|
||||
let service = container.register { ServiceImp1() }
|
||||
.resolveDependencies { container, _ in
|
||||
if container.context.resolvingType == ServiceImp1.self {
|
||||
createdService1 = true
|
||||
}
|
||||
if container.context.resolvingType == Service.self {
|
||||
createdService = true
|
||||
}
|
||||
}
|
||||
container.register(service, type: Service.self)
|
||||
|
||||
container.register(tag: "tag") { ServiceImp2() as Service }
|
||||
.resolveDependencies { _ in
|
||||
createdService2 = true
|
||||
}
|
||||
|
||||
container.register() { (arg: String) in ServiceImp1() }
|
||||
.resolveDependencies { _ in
|
||||
createdService3 = true
|
||||
}
|
||||
|
||||
//then
|
||||
AssertNoThrow(expression: try container.validate("arg"))
|
||||
XCTAssertTrue(createdService1)
|
||||
XCTAssertTrue(createdService2)
|
||||
XCTAssertTrue(createdService3)
|
||||
XCTAssertTrue(createdService)
|
||||
}
|
||||
|
||||
func testThatItPicksRuntimeArgumentsWhenValidatingConfiguration() {
|
||||
//given
|
||||
let expectedIntArgument = 1
|
||||
let expectedStringArgument = "a"
|
||||
container.register { (a: Int) -> Service in
|
||||
XCTAssertEqual(a, expectedIntArgument)
|
||||
return ServiceImp1() as Service
|
||||
}
|
||||
|
||||
container.register { (a: Int, b: String) -> Service in
|
||||
XCTAssertEqual(a, expectedIntArgument)
|
||||
XCTAssertEqual(b, expectedStringArgument)
|
||||
return ServiceImp1() as Service
|
||||
}
|
||||
|
||||
//then
|
||||
AssertNoThrow(expression:
|
||||
try container.validate(
|
||||
"1",
|
||||
expectedIntArgument,
|
||||
"x",
|
||||
(expectedStringArgument, expectedIntArgument),
|
||||
(expectedIntArgument, expectedStringArgument)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
func testThatItFailsValidationIfNoMatchingArgumentsFound() {
|
||||
//given
|
||||
container.register { (a: Int) -> Service in ServiceImp1() as Service }
|
||||
|
||||
//then
|
||||
AssertThrows(expression: try container.validate()) { error in error is DipError }
|
||||
AssertThrows(expression: try container.validate("1")) { error in error is DipError }
|
||||
}
|
||||
|
||||
func testThatItFailsValidationOnlyForDipErrors() {
|
||||
//given
|
||||
container.register { () -> Service in
|
||||
throw NSError(domain: "", code: 0, userInfo: nil)
|
||||
}
|
||||
|
||||
//then
|
||||
AssertNoThrow(expression: try container.validate())
|
||||
|
||||
//given
|
||||
let key = DefinitionKey(protocolType: Service.self, argumentsType: Void.self, associatedTag: nil)
|
||||
container.register { () -> Service in
|
||||
throw DipError.DefinitionNotFound(key: key)
|
||||
}
|
||||
|
||||
//then
|
||||
AssertThrows(expression: try container.validate()) { error in
|
||||
if case let DipError.DefinitionNotFound(_key) = error where _key == key { return true }
|
||||
else { return false }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+37
-1
@@ -52,7 +52,7 @@ class RuntimeArgumentsTests: XCTestCase {
|
||||
let container = DependencyContainer()
|
||||
|
||||
#if os(Linux)
|
||||
var allTests: [(String, () throws -> Void)] {
|
||||
static var allTests: [(String, RuntimeArgumentsTests -> () throws -> Void)] {
|
||||
return [
|
||||
("testThatItResolvesInstanceWithOneArgument", testThatItResolvesInstanceWithOneArgument),
|
||||
("testThatItResolvesInstanceWithTwoArguments", testThatItResolvesInstanceWithTwoArguments),
|
||||
@@ -90,6 +90,12 @@ class RuntimeArgumentsTests: XCTestCase {
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service is ServiceImp1)
|
||||
|
||||
//when
|
||||
let anyService = try! container.resolve(Service.self, withArguments: arg1)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(anyService is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItResolvesInstanceWithTwoArguments() {
|
||||
@@ -106,6 +112,12 @@ class RuntimeArgumentsTests: XCTestCase {
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service is ServiceImp1)
|
||||
|
||||
//when
|
||||
let anyService = try! container.resolve(Service.self, withArguments: arg1, arg2)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(anyService is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItResolvesInstanceWithThreeArguments() {
|
||||
@@ -122,6 +134,12 @@ class RuntimeArgumentsTests: XCTestCase {
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service is ServiceImp1)
|
||||
|
||||
//when
|
||||
let anyService = try! container.resolve(Service.self, withArguments: arg1, arg2, arg3)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(anyService is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItResolvesInstanceWithFourArguments() {
|
||||
@@ -139,6 +157,12 @@ class RuntimeArgumentsTests: XCTestCase {
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service is ServiceImp1)
|
||||
|
||||
//when
|
||||
let anyService = try! container.resolve(Service.self, withArguments: arg1, arg2, arg3, arg4)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(anyService is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItResolvesInstanceWithFiveArguments() {
|
||||
@@ -157,6 +181,12 @@ class RuntimeArgumentsTests: XCTestCase {
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service is ServiceImp1)
|
||||
|
||||
//when
|
||||
let anyService = try! container.resolve(Service.self, withArguments: arg1, arg2, arg3, arg4, arg5)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(anyService is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItResolvesInstanceWithSixArguments() {
|
||||
@@ -176,6 +206,12 @@ class RuntimeArgumentsTests: XCTestCase {
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service is ServiceImp1)
|
||||
|
||||
//when
|
||||
let anyService = try! container.resolve(Service.self, withArguments: arg1, arg2, arg3, arg4, arg5, arg6)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(anyService is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItRegistersDifferentFactoriesForDifferentNumberOfArguments() {
|
||||
@@ -106,11 +106,11 @@ let resolveClientAsync = {
|
||||
class ThreadSafetyTests: XCTestCase {
|
||||
|
||||
#if os(Linux)
|
||||
init() {
|
||||
required init(name: String, testClosure: XCTestCase throws -> Void) {
|
||||
pthread_spin_init(&lock, 0)
|
||||
}
|
||||
|
||||
var allTests: [(String, () throws -> Void)] {
|
||||
static var allTests: [(String, ThreadSafetyTests -> () throws -> Void)] {
|
||||
return [
|
||||
("testSingletonThreadSafety", testSingletonThreadSafety),
|
||||
("testFactoryThreadSafety", testFactoryThreadSafety),
|
||||
@@ -24,40 +24,34 @@
|
||||
|
||||
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) {
|
||||
func AssertThrows<T>(file: StaticString = #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) {
|
||||
func AssertThrows<T>(file: StaticString = #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) {
|
||||
func AssertThrows<T>(file: StaticString = #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) {
|
||||
func AssertThrows<T>(file: StaticString = #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)")
|
||||
XCTAssertTrue(checkError(error), "Thrown unexpected error: \(error)", file: file, line: line)
|
||||
}
|
||||
}
|
||||
|
||||
func AssertNoThrow<T>(file: FileString = __FILE__, line: UInt = __LINE__, @autoclosure expression: () throws -> T) {
|
||||
func AssertNoThrow<T>(file: StaticString = #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) {
|
||||
func AssertNoThrow<T>(file: StaticString = #file, line: UInt = #line, @autoclosure expression: () throws -> T, _ message: String) {
|
||||
do {
|
||||
try expression()
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import XCTest
|
||||
@testable import DipTestSuite
|
||||
|
||||
XCTMain([
|
||||
testCase(DipTests.allTests),
|
||||
testCase(DefinitionTests.allTests),
|
||||
testCase(RuntimeArgumentsTests.allTests),
|
||||
testCase(ComponentScopeTests.allTests),
|
||||
testCase(AutoInjectionTests.allTests),
|
||||
testCase(ThreadSafetyTests.allTests),
|
||||
testCase(AutoWiringTests.allTests),
|
||||
testCase(ContextTests.allTests)
|
||||
])
|
||||
Reference in New Issue
Block a user