Compare commits

...

106 Commits

Author SHA1 Message Date
Ilya Puchka 967e1a43cb Merge pull request #111 from AliSoftware/release/4.6.1
Release 4.6.1
2016-08-14 22:31:48 +02:00
Ilya Puchka 73225ddd10 bumped version to 4.6.1, updated README and CHANGELOG 2016-08-14 21:41:45 +02:00
Ilya Puchka 2d238265ca Merge pull request #105 from AliSoftware/feature/grand-renaming
Grand renaming
2016-08-14 20:56:26 +02:00
Ilya Puchka cdfbf3dbfe Update README.md 2016-08-07 00:26:51 +02:00
Ilya Puchka 21a3b43d9b renamed resolveDependencies to resolingProperities 2016-07-28 23:58:24 +02:00
Ilya Puchka 8f0f49e790 renamed withArguments to arguments 2016-07-28 23:35:26 +02:00
Ilya Puchka 7c5966ab61 switched tag and scope parameters in register method 2016-07-28 23:35:26 +02:00
Ilya Puchka c41696cd23 some internal methods renaming 2016-07-28 23:35:26 +02:00
Ilya Puchka 6e7a168fb7 renamed properties of DefinitionKey 2016-07-28 23:35:26 +02:00
Ilya Puchka a2efe0abff renamed Prototype and ObjectGraph scopes 2016-07-28 23:35:26 +02:00
Ilya Puchka d876389c19 Update README.md 2016-07-27 23:05:43 +02:00
Ilya Puchka 5c54e07219 Merge pull request #104 from AliSoftware/feature/config-retain-cycle
Some notes about avoiding retain cycle when configuring container
2016-07-27 22:29:48 +02:00
Ilya Puchka 0e32a79d8c added some notes about avoiding retain cycle when configuring container 2016-07-27 22:02:52 +02:00
Ilya Puchka 37c8b3d03d Merge pull request #103 from AliSoftware/feature/shared-singletons
Sharing singletons between collaborating containers
2016-07-27 21:14:54 +02:00
Ilya Puchka 21ea965607 fixed releasing weak singletons when resetting definition 2016-07-27 20:41:37 +02:00
Ilya Puchka 80d8b1ba99 fixed sharing singletons between collaborators 2016-07-27 20:41:37 +02:00
Ilya Puchka 975041933e Update README.md 2016-07-27 02:18:36 +02:00
Ilya Puchka 404b65b633 Update README.md 2016-07-27 02:16:07 +02:00
Ilya Puchka 73a78ed5a0 Merge branch 'release/4.6.0' into develop 2016-07-17 21:59:32 +02:00
Ilya Puchka 2371338e9b Merge pull request #100 from AliSoftware/release/4.6.0
Release 4.6.0
2016-07-17 21:58:33 +02:00
Ilya Puchka 65f4b0a4ab bumped version to 4.6.0 2016-07-17 21:48:13 +02:00
Ilya Puchka 2c7444fef8 updated CHANGELOG and README 2016-07-17 21:47:32 +02:00
Ilya Puchka daad001e17 fixed building on linux 2016-07-17 15:38:07 +02:00
Ilya Puchka 11ed3c975a Merge pull request #96 from AliSoftware/feature/weak-singletons
WeakSingleton scope
2016-07-17 15:36:38 +02:00
Ilya Puchka 02e0877fcb weak singleton scope 2016-07-17 15:24:05 +02:00
Ilya Puchka 54844f8459 improved comment explaining need of manual unboxing of optionals 2016-07-17 15:23:21 +02:00
Ilya Puchka fe006c4f13 Merge pull request #99 from AliSoftware/feature/improved-logging
Improved logging
2016-07-17 15:22:54 +02:00
Ilya Puchka 8e0e165aa4 Merge pull request #98 from AliSoftware/feature/fix-updating-context
Fix updating context
2016-07-17 15:07:57 +02:00
Ilya Puchka 0319bcff97 improved logging 2016-07-17 15:00:36 +02:00
Ilya Puchka ffdfdb0826 fix build error 2016-07-17 14:53:33 +02:00
Ilya Puchka c0884deddd simplified resolving with collaborator 2016-07-17 14:11:56 +02:00
Ilya Puchka 23318bbb7b Merge pull request #97 from AliSoftware/feature/auto-injecting-properties-first
Auto-injecting properties before calling resolveDependencies block
2016-07-17 13:04:50 +02:00
Ilya Puchka 2800c84553 fixed updating context when auto-injecting and resolving with collaborator 2016-07-17 12:51:13 +02:00
Ilya Puchka 18087d67c9 auto-injecting properties before calling resolve dependencies block 2016-07-17 12:27:20 +02:00
Ilya Puchka ab235c23e9 accessing previously resolved instances with subscript 2016-07-17 12:26:54 +02:00
Ilya Puchka c82dfb3dbe Merge pull request #95 from AliSoftware/feature/collaborating-containers
Containers collaboration
2016-07-12 00:07:09 +02:00
Ilya Puchka 2865a07e5a removed underscore prefix from internal methods names 2016-07-11 01:54:13 +02:00
Ilya Puchka ac5999011d filter matching definitions by arguments type 2016-07-11 01:54:13 +02:00
Ilya Puchka cd92deeccd added playground page for containers collaboration 2016-07-11 01:54:13 +02:00
Ilya Puchka 4f1dbd1e48 added context property to disable logging 2016-07-11 01:54:13 +02:00
Ilya Puchka 51e95fe73c improved collaborating containers implementation 2016-07-11 01:54:13 +02:00
Ilya Puchka aaab404d45 containers collaboration 2016-07-04 15:13:10 +02:00
Ilya Puchka 55da6f3707 Merge pull request #94 from AliSoftware/feature/logging
Logging all errors
2016-07-04 10:45:49 +02:00
Ilya Puchka e24d4cb577 logging all errors 2016-07-04 10:30:54 +02:00
Ilya Puchka b733c74e34 Fixed wiki links 2016-07-04 02:19:58 +02:00
Ilya Puchka 5927ab9471 Added link to VIPER example using Dip in README 2016-07-02 20:38:45 +02:00
Ilya Puchka 86a7289d2c Merge pull request #92 from mwoollard/develop
Enable the 'allow app extension api only' option
2016-06-13 00:15:44 +04:00
Mark Woollard 2c85a01f0d Enable the 'allow app extension api only' option
Dip doesn't appear to use any API calls that are unavailable to app extensions / watch kit. Enabling this flag removes warnings in extension / watch kit projects that link against dip.
2016-06-10 08:07:50 +01:00
Ilya Puchka bb85e27db8 Merge branch 'release/4.5.0' into develop 2016-06-08 23:58:37 +03:00
Ilya Puchka eadd87a5bb Merge pull request #90 from AliSoftware/release/4.5.0
Release 4.5.0
2016-06-08 22:57:58 +02:00
Ilya Puchka 4c4b9ef57a bumped version to 4.5.0 2016-06-08 23:45:01 +03:00
Ilya Puchka ee99cf4fdc updated CHANGELOG and README 2016-06-08 23:44:50 +03:00
Ilya Puchka 40dfd6419f improved tests playground page 2016-06-08 23:44:25 +03:00
Ilya Puchka 88fa94ed30 Merge pull request #89 from AliSoftware/feature/type-forwarding
Type forwarding
2016-06-08 21:21:12 +02:00
Ilya Puchka 58d1813681 sources layout fixed 2016-06-05 21:39:46 +02:00
Ilya Puchka 7277b940ca type forwarding playground page 2016-06-05 21:39:46 +02:00
Ilya Puchka 581c241fd1 improved type-forwarding implementation 2016-06-05 17:23:45 +02:00
Ilya Puchka 3c9d2a6db8 type forwarding 2016-05-22 17:17:57 +02:00
Ilya Puchka 10309c1d5d incremented swift snapshot version 2016-05-22 17:17:36 +02:00
Ilya Puchka 2a27152fa1 skip loading SPM 2016-05-22 17:16:57 +02:00
Ilya Puchka e04a741479 fixed travis script 2016-05-22 16:23:15 +02:00
Ilya Puchka dbaba50c88 fixed travis script indentation 2016-05-22 16:18:03 +02:00
Ilya Puchka 032db2d2b1 fixed travis scrip 2016-05-22 16:16:56 +02:00
Ilya Puchka b2456e0430 fixed cloning spm in travis script 2016-05-22 16:08:31 +02:00
Ilya Puchka fd17075a8a some minor docs changes 2016-05-22 15:55:31 +02:00
Ilya Puchka 2d9be9acc0 Merge pull request #87 from AliSoftware/feature/validate-container
Validating container
2016-05-13 16:16:30 +02:00
Ilya Puchka efd279b26b validate method 2016-05-12 22:28:29 +02:00
Ilya Puchka b5cb796f5f Merge pull request #88 from AliSoftware/linux-test-update
Linux test update
2016-05-06 00:20:41 +02:00
Ilya Puchka e1fd904842 added linux step on travis 2016-05-05 21:26:39 +02:00
Ilya Puchka c65ae6cedd adopted xctest api changes 2016-05-05 17:27:15 +02:00
Ilya Puchka 21aedcb655 minor fix 2016-05-05 16:43:12 +02:00
Ilya Puchka 37d0e4750f Merge pull request #86 from phatblat/phatblat-patch-1
Fix typo on readme
2016-05-01 18:51:25 +02:00
Ben Chatelain 95157a99f6 Fix typo on readme 2016-05-01 09:47:20 -06:00
Ilya Puchka 1ad3aa2c18 Merge pull request #85 from AliSoftware/feature/auto-wiring-error
Auto wiring error
2016-05-01 15:10:27 +02:00
Ilya Puchka 1c916cbafc Added AutoWiringFailed error 2016-05-01 14:31:27 +02:00
Ilya Puchka 86b6afe5d9 Merge pull request #84 from AliSoftware/feature/resolving-optionals
Resolving optionals
2016-05-01 14:22:56 +02:00
Ilya Puchka a1e7a1f958 resolving optionals 2016-05-01 12:39:02 +02:00
Ilya Puchka c414882c6a Merge pull request #83 from AliSoftware/feature/container-context
Container context
2016-04-29 10:22:00 +02:00
Ilya Puchka f8cd1c4dc3 container context implementation 2016-04-27 14:59:23 +02:00
Ilya Puchka 3e68acd37b settable auto-injected property without calling didInject observer 2016-04-25 22:51:34 +02:00
Ilya Puchka 9b047e4145 Merge pull request #81 from AliSoftware/feature/settable-auto-injected-properties
Settable auto-injected properties
2016-04-25 22:43:28 +02:00
Ilya Puchka f31236688d settable auto-injected properties 2016-04-25 14:10:25 +02:00
Ilya Puchka af61fea49e Merge pull request #79 from AliSoftware/feature/weakly-typed-resolve
Weakly typed resolve
2016-04-21 22:42:14 +02:00
Ilya Puchka 8c9172aac9 Merge pull request #74 from AliSoftware/docs
Cleaned up README
2016-04-21 22:41:59 +02:00
Ilya Puchka 5495bd2b97 removed description that encouraged service locator anti-pattern from podspec 2016-04-21 22:20:43 +02:00
Ilya Puchka b3e6c3c61a Cleaned up README 2016-04-21 22:19:54 +02:00
Ilya Puchka be86b30da3 weakly typed resolve 2016-04-20 01:54:25 +02:00
Ilya Puchka 26ba11dcbf Merge branch 'release/4.4.0' into develop 2016-03-31 22:57:29 +02:00
Ilya Puchka 1356a8056f Merge pull request #70 from ilyapuchka/release/4.4.0
Release 4.4.0
2016-03-31 22:55:38 +02:00
Ilya Puchka 0ddb37bea3 Bumped version to 4.4.0 2016-03-31 22:39:00 +02:00
Ilya Puchka 537cad5923 updated docs 2016-03-31 22:37:53 +02:00
Ilya Puchka 0c4ce2213b bootstrap method can throw 2016-03-31 22:19:29 +02:00
Ilya Puchka a91dacb29c Merge pull request #65 from ilyapuchka/feature/eager-singleton-scope
EagerSingleton scope and bootstrap method
2016-03-31 10:03:59 +02:00
Ilya Puchka 895a6f2583 Merge pull request #67 from ilyapuchka/feature/resolvable-calls-order
Reversed order of Resolvable callback calls
2016-03-31 10:03:52 +02:00
Ilya Puchka 73f71a99b2 Reversed order of Resolvable callback calls 2016-03-30 21:56:45 +02:00
Ilya Puchka 41664914f4 EagerSingleton scope and bootstrap method 2016-03-25 10:24:49 +01:00
Ilya Puchka 53bc97ba63 Update README.md
Added some fancy badges
2016-03-24 14:34:38 +01:00
Ilya Puchka c321189b66 Merge branch 'release/4.3.1' into develop 2016-03-24 12:24:05 +01:00
Ilya Puchka 317d67ca90 Merge pull request #64 from AliSoftware/release/4.3.1
Release 4.3.1
2016-03-24 12:23:17 +01:00
Ilya Puchka cbcef835f7 Bumped version to 4.3.1 2016-03-24 12:10:21 +01:00
Ilya Puchka 47a1870de1 Fixed sample app warnings 2016-03-24 12:02:50 +01:00
Ilya Puchka da2197c909 updated travis script to use Xcode 7.3 2016-03-24 11:48:41 +01:00
Ilya Puchka 3ee8b04118 Merge pull request #62 from mwoollard/develop
Fix build warnings / issues from Swift 2.2
2016-03-24 10:50:45 +01:00
Mark Woollard 29c1a3805f Fix build warnings / issues from Swift 2.2 2016-03-22 13:32:37 +00:00
Ilya Puchka 4dc11a6e2d Merge branch 'hotfix/pods' into develop 2016-03-19 17:53:09 +01:00
Ilya Puchka 4c6718efb3 Merge branch 'release/4.3.0' into develop 2016-03-19 17:06:41 +01:00
52 changed files with 3977 additions and 1805 deletions
+39 -17
View File
@@ -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
+54
View File
@@ -1,5 +1,59 @@
# CHANGELOG
## 4.6.1
#### Fixed
* Fixed sharing singletons between collaborating containers
[#103](https://github.com/AliSoftware/Dip/issues/103), [@ilyapuchka](https://github.com/ilyapuchka)
* Renamed some public API's (see release notes for more info)
[#105](https://github.com/AliSoftware/Dip/issues/105), [@ilyapuchka](https://github.com/ilyapuchka)
## 4.6.0
* Containers collaboration. Break your definitions in modules and link them together.
[#95](https://github.com/AliSoftware/Dip/pull/95), [@ilyapuchka](https://github.com/ilyapuchka)
* Added WeakSingleton scope.
[#96](https://github.com/AliSoftware/Dip/pull/96), [@ilyapuchka](https://github.com/ilyapuchka)
* Properties Auto-injection now is performed before calling `resolveDependencies` block
[#97](https://github.com/AliSoftware/Dip/pull/97), [@ilyapuchka](https://github.com/ilyapuchka)
* Fixed updating container's context when resolving properties with auto-injection.
[#98](https://github.com/AliSoftware/Dip/pull/98), [@ilyapuchka](https://github.com/ilyapuchka)
* Improved logging.
[#94](https://github.com/AliSoftware/Dip/pull/94), [#99](https://github.com/AliSoftware/Dip/pull/99), [@ilyapuchka](https://github.com/ilyapuchka)
* Fixed warning about using only extensions api.
[#92](https://github.com/AliSoftware/Dip/pull/92), [@mwoollard](https://github.com/mwoollard)
## 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.
+6 -14
View File
@@ -1,21 +1,13 @@
Pod::Spec.new do |s|
s.name = "Dip"
s.version = "4.3.0"
s.summary = "A simple Dependency Resolver: Dependency Injection using Protocol resolution."
s.version = "4.6.1"
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'
+42 -10
View File
@@ -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 */; };
@@ -62,6 +69,9 @@
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 */; };
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 */
@@ -101,18 +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>"; };
09B035FB1C5D2AD6001EA5B7 /* AutoWiringTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AutoWiringTests.swift; path = Sources/AutoWiringTests.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 = Sources/ThreadSafetyTests.swift; sourceTree = "<group>"; };
09D598321C6F9EC100F24D49 /* Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Utils.swift; path = Sources/Utils.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 */
@@ -180,6 +193,7 @@
0919F4CC1C16417000DC3B10 /* RuntimeArguments.swift */,
09873F551C1E0237000C02F6 /* AutoInjection.swift */,
09B035FF1C5D2B83001EA5B7 /* AutoWiring.swift */,
095F829B1D043B41008CD706 /* TypeForwarding.swift */,
0982AF0B1C5183A000B62463 /* Utils.swift */,
0919F4CB1C16417000DC3B10 /* Info.plist */,
);
@@ -190,12 +204,14 @@
isa = PBXGroup;
children = (
0919F4D01C16417000DC3B10 /* DipTests.swift */,
64EC9B501CD0CB4000FF6A4C /* ContextTests.swift */,
0919F4CF1C16417000DC3B10 /* DefinitionTests.swift */,
0919F4D21C16417000DC3B10 /* RuntimeArgumentsTests.swift */,
0919F4CE1C16417000DC3B10 /* ComponentScopeTests.swift */,
09873F751C1E0249000C02F6 /* AutoInjectionTests.swift */,
09C20EBF1C8B3BC3009A082B /* ThreadSafetyTests.swift */,
09B035FB1C5D2AD6001EA5B7 /* AutoWiringTests.swift */,
095A51CE1CEA1664006B957C /* TypeForwardingTests.swift */,
09D598321C6F9EC100F24D49 /* Utils.swift */,
0919F4D11C16417000DC3B10 /* Info.plist */,
);
@@ -508,6 +524,7 @@
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 */,
);
@@ -525,6 +542,8 @@
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;
};
@@ -536,6 +555,7 @@
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 */,
);
@@ -553,6 +573,8 @@
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;
};
@@ -564,6 +586,7 @@
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 */,
);
@@ -581,6 +604,8 @@
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;
};
@@ -592,6 +617,7 @@
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 */,
);
@@ -621,6 +647,7 @@
0903B3691C161544002241C1 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
"CODE_SIGN_IDENTITY[sdk=*]" = "";
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
@@ -646,6 +673,7 @@
0903B36A1C161544002241C1 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
"CODE_SIGN_IDENTITY[sdk=*]" = "";
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
@@ -694,6 +722,7 @@
0903B38A1C1615EC002241C1 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -711,6 +740,7 @@
0903B38B1C1615EC002241C1 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -750,6 +780,7 @@
0903B3B81C1618AF002241C1 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -769,6 +800,7 @@
0903B3B91C1618AF002241C1 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -866,7 +898,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 4.3.0;
CURRENT_PROJECT_VERSION = 4.6.1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
@@ -915,7 +947,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 4.3.0;
CURRENT_PROJECT_VERSION = 4.6.1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
-9
View File
@@ -1,9 +0,0 @@
import PackageDescription
let package = Package(
name: "DipTests",
dependencies: [
.Package(url: "../../../Dip", majorVersion: 4, minor: 2),
]
)
-299
View File
@@ -1,299 +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 { }
private protocol AutoWiredClient: class {
var service1: Service! { get set }
var service2: Service! { get set }
}
private class AutoWiredClientImp: AutoWiredClient {
var service1: Service!
var service2: Service!
init(service1: Service, service2: ServiceImp2) {
self.service1 = service1
self.service2 = service2
}
init() {}
}
class AutoWiringTests: XCTestCase {
let container = DependencyContainer()
#if os(Linux)
var allTests: [(String, () throws -> Void)] {
return [
("testThatItCanResolveWithAutoWiring", testThatItCanResolveWithAutoWiring),
("testThatItUsesAutoWireFactoryWithMostNumberOfArguments", testThatItUsesAutoWireFactoryWithMostNumberOfArguments),
("testThatItThrowsAmbiguityErrorWhenUsingAutoWire", testThatItThrowsAmbiguityErrorWhenUsingAutoWire),
("testThatItFirstTriesToUseTaggedFactoriesWhenUsingAutoWire", testThatItFirstTriesToUseTaggedFactoriesWhenUsingAutoWire),
("testThatItFallbackToNotTaggedFactoryWhenUsingAutoWire", testThatItFallbackToNotTaggedFactoryWhenUsingAutoWire),
("testThatItDoesNotTryToUseAutoWiringWhenCallingResolveWithArguments", testThatItDoesNotTryToUseAutoWiringWhenCallingResolveWithArguments),
("testThatItDoesNotUseAutoWiringWhenFailedToResolveLowLevelDependency", testThatItDoesNotUseAutoWiringWhenFailedToResolveLowLevelDependency),
("testThatItReusesInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgain", testThatItReusesInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgain),
("testThatItReusesInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgainWithTheSameTagged", testThatItReusesInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgainWithTheSameTagged),
("testThatItDoesNotReuseInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgainWithNoTag", testThatItDoesNotReuseInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgainWithNoTag)
]
}
func setUp() {
container.reset()
}
#else
override func setUp() {
container.reset()
}
#endif
func testThatItCanResolveWithAutoWiring() {
//given
container.register(.ObjectGraph) { ServiceImp1() as Service }
container.register(.ObjectGraph) { ServiceImp2() }
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
//when
let client = try! container.resolve() as AutoWiredClient
//then
let service1 = client.service1
XCTAssertTrue(service1 is ServiceImp1)
let service2 = client.service2
XCTAssertTrue(service2 is ServiceImp2)
}
func testThatItUsesAutoWireFactoryWithMostNumberOfArguments() {
//given
//1 arg
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
//1 arg
container.register(.ObjectGraph) { AutoWiredClientImp(service1: try self.container.resolve(), service2: $0) as AutoWiredClient }
//2 args
var factoryWithMostNumberOfArgumentsCalled = false
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
.resolveDependencies { _ in
factoryWithMostNumberOfArgumentsCalled = true
}
container.register(.ObjectGraph) { ServiceImp1() as Service }
container.register(.ObjectGraph) { ServiceImp2() }
//when
let _ = try! container.resolve() as AutoWiredClient
//then
XCTAssertTrue(factoryWithMostNumberOfArgumentsCalled)
}
func testThatItThrowsAmbiguityErrorWhenUsingAutoWire() {
//given
//1 arg
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
//1 arg
container.register(.ObjectGraph) { AutoWiredClientImp(service1: try self.container.resolve(), service2: $0) as AutoWiredClient }
container.register(.ObjectGraph) { ServiceImp1() as Service }
container.register(.ObjectGraph) { ServiceImp2() }
//when
AssertThrows(expression: try container.resolve() as AutoWiredClient) { error -> Bool in
switch error {
case DipError.AmbiguousDefinitions: return true
default: return false
}
}
}
func testThatItFirstTriesToUseTaggedFactoriesWhenUsingAutoWire() {
//given
//1 arg
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
//1 arg
container.register(.ObjectGraph) { AutoWiredClientImp(service1: try self.container.resolve(), service2: $0) as AutoWiredClient }
//2 args
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
//1 arg tagged
var taggedFactoryWithMostNumberOfArgumentsCalled = false
container.register(tag: "tag", .ObjectGraph) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
//2 arg tagged
container.register(tag: "tag", .ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }.resolveDependencies { _ in
taggedFactoryWithMostNumberOfArgumentsCalled = true
}
container.register(.ObjectGraph) { ServiceImp1() as Service }
container.register(.ObjectGraph) { ServiceImp2() }
//when
let _ = try! container.resolve(tag: "tag") as AutoWiredClient
//then
XCTAssertTrue(taggedFactoryWithMostNumberOfArgumentsCalled)
}
func testThatItFallbackToNotTaggedFactoryWhenUsingAutoWire() {
//given
//1 arg
var notTaggedFactoryWithMostNumberOfArgumentsCalled = false
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }.resolveDependencies {_ in
notTaggedFactoryWithMostNumberOfArgumentsCalled = true
}
//1 arg tagged
container.register(tag: "tag", .ObjectGraph) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
container.register(.ObjectGraph) { ServiceImp1() as Service }
container.register(.ObjectGraph) { ServiceImp2() }
//when
let _ = try! container.resolve(tag: "other tag") as AutoWiredClient
//then
XCTAssertTrue(notTaggedFactoryWithMostNumberOfArgumentsCalled)
}
func testThatItDoesNotTryToUseAutoWiringWhenCallingResolveWithArguments() {
//given
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
container.register(.ObjectGraph) { ServiceImp1() as Service }
container.register(.ObjectGraph) { ServiceImp2() }
//when
let service = try! container.resolve() as Service
AssertThrows(expression: try container.resolve(withArguments: service) as AutoWiredClient,
"Container should not use auto-wiring when resolving with runtime arguments")
}
func testThatItDoesNotUseAutoWiringWhenFailedToResolveLowLevelDependency() {
//given
container.register(.ObjectGraph) { AutoWiredClientImp() as AutoWiredClient }
.resolveDependencies { container, resolved in
resolved.service1 = try container.resolve() as Service
resolved.service2 = try container.resolve() as ServiceImp2
//simulate that something goes wrong on the way
throw DipError.DefinitionNotFound(key: DefinitionKey(protocolType: ServiceImp1.self, factoryType: Any.self))
}
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
.resolveDependencies { container, resolved in
//auto-wiring should be performed only when definition for type to resolve is not found
//but not for any other type along the way in the graph
XCTFail("Auto-wiring should not be performed if instance was actually resolved.")
}
container.register(.ObjectGraph) { ServiceImp1() as Service }
container.register(.ObjectGraph) { ServiceImp2() }
//then
AssertThrows(expression: try container.resolve() as AutoWiredClient,
"Container should not use auto-wiring when definition for resolved type is registered.")
}
func testThatItReusesInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgain() {
//given
container.register(.ObjectGraph) { ServiceImp1() as Service }
container.register(.ObjectGraph) { ServiceImp2() }
var anotherInstance: AutoWiredClient?
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
.resolveDependencies { container, _ in
if anotherInstance == nil {
anotherInstance = try! container.resolve() as AutoWiredClient
}
}
//when
let resolved = try! container.resolve() as AutoWiredClient
//then
//when doing another auto-wiring during resolve we should reuse instance
XCTAssertTrue((resolved as! AutoWiredClientImp) === (anotherInstance as! AutoWiredClientImp))
}
func testThatItReusesInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgainWithTheSameTagged() {
//given
container.register(.ObjectGraph) { ServiceImp1() as Service }
container.register(.ObjectGraph) { ServiceImp2() }
var anotherInstance: AutoWiredClient?
container.register(tag: "tag", .ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
.resolveDependencies { container, _ in
if anotherInstance == nil {
anotherInstance = try! container.resolve(tag: "tag") as AutoWiredClient
}
}
//when
let resolved = try! container.resolve(tag: "tag") as AutoWiredClient
//then
//when doing another auto-wiring during resolve we should reuse instance
XCTAssertTrue((resolved as! AutoWiredClientImp) === (anotherInstance as! AutoWiredClientImp))
}
func testThatItDoesNotReuseInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgainWithNoTag() {
//given
container.register(.ObjectGraph) { ServiceImp1() as Service }
container.register(.ObjectGraph) { ServiceImp2() }
var anotherInstance: AutoWiredClient?
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
.resolveDependencies { container, _ in
if anotherInstance == nil {
anotherInstance = try! container.resolve() as AutoWiredClient
}
}
//when
let resolved = try! container.resolve(tag: "tag") as AutoWiredClient
//then
//when doing another auto-wiring during resolve we should reuse instance
XCTAssertTrue((resolved as! AutoWiredClientImp) !== (anotherInstance as! AutoWiredClientImp))
}
}
@@ -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 {}
private class Server {
weak var client: Client?
init() {}
}
private class Client {
var server: Server
init(server: Server) {
self.server = server
}
}
class ComponentScopeTests: XCTestCase {
let container = DependencyContainer()
#if os(Linux)
var allTests: [(String, () throws -> Void)] {
return [
("testThatPrototypeIsDefaultScope", testThatPrototypeIsDefaultScope),
("testThatScopeCanBeChanged", testThatScopeCanBeChanged),
("testThatItResolvesTypeAsNewInstanceForPrototypeScope", testThatItResolvesTypeAsNewInstanceForPrototypeScope),
("testThatItReusesInstanceForSingletonScope", testThatItReusesInstanceForSingletonScope),
("testThatSingletonIsNotReusedAcrossContainers", testThatSingletonIsNotReusedAcrossContainers),
("testThatSingletonIsReleasedWhenDefinitionIsRemoved", testThatSingletonIsReleasedWhenDefinitionIsRemoved),
("testThatSingletonIsReleasedWhenDefinitionIsOverridden", testThatSingletonIsReleasedWhenDefinitionIsOverridden),
("testThatSingletonIsReleasedWhenContainerIsReset", testThatSingletonIsReleasedWhenContainerIsReset),
("testThatItReusesInstanceInObjectGraphScopeDuringResolve", testThatItReusesInstanceInObjectGraphScopeDuringResolve),
("testThatItDoesNotReuseInstanceInObjectGraphScopeInNextResolve", testThatItDoesNotReuseInstanceInObjectGraphScopeInNextResolve),
("testThatItDoesNotReuseInstanceInObjectGraphScopeResolvedForNilTag", testThatItDoesNotReuseInstanceInObjectGraphScopeResolvedForNilTag)
]
}
func setUp() {
container.reset()
}
#else
override func setUp() {
container.reset()
}
#endif
func testThatPrototypeIsDefaultScope() {
let def = container.register { ServiceImp1() as Service }
XCTAssertEqual(def.scope, ComponentScope.Prototype)
}
func testThatScopeCanBeChanged() {
let def = container.register(.Singleton) { ServiceImp1() as Service }
XCTAssertEqual(def.scope, ComponentScope.Singleton)
}
func testThatItResolvesTypeAsNewInstanceForPrototypeScope() {
//given
container.register { ServiceImp1() as Service }
//when
let service1 = try! container.resolve() as Service
let service2 = try! container.resolve() as Service
//then
XCTAssertFalse(service1 === service2)
}
func testThatItReusesInstanceForSingletonScope() {
//given
container.register(.Singleton) { ServiceImp1() as Service }
//when
let service1 = try! container.resolve() as Service
let service2 = try! container.resolve() as Service
//then
XCTAssertTrue(service1 === service2)
}
func testThatSingletonIsNotReusedAcrossContainers() {
//given
let def = container.register(.Singleton) { ServiceImp1() as Service }
let secondContainer = DependencyContainer()
secondContainer.register(def, forTag: nil)
//when
let service1 = try! container.resolve() as Service
let service2 = try! secondContainer.resolve() as Service
//then
XCTAssertTrue(service1 !== service2, "Singleton instances should not be reused across containers")
}
func testThatSingletonIsReleasedWhenDefinitionIsRemoved() {
//given
let def = container.register(.Singleton) { ServiceImp1() as Service }
let service1 = try! container.resolve() as Service
//when
container.remove(def, forTag: nil)
container.register(def, forTag: nil)
//then
let service2 = try! container.resolve() as Service
XCTAssertTrue(service1 !== service2, "Singleton instances should be released when definition is removed from the container")
}
func testThatSingletonIsReleasedWhenDefinitionIsOverridden() {
//given
let def = container.register(.Singleton) { ServiceImp1() as Service }
let service1 = try! container.resolve() as Service
//when
container.register(def, forTag: nil)
//then
let service2 = try! container.resolve() as Service
XCTAssertTrue(service1 !== service2, "Singleton instances should be released when definition is overridden")
}
func testThatSingletonIsReleasedWhenContainerIsReset() {
//given
let def = container.register(.Singleton) { ServiceImp1() as Service }
let service1 = try! container.resolve() as Service
//when
container.reset()
container.register(def, forTag: nil)
//then
let service2 = try! container.resolve() as Service
XCTAssertTrue(service1 !== service2, "Singleton instances should be released when container is reset")
}
func testThatItReusesInstanceInObjectGraphScopeDuringResolve() {
//given
container.register(.ObjectGraph) { Client(server: try self.container.resolve()) as Client }
container.register(.ObjectGraph) { Server() as Server }.resolveDependencies { container, server in
server.client = try container.resolve() as Client
}
//when
let client = try! container.resolve() as Client
//then
let server = client.server
XCTAssertTrue(server.client === client)
}
func testThatItDoesNotReuseInstanceInObjectGraphScopeInNextResolve() {
//given
container.register(.ObjectGraph) { Client(server: try self.container.resolve()) as Client }
container.register(.ObjectGraph) { Server() as Server }.resolveDependencies { container, server in
server.client = try container.resolve() as Client
}
//when
let client = try! container.resolve() as Client
let server = client.server
let anotherClient = try! container.resolve() as Client
let anotherServer = anotherClient.server
//then
XCTAssertFalse(server === anotherServer)
XCTAssertFalse(client === anotherClient)
}
func testThatItDoesNotReuseInstanceInObjectGraphScopeResolvedForNilTag() {
//given
var service2: Service?
container.register(.ObjectGraph) { ServiceImp1() as Service }.resolveDependencies { (c, _) in
service2 = try c.resolve(tag: "service") as Service
//then
//when service1 is resolved using this definition due to fallback to nil tag
//we don't want every next resolve of service reuse it
XCTAssertTrue(service2 is ServiceImp2)
}
container.register(tag: "service", .ObjectGraph) { ServiceImp2() as Service}
//when
let service1 = try! container.resolve(tag: "tag") as Service
//then
XCTAssertTrue(service1 is ServiceImp1)
}
}
-333
View File
@@ -1,333 +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 { }
private protocol Server: class {
weak var client: Client? { get }
}
private protocol Client: class {
var server: Server? { get }
}
class DipTests: XCTestCase {
let container = DependencyContainer()
#if os(Linux)
var allTests: [(String, () throws -> Void)] {
return [
("testThatItResolvesInstanceRegisteredWithoutTag", testThatItResolvesInstanceRegisteredWithoutTag),
("testThatItResolvesInstanceRegisteredWithTag", testThatItResolvesInstanceRegisteredWithTag),
("testThatItResolvesDifferentInstancesRegisteredForDifferentTags", testThatItResolvesDifferentInstancesRegisteredForDifferentTags),
("testThatNewRegistrationOverridesPreviousRegistration", testThatNewRegistrationOverridesPreviousRegistration),
("testThatItCallsResolveDependenciesOnDefinition", testThatItCallsResolveDependenciesOnDefinition),
("testThatItThrowsErrorIfCanNotFindDefinitionForType", testThatItThrowsErrorIfCanNotFindDefinitionForType),
("testThatItThrowsErrorIfCanNotFindDefinitionForTag", testThatItThrowsErrorIfCanNotFindDefinitionForTag),
("testThatItThrowsErrorIfCanNotFindDefinitionForFactoryWithArguments", testThatItThrowsErrorIfCanNotFindDefinitionForFactoryWithArguments),
("testThatItThrowsErrorIfConstructorThrows", testThatItThrowsErrorIfConstructorThrows),
("testThatItThrowsErrorIfFailsToResolveDependency", testThatItThrowsErrorIfFailsToResolveDependency),
("testThatItCallsDidResolveDependenciesOnResolvableIntance", testThatItCallsDidResolveDependenciesOnResolvableIntance),
("testThatItResolvesCircularDependencies", testThatItResolvesCircularDependencies)
]
}
func setUp() {
container.reset()
}
#else
override func setUp() {
container.reset()
}
#endif
func testThatItResolvesInstanceRegisteredWithoutTag() {
//given
container.register { ServiceImp1() as Service }
//when
let serviceInstance = try! container.resolve() as Service
//then
XCTAssertTrue(serviceInstance is ServiceImp1)
}
func testThatItResolvesInstanceRegisteredWithTag() {
//given
container.register(tag: "service") { ServiceImp1() as Service }
//when
let serviceInstance = try! container.resolve(tag: "service") as Service
//then
XCTAssertTrue(serviceInstance is ServiceImp1)
}
func testThatItResolvesDifferentInstancesRegisteredForDifferentTags() {
//given
container.register(tag: "service1") { ServiceImp1() as Service }
container.register(tag: "service2") { ServiceImp2() as Service }
//when
let service1Instance = try! container.resolve(tag: "service1") as Service
let service2Instance = try! container.resolve(tag: "service2") as Service
//then
XCTAssertTrue(service1Instance is ServiceImp1)
XCTAssertTrue(service2Instance is ServiceImp2)
}
func testThatNewRegistrationOverridesPreviousRegistration() {
//given
container.register { ServiceImp1() as Service }
let service1 = try! container.resolve() as Service
//when
container.register { ServiceImp2() as Service }
let service2 = try! container.resolve() as Service
//then
XCTAssertTrue(service1 is ServiceImp1)
XCTAssertTrue(service2 is ServiceImp2)
}
func testThatItCallsResolveDependenciesOnDefinition() {
//given
var resolveDependenciesCalled = false
container.register { ServiceImp1() as Service }.resolveDependencies { (c, s) in
resolveDependenciesCalled = true
}
//when
try! container.resolve() as Service
//then
XCTAssertTrue(resolveDependenciesCalled)
}
func testThatItThrowsErrorIfCanNotFindDefinitionForType() {
//given
container.register { ServiceImp1() as ServiceImp1 }
//when
AssertThrows(expression: try container.resolve() as Service) { error in
guard case let DipError.DefinitionNotFound(key) = error else { return false }
//then
typealias F = () throws -> Service
let expectedKey = DefinitionKey(protocolType: Service.self, factoryType: F.self, associatedTag: nil)
XCTAssertEqual(key, expectedKey)
return true
}
}
func testThatItThrowsErrorIfCanNotFindDefinitionForTag() {
//given
container.register(tag: "some tag") { ServiceImp1() as Service }
//when
AssertThrows(expression: try container.resolve(tag: "other tag") as Service) { error in
guard case let DipError.DefinitionNotFound(key) = error else { return false }
//then
typealias F = () throws -> Service
let expectedKey = DefinitionKey(protocolType: Service.self, factoryType: F.self, associatedTag: "other tag")
XCTAssertEqual(key, expectedKey)
return true
}
}
func testThatItThrowsErrorIfCanNotFindDefinitionForFactoryWithArguments() {
//given
container.register { ServiceImp1() as Service }
//when
AssertThrows(expression: try container.resolve(withArguments: "some string") as Service) { error in
guard case let DipError.DefinitionNotFound(key) = error else { return false }
//then
typealias F = (String) throws -> Service
let expectedKey = DefinitionKey(protocolType: Service.self, factoryType: F.self, associatedTag: nil)
XCTAssertEqual(key, expectedKey)
return true
}
}
func testThatItThrowsErrorIfConstructorThrows() {
//given
let failedKey = DefinitionKey(protocolType: Any.self, factoryType: Any.self)
let expectedError = DipError.DefinitionNotFound(key: failedKey)
container.register { () throws -> Service in throw expectedError }
//when
AssertThrows(expression: try container.resolve() as Service) { error in
switch error {
case let DipError.DefinitionNotFound(key) where key == failedKey: return true
default: return false
}
}
}
func testThatItThrowsErrorIfFailsToResolveDependency() {
//given
let failedKey = DefinitionKey(protocolType: Any.self, factoryType: Any.self)
let expectedError = DipError.DefinitionNotFound(key: failedKey)
container.register { ServiceImp1() as Service }
.resolveDependencies { container, service in
//simulate throwing error when resolving dependency
throw expectedError
}
//when
AssertThrows(expression: try container.resolve() as Service) { error in
switch error {
case let DipError.DefinitionNotFound(key) where key == failedKey: return true
default: return false
}
}
}
func testThatItCallsDidResolveDependenciesOnResolvableIntance() {
class ResolvableService: Service, Resolvable {
var didResolveDependenciesCalled = false
func didResolveDependencies() {
XCTAssertFalse(didResolveDependenciesCalled, "didResolveDependencies should be called only once per instance")
didResolveDependenciesCalled = true
}
}
container.register { ResolvableService() as Service }
.resolveDependencies { _, service in
XCTAssertFalse((service as! ResolvableService).didResolveDependenciesCalled, "didResolveDependencies should not be called yet")
return
}
container.register(tag: "graph", .ObjectGraph) { ResolvableService() as Service }
.resolveDependencies { _, service in
XCTAssertFalse((service as! ResolvableService).didResolveDependenciesCalled)
return
}
container.register(tag: "singleton", .Singleton) { ResolvableService() as Service }
.resolveDependencies { _, service in
XCTAssertFalse((service as! ResolvableService).didResolveDependenciesCalled)
return
}
let service = try! container.resolve() as Service
XCTAssertTrue((service as! ResolvableService).didResolveDependenciesCalled)
let graphService = try! container.resolve(tag: "graph") as Service
XCTAssertTrue((graphService as! ResolvableService).didResolveDependenciesCalled)
let singletonService = try! container.resolve(tag: "singleton") as Service
let _ = try! container.resolve(tag: "singleton") as Service
XCTAssertTrue((singletonService as! ResolvableService).didResolveDependenciesCalled)
}
func testThatItResolvesCircularDependencies() {
class ResolvableServer: Server, Resolvable {
weak var client: Client?
weak var secondClient: Client?
init(client: Client) {
self.client = client
}
var didResolveDependenciesCalled = false
func didResolveDependencies() {
XCTAssertFalse(didResolveDependenciesCalled, "didResolveDependencies should be called only once per instance")
didResolveDependenciesCalled = true
XCTAssertNotNil(self.client)
XCTAssertNotNil(self.secondClient)
XCTAssertNotNil(self.client?.server)
XCTAssertNotNil(self.secondClient)
XCTAssertNotNil(self.secondClient?.server)
}
}
class ResolvableClient: Client, Resolvable {
var server: Server?
var secondServer: Server?
init() {}
var didResolveDependenciesCalled = false
func didResolveDependencies() {
XCTAssertFalse(didResolveDependenciesCalled, "didResolveDependencies should be called only once per instance")
didResolveDependenciesCalled = true
XCTAssertNotNil(self.server)
XCTAssertNotNil(self.secondServer)
XCTAssertNotNil(self.server?.client)
XCTAssertNotNil(self.secondServer?.client)
}
}
container.register(.ObjectGraph) { try ResolvableServer(client: self.container.resolve()) as Server }
.resolveDependencies { (container: DependencyContainer, server: Server) in
let server = server as! ResolvableServer
server.secondClient = try container.resolve() as Client
}
container.register(.ObjectGraph) { ResolvableClient() as Client }
.resolveDependencies { (container: DependencyContainer, client: Client) in
let client = client as! ResolvableClient
client.server = try container.resolve() as Server
client.secondServer = try container.resolve() as Server
}
let client = (try! container.resolve() as Client) as! ResolvableClient
let server = client.server as! ResolvableServer
let secondServer = client.secondServer as! ResolvableServer
let secondClient = server.secondClient as! ResolvableClient
XCTAssertTrue(client === server.client)
XCTAssertTrue(client === server.secondClient)
XCTAssertTrue(client === secondServer.client)
XCTAssertTrue(client === secondServer.secondClient)
XCTAssertTrue(client === secondClient)
XCTAssertTrue(server === secondServer)
XCTAssertTrue(client.didResolveDependenciesCalled)
XCTAssertTrue(server.didResolveDependenciesCalled)
}
}
-11
View File
@@ -1,11 +0,0 @@
import XCTest
XCTMain([
DipTests(),
DefinitionTests(),
RuntimeArgumentsTests(),
ComponentScopeTests(),
AutoInjectionTests(),
ThreadSafetyTests(),
AutoWiringTests()
])
+252
View File
@@ -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(.Shared) { ServiceImp1() as Service }
.resolvingProperties { 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(.Shared) { ServiceImp1() as Service }
.resolvingProperties { 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() }
.resolvingProperties { container, service in
originalResolveDependenciesCalled = true
}
container.register(def, type: Service.self)
.resolvingProperties { 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 }
.resolvingProperties { 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)
}
}
@@ -1,4 +1,4 @@
//: [Previous: Shared Instances](@previous)
//: [Previous: Auto-wiring](@previous)
import UIKit
import Dip
@@ -31,7 +31,7 @@ container.register() { TrackerImp() as Tracker }
container.register() { LoggerImp() as Logger }
container.register() { ServiceImp() as Service }
.resolveDependencies { container, service in
.resolvingProperties { container, service in
let service = service as! ServiceImp
service.logger = try container.resolve() as Logger
service.tracker = try container.resolve() as Tracker
@@ -127,12 +127,12 @@ class ServerClientImp: ServerClient {
The standard way to register such components in `DependencyContainer` will lead to such code:
*/
container.register(.ObjectGraph) {
container.register(.Shared) {
ServerClientImp(server: try container.resolve()) as ServerClient
}
container.register(.ObjectGraph) { ServerImp() as Server }
.resolveDependencies { (container: DependencyContainer, server: Server) in
container.register(.Shared) { ServerImp() as Server }
.resolvingProperties { (container: DependencyContainer, server: Server) in
(server as! ServerImp).client = try container.resolve() as ServerClient
}
@@ -153,8 +153,8 @@ class InjectedClientImp: ServerClient {
var server: Server? { get { return injectedServer.value } }
}
container.register(.ObjectGraph) { InjectedServerImp() as Server }
container.register(.ObjectGraph) { InjectedClientImp() as ServerClient }
container.register(.Shared) { InjectedServerImp() as Server }
container.register(.Shared) { InjectedClientImp() as ServerClient }
let injectedClient = try! container.resolve() as ServerClient
injectedClient.server
@@ -176,7 +176,7 @@ class ViewController: UIViewController {
}
container.register { ViewController() }
.resolveDependencies { container, controller in
.resolvingProperties { container, controller in
controller.logger = try container.resolve() as Logger
controller.tracker = try container.resolve() as Tracker
controller.dataProvider = try container.resolve() as DataProvider
@@ -210,4 +210,4 @@ In such scenario when view controller is created by storyboard you will need to
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)
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Timeline
version = "3.0">
<TimelineItems>
</TimelineItems>
</Timeline>
@@ -86,10 +86,10 @@ But then to resolve presenter or interactor you will first need to resolve their
*/
let service = try! container.resolve() as Service
let interactor = try! container.resolve(withArguments: service) as Interactor
let interactor = try! container.resolve(arguments: 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 = try! container.resolve(arguments: view, interactor, router) as Presenter
presenter.interactor.service
/*:
@@ -43,12 +43,12 @@ It's very important that _at least one_ of them uses property injection, because
Now you can register those classes in container:
*/
container.register(.ObjectGraph) {
container.register(.Shared) {
Interactor(networkClient: try container.resolve()) as NetworkClientDelegate
}
container.register(.ObjectGraph) { NetworkClientImp() as NetworkClient }
.resolveDependencies { (container, client) -> () in
container.register(.Shared) { NetworkClientImp() as NetworkClient }
.resolvingProperties { (container, client) -> () in
client.delegate = try container.resolve() as NetworkClientDelegate
}
@@ -77,26 +77,26 @@ let networkClient = try! container.resolve() as NetworkClient
networkClient.delegate // delegate was alread released =(
/*:
Note also that we used `.ObjectGraph` scope to register implementations. This is also very important to preserve consistency of objects relationships.
Note also that we used `.Shared` scope to register implementations. This is also very important to preserve consistency of objects relationships.
If we would have used `.Prototype` scope for both components then container would not reuse instances and we would have an infinite loop:
If we would have used `.Unique` scope for both components then container would not reuse instances and we would have an infinite loop:
- Each attempt to resolve `NetworkClientDelegate` will create new instance of `Interactor`.
- It will resolve `NetworkClient` which will create new instance of `NetworkClientImp`.
- It will try to resolve it's delegate property and that will create new instance of `Interactor`
- And so on and so on.
If we would have used `.Prototype` for one of the components it will lead to the same infinite loop or one of the relationships will be invalid:
If we would have used `.Unique` for one of the components it will lead to the same infinite loop or one of the relationships will be invalid:
*/
container.reset()
container.register(.Prototype) {
container.register(.Unique) {
Interactor(networkClient: try container.resolve()) as NetworkClientDelegate
}
container.register(.ObjectGraph) { NetworkClientImp() as NetworkClient }
.resolveDependencies { (container, client) -> () in
container.register(.Shared) { NetworkClientImp() as NetworkClient }
.resolvingProperties { (container, client) -> () in
client.delegate = try container.resolve() as NetworkClientDelegate
}
@@ -0,0 +1,71 @@
//: [Previous: Type Forwarding](@previous)
import Dip
/*:
### Containers collaboration
Sometimes it makes sence to break your configuration in separate modules. For that you can use containers collaboration. You can link containers with each other and when you try to resolve a type using container where it was not registered, this container will forward request to its collaborating container. This way you can share core configurations or break them in separate modules, for example matching user stories, and still be able to link components from different modules.
*/
protocol DataStore {}
class CoreDataStore: DataStore {}
class AddEventWireframe {
var eventsListWireframe: EventsListWireframe!
}
class EventsListWireframe {
var addEventWireframe: AddEventWireframe!
let dataStore: DataStore
init(dataStore: DataStore) {
self.dataStore = dataStore
}
}
let rootContainer = DependencyContainer()
rootContainer.register(.Singleton) { CoreDataStore() as DataStore }
let eventsListModule = DependencyContainer()
eventsListModule.register(.Shared) { EventsListWireframe(dataStore: $0) }
.resolvingProperties { container, wireframe in
wireframe.addEventWireframe = try container.resolve()
}
let addEventModule = DependencyContainer()
addEventModule.register { AddEventWireframe() }
eventsListModule.collaborate(with: addEventModule, rootContainer)
var eventsListWireframe = try eventsListModule.resolve() as EventsListWireframe
eventsListWireframe.dataStore
eventsListWireframe.addEventWireframe
/*:
As you can see dependencies were resolved even though not all components were registered in the same container.
It is even safe to make circular references between containers. This way you can resolve circular dependencies between components registered in different containers.
*/
eventsListModule.reset()
addEventModule.reset()
eventsListModule.register(.Shared) { EventsListWireframe(dataStore: $0) }
.resolvingProperties { container, wireframe in
wireframe.addEventWireframe = try container.resolve()
}
addEventModule.register(.Shared) { AddEventWireframe() }
.resolvingProperties { container, wireframe in
wireframe.eventsListWireframe = try container.resolve()
}
addEventModule.collaborate(with: eventsListModule)
eventsListWireframe = try eventsListModule.resolve() as EventsListWireframe
eventsListWireframe.addEventWireframe
eventsListWireframe.addEventWireframe.eventsListWireframe === eventsListWireframe
/*:
If you try to link container with itself it will be silently ignored. When forwarding request collaborating containers will be iterated in the same order that they were added.
*/
//: [Next: Testing](@next)
@@ -21,6 +21,9 @@ or using a configuration block:
*/
container = DependencyContainer { container in
//do not forget to use unowned reference if you will need
//to reference container inside definition's factory
unowned let container = container
//register components here
}
@@ -35,7 +35,7 @@ You can use `DependencyContainer.Tag` enum, String or Integer literals, or insta
*/
container.register(tag: "tag") { ServiceImp1() as Service }
container.register(tag: .Int(0)) { ServiceImp1() as Service }
container.register(tag: 0) { ServiceImp1() as Service }
enum MyCustomTag: String, DependencyTagConvertible {
case SomeTag
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Timeline
version = "3.0">
<TimelineItems>
</TimelineItems>
</Timeline>
@@ -1,9 +1,8 @@
//: [Previous: Registering Components](@previous)
import Dip
let container = DependencyContainer { container in
container.register { ServiceImp1() as Service }
}
let container = DependencyContainer()
container.register { ServiceImp1() as Service }
/*:
@@ -42,6 +41,6 @@ let defaultService = try! container.resolve() as Service
You can use runtime arguments to resolve components. Dip supports up to six arguments. For more details see ["Runtime arguments"](Runtime%20arguments).
*/
container.register { service in ClientImp1(service: service) as Client }
let client = try! container.resolve(withArguments: service) as Client
let client = try! container.resolve(arguments: service) as Client
//: [Next: Runtime Arguments](@next)
@@ -18,10 +18,10 @@ container.register { (port: Int, url: NSURL?) in ServiceImp4(name: "3", baseURL:
container.register { (port: Int, url: NSURL!) in ServiceImp4(name: "4", baseURL: url, port: port) as Service }
let url: NSURL = NSURL(string: "http://example.com")!
let service1 = try! container.resolve(withArguments: url, 80) as Service
let service2 = try! container.resolve(withArguments: 80, url) as Service
let service3 = try! container.resolve(withArguments: 80, NSURL(string: "http://example.com")) as Service
let service4 = try! container.resolve(withArguments: 80, NSURL(string: "http://example.com")! as NSURL!) as Service
let service1 = try! container.resolve(arguments: url, 80) as Service
let service2 = try! container.resolve(arguments: 80, url) as Service
let service3 = try! container.resolve(arguments: 80, NSURL(string: "http://example.com")) as Service
let service4 = try! container.resolve(arguments: 80, NSURL(string: "http://example.com")! as NSURL!) as Service
(service1 as! ServiceImp4).name
(service2 as! ServiceImp4).name
@@ -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 = .Unique, 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>
@@ -8,40 +8,60 @@ let container = DependencyContainer()
### Scopes
Dip supports three different scopes of objects: _Prototype_, _ObjectGraph_ and _Singleton_.
Dip supports three different scopes of objects: _Unique_, _Shared_ and _Singleton_.
* 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 `Unique` scope will make the `DependencyContainer` resolve your type as __a new instance every time__ you call `resolve`. This is the default scope.
* The `Shared` scope is like `Unique` 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 `WeakSingleton` scope is the same as `Singleton` scope but instances are stored in container as weak references. This scope can be usefull when you need to recreate object graph without reseting container.
The `.Prototype` scope is the default. To set a scope you pass it as an argument to `register` method.
The `Unique` scope is the default. To set a scope you pass it as an argument to `register` method.
*/
container.register { ServiceImp1() as Service }
container.register(tag: "prototype", .Prototype) { ServiceImp1() as Service }
container.register(tag: "object graph", .ObjectGraph) { ServiceImp2() as Service }
container.register(tag: "prototype", .Unique) { ServiceImp1() as Service }
container.register(tag: "object graph", .Shared) { ServiceImp2() as Service }
container.register(tag: "shared instance", .Singleton) { ServiceImp3() as Service }
let service = try! container.resolve() as Service
let anotherService = try! container.resolve() as Service
// They are different instances as the scope defaults to .Prototype
// They are different instances as the scope defaults to .Unique
service as! ServiceImp1 === anotherService as! ServiceImp1 // false
let prototypeService = try! container.resolve(tag: "prototype") as Service
let anotherPrototypeService = try! container.resolve(tag: "prototype") as Service
let anotherUniqueService = try! container.resolve(tag: "prototype") as Service
// They are different instances:
prototypeService as! ServiceImp1 === anotherPrototypeService as! ServiceImp1 // false
prototypeService === anotherUniqueService // false
let graphService = try! container.resolve(tag: "object graph") as Service
let anotherGraphService = try! container.resolve(tag: "object graph") as Service
// still different instances the ObjectGraph scope only keep instances during one (recursive) resolution call,
// still different instances the Shared scope only keep instances during one (recursive) resolution call,
// so the two calls on the two lines above are different calls and use different instances
graphService as! ServiceImp2 === anotherGraphService as! ServiceImp2 // false
graphService === anotherGraphService // false
let sharedService = try! container.resolve(tag: "shared instance") as Service
let sameSharedService = try! container.resolve(tag: "shared instance") as Service
// same instances, the singleton scope keep and reuse instances during the lifetime of the container
sharedService as! ServiceImp3 === sameSharedService as! ServiceImp3
/*:
### 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 }
.resolvingProperties { _ 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>
@@ -90,7 +90,7 @@ class MyViewController: UIViewController {
}
//inject with constructor
let viewController = MyViewController(apiClient: ApiClient())
var viewController = MyViewController(apiClient: ApiClient())
//or with property
viewController.apiClient = ApiClient()
@@ -98,61 +98,18 @@ viewController.apiClient = ApiClient()
With Dip this code can look like this:
*/
let container = DependencyContainer { container in
container.register { ApiClient() as ApiClientProtocol }
}
let container = DependencyContainer()
container.register { ApiClient() as ApiClientProtocol }
class DipViewController: UIViewController {
var apiClient: ApiClientProtocol!
override func viewDidAppear(amimated: Bool) {
super.viewDidAppear(amimated)
apiClient.get("path") {}
}
convenience init(dependencies: DependencyContainer) {
self.init()
self.apiClient = try! dependencies.resolve() as ApiClientProtocol
}
init() {
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
var dipController = DipViewController(dependencies: container)
//inject with constructor
viewController = try MyViewController(apiClient: container.resolve())
//or with property
viewController.apiClient = try container.resolve()
/*:
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.
@@ -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)
//: [Previous: Containers Collaboration](@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() }
.resolvingProperties { 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(.Shared) { ListWireframe(addWireFrame: $0, listPresenter: $1) }
container.register(.Shared) { AddWireframe(addPresenter: $0) }
var listInteractorDefinition = container.register(.Shared) { ListInteractor() }
.resolvingProperties { container, interactor in
interactor.output = try container.resolve() as ListPresenter
}
var listPresenterDefinition = container.register(.Shared) { ListPresenter() }
.resolvingProperties { container, presenter in
presenter.listInteractor = try container.resolve() as ListInteractor
presenter.listWireframe = try container.resolve()
}
var addPresenterDefinition = container.register(.Shared) { AddPresenter() }
.resolvingProperties { 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 `Shared` 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(.Shared) { ListInteractor() }
.resolvingProperties { container, interactor in
interactor.output = try container.resolve()
}
listPresenterDefinition = container.register(.Shared) { ListPresenter() }
.resolvingProperties { container, presenter in
presenter.listInteractor = try container.resolve()
presenter.listWireframe = try container.resolve()
}
addPresenterDefinition = container.register(.Shared) { AddPresenter() }
.resolvingProperties { 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
.resolvingProperties { container, interactor in
print("resolved ListInteractor")
}
container.register(listInteractorDefinition, type: ListInteractorInput.self)
.resolvingProperties { container, interactor in
print("resolved ListInteractorInput")
}
addPresenter = try! container.resolve() as AddPresenter
//: [Next: Containers Collaboration](@next)
+33 -1
View File
@@ -1,6 +1,6 @@
import Foundation
public protocol Service {}
public protocol Service: class {}
public class ServiceImp1: Service {
public init() {}
@@ -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() {}
}
@@ -11,6 +11,8 @@
<page name='Shared Instances'/>
<page name='Auto-wiring'/>
<page name='Auto-injection'/>
<page name='Type Forwarding'/>
<page name='Containers Collaboration'/>
<page name='Testing'/>
</pages>
</playground>
+125 -317
View File
@@ -1,10 +1,12 @@
# Dip
[![CI Status](http://img.shields.io/travis/AliSoftware/Dip.svg?style=flat)](https://travis-ci.org/AliSoftware/Dip)
[![CI Status](https://travis-ci.org/AliSoftware/Dip.svg?branch=develop)](https://travis-ci.org/AliSoftware/Dip)
[![Version](https://img.shields.io/cocoapods/v/Dip.svg?style=flat)](http://cocoapods.org/pods/Dip)
[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![License](https://img.shields.io/cocoapods/l/Dip.svg?style=flat)](http://cocoapods.org/pods/Dip)
[![Platform](https://img.shields.io/cocoapods/p/Dip.svg?style=flat)](http://cocoapods.org/pods/Dip)
[![Swift Version](https://img.shields.io/badge/Linux-compatible-4BC51D.svg?style=flat)](https://developer.apple.com/swift)
[![Swift Version](https://img.shields.io/badge/Swift-2.2-F16D39.svg?style=flat)](https://developer.apple.com/swift)
![Animated Dipping GIF](cinnamon-pretzels-caramel-dipping.gif)
_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,129 @@ _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.6.1/) 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).
If your are using [VIPER](https://www.objc.io/issues/13-architecture/viper/) architecture - [here](https://github.com/ilyapuchka/VIPER-SWIFT) is VIPER demo app that uses Dip instead of manual dependency injection.
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 5 different scopes (or life cycle strategies): _Prototype_, _Shared_, _Singleton_, _EagerSingleton_, _WeakSingleton_;
- **[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](../../wiki/containers-collaboration)**. 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() }
.resolvingProperties { 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,330 +147,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.3.0")
.Package(url: "https://github.com/AliSoftware/Dip.git", "4.6.1")
]
)
```
## 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-wiring
When you use constructor injection to inject dependencies in your component auto-wiring enables you to resolve it just with one call to `resolve` method without carying about how to resolve all constructor arguments - container will resolve them for you.
```swift
class PresenterImp: Presenter {
init(view: ViewOutput, interactor: Interactor, router: Router) { ... }
...
}
container.register { RouterImp() as Router }
container.register { View() as ViewOutput }
container.register { InteractorImp() as Interactor }
container.register { PresenterImp(view: $0, interactor: $1, router: $2) as Presenter }
let presenter = try! container.resolve() as Presenter
```
### Auto-injection
Auto-injection lets your resolve all property dependencies of the instance resolved by container with just one call, also allowing a simpler syntax to register circular dependencies.
```swift
protocol Server {
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
+1 -1
View File
@@ -36,6 +36,6 @@ extension BaseCell where Self : UITableViewCell {
}
protocol FillableCell: BaseCell {
typealias ObjectType
associatedtype ObjectType
func fillWithObject(object: ObjectType)
}
@@ -33,7 +33,7 @@ func configureContainer(dip: DependencyContainer) {
// 1) Register fake persons provider
//Here we use constructor injection for one of the dependencies property injection for another, and we provide dependencies manually
dip.register() { FakePersonsProvider(dummyProvider: DummyPilotProvider()) as PersonProviderAPI }
.resolveDependencies { (_, resolved: PersonProviderAPI) in
.resolvingProperties { (_, resolved: PersonProviderAPI) in
//here we resolve optional dependencies
//see what happens when you comment this out
(resolved as! FakePersonsProvider).plistProvider = PlistPersonProvider(plist: "mainPilot")
@@ -60,7 +60,7 @@ func configureContainer(dip: DependencyContainer) {
//Here we use constructor injection, but instead of providing dependencies manually container resolves them for us
dip.register() {
FakeStarshipProvider(
dummyProvider: try dip.resolve(tag: DependencyTags.Dummy, withArguments: "Main Pilot"),
dummyProvider: try dip.resolve(tag: DependencyTags.Dummy, arguments: "Main Pilot"),
hardCodedProvider: try dip.resolve(tag: DependencyTags.Hardcoded)) as StarshipProviderAPI
}
@@ -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 }
+80 -23
View File
@@ -28,17 +28,15 @@ extension DependencyContainer {
Resolves properties of passed object wrapped with `Injected<T>` or `InjectedWeak<T>`
*/
func autoInjectProperties(instance: Any) throws {
try Mirror(reflecting: instance).children.forEach(_resolveChild)
try Mirror(reflecting: instance).children.forEach(resolveChild)
}
private func _resolveChild(child: Mirror.Child) throws {
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)
let contextKey = DefinitionKey(type: injectedPropertyBox.dynamicType.wrappedType, typeOfArguments: Void.self, tag: context.tag)
try inContext(contextKey, injectedInProperty: child.label, logErrors: false) {
try injectedPropertyBox.resolve(self)
}
}
@@ -49,6 +47,7 @@ extension DependencyContainer {
instead of using `Injected<T>` or `InjectedWeak<T>` types.
**Example**:
```swift
class MyCustomBox<T> {
private(set) var value: T?
@@ -75,7 +74,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 +92,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 +121,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: DependencyTagConvertible? = 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 +186,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 +213,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: DependencyTagConvertible? = 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 +234,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,22 +254,39 @@ private class _InjectedPropertyBox<T> {
let required: Bool
let didInject: T -> ()
let tag: DependencyContainer.Tag?
let overrideTag: Bool
init(required: Bool = true, tag: DependencyTagConvertible?, didInject: T -> () = { _ in }) {
init(required: Bool = true, tag: DependencyTagConvertible?, overrideTag: Bool, didInject: T -> () = { _ in }) {
self.required = required
self.tag = tag?.dependencyTag
self.overrideTag = overrideTag
self.didInject = didInject
}
private func resolve(container: DependencyContainer) throws -> T? {
let resolved: T?
if required {
resolved = try container.resolve(tag: tag) as T
let tag = overrideTag ? self.tag : container.context.tag
do {
container.context.key = container.context.key.tagged(tag)
let key = DefinitionKey(type: T.self, typeOfArguments: Void.self, tag: tag?.dependencyTag)
return try resolve(container, key: key, builder: { factory in try factory() }) as? T
}
else {
resolved = try? container.resolve(tag: tag) as T
catch {
let error = DipError.AutoInjectionFailed(label: container.context.injectedInProperty, type: container.context.resolvingType, underlyingError: error)
if required {
throw error
}
else {
log(.Errors, error)
return nil
}
}
return resolved
}
private func resolve<U>(container: DependencyContainer, key: DefinitionKey, builder: (U throws -> Any) throws -> Any) throws -> Any {
return try container.resolveKey(key, builder: { definition throws -> Any in
try builder(definition.weakFactory)
})
}
}
+84 -48
View File
@@ -22,74 +22,89 @@
// 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 _resolveByAutoWiring<T>(key: DefinitionKey) throws -> T? {
typealias NoArgumentsFactory = () throws -> T
guard key.factoryType == NoArgumentsFactory.self else { return nil }
func autowire<T>(key: DefinitionKey) throws -> T {
let shouldLogErrors = context.logErrors
defer { context.logErrors = shouldLogErrors }
context.logErrors = false
let autoWiringDefinitions = self.autoWiringDefinitionsFor(T.self, tag: key.associatedTag)
return try _resolveEnumeratingKeys(autoWiringDefinitions, tag: key.associatedTag)
guard key.typeOfArguments == Void.self else {
throw DipError.DefinitionNotFound(key: key)
}
let tag = key.tag
let type = key.type
let resolved: Any?
do {
let definitions = autoWiringDefinitions(byKey: key)
resolved = try resolve(enumerating: definitions, tag: tag)
}
catch {
throw DipError.AutoWiringFailed(type: type, underlyingError: error)
}
if let resolved = resolved as? T {
return resolved
}
else {
throw DipError.DefinitionNotFound(key: key)
}
}
private func autoWiringDefinitionsFor(type: Any.Type, tag: DependencyContainer.Tag?) -> [(DefinitionKey, _Definition)] {
var definitions = self.definitions
.map({ ($0.0, $0.1 as! _Definition) })
private func autoWiringDefinitions(byKey key: DefinitionKey) -> [KeyDefinitionPair] {
var definitions = self.definitions.map({ (key: $0.0, definition: $0.1) })
//filter definitions
definitions = definitions
.filter({ $0.1.supportsAutoWiring() })
.filter({ $0.0.protocolType == type })
.filter({ $0.0.associatedTag == tag || $0.0.associatedTag == nil })
//order definitions
definitions = definitions
.sort({ $0.1.numberOfArguments > $1.1.numberOfArguments })
definitions =
//first will try to use tagged definitions
definitions.filter({ $0.0.associatedTag == tag }) +
//then will use not tagged definitions
definitions.filter({ $0.0.associatedTag != tag })
.filter({ $0.definition.supportsAutoWiring() })
.sort({ $0.definition.numberOfArguments > $1.definition.numberOfArguments })
definitions = filter(definitions, byKey: key)
definitions = order(definitions, byTag: key.tag)
return definitions
}
/// Tries definitions one by one until one of them succeeds, otherwise returns nil
private func _resolveEnumeratingKeys<T>(definitions: [(DefinitionKey, _Definition)], tag: DependencyContainer.Tag?) throws -> T? {
for (index, definition) in definitions.enumerate() {
/// Enumerates definitions one by one until one of them succeeds, otherwise returns nil
private func resolve(enumerating autoWiringDefinitions: [KeyDefinitionPair], tag: DependencyContainer.Tag?) throws -> Any? {
for (index, autoWiringDefinition) in autoWiringDefinitions.enumerate() {
//If the next definition matches current definition then they are ambigous
if case definition? = definitions[next: index] {
if let nextPair = autoWiringDefinitions[next: index], case autoWiringDefinition = nextPair {
throw DipError.AmbiguousDefinitions(
type: definition.0.protocolType,
definitions: [definition.1, definitions[next: index]!.1]
type: autoWiringDefinition.key.type,
definitions: [autoWiringDefinition.definition, nextPair.definition]
)
}
if let resolved: T = _resolveKey(definition.0, tag: tag) {
let key = autoWiringDefinition.key.tagged(tag ?? context.tag)
let resolved: Any? = try? resolveKey(key) { definition in
try definition.autoWiringFactory!(self, tag)
}
if let resolved = resolved {
return resolved
}
}
return nil
}
private func _resolveKey<T>(key: DefinitionKey, tag: DependencyContainer.Tag?) -> T? {
let key = DefinitionKey(protocolType: key.protocolType, factoryType: key.factoryType, associatedTag: tag)
return try? _resolveKey(key, builder: { definition throws -> T in
guard let resolved = try definition._autoWiringFactory!(self, tag) as? T else {
fatalError("Internal inconsistency exception! Expected type: \(T.self); Definition: \(definition)")
}
return resolved
})
}
}
extension CollectionType {
extension CollectionType where Self.Index: Comparable {
subscript(safe index: Index) -> Generator.Element? {
guard indices.contains(index) else { return nil }
guard indices ~= index else { return nil }
return self[index]
}
subscript(next index: Index) -> Generator.Element? {
@@ -97,10 +112,31 @@ extension CollectionType {
}
}
/// Definitions are matched if they are registered for the same tag and thier factoris accept the same number of runtime arguments.
private func ~=(lhs: (DefinitionKey, _Definition), rhs: (DefinitionKey, _Definition)) -> Bool {
return
lhs.0.protocolType == rhs.0.protocolType &&
lhs.0.associatedTag == rhs.0.associatedTag &&
lhs.1.numberOfArguments == rhs.1.numberOfArguments
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.type == rhs.key.type else { return false }
guard lhs.key.tag == rhs.key.tag else { return false }
guard lhs.definition.numberOfArguments == rhs.definition.numberOfArguments else { return false }
return true
}
func filter(definitions: [KeyDefinitionPair], byKey key: DefinitionKey) -> [KeyDefinitionPair] {
return definitions
.filter({ $0.key.type == key.type || $0.definition.doesImplements(key.type) })
.filter({ $0.key.tag == key.tag || $0.key.tag == nil })
}
func filter(definitions: [KeyDefinitionPair], byKeyAndTypeOfArguments key: DefinitionKey) -> [KeyDefinitionPair] {
return filter(definitions, byKey: key)
.filter({ $0.key.typeOfArguments == key.typeOfArguments })
}
func order(definitions: [KeyDefinitionPair], byTag tag: DependencyContainer.Tag?) -> [KeyDefinitionPair] {
return
//first will try to use tagged definitions
definitions.filter({ $0.key.tag == tag }) +
//then will use not tagged definitions
definitions.filter({ $0.key.tag != tag })
}
+182 -71
View File
@@ -24,32 +24,49 @@
///A key used to store definitons in a container.
public struct DefinitionKey : Hashable, CustomStringConvertible {
public let protocolType: Any.Type
public let factoryType: Any.Type
public let associatedTag: DependencyContainer.Tag?
init(protocolType: Any.Type, factoryType: Any.Type, associatedTag: DependencyContainer.Tag? = nil) {
self.protocolType = protocolType
self.factoryType = factoryType
self.associatedTag = associatedTag
public let type: Any.Type
public let typeOfArguments: Any.Type
public private(set) var tag: DependencyContainer.Tag?
init(type: Any.Type, typeOfArguments: Any.Type, tag: DependencyContainer.Tag? = nil) {
self.type = type
self.typeOfArguments = typeOfArguments
self.tag = tag
}
public var hashValue: Int {
return "\(protocolType)-\(factoryType)-\(associatedTag)".hashValue
return "\(type)-\(typeOfArguments)-\(tag)".hashValue
}
public var description: String {
return "type: \(protocolType), factory: \(factoryType), tag: \(associatedTag.desc)"
return "type: \(type), arguments: \(typeOfArguments), tag: \(tag.desc)"
}
func tagged(tag: DependencyContainer.Tag?) -> DefinitionKey {
var tagged = self
tagged.tag = tag
return tagged
}
}
/// Check two definition keys on equality by comparing their `protocolType`, `factoryType` and `associatedTag` properties.
//MARK: - Deprecated
extension DefinitionKey {
@available(*, deprecated=4.6.1, message="Property protocolType was renamed to type")
public var protocolType: Any.Type { return type }
@available(*, deprecated=4.6.1, message="Property argumentsType was renamed to typeOfArguments")
public var argumentsType: Any.Type { return typeOfArguments }
@available(*, deprecated=4.6.1, message="Property associatedTag was renamed to tag")
public var associatedTag: DependencyContainer.Tag? { return tag }
}
/// Check two definition keys on equality by comparing their `type`, `factoryType` and `tag` properties.
public func ==(lhs: DefinitionKey, rhs: DefinitionKey) -> Bool {
return
lhs.protocolType == rhs.protocolType &&
lhs.factoryType == rhs.factoryType &&
lhs.associatedTag == rhs.associatedTag
lhs.type == rhs.type &&
lhs.typeOfArguments == rhs.typeOfArguments &&
lhs.tag == rhs.tag
}
///Component scope defines a strategy used by the `DependencyContainer` to manage resolved instances life cycle.
@@ -74,6 +91,9 @@ public enum ComponentScope {
```
*/
case Unique
@available(*, deprecated=4.6.1, message="Prototype scope is renamed to Unique")
case Prototype
/**
@@ -86,7 +106,7 @@ public enum ComponentScope {
**Example**:
```
container.register(.ObjectGraph) { ServiceImp() as Service }
container.register(.Shared) { ServiceImp() as Service }
container.register {
ServiceConsumerImp(
service1: try container.resolve() as Service
@@ -100,12 +120,15 @@ public enum ComponentScope {
consumer1.service1 !== consumer2.service1 //true
```
*/
case Shared
@available(*, deprecated=4.6.1, message="ObjectGraph scope is renamed to Shared")
case ObjectGraph
/**
Resolved instance will be retained by the container and always reused.
Do not mix this life cycle with _singleton pattern_.
Instance will be not shared between different containers.
Instance will be not shared between different containers unless they collaborate.
- warning: Make sure this component is thread safe or accessed always from the same thread.
@@ -131,8 +154,25 @@ public enum ComponentScope {
```
*/
case Singleton
/**
The same scope as a `Singleton`, but instance will be created when container is bootstrapped.
- seealso: `bootstrap()`
*/
case EagerSingleton
/**
The same scope as a `Singleton`, but container stores week reference to the resolved instance.
While a strong reference to the resolved instance exists resolve will return the same instance.
After the resolved instance is deallocated next resolve will produce a new instance.
*/
case WeakSingleton
}
///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`.
@@ -143,101 +183,132 @@ 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
.resolvingProperties { 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 resolvingProperties(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)
func resolveProperties(instance instance: Any, container: DependencyContainer) throws {
guard let resolvedInstance = instance as? T else { return }
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?
//Auto-wiring helpers
private(set) var autoWiringFactory: ((DependencyContainer, DependencyContainer.Tag?) throws -> T)?
private(set) var numberOfArguments: Int = 0
//MARK: - _TypeForwardingDefinition
convenience init(scope: ComponentScope, factory: F, autoWiringFactory: (DependencyContainer, DependencyContainer.Tag?) throws -> T, numberOfArguments: Int) {
self.init(scope: scope, factory: factory)
self.autoWiringFactory = autoWiringFactory
self.numberOfArguments = numberOfArguments
/// 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)
resolvingProperties({ try forwardsToDefinition.resolveProperties(instance: $1, container: $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 _autoWiringFactory: ((DependencyContainer, DependencyContainer.Tag?) throws -> Any)? { get }
var _factory: Any { get }
var numberOfArguments: Int { get }
func resolveDependenciesOf(resolvedInstance: Any, withContainer container: DependencyContainer) throws
var weakFactory: (Any throws -> Any)! { get }
func resolveProperties(instance instance: Any, container: DependencyContainer) throws
}
extension _Definition {
func supportsAutoWiring() -> Bool {
return _autoWiringFactory != nil && numberOfArguments > 0
}
//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: _Definition {
var _resolveDependenciesBlock: ((DependencyContainer, Any) throws -> ())? {
return resolveDependenciesBlock.map({ block in { try block($0, $1 as! T) } })
extension DefinitionOf: _TypeForwardingDefinition {
var type: Any.Type {
return T.self
}
var _autoWiringFactory: ((DependencyContainer, DependencyContainer.Tag?) throws -> Any)? {
return autoWiringFactory.map({ factory in { try factory($0.0, $0.1)} })
}
var _factory: Any {
return factory
}
}
extension DefinitionOf: CustomStringConvertible {
@@ -246,3 +317,43 @@ 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 = { try factory($0 as! U) }
definition.forwardsToDefinition = forwardsDefinition as? _TypeForwardingDefinition
return definition
}
}
//MARK: - Deprecated methods
extension DefinitionOf {
@available(*, deprecated=4.6.1, message="Use resolvingProperties(_:)")
public func resolveDependencies(block: (DependencyContainer, T) throws -> ()) -> DefinitionOf {
return resolvingProperties(block)
}
}
+501 -159
View File
@@ -39,10 +39,24 @@ public final class DependencyContainer {
case Int(IntegerLiteralType)
}
var definitions = [DefinitionKey : Definition]()
let resolvedInstances = ResolvedInstances()
let lock = RecursiveLock()
internal(set) public var context: Context!
var definitions = [DefinitionKey : _Definition]()
private var resolvedInstances = ResolvedInstances()
private let lock = RecursiveLock()
private(set) var bootstrapped = false
private var bootstrapQueue: [() throws -> ()] = []
private var _weakCollaborators: [WeakBox<DependencyContainer>] = []
private(set) var _collaborators: [DependencyContainer] {
get {
return _weakCollaborators.flatMap({ $0.value })
}
set {
_weakCollaborators = newValue.filter({ $0 !== self }).map(WeakBox.init)
}
}
/**
Designated initializer for a DependencyContainer
@@ -50,7 +64,18 @@ public final class DependencyContainer {
- note: The `configBlock` is simply called at the end of the `init` to let you configure everything.
It is only present for convenience to have a cleaner syntax when declaring and initializing
your `DependencyContainer` instances.
your `DependencyContainer` instances.
- warning: If you use `configBlock` you need to make sure you don't create retain cycles. For example
there will be a retain cycle between container and its definition if you reference container
inside definition's factory. You can avoid that by using unowned reference to container:
```swift
let container = DependencyContainer() { container in
unowned let container = container
//register definitions
}
```
- returns: A new DependencyContainer.
*/
@@ -58,6 +83,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 +109,139 @@ 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
}.resolvingProperties { 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: CustomStringConvertible, CustomDebugStringConvertible {
internal(set) public var key: DefinitionKey
/// Currently resolving type.
public var resolvingType: Any.Type {
return key.type
}
/// The tag used to resolve currently resolving type.
public var tag: Tag? {
return key.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?
var logErrors: Bool = true
init(key: DefinitionKey, injectedInType: Any.Type?, injectedInProperty: String?) {
self.key = key
self.injectedInType = injectedInType
self.injectedInProperty = injectedInProperty
}
public var debugDescription: String {
return "Context(key: \(key), injectedInType: \(injectedInType.desc), injectedInProperty: \(injectedInProperty.desc) logErrors: \(logErrors))"
}
public var description: String {
let resolvingDescription = "Resolving type \(key.type) with arguments \(key.typeOfArguments) tagged with \(key.tag.desc)"
if injectedInProperty != nil {
return "\(resolvingDescription) while auto-injecting property \(injectedInProperty.desc) of \(injectedInType.desc)"
}
else if injectedInType != nil {
return "\(resolvingDescription) while injecting in type \(injectedInType.desc)"
}
else {
return resolvingDescription
}
}
}
/// 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>(key: DefinitionKey, injectedInProperty: String? = nil, injectedInType: Any.Type? = nil, logErrors: Bool! = nil, @noescape block: () throws -> T) rethrows -> T {
return try threadSafe {
let currentContext = self.context
defer {
context = currentContext
//clean instances pool if it is owned not by other container
if context == nil {
resolvedInstances.resolvedInstances.removeAll()
for (key, instance) in resolvedInstances.weakSingletons {
if resolvedInstances.weakSingletons[key] is WeakBoxType { continue }
resolvedInstances.weakSingletons[key] = WeakBox(instance)
}
// We call didResolveDependencies only at this point
// because this is a point when dependencies graph is complete.
for resolvedInstance in resolvedInstances.resolvableInstances.reverse() {
resolvedInstance.didResolveDependencies()
}
resolvedInstances.resolvableInstances.removeAll()
}
}
context = Context(
key: key,
injectedInType: injectedInType ?? currentContext?.resolvingType,
injectedInProperty: injectedInProperty
)
context.logErrors = logErrors ?? currentContext?.logErrors ?? true
do {
return try block()
}
catch {
if context.logErrors { log(.Errors, error) }
throw error
}
}
}
}
// MARK: - Registering definitions
extension DependencyContainer {
@@ -89,45 +263,19 @@ extension DependencyContainer {
```swift
container.register { ServiceImp() as Service }
container.register(tag: "service") { ServiceImp() as Service }
container.register(.ObjectGraph) { ServiceImp() as Service }
container.register { ClientImp(service: try! container.resolve() as Service) as Client }
container.register(.Shared) { ServiceImp() as Service }
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> {
let definition = DefinitionOf<T, () throws -> T>(scope: scope, factory: factory)
public func register<T>(scope: ComponentScope = .Unique, tag: DependencyTagConvertible? = nil, 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.
- parameters:
- tag: The arbitrary tag to associate this factory with. Pass `nil` to associate with any tag. Default value is `nil`.
- scope: The scope to use for instance created by the factory.
- factory: The factory to register.
- returns: A registered definition.
- note: You _should not_ call this method directly, instead call any of other `register` methods.
You _should_ use this method only to register dependency with more runtime arguments
than _Dip_ supports (currently it's up to six) like in the following example:
```swift
public func register<T, Arg1, Arg2, Arg3, ...>(tag: Tag? = nil, scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, ...) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, ...) throws -> T> {
return registerFactory(tag: tag, scope: scope, factory: factory)
}
```
Though before you do so you should probably review your design and try to reduce number of depnedencies.
*/
@available(*, deprecated=4.3.0, message="Use registerFactory(tag:scope:factory:numberOfArguments:autoWiringFactory:) instead.")
public func registerFactory<T, F>(tag tag: DependencyTagConvertible? = nil, scope: ComponentScope, factory: F) -> DefinitionOf<T, F> {
let definition = DefinitionOf<T, F>(scope: scope, factory: factory)
register(definition, forTag: tag)
return definition
}
/**
Register generic factory and auto-wiring factory and associate it with an optional tag.
@@ -145,17 +293,22 @@ extension DependencyContainer {
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> {
public func register<T, A, B, C, ...>(tag: Tag? = nil, scope: ComponentScope = .Unique, 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(try container.resolve(tag: tag), ...)
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: DependencyTagConvertible? = nil, scope: ComponentScope, factory: F, numberOfArguments: Int, autoWiringFactory: (DependencyContainer, Tag?) throws -> T) -> DefinitionOf<T, F> {
let definition = DefinitionOf<T, F>(scope: scope, factory: factory, autoWiringFactory: autoWiringFactory, numberOfArguments: numberOfArguments)
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
}
@@ -169,18 +322,22 @@ extension DependencyContainer {
- definition: The definition to register in the container.
*/
public func register<T, F>(definition: DefinitionOf<T, F>, forTag tag: DependencyTagConvertible? = nil) {
let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag?.dependencyTag)
register(definition, forKey: key)
}
func register(definition: Definition, forKey key: DefinitionKey) {
public func register<T, U>(definition: DefinitionOf<T, U throws -> T>, forTag tag: DependencyTagConvertible? = nil) {
precondition(!bootstrapped, "You can not modify container's definitions after it was bootstrapped.")
threadSafe {
let key = DefinitionKey(type: T.self, typeOfArguments: U.self, tag: tag?.dependencyTag)
definitions[key] = definition
resolvedInstances.singletons[key] = nil
resolvedInstances.weakSingletons[key] = nil
if case .EagerSingleton = definition.scope {
bootstrapQueue.append({ let _ = try self.resolve(tag: tag) as T })
}
}
}
}
// MARK: - Resolve dependencies
@@ -210,9 +367,28 @@ extension DependencyContainer {
*/
public func resolve<T>(tag tag: DependencyTagConvertible? = nil) throws -> T {
return try resolve(tag: tag) { (factory: () throws -> T) in try factory() }
return try resolve(tag: tag) { factory in try factory() }
}
/**
Resolve 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 requested 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:)`
*/
public func resolve(type: Any.Type, tag: DependencyTagConvertible? = nil) throws -> Any {
return try 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.
@@ -230,127 +406,175 @@ 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: DependencyTagConvertible? = nil, builder: F throws -> T) throws -> T {
let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag?.dependencyTag)
do {
//first we try to find defintion that exactly matches parameters
return try _resolveKey(key, builder: { definition throws -> T in
guard let factory = definition._factory as? F else {
throw DipError.DefinitionNotFound(key: key)
}
return try builder(factory)
public func resolve<T, U>(tag tag: DependencyTagConvertible? = nil, builder: (U throws -> T) throws -> T) rethrows -> T {
return try resolve(T.self, tag: tag, builder: { factory in
try builder({ try factory($0) as! T })
}) 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) rethrows -> Any {
let key = DefinitionKey(type: type, typeOfArguments: U.self, tag: tag?.dependencyTag)
return try inContext(key) {
try resolveKey(key, builder: { definition in
try builder(definition.weakFactory)
})
}
catch {
switch error {
case let DipError.DefinitionNotFound(errorKey) where key == errorKey:
//then if no definition found we try atuo-wiring
return try threadSafe {
guard let resolved: T = try _resolveByAutoWiring(key) else {
throw error
}
return resolved
}
default:
throw error
}
}
}
/// Lookup definition by the key and use it to resolve instance. Fallback to the key with `nil` tag.
func _resolveKey<T>(key: DefinitionKey, builder: _Definition throws -> T) throws -> T {
return try threadSafe {
let nilTagKey = key.associatedTag.map { _ in DefinitionKey(protocolType: T.self, factoryType: key.factoryType, associatedTag: nil) }
guard let definition = (self.definitions[key] ?? self.definitions[nilTagKey]) as? _Definition else {
throw DipError.DefinitionNotFound(key: key)
}
return try self._resolveDefinition(definition, usingKey: key, builder: builder)
}
}
/// Actually resolve dependency.
private func _resolveDefinition<T>(definition: _Definition, usingKey key: DefinitionKey, builder: _Definition throws -> T) rethrows -> T {
return try resolvedInstances.resolve {
if let previouslyResolved: T = resolvedInstances.previouslyResolvedInstance(forKey: key, inScope: definition.scope) {
/// Lookup definition by the key and use it to resolve instance. Fallback to the key with `nil` tag.
func resolveKey<T>(key: DefinitionKey, builder: (_Definition throws -> T)) throws -> T {
guard let matching = self.definition(matching: key) else {
return try resolveWithCollaborators(key, builder: builder) ?? autowire(key)
}
let (key, definition) = matching
//first search for already resolved instance for this type or any of forwarding types
if let previouslyResolved: T = previouslyResolved(definition, key: key) {
log(.Verbose, "Reusing previously resolved instance \(previouslyResolved)")
return previouslyResolved
}
log(.Verbose, context)
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
That happens because when Optional is casted to Any Swift can not implicitly unwrap it with as operator.
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) {
log(.Verbose, "Reusing previously resolved instance \(previouslyResolved)")
return previouslyResolved
}
resolvedInstances[forKey: key, inScope: definition.scope] = resolvedInstance
if let resolvable = resolvedInstance as? Resolvable {
resolvedInstances.resolvableInstances.append(resolvable)
}
try autoInjectProperties(resolvedInstance)
try definition.resolveProperties(instance: resolvedInstance, container: self)
log(.Verbose, "Resolved type \(key.type) with \(resolvedInstance)")
return resolvedInstance
}
private func previouslyResolved<T>(definition: _Definition, key: DefinitionKey) -> T? {
let keys = definition.implementingTypes.map({
DefinitionKey(type: $0, typeOfArguments: key.typeOfArguments, tag: key.tag)
})
for key in [key] + keys {
if let previouslyResolved = resolvedInstances[forKey: key, inScope: definition.scope] as? T {
return previouslyResolved
}
else {
let resolvedInstance = try builder(definition)
//when builder calls factory it will in turn resolve sub-dependencies (if there are any)
//when it returns instance that we try to resolve here can be already resolved
//so we return it, throwing away instance created by previous call to builder
if let previouslyResolved: T = resolvedInstances.previouslyResolvedInstance(forKey: key, inScope: definition.scope) {
return previouslyResolved
}
resolvedInstances.storeResolvedInstance(resolvedInstance, forKey: key, inScope: definition.scope)
try definition.resolveDependenciesOf(resolvedInstance, withContainer: self)
try autoInjectProperties(resolvedInstance)
return resolvedInstance
}
}
return nil
}
/// Searches for definition that matches provided key
private func definition(matching key: DefinitionKey) -> KeyDefinitionPair? {
let typeDefinitions = definitions.filter({ $0.0.type == key.type })
guard !typeDefinitions.isEmpty else {
return typeForwardingDefinition(key)
}
if let definition = (self.definitions[key] ?? self.definitions[key.tagged(nil)]) {
return (key, definition)
}
return nil
}
}
//MARK: - Collaborating containers
extension DependencyContainer {
/**
Adds collaborating containers as weak references. Circular references are allowed.
References to the container itself are ignored.
*/
public func collaborate(with containers: DependencyContainer...) {
collaborate(with: containers)
}
/**
Adds collaborating containers as weak references. Circular references are allowed.
References to the container itself are ignored.
*/
public func collaborate(with containers: [DependencyContainer]) {
_collaborators += containers
for container in containers {
container.resolvedInstances.singletonsBox = self.resolvedInstances.singletonsBox
container.resolvedInstances.weakSingletonsBox = self.resolvedInstances.weakSingletonsBox
}
}
///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]()
var resolvableInstances = [Resolvable]()
/// Tries to resolve key using collaborating containers
private func resolveWithCollaborators<T>(key: DefinitionKey, builder: _Definition throws -> T) -> T? {
for collaborator in _collaborators {
do {
//if container is already in a context resolving this type
//it means that it has been already called to resolve this type,
//so there is probably a cercular reference between containers.
//To break it skip this container
if let context = collaborator.context where
context.resolvingType == key.type &&
context.tag == key.tag { continue }
func storeResolvedInstance<T>(instance: T, forKey key: DefinitionKey, inScope scope: ComponentScope) {
switch scope {
case .Singleton: singletons[key] = instance
case .ObjectGraph: resolvedInstances[key] = instance
case .Prototype: break
}
if let resolvable = instance as? Resolvable {
resolvableInstances.append(resolvable)
}
}
func 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 {
// We call didResolveDependencies only at this point
// because this is a point when dependencies graph is complete.
for resolvedInstance in resolvableInstances {
resolvedInstance.didResolveDependencies()
}
resolvedInstances.removeAll()
resolvableInstances.removeAll()
//Pass current container's instances pool to collect instances resolved by collaborator
let resolvedInstances = collaborator.resolvedInstances
collaborator.resolvedInstances = self.resolvedInstances
//Set collaborator context to preserve current container context
let context = collaborator.context
collaborator.context = self.context
defer {
collaborator.resolvedInstances = resolvedInstances
collaborator.context = context
}
let resolved = try collaborator.inContext(key, injectedInProperty: self.context.injectedInProperty, injectedInType: self.context.injectedInType, logErrors: false) {
try collaborator.resolveKey(key, builder: builder)
}
return resolved
}
catch {
continue
}
let resolved = try block()
return resolved
}
return nil
}
}
@@ -366,15 +590,18 @@ 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: DependencyTagConvertible? = nil) {
let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag?.dependencyTag)
public func remove<T, U>(definition: DefinitionOf<T, U>, forTag tag: DependencyTagConvertible? = nil) {
let key = DefinitionKey(type: T.self, typeOfArguments: U.self, tag: 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
resolvedInstances.weakSingletons[key] = nil
}
}
@@ -385,11 +612,99 @@ extension DependencyContainer {
threadSafe {
definitions.removeAll()
resolvedInstances.singletons.removeAll()
resolvedInstances.weakSingletons.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.typeOfArguments {
do {
try inContext(key) {
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 { log(.Errors, error) }
}
//try to resolve key using auto-wiring
do {
try self.resolve(key.type, tag: key.tag)
}
catch let error as DipError {
throw error
}
//ignore other errors
catch { log(.Errors, error) }
}
}
}
}
///Pool to hold instances, created during call to `resolve()`.
///Before `resolve()` returns pool is drained.
private class ResolvedInstances {
var resolvedInstances = [DefinitionKey: Any]()
var resolvableInstances = [Resolvable]()
//singletons are stored using reference type wrapper to be able to share them between containers
private var singletonsBox = Box<[DefinitionKey: Any]>([:])
var singletons: [DefinitionKey: Any] {
get { return singletonsBox.unboxed }
set { singletonsBox.unboxed = newValue }
}
private var weakSingletonsBox = Box<[DefinitionKey: Any]>([:])
var weakSingletons: [DefinitionKey: Any] {
get { return weakSingletonsBox.unboxed }
set { weakSingletonsBox.unboxed = newValue }
}
subscript(forKey key: DefinitionKey, inScope scope: ComponentScope) -> Any? {
get {
switch scope {
case .Singleton, .EagerSingleton: return singletons[key]
case .WeakSingleton: return (weakSingletons[key] as? WeakBoxType)?.unboxed ?? weakSingletons[key]
case .Shared, .ObjectGraph: return resolvedInstances[key]
case .Unique, .Prototype: return nil
}
}
set {
switch scope {
case .Singleton, .EagerSingleton: singletons[key] = newValue
case .WeakSingleton: weakSingletons[key] = newValue
case .Shared, .ObjectGraph: resolvedInstances[key] = newValue
case .Unique, .Prototype: break
}
}
}
}
extension DependencyContainer: CustomStringConvertible {
public var description: String {
@@ -504,12 +819,22 @@ public enum DipError: ErrorType, CustomStringConvertible {
- underlyingError: The error that caused auto-injection to fail
*/
case AutoInjectionFailed(label: String?, type: Any.Type, underlyingError: ErrorType)
/**
Thrown by `resolve(tag:)` if found ambigous definitions registered for resolved type
Thrown by `resolve(tag:)` if failed to auto-wire a type.
- parameters:
- type: The type that failed to be resolved
- 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])
@@ -517,12 +842,29 @@ public enum DipError: ErrorType, CustomStringConvertible {
public var description: String {
switch self {
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)."
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.type)."
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")
}
}
}
//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
}
}
+136 -31
View File
@@ -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,
@@ -37,19 +37,28 @@ extension DependencyContainer {
- parameters:
- tag: The arbitrary tag to associate this factory with. Pass `nil` to associate with any tag. Default value is `nil`.
- scope: The scope to use for this component. Default value is `.Prototype`.
- scope: The scope to use for this component. Default value is `.Unique`.
- factory: The factory to register.
- seealso: `registerFactory(tag:scope:factory:)`
*/
public func register<T, Arg1>(tag tag: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1) throws -> T) -> DefinitionOf<T, (Arg1) throws -> T> {
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: 1) { container, tag in try factory(try container.resolve(tag: tag)) }
public func register<T, A>(scope: ComponentScope = .Unique, tag: DependencyTagConvertible? = nil, 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 type container will first try to use definition that matches types of arguments that you pass to resolve method. If it fails or no such definition is found container will try to _auto-wire_ component. For that it will iterate through all the definitions registered for that type which factories accept any number of runtime arguments and are tagged with the same tag, passed to `resolve` method, or with no tag. Container will try to use these definitions to resolve a component one by one until one of them succeeds, starting with tagged definitions in order of decreasing their's factories number of arguments.
- 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.
@@ -61,69 +70,165 @@ extension DependencyContainer {
- seealso: `register(tag:_:factory:)`, `resolve(tag:builder:)`
*/
public func resolve<T, Arg1>(tag tag: DependencyTagConvertible? = nil, withArguments arg1: Arg1) throws -> T {
return try resolve(tag: tag) { (factory: (Arg1) throws -> T) in try factory(arg1) }
public func resolve<T, A>(tag tag: DependencyTagConvertible? = nil, arguments 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, arguments 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: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2) throws -> T) -> DefinitionOf<T, (Arg1, Arg2) throws -> T> {
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: 2) { container, tag in try factory(try container.resolve(tag: tag), try container.resolve(tag: tag)) }
public func register<T, A, B>(scope: ComponentScope = .Unique, tag: DependencyTagConvertible? = nil, 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: DependencyTagConvertible? = 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, arguments 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, arguments 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: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3) throws -> T> {
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: 3) { container, tag in try factory(try container.resolve(tag: tag), try container.resolve(), try container.resolve(tag: tag)) }
public func register<T, A, B, C>(scope: ComponentScope = .Unique, tag: DependencyTagConvertible? = nil, 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: DependencyTagConvertible? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3) throws -> T {
return try resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3) throws -> T) in try factory(arg1, arg2, arg3) }
public func resolve<T, A, B, C>(tag tag: DependencyTagConvertible? = nil, arguments 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, arguments 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: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4) throws -> T> {
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: 4) { container, tag in try factory(try container.resolve(tag: tag), try container.resolve(tag: tag), try container.resolve(tag: tag), try container.resolve(tag: tag)) }
public func register<T, A, B, C, D>(scope: ComponentScope = .Unique, tag: DependencyTagConvertible? = nil, 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: DependencyTagConvertible? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4) throws -> T {
return try resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4) throws -> T) in try factory(arg1, arg2, arg3, arg4) }
public func resolve<T, A, B, C, D>(tag tag: DependencyTagConvertible? = nil, arguments 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, arguments 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: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4, Arg5) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4, Arg5) throws -> T> {
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: 5) { container, tag in try factory(try container.resolve(tag: tag), try container.resolve(tag: tag), try container.resolve(tag: tag), try container.resolve(tag: tag), try container.resolve(tag: tag)) }
public func register<T, A, B, C, D, E>(scope: ComponentScope = .Unique, tag: DependencyTagConvertible? = nil, 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: DependencyTagConvertible? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5) throws -> T {
return try resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5) throws -> T) in try factory(arg1, arg2, arg3, arg4, arg5) }
public func resolve<T, A, B, C, D, E>(tag tag: DependencyTagConvertible? = nil, arguments 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, arguments 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: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) throws -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) throws -> T> {
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: 6) { container, tag in try factory(try container.resolve(tag: tag), try container.resolve(tag: tag), try container.resolve(tag: tag), try container.resolve(tag: tag), try container.resolve(tag: tag), try container.resolve(tag: tag)) }
public func register<T, A, B, C, D, E, F>(scope: ComponentScope = .Unique, tag: DependencyTagConvertible? = nil, 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: DependencyTagConvertible? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6) throws -> T {
return try resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) throws -> T) in try factory(arg1, arg2, arg3, arg4, arg5, arg6) }
public func resolve<T, A, B, C, D, E, F>(tag tag: DependencyTagConvertible? = nil, arguments 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, arguments 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)) }
}
}
//MARK - Deprecated methods
extension DependencyContainer {
@available(*, deprecated=4.6.1, message="Use resolve(tag:arguments:) instead")
public func resolve<T, A>(tag tag: DependencyTagConvertible? = nil, withArguments arg1: A) throws -> T {
return try resolve(tag: tag) { factory in try factory(arg1) }
}
@available(*, deprecated=4.6.1, message="Use resolve(_:tag:arguments:) instead")
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) }
}
@available(*, deprecated=4.6.1, message="Use resolve(tag:arguments:_:) instead")
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) }
}
@available(*, deprecated=4.6.1, message="Use resolve(_:tag:arguments:_:) instead")
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)) }
}
@available(*, deprecated=4.6.1, message="Use resolve(tag:arguments:_:_:) instead")
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) }
}
@available(*, deprecated=4.6.1, message="Use resolve(_:tag:arguments:_:_:) instead")
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)) }
}
@available(*, deprecated=4.6.1, message="Use resolve(tag:arguments:_:_:_:) instead")
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) }
}
@available(*, deprecated=4.6.1, message="Use resolve(_:tag:arguments:_:_:_:) instead")
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)) }
}
@available(*, deprecated=4.6.1, message="Use resolve(tag:arguments:_:_:_:_:) instead")
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) }
}
@available(*, deprecated=4.6.1, message="Use resolve(_: tag:arguments:_:_:_:_:) instead")
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)) }
}
@available(*, deprecated=4.6.1, message="Use resolve(tag:arguments:_:_:_:_:_:) instead")
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) }
}
@available(*, deprecated=4.6.1, message="Use resolve(_:tag:arguments:_:_:_:_:_:) instead")
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)) }
}
}
+87
View File
@@ -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(type: F.self, typeOfArguments: 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(key: DefinitionKey) -> KeyDefinitionPair? {
var forwardingDefinitions = self.definitions.map({ (key: $0.0, definition: $0.1) })
forwardingDefinitions = filter(forwardingDefinitions, byKeyAndTypeOfArguments: key)
forwardingDefinitions = order(forwardingDefinitions, byTag: key.tag)
//we need to carry on original tag
return forwardingDefinitions.first.map({ ($0.key.tagged(key.tag), $0.definition) })
}
}
+60
View File
@@ -22,6 +22,66 @@
// THE SOFTWARE.
//
public enum LogLevel {
case Verbose
case Errors
case None
}
public var logLevel: LogLevel = .Errors
func log(logLevel: LogLevel, _ message: Any) {
guard case logLevel = Dip.logLevel else { return }
print(message)
}
///Internal protocol used to unwrap optional values.
protocol BoxType {
var unboxed: Any? { get }
}
extension Optional: BoxType {
var unboxed: Any? {
switch self {
case let .Some(value): return value
default: return nil
}
}
}
extension ImplicitlyUnwrappedOptional: BoxType {
var unboxed: Any? {
switch self {
case let .Some(value): return value
default: return nil
}
}
}
class Box<T> {
var unboxed: T
init(_ value: T) {
self.unboxed = value
}
}
protocol WeakBoxType {
var unboxed: AnyObject? { get }
}
class WeakBox<T>: WeakBoxType {
weak var unboxed: AnyObject?
var value: T? {
return unboxed as? T
}
init(_ value: T) {
guard let value = value as? AnyObject else {
fatalError("Can not store weak reference to not a class instance (\(T.self))")
}
self.unboxed = value
}
}
extension Dictionary {
subscript(key: Key?) -> Value? {
get {
@@ -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,7 +104,9 @@ class AutoInjectionTests: XCTestCase {
("testThatThereIsNoRetainCycleBetweenAutoInjectedCircularDependencies", testThatThereIsNoRetainCycleBetweenAutoInjectedCircularDependencies),
("testThatItCallsDidInjectOnAutoInjectedProperty", testThatItCallsDidInjectOnAutoInjectedProperty),
("testThatNoErrorThrownWhenOptionalPropertiesAreNotAutoInjected", testThatNoErrorThrownWhenOptionalPropertiesAreNotAutoInjected),
("testThatItResolvesTaggedAutoInjectedProperties", testThatItResolvesTaggedAutoInjectedProperties)
("testThatItResolvesTaggedAutoInjectedProperties", testThatItResolvesTaggedAutoInjectedProperties),
("testThatItPassesTagToAutoInjectedProperty", testThatItPassesTagToAutoInjectedProperty),
("testThatItDoesNotPassTagToAutoInjectedPropertyWithExplicitTag", testThatItDoesNotPassTagToAutoInjectedPropertyWithExplicitTag)
]
}
@@ -120,16 +120,32 @@ class AutoInjectionTests: XCTestCase {
#endif
func testThatItResolvesAutoInjectedDependencies() {
container.register(.ObjectGraph) { ServerImp() as Server }
container.register(.ObjectGraph) { ClientImp() as Client }
container.register(.Shared) { ServerImp() as Server }
container.register(.Shared) { ClientImp() as Client }
let client = try! container.resolve() as Client
let server = client.server
XCTAssertTrue(client === server?.client)
}
func testThatItCanSetInjectedProperty() {
container.register(.Shared) { ServerImp() as Server }
container.register(.Shared) { 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 }
container.register(.Shared) { ClientImp() as Client }
AssertThrows(expression: try container.resolve() as Client)
}
@@ -156,14 +172,14 @@ class AutoInjectionTests: XCTestCase {
var serverBlockWasCalled = false
//given
container.register(.ObjectGraph) { ServerImp() as Server }
.resolveDependencies { (container, server) -> () in
container.register(.Shared) { ServerImp() as Server }
.resolvingProperties { (container, server) -> () in
serverBlockWasCalled = true
}
var clientBlockWasCalled = false
container.register(.ObjectGraph) { ClientImp() as Client }
.resolveDependencies { (container, client) -> () in
container.register(.Shared) { ClientImp() as Client }
.resolvingProperties { (container, client) -> () in
clientBlockWasCalled = true
}
@@ -177,13 +193,13 @@ class AutoInjectionTests: XCTestCase {
func testThatItReusesResolvedAutoInjectedInstances() {
//given
container.register(.ObjectGraph) { ServerImp() as Server }
.resolveDependencies { (container, server) -> () in
container.register(.Shared) { ServerImp() as Server }
.resolvingProperties { (container, server) -> () in
server.anotherClient = try! container.resolve() as Client
}
container.register(.ObjectGraph) { ClientImp() as Client }
.resolveDependencies { (container, client) -> () in
container.register(.Shared) { ClientImp() as Client }
.resolvingProperties { (container, client) -> () in
client.anotherServer = try! container.resolve() as Server
}
@@ -205,9 +221,9 @@ class AutoInjectionTests: XCTestCase {
func testThatItReusesAutoInjectedInstancesOnNextResolveOrAutoInjection() {
//given
container.register(.ObjectGraph) { Obj1() }
container.register(.ObjectGraph) { Obj2() }
container.register(.ObjectGraph) { Obj3(obj: try self.container.resolve()) }
container.register(.Shared) { Obj1() }
container.register(.Shared) { Obj2() }
container.register(.Shared) { Obj3(obj: try self.container.resolve()) }
//when
let obj2 = try! container.resolve() as Obj2
@@ -222,8 +238,8 @@ class AutoInjectionTests: XCTestCase {
func testThatThereIsNoRetainCycleBetweenAutoInjectedCircularDependencies() {
//given
container.register(.ObjectGraph) { ServerImp() as Server }
container.register(.ObjectGraph) { ClientImp() as Client }
container.register(.Shared) { ServerImp() as Server }
container.register(.Shared) { ClientImp() as Client }
//when
var client: Client? = try! container.resolve() as Client
@@ -246,8 +262,8 @@ class AutoInjectionTests: XCTestCase {
AutoInjectionTests.serverDidInjectCalled = false
//given
container.register(.ObjectGraph) { ServerImp() as Server }
container.register(.ObjectGraph) { ClientImp() as Client }
container.register(.Shared) { ServerImp() as Server }
container.register(.Shared) { ClientImp() as Client }
//when
try! container.resolve() as Client
@@ -259,17 +275,17 @@ class AutoInjectionTests: XCTestCase {
func testThatNoErrorThrownWhenOptionalPropertiesAreNotAutoInjected() {
//given
container.register(.ObjectGraph) { ServerImp() as Server }
container.register(.ObjectGraph) { ClientImp() as Client }
container.register(.Shared) { ServerImp() as Server }
container.register(.Shared) { ClientImp() as Client }
AssertNoThrow(expression: try container.resolve() as Client, "Container should not throw error if failed to resolve optional auto-injected properties.")
}
func testThatItResolvesTaggedAutoInjectedProperties() {
//given
container.register(.ObjectGraph) { ServerImp() as Server }
container.register(tag: "tagged", .ObjectGraph) { ServerImp() as Server }
container.register(.ObjectGraph) { ClientImp() as Client }
container.register(.Shared) { ServerImp() as Server }
container.register(tag: "tagged", .Shared) { ServerImp() as Server }
container.register(.Shared) { ClientImp() as Client }
//when
let client = try! container.resolve() as Client
@@ -280,6 +296,65 @@ 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(.Shared) { ServerImp() as Server }
container.register(tag: "tagged", .Shared) { ServerImp() as Server }
container.register(.Shared) { 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(.Shared) { ServerImp() as Server }
container.register(tag: "tagged", .Shared) { ServerImp() as Server }
container.register(.Shared) { ClientImp() as Client }
.resolvingProperties { (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)
}
func testThatItAutoInjectsPropertyWithCollaboratingContainer() {
let collaborator = DependencyContainer()
collaborator.register(.Shared) { ServerImp() as Server }
container.register(.Shared) { ClientImp() as Client }
container.collaborate(with: collaborator)
collaborator.collaborate(with: container)
let client = try! container.resolve() as Client
let server = client.server
XCTAssertTrue(client === server?.client)
}
}
+463
View File
@@ -0,0 +1,463 @@
//
// 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),
("testThatItDoesNotReuseInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgainWithAnotherTag", testThatItDoesNotReuseInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgainWithAnotherTag),
("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(.Shared) { ServiceImp1() as Service }
container.register(.Shared) { ServiceImp2() }
container.register(.Shared) { 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(.Shared) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
//1 arg
container.register(.Shared) { AutoWiredClientImp(service1: try self.container.resolve(), service2: $0) as AutoWiredClient }
//2 args
var factoryWithMostNumberOfArgumentsCalled = false
container.register(.Shared) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
.resolvingProperties { _ in
factoryWithMostNumberOfArgumentsCalled = true
}
container.register(.Shared) { ServiceImp1() as Service }
container.register(.Shared) { ServiceImp2() }
//when
let _ = try! container.resolve() as AutoWiredClient
//then
XCTAssertTrue(factoryWithMostNumberOfArgumentsCalled)
}
func testThatItThrowsAmbiguityErrorWhenUsingAutoWire() {
//given
//1 arg
container.register(.Shared) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
//1 arg
container.register(.Shared) { AutoWiredClientImp(service1: try self.container.resolve(), service2: $0) as AutoWiredClient }
container.register(.Shared) { ServiceImp1() as Service }
container.register(.Shared) { 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(.Shared) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
//1 arg
container.register(.Shared) { AutoWiredClientImp(service1: try self.container.resolve(), service2: $0) as AutoWiredClient }
//2 args
container.register(.Shared) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
//1 arg tagged
var taggedFactoryWithMostNumberOfArgumentsCalled = false
container.register(tag: "tag", .Shared) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
//2 arg tagged
container.register(tag: "tag", .Shared) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }.resolvingProperties { _ in
taggedFactoryWithMostNumberOfArgumentsCalled = true
}
container.register(.Shared) { ServiceImp1() as Service }
container.register(.Shared) { ServiceImp2() }
//when
let _ = try! container.resolve(tag: "tag") as AutoWiredClient
//then
XCTAssertTrue(taggedFactoryWithMostNumberOfArgumentsCalled)
}
func testThatItFallbackToNotTaggedFactoryWhenUsingAutoWire() {
//given
//1 arg
var notTaggedFactoryWithMostNumberOfArgumentsCalled = false
container.register(.Shared) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }.resolvingProperties { _ in
notTaggedFactoryWithMostNumberOfArgumentsCalled = true
}
//1 arg tagged
container.register(tag: "tag", .Shared) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
container.register(.Shared) { ServiceImp1() as Service }
container.register(.Shared) { ServiceImp2() }
//when
let _ = try! container.resolve(tag: "other tag") as AutoWiredClient
//then
XCTAssertTrue(notTaggedFactoryWithMostNumberOfArgumentsCalled)
}
func testThatItDoesNotTryToUseAutoWiringWhenCallingResolveWithArguments() {
//given
container.register(.Shared) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
container.register(.Shared) { ServiceImp1() as Service }
container.register(.Shared) { ServiceImp2() }
//when
let service = try! container.resolve() as Service
AssertThrows(expression: try container.resolve(arguments: service) as AutoWiredClient,
"Container should not use auto-wiring when resolving with runtime arguments")
}
func testThatItDoesNotUseAutoWiringWhenFailedToResolveLowLevelDependency() {
//given
container.register(.Shared) { AutoWiredClientImp() as AutoWiredClient }
.resolvingProperties { 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(type: ServiceImp1.self, typeOfArguments: Any.self))
}
container.register(.Shared) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
.resolvingProperties { 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(.Shared) { ServiceImp1() as Service }
container.register(.Shared) { 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(.Shared) { ServiceImp1() as Service }
container.register(.Shared) { ServiceImp2() }
var anotherInstance: AutoWiredClient?
container.register(.Shared) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
.resolvingProperties { 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 testThatItReusesInstancesResolvedWithoutAutoWiringWhenUsingAutoWiringAgain() {
//given
container.register(.Shared) { ServiceImp1() as Service }
container.register(.Shared) { ServiceImp2() }
var anotherInstance: AutoWiredClient?
container.register(.Shared) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
.resolvingProperties { container, _ in
if anotherInstance == nil {
anotherInstance = try! container.resolve() as AutoWiredClient
}
}
//when
let service1 = try! container.resolve() as Service?
let service2 = try! container.resolve() as ServiceImp2
let resolved = try! container.resolve(arguments: service1, service2) 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(.Shared) { ServiceImp1() as Service }
container.register(.Shared) { ServiceImp2() }
var anotherInstance: AutoWiredClient?
container.register(tag: "tag", .Shared) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
.resolvingProperties { 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(.Shared) { ServiceImp1() as Service }
container.register(.Shared) { ServiceImp2() }
var anotherInstance: AutoWiredClient?
container.register(.Shared) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
.resolvingProperties { 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(.Shared) { ServiceImp1() as Service }
container.register(tag: "tag", .Shared) { ServiceImp2() as Service }
container.register(.Shared) { (dep1: Service) -> ServiceImp3 in
XCTAssertTrue(dep1 is ServiceImp2)
return ServiceImp3()
}
//when
let _ = try! container.resolve(tag: "tag") as ServiceImp3
}
func testThatItUsesTagToResolveDependenciesWithAutoWiringWith2Arguments() {
//given
container.register(.Shared) { ServiceImp1() as Service }
container.register(tag: "tag", .Shared) { ServiceImp2() as Service }
container.register(.Shared) { (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(.Shared) { ServiceImp1() as Service }
container.register(tag: "tag", .Shared) { ServiceImp2() as Service }
container.register(.Shared) { (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(.Shared) { ServiceImp1() as Service }
container.register(tag: "tag", .Shared) { ServiceImp2() as Service }
container.register(.Shared) { (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(.Shared) { ServiceImp1() as Service }
container.register(tag: "tag", .Shared) { ServiceImp2() as Service }
container.register(.Shared) { (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(.Shared) { ServiceImp1() as Service }
container.register(tag: "tag", .Shared) { ServiceImp2() as Service }
container.register(.Shared) { (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(.Shared) { ServiceImp1() as Service }
container.register(.Shared) { ServiceImp2() }
container.register(.Shared) { 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)
}
}
+368
View File
@@ -0,0 +1,368 @@
//
// Dip
//
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import XCTest
@testable import Dip
private protocol Service: class {}
private class ServiceImp1: Service {}
private class ServiceImp2: Service {}
private class Server {
weak var client: Client?
init() {}
}
private class Client {
var server: Server
init(server: Server) {
self.server = server
}
}
class ComponentScopeTests: XCTestCase {
let container = DependencyContainer()
#if os(Linux)
static var allTests: [(String, ComponentScopeTests -> () throws -> Void)] {
return [
("testThatUniqueIsDefaultScope", testThatUniqueIsDefaultScope),
("testThatScopeCanBeChanged", testThatScopeCanBeChanged),
("testThatItResolvesTypeAsNewInstanceForUniqueScope", testThatItResolvesTypeAsNewInstanceForUniqueScope),
("testThatItReusesInstanceForSingletonScope", testThatItReusesInstanceForSingletonScope),
("testThatSingletonIsNotReusedAcrossContainers", testThatSingletonIsNotReusedAcrossContainers),
("testThatSingletonIsReleasedWhenDefinitionIsRemoved", testThatSingletonIsReleasedWhenDefinitionIsRemoved),
("testThatSingletonIsReleasedWhenDefinitionIsOverridden", testThatSingletonIsReleasedWhenDefinitionIsOverridden),
("testThatSingletonIsReleasedWhenContainerIsReset", testThatSingletonIsReleasedWhenContainerIsReset),
("testThatItReusesInstanceInSharedScopeDuringResolve", testThatItReusesInstanceInSharedScopeDuringResolve),
("testThatItDoesNotReuseInstanceInSharedScopeInNextResolve", testThatItDoesNotReuseInstanceInSharedScopeInNextResolve),
("testThatItDoesNotReuseInstanceInSharedScopeResolvedForNilTag", testThatItDoesNotReuseInstanceInSharedScopeResolvedForNilTagWhenResolvingForAnotherTag),
("testThatItReusesInstanceInSharedScopeResolvedForNilTag", testThatItReusesInstanceInSharedScopeResolvedForNilTag),
("testThatItReusesResolvedInstanceWhenResolvingOptional", testThatItReusesResolvedInstanceWhenResolvingOptional),
("testThatItHoldsWeakReferenceToWeakSingletonInstance",
testThatItHoldsWeakReferenceToWeakSingletonInstance)
]
}
func setUp() {
container.reset()
}
#else
override func setUp() {
container.reset()
}
#endif
func testThatUniqueIsDefaultScope() {
let def = container.register { ServiceImp1() as Service }
XCTAssertEqual(def.scope, ComponentScope.Unique)
}
func testThatScopeCanBeChanged() {
let def = container.register(.Singleton) { ServiceImp1() as Service }
XCTAssertEqual(def.scope, ComponentScope.Singleton)
}
func testThatItResolvesTypeAsNewInstanceForUniqueScope() {
//given
container.register { ServiceImp1() as Service }
//when
let service1 = try! container.resolve() as Service
let service2 = try! container.resolve() as Service
//then
XCTAssertFalse(service1 === service2)
}
func testThatItReusesInstanceForSingletonScope() {
func test(scope: ComponentScope, line: UInt = #line) {
//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, line: line)
}
test(.Singleton)
test(.WeakSingleton)
test(.EagerSingleton)
}
func testThatSingletonIsNotReusedAcrossContainers() {
func test(scope: ComponentScope, line: UInt = #line) {
//given
let def = container.register(scope) { 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", line: line)
}
test(.Singleton)
test(.WeakSingleton)
test(.EagerSingleton)
}
func testThatSingletonIsReleasedWhenDefinitionIsRemoved() {
func test(scope: ComponentScope, line: UInt = #line) {
//given
let def = container.register(scope) { 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", line: line)
}
test(.Singleton)
test(.WeakSingleton)
test(.EagerSingleton)
}
func testThatSingletonIsReleasedWhenDefinitionIsOverridden() {
func test(scope: ComponentScope, line: UInt = #line) {
//given
let def = container.register(scope) { 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", line: line)
}
test(.Singleton)
test(.WeakSingleton)
test(.EagerSingleton)
}
func testThatSingletonIsReleasedWhenContainerIsReset() {
func test(scope: ComponentScope, line: UInt = #line) {
//given
let def = container.register(scope) { 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", line: line)
}
test(.Singleton)
test(.WeakSingleton)
test(.EagerSingleton)
}
func testThatItReusesInstanceInSharedScopeDuringResolve() {
//given
container.register(.Shared) { Client(server: try self.container.resolve()) as Client }
container.register(.Shared) { Server() as Server }
.resolvingProperties { container, server in
server.client = try container.resolve() as Client
}
//when
let client = try! container.resolve() as Client
//then
let server = client.server
XCTAssertTrue(server.client === client)
}
func testThatItDoesNotReuseInstanceInSharedScopeInNextResolve() {
//given
container.register(.Shared) { Client(server: try self.container.resolve()) as Client }
container.register(.Shared) { Server() as Server }
.resolvingProperties { container, server in
server.client = try container.resolve() as Client
}
//when
let client = try! container.resolve() as Client
let server = client.server
let anotherClient = try! container.resolve() as Client
let anotherServer = anotherClient.server
//then
XCTAssertFalse(server === anotherServer)
XCTAssertFalse(client === anotherClient)
}
func testThatItDoesNotReuseInstanceInSharedScopeResolvedForNilTagWhenResolvingForAnotherTag() {
//given
var service2: Service?
container.register(.Shared) { ServiceImp1() as Service }
.resolvingProperties { (c, _) in
//when service1 is resolved using this definition due to fallback to nil tag
service2 = try c.resolve(tag: "service") as Service
//then we don't want every next resolve of service for other tags to reuse it
XCTAssertTrue(service2 is ServiceImp2)
}
container.register(tag: "service", .Shared) { ServiceImp2() as Service}
//when
let service1 = try! container.resolve(tag: "tag") as Service
//then
XCTAssertTrue(service1 is ServiceImp1)
}
func testThatItReusesInstanceInSharedScopeResolvedForNilTag() {
//given
var service2: Service?
container.register(.Shared) { ServiceImp1() as Service }
.resolvingProperties { (c, service1) in
guard service2 == nil else { return }
//when service1 is resolved using this definition due to fallback to nil tag
//and service is resolved again with another (existing) tag
service2 = try c.resolve(tag: "tag") as Service
//than we don't want every next resolve of service to reuse it
XCTAssertTrue(service1 as! ServiceImp1 === service1)
}
//when
let service1 = try! container.resolve(tag: "tag") as Service
//then
XCTAssertTrue(service1 is ServiceImp1)
}
func testThatOnlyEagerSingletonIsCreatedWhenContainerIsBootsrapped() {
//given
var eagerSingletonResolved = false
container.register(tag: "eager", .EagerSingleton) { ServiceImp1() as Service }
.resolvingProperties { container, service in eagerSingletonResolved = true }
container.register(tag: "singleton", .Singleton) { ServiceImp1() as Service }
.resolvingProperties { container, service in XCTFail() }
container.register(tag: "prototype", .Unique) { ServiceImp1() as Service }
.resolvingProperties { container, service in XCTFail() }
container.register(tag: "graph", .Shared) { ServiceImp1() as Service }
.resolvingProperties { 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(.Shared) { ServiceImp1() as Service }
.resolvingProperties { 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)
}
func testThatItHoldsWeakReferenceToWeakSingletonInstance() {
//given
container.register(.WeakSingleton) { ServiceImp1() as Service }
var strongSingleton: Service? = try! container.resolve() as Service
weak var weakSingleton = try! container.resolve() as Service
//then
XCTAssertTrue(weakSingleton === strongSingleton)
//when
strongSingleton = nil
//then
XCTAssertNil(weakSingleton)
}
func testThatCollaboratingContainersReuseSingletonsResolvedByAnotherContainer() {
func test(scope: ComponentScope, line: UInt = #line) {
let container1 = DependencyContainer()
container1.register(scope) { ServiceImp1() as Service }
let container2 = DependencyContainer()
container1.collaborate(with: container2)
container2.collaborate(with: container1)
try! container1.bootstrap()
try! container2.bootstrap()
let service1 = try! container1.resolve() as Service
let service2 = try! container2.resolve() as Service
XCTAssertTrue(service1 === service2, line: line, "\(scope) should be reused when first resolved from another container")
}
test(.Singleton)
test(.WeakSingleton)
test(.EagerSingleton)
}
}
+222
View File
@@ -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
}.resolvingProperties { _ 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()
}.resolvingProperties { _ 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
}.resolvingProperties { _ 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()
}.resolvingProperties { _ 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
}.resolvingProperties { _ 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()
}.resolvingProperties { _ 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()
}.resolvingProperties { _ 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()
}.resolvingProperties { _ 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
}.resolvingProperties { _ in
}
container.register { () -> ServiceImp1 in
XCTAssertNotNil(self.container.context.tag)
XCTAssertTrue(DependencyContainer.Tag.String("tag") ~= self.container.context.tag!)
return ServiceImp1()
}.resolvingProperties { _ 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
}.resolvingProperties { _ 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()
}.resolvingProperties { _ 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()
}.resolvingProperties { _ 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(type: Service.self, typeOfArguments: F1.self, tag: tag1)
let equalKey2 = DefinitionKey(type: Service.self, typeOfArguments: F1.self, tag: 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(type: Service.self, typeOfArguments: F1.self, tag: nil)
let keyWithDifferentType2 = DefinitionKey(type: AnyObject.self, typeOfArguments: F1.self, tag: 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(type: Service.self, typeOfArguments: F1.self, tag: nil)
let keyWithDifferentFactory2 = DefinitionKey(type: Service.self, typeOfArguments: F2.self, tag: 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(type: Service.self, typeOfArguments: F1.self, tag: tag1)
let keyWithDifferentTag2 = DefinitionKey(type: Service.self, typeOfArguments: F1.self, tag: tag2)
XCTAssertNotEqual(keyWithDifferentTag1, keyWithDifferentTag2)
XCTAssertNotEqual(keyWithDifferentTag1.hashValue, keyWithDifferentTag2.hashValue)
@@ -85,12 +86,13 @@ 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: .Unique) { ServiceImp() as Service }
.resolvingProperties { container, service in
blockCalled = true
}
//when
try! def.resolveDependenciesOf(ServiceImp(), withContainer: DependencyContainer())
try! def.resolveProperties(instance: ServiceImp(), container: DependencyContainer())
//then
XCTAssertTrue(blockCalled)
@@ -100,15 +102,24 @@ 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: .Unique) { ServiceImp() as Service }
.resolvingProperties { container, service in
blockCalled = true
}
//when
try! def.resolveDependenciesOf(String(), withContainer: DependencyContainer())
try! def.resolveProperties(instance: String(), container: DependencyContainer())
//then
XCTAssertFalse(blockCalled)
}
func testThatItRegisteresOptionalTypesAsForwardedTypes() {
let def = DefinitionOf<Service, () -> Service>(scope: .Unique) { ServiceImp() as Service }
XCTAssertTrue(def.implementingTypes.contains({ $0 == Service?.self }))
XCTAssertTrue(def.implementingTypes.contains({ $0 == Service!.self }))
}
}
+706
View File
@@ -0,0 +1,706 @@
//
// 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 ResolvableService: Service, Resolvable {
var didResolveDependenciesCalled = false
func didResolveDependencies() {
XCTAssertFalse(didResolveDependenciesCalled, "didResolveDependencies should be called only once per instance")
didResolveDependenciesCalled = true
}
}
class DipTests: XCTestCase {
let container = DependencyContainer()
#if os(Linux)
static var allTests: [(String, DipTests -> () throws -> Void)] {
return [
("testThatCreatingContainerWithConfigBlockDoesNotCreateRetainCycle", testThatCreatingContainerWithConfigBlockDoesNotCreateRetainCycle),
("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),
("testContainerCollaborators", testContainerCollaborators)
]
}
func setUp() {
container.reset()
}
#else
override func setUp() {
container.reset()
}
#endif
func testThatCreatingContainerWithConfigBlockDoesNotCreateRetainCycle() {
var container: DependencyContainer! = DependencyContainer() { container in
//compiler crashes if you try to capture container in capture list
//so instead we capture it in a variable
unowned let container = container
container.register { ServiceImp1() }
container.register { (_: ServiceImp1)->Service in
//referencing container in factory
let _ = container
return ServiceImp1() as Service
}.resolvingProperties { container, _ in
//when container is passed as argument there will be no retain cycle
let _ = container
}
}
let _ = try! container.resolve() as Service
weak var weakContainer = container
container = nil
XCTAssertNil(weakContainer)
}
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 }.resolvingProperties { (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(type: Service.self, typeOfArguments: Void.self, tag: 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(type: Service.self, typeOfArguments: Void.self, tag: 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(type: Service.self, typeOfArguments: Void.self, tag: "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(type: Service.self, typeOfArguments: Void.self, tag: "other tag")
XCTAssertEqual(key, expectedKey)
return true
}
}
func testThatItThrowsErrorIfCanNotFindDefinitionForFactoryWithArguments() {
//given
container.register { ServiceImp1() as Service }
//when
AssertThrows(expression: try container.resolve(arguments: "some string") as Service) { error in
guard case let DipError.DefinitionNotFound(key) = error else { return false }
//then
let expectedKey = DefinitionKey(type: Service.self, typeOfArguments: String.self, tag: nil)
XCTAssertEqual(key, expectedKey)
return true
}
//and when
AssertThrows(expression: try container.resolve(Service.self, arguments: "some string")) { error in
guard case let DipError.DefinitionNotFound(key) = error else { return false }
//then
let expectedKey = DefinitionKey(type: Service.self, typeOfArguments: String.self, tag: nil)
XCTAssertEqual(key, expectedKey)
return true
}
}
func testThatItThrowsErrorIfConstructorThrows() {
//given
let failedKey = DefinitionKey(type: Any.self, typeOfArguments: 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(type: Any.self, typeOfArguments: Any.self)
let expectedError = DipError.DefinitionNotFound(key: failedKey)
container.register { ServiceImp1() as Service }
.resolvingProperties { 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() {
//given
container.register { ResolvableService() as Service }
.resolvingProperties { _, service in
XCTAssertFalse((service as! ResolvableService).didResolveDependenciesCalled, "didResolveDependencies should not be called yet")
return
}
container.register(tag: "graph", .Shared) { ResolvableService() as Service }
.resolvingProperties { _, service in
XCTAssertFalse((service as! ResolvableService).didResolveDependenciesCalled, "didResolveDependencies should not be called yet")
return
}
container.register(tag: "singleton", .Singleton) { ResolvableService() as Service }
.resolvingProperties { _, 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 }
.resolvingProperties { _, 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(.Shared) { try ResolvableServer(client: self.container.resolve()) as Server }
.resolvingProperties { (container: DependencyContainer, server: Server) in
let server = server as! ResolvableServer
server.secondClient = try container.resolve() as Client
}
container.register(.Shared) { ResolvableClient() as Client }
.resolvingProperties { (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() }
.resolvingProperties { 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 }
.resolvingProperties { _ in
createdService2 = true
}
container.register() { (arg: String) in ServiceImp1() }
.resolvingProperties { _ 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(type: Service.self, typeOfArguments: Void.self, tag: 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 }
}
}
}
extension DipTests {
func testThatItCanResolveUsingContainersCollaboration() {
//given
let collaborator = DependencyContainer()
collaborator.register { ResolvableService() as Service }
//when
container.collaborate(with: collaborator)
//then
AssertNoThrow(expression: try container.resolve() as Service)
AssertNoThrow(expression: try container.resolve(Service.self))
}
func testThatCollaboratingWithSelfIsIgnored() {
let collaborator = DependencyContainer()
collaborator.collaborate(with: collaborator)
XCTAssertTrue(collaborator._collaborators.isEmpty, "Container should not collaborate with itself")
}
func testThatCollaboratingContainersAreWeakReferences() {
//given
var collaborator: DependencyContainer? = DependencyContainer()
weak var weakCollaborator = collaborator
//when
container.collaborate(with: collaborator!)
collaborator = nil
//then
XCTAssertNil(weakCollaborator)
}
func testThatCollaboratingContainersReuseInstancesResolvedByAnotherContainer() {
//given
class ServerImp: Server {
weak var client: Client?
init(client: Client) { self.client = client }
}
class ClientImp: Client {
var server: Server?
var anotherServer: Server?
init() {}
}
let serverContainer = DependencyContainer()
serverContainer.register(.Shared) { ServerImp(client: $0) as Server }
let clientContainer = DependencyContainer()
clientContainer.register(.Shared) { ClientImp() as Client }
.resolvingProperties { container, client in
let client = client as! ClientImp
client.server = try container.resolve() as Server
client.anotherServer = try container.resolve() as Server
}
//when
serverContainer.collaborate(with: clientContainer)
clientContainer.collaborate(with: serverContainer)
var client = try? clientContainer.resolve() as Client
//then
XCTAssertNotNil(client)
XCTAssertTrue(client === client?.server?.client)
XCTAssertTrue(client === (client as? ClientImp)?.anotherServer?.client)
XCTAssertTrue(client?.server === (client as? ClientImp)?.anotherServer)
client = try? serverContainer.resolve() as Client
//then
XCTAssertNotNil(client)
XCTAssertTrue(client === client?.server?.client)
XCTAssertTrue(client === (client as? ClientImp)?.anotherServer?.client)
XCTAssertTrue(client?.server === (client as? ClientImp)?.anotherServer)
}
}
View File
@@ -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),
@@ -86,10 +86,16 @@ class RuntimeArgumentsTests: XCTestCase {
})
//when
let service = try! container.resolve(withArguments: arg1) as Service
let service = try! container.resolve(arguments: arg1) as Service
//then
XCTAssertTrue(service is ServiceImp1)
//when
let anyService = try! container.resolve(Service.self, arguments: arg1)
//then
XCTAssertTrue(anyService is ServiceImp1)
}
func testThatItResolvesInstanceWithTwoArguments() {
@@ -102,10 +108,16 @@ class RuntimeArgumentsTests: XCTestCase {
}
//when
let service = try! container.resolve(withArguments: arg1, arg2) as Service
let service = try! container.resolve(arguments: arg1, arg2) as Service
//then
XCTAssertTrue(service is ServiceImp1)
//when
let anyService = try! container.resolve(Service.self, arguments: arg1, arg2)
//then
XCTAssertTrue(anyService is ServiceImp1)
}
func testThatItResolvesInstanceWithThreeArguments() {
@@ -118,10 +130,16 @@ class RuntimeArgumentsTests: XCTestCase {
}
//when
let service = try! container.resolve(withArguments: arg1, arg2, arg3) as Service
let service = try! container.resolve(arguments: arg1, arg2, arg3) as Service
//then
XCTAssertTrue(service is ServiceImp1)
//when
let anyService = try! container.resolve(Service.self, arguments: arg1, arg2, arg3)
//then
XCTAssertTrue(anyService is ServiceImp1)
}
func testThatItResolvesInstanceWithFourArguments() {
@@ -135,10 +153,16 @@ class RuntimeArgumentsTests: XCTestCase {
}
//when
let service = try! container.resolve(withArguments: arg1, arg2, arg3, arg4) as Service
let service = try! container.resolve(arguments: arg1, arg2, arg3, arg4) as Service
//then
XCTAssertTrue(service is ServiceImp1)
//when
let anyService = try! container.resolve(Service.self, arguments: arg1, arg2, arg3, arg4)
//then
XCTAssertTrue(anyService is ServiceImp1)
}
func testThatItResolvesInstanceWithFiveArguments() {
@@ -153,10 +177,16 @@ class RuntimeArgumentsTests: XCTestCase {
}
//when
let service = try! container.resolve(withArguments: arg1, arg2, arg3, arg4, arg5) as Service
let service = try! container.resolve(arguments: arg1, arg2, arg3, arg4, arg5) as Service
//then
XCTAssertTrue(service is ServiceImp1)
//when
let anyService = try! container.resolve(Service.self, arguments: arg1, arg2, arg3, arg4, arg5)
//then
XCTAssertTrue(anyService is ServiceImp1)
}
func testThatItResolvesInstanceWithSixArguments() {
@@ -172,10 +202,16 @@ class RuntimeArgumentsTests: XCTestCase {
}
//when
let service = try! container.resolve(withArguments: arg1, arg2, arg3, arg4, arg5, arg6) as Service
let service = try! container.resolve(arguments: arg1, arg2, arg3, arg4, arg5, arg6) as Service
//then
XCTAssertTrue(service is ServiceImp1)
//when
let anyService = try! container.resolve(Service.self, arguments: arg1, arg2, arg3, arg4, arg5, arg6)
//then
XCTAssertTrue(anyService is ServiceImp1)
}
func testThatItRegistersDifferentFactoriesForDifferentNumberOfArguments() {
@@ -185,8 +221,8 @@ class RuntimeArgumentsTests: XCTestCase {
container.register { (a1: Int, a2: Int) in ServiceImp2() as Service }
//when
let service1 = try! container.resolve(withArguments: arg1) as Service
let service2 = try! container.resolve(withArguments: arg1, arg2) as Service
let service1 = try! container.resolve(arguments: arg1) as Service
let service2 = try! container.resolve(arguments: arg1, arg2) as Service
//then
XCTAssertTrue(service1 is ServiceImp1)
@@ -200,8 +236,8 @@ class RuntimeArgumentsTests: XCTestCase {
container.register(factory: { (a1: String) in ServiceImp2() as Service })
//when
let service1 = try! container.resolve(withArguments: arg1) as Service
let service2 = try! container.resolve(withArguments: arg2) as Service
let service1 = try! container.resolve(arguments: arg1) as Service
let service2 = try! container.resolve(arguments: arg2) as Service
//then
XCTAssertTrue(service1 is ServiceImp1)
@@ -215,8 +251,8 @@ class RuntimeArgumentsTests: XCTestCase {
container.register { (a1: String, a2: Int) in ServiceImp2() as Service }
//when
let service1 = try! container.resolve(withArguments: arg1, arg2) as Service
let service2 = try! container.resolve(withArguments: arg2, arg1) as Service
let service1 = try! container.resolve(arguments: arg1, arg2) as Service
let service2 = try! container.resolve(arguments: arg2, arg1) as Service
//then
XCTAssertTrue(service1 is ServiceImp1)
@@ -227,11 +263,11 @@ class RuntimeArgumentsTests: XCTestCase {
//given
let arg1 = 1, arg2 = 2
container.register { (a1: Int, a2: Int) in ServiceImp1() as Service }
let service1 = try! container.resolve(withArguments: arg1, arg2) as Service
let service1 = try! container.resolve(arguments: arg1, arg2) as Service
//when
container.register { (a1: Int, a2: Int) in ServiceImp2() as Service }
let service2 = try! container.resolve(withArguments: arg1, arg2) as Service
let service2 = try! container.resolve(arguments: arg1, arg2) as Service
//then
XCTAssertTrue(service1 is ServiceImp1)
@@ -246,9 +282,9 @@ class RuntimeArgumentsTests: XCTestCase {
container.register { (port: Int, url: String!) in ServiceImp(name: name3, baseURL: url, port: port) as Service }
//when
let service1 = try! container.resolve(withArguments: 80, "http://example.com") as Service
let service2 = try! container.resolve(withArguments: 80, "http://example.com" as String?) as Service
let service3 = try! container.resolve(withArguments: 80, "http://example.com" as String!) as Service
let service1 = try! container.resolve(arguments: 80, "http://example.com") as Service
let service2 = try! container.resolve(arguments: 80, "http://example.com" as String?) as Service
let service3 = try! container.resolve(arguments: 80, "http://example.com" as String!) as Service
//then
XCTAssertEqual(service1.name, name1)
@@ -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),
@@ -186,12 +186,12 @@ class ThreadSafetyTests: XCTestCase {
func testCircularReferenceThreadSafety() {
container.register(.ObjectGraph) {
container.register(.Shared) {
ClientImp(server: try container.resolve()) as Client
}
container.register(.ObjectGraph) { ServerImp() as Server }
.resolveDependencies { container, server in
container.register(.Shared) { ServerImp() as Server }
.resolvingProperties { container, server in
server.client = resolveClientSync()
}
@@ -24,26 +24,20 @@
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)
@@ -53,11 +47,11 @@ func AssertThrows<T>(file: FileString = __FILE__, line: UInt = __LINE__, @autocl
}
}
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()
}
+13
View File
@@ -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)
])