Compare commits
221 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c3b601df0f | |||
| 56b0768d60 | |||
| 8cf87efb95 | |||
| 3aa9b334f1 | |||
| b2a0cda242 | |||
| 07eb66e87d | |||
| 82e3d02497 | |||
| 3b2f5a272f | |||
| eab2d424e7 | |||
| 0f2cda0a52 | |||
| 05c02c9645 | |||
| fa086dad8f | |||
| cdc09f5a03 | |||
| b94ed3bea2 | |||
| 0fd70e65dd | |||
| 97e7f4782f | |||
| f1fdbbc988 | |||
| e368bb3051 | |||
| 4ce4832960 | |||
| d3bd0c27e3 | |||
| be0c39ab6b | |||
| 0de45e7d53 | |||
| 3b421d0cff | |||
| f1234bf2b9 | |||
| b0d7153bfe | |||
| 0193aa6bf6 | |||
| de7ad3ac48 | |||
| 40ad490c48 | |||
| 95cc5df26f | |||
| 09a7115aff | |||
| 15c0002ea3 | |||
| 0644d85a9c | |||
| e1522a0bf3 | |||
| a659415496 | |||
| efe25d575b | |||
| 22c67fa30c | |||
| 7a091da44d | |||
| 9580976dd8 | |||
| a4f0256313 | |||
| 815e9ccd66 | |||
| b9bf9c4d75 | |||
| 530a20999a | |||
| 4f3bc1e498 | |||
| 8dcb136ff0 | |||
| 88d02af706 | |||
| a8777c067e | |||
| db40e8580c | |||
| 334fb384a5 | |||
| 54e1db2232 | |||
| c5c6bd2821 | |||
| 593129c63e | |||
| 9a8d919a13 | |||
| 17a2d64361 | |||
| 27037c2dd7 | |||
| bd253f4b0a | |||
| 882b2a2031 | |||
| 6a14c1f96d | |||
| f60e322445 | |||
| 77f6fde2fa | |||
| a8f7a2972a | |||
| ce9088afc7 | |||
| ef563c65ba | |||
| 90ff39c720 | |||
| e45dbd634f | |||
| f81c267c61 | |||
| d5b07b1916 | |||
| 33993df68a | |||
| dadb68862a | |||
| 2e53aa8ae2 | |||
| 142a348b20 | |||
| 88f101639d | |||
| 5e70633ece | |||
| af57aa26ed | |||
| 6d6d77d603 | |||
| 8d2f6ab8fb | |||
| 2cc5310d4b | |||
| 5cb03a11bb | |||
| aff01c541b | |||
| a8dd47cee5 | |||
| 12738a665f | |||
| 9e7bd51bcd | |||
| ce394e7d2b | |||
| cd2c66f4a8 | |||
| 5464f00bad | |||
| 5b9cc50190 | |||
| a7c8616300 | |||
| 8349fbd8d8 | |||
| 4b1ecbcb8e | |||
| 2f0e1fdb10 | |||
| 4a8a1daeca | |||
| 56554147cb | |||
| a554454afe | |||
| dde2e98953 | |||
| a2c04ed61e | |||
| c64cf720b8 | |||
| 3429c6ae0b | |||
| 451fb03bc8 | |||
| b1ad2a65b2 | |||
| 3f8b83b87c | |||
| b7bf4904e9 | |||
| 199b1aec8f | |||
| 22d19697f1 | |||
| d809177273 | |||
| 420c866726 | |||
| 6e6cc0b1df | |||
| b71f50cef5 | |||
| 462d528474 | |||
| 01c318bd5c | |||
| a0c890c931 | |||
| 9b2a7918d8 | |||
| 218b5a4e9a | |||
| 0b221f3368 | |||
| 3b943c10ef | |||
| a1da60bd14 | |||
| 1825cd7d4c | |||
| e7d8fb41e1 | |||
| 1c398defeb | |||
| ab561546f0 | |||
| c0fb925ab8 | |||
| 80ee4865ce | |||
| d95df1343e | |||
| a83d866cbd | |||
| cc1dcba4b9 | |||
| 0ceb6a3503 | |||
| 4e641a6465 | |||
| 35b6da8556 | |||
| 47bc1913e3 | |||
| 24d341503e | |||
| 3c1331089b | |||
| 7af8957a01 | |||
| 6b68bea55d | |||
| 5b2168ecef | |||
| 5eb0eece56 | |||
| bdf4477774 | |||
| 84573d967b | |||
| 83511d601b | |||
| b24734e1c9 | |||
| ab9abbe7ab | |||
| 298cf83be3 | |||
| 455456e817 | |||
| 905cbdadb0 | |||
| 945caa451a | |||
| bfacca6fd0 | |||
| 21673d1f4d | |||
| 65175fa372 | |||
| 99fb4ea081 | |||
| a1ece4b0ab | |||
| 0b00e13e00 | |||
| 4aec626a1f | |||
| d477392deb | |||
| 3b23dc1a6f | |||
| 1d7d0052bf | |||
| b1fee5c1db | |||
| e19b65d9c2 | |||
| 86bb28da14 | |||
| e4545e3ed3 | |||
| 52455e663c | |||
| 7a10d1e46e | |||
| e95df85503 | |||
| a028c05271 | |||
| e18a4b131f | |||
| e2799279ab | |||
| ffb61be63c | |||
| 0070522341 | |||
| 6842a016bf | |||
| 6a6c4a358e | |||
| 734e67f124 | |||
| aa46ef79b9 | |||
| 2d177b7aea | |||
| 3ada18d756 | |||
| 2441f096d6 | |||
| f9f2777474 | |||
| 1dca889730 | |||
| 86b23d7260 | |||
| 85897e562d | |||
| cdb5f1b901 | |||
| 03903931f2 | |||
| 5016ce860c | |||
| 8da6be8cad | |||
| 873347aed0 | |||
| 637ff1f916 | |||
| c8ddfad188 | |||
| 22d73210d0 | |||
| feeb7b424a | |||
| a6d2a1b1ab | |||
| fa400b9a50 | |||
| 771c993d78 | |||
| 10d2560b57 | |||
| 2887b78944 | |||
| ed6d45e372 | |||
| 7654737a34 | |||
| f1d35a6a96 | |||
| 0b355e0fbe | |||
| 9a8e189ce7 | |||
| 53ff00f00d | |||
| 1c18eab371 | |||
| cd47d888aa | |||
| 499b0c1e38 | |||
| 6536a2182d | |||
| e2ae164d3e | |||
| 092afd8a8b | |||
| a7985f949a | |||
| 967e1a43cb | |||
| 73225ddd10 | |||
| 2d238265ca | |||
| cdfbf3dbfe | |||
| 21a3b43d9b | |||
| 8f0f49e790 | |||
| 7c5966ab61 | |||
| c41696cd23 | |||
| 6e7a168fb7 | |||
| a2efe0abff | |||
| d876389c19 | |||
| 5c54e07219 | |||
| 0e32a79d8c | |||
| 37c8b3d03d | |||
| 21ea965607 | |||
| 80d8b1ba99 | |||
| 975041933e | |||
| 404b65b633 | |||
| 73a78ed5a0 |
@@ -36,3 +36,4 @@ Carthage
|
||||
# SPM
|
||||
.build/
|
||||
Packages
|
||||
.swiftpm
|
||||
|
||||
+36
-20
@@ -1,24 +1,31 @@
|
||||
env:
|
||||
global:
|
||||
- MODULE_NAME=Dip
|
||||
|
||||
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
|
||||
- set -o pipefail && xcodebuild test -workspace Dip.xcworkspace -scheme Dip -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 6,OS=latest' ONLY_ACTIVE_ARCH=NO | xcpretty -c
|
||||
- set -o pipefail && xcodebuild test -workspace Dip.xcworkspace -scheme Dip -sdk macosx -destination 'platform=macOS,arch=x86_64' ONLY_ACTIVE_ARCH=NO | xcpretty -c
|
||||
# - pod spec lint --allow-warnings
|
||||
- carthage build --no-skip-current
|
||||
- swift package clean && swift build && swift test
|
||||
os: osx
|
||||
osx_image: xcode7.3
|
||||
osx_image: xcode10.2
|
||||
language: objective-c
|
||||
before_install:
|
||||
- gem install cocoapods --version 1.8.4 --no-document
|
||||
- script:
|
||||
- swift build
|
||||
- set -o pipefail && xcodebuild test -workspace Dip.xcworkspace -scheme Dip -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 11,OS=latest' ONLY_ACTIVE_ARCH=NO | xcpretty -c
|
||||
- set -o pipefail && xcodebuild test -workspace Dip.xcworkspace -scheme Dip -sdk macosx -destination 'platform=macOS,arch=x86_64' ONLY_ACTIVE_ARCH=NO | xcpretty -c
|
||||
# - pod spec lint --allow-warnings
|
||||
- carthage build --no-skip-current
|
||||
- swift package clean && swift build && swift test
|
||||
os: osx
|
||||
osx_image: xcode11.2
|
||||
language: objective-c
|
||||
before_install:
|
||||
- gem install cocoapods --version 1.8.4 --no-document
|
||||
- script:
|
||||
- swift package clean && swift build && swift test
|
||||
os: linux
|
||||
dist: trusty
|
||||
sudo: required
|
||||
@@ -26,16 +33,25 @@ matrix:
|
||||
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
|
||||
- export SWIFT_VERSION=swift-5.1-RELEASE
|
||||
- wget https://swift.org/builds/swift-5.1-release/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
|
||||
- cd Dip
|
||||
- script:
|
||||
- swift package clean && swift build && swift test
|
||||
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-5.0-RELEASE
|
||||
- wget https://swift.org/builds/swift-5.0-release/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}"
|
||||
- cd Dip
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
+119
-1
@@ -1,5 +1,123 @@
|
||||
# CHANGELOG
|
||||
|
||||
## Develop
|
||||
|
||||
## 7.1.1
|
||||
|
||||
* Fixed using `StoryboardInstantiatable` with SPM ([#233](https://github.com/AliSoftware/Dip/pull/233)).
|
||||
|
||||
## 7.1.0
|
||||
|
||||
* You can now use a shorthand syntax for resolving a single property using a key path, i.e. `resolvingProperty(\.value)`.
|
||||
* Swift 5.0 support ([#224](https://github.com/AliSoftware/Dip/pull/224)).
|
||||
* Fixed resolving nested types with the same local names ([#221](https://github.com/AliSoftware/Dip/pull/221)).
|
||||
* `@Injected` and `@IntectedWeak` property wrappers ([#225](https://github.com/AliSoftware/Dip/pull/225)).
|
||||
* Thread safety can be disabled on container level.
|
||||
|
||||
## 7.0.1
|
||||
|
||||
* Added a workaround for Swift 4.2 regression related to retaining weak properties ([#214](https://github.com/AliSoftware/Dip/issues/214)).
|
||||
For that auto-injection can be disabled or enabled for the whole container or individula registrations.
|
||||
|
||||
## 7.0.0
|
||||
|
||||
* Swift 4.2 support.
|
||||
* Fixed some issues when reusing instances previously resolved as optionals.
|
||||
* Dip-UI is now part of Dip.
|
||||
|
||||
## 6.1
|
||||
|
||||
* Swift 4.1 support.
|
||||
* Fixed crashes resolving singletons using collaborating containers.
|
||||
[#179](https://github.com/AliSoftware/Dip/pull/179), [@trimmurrti](https://github.com/trimmurrti)
|
||||
[#182](https://github.com/AliSoftware/Dip/pull/182), [@tapsandswipes](https://github.com/tapsandswipes)
|
||||
|
||||
## 6.0
|
||||
|
||||
* Swift 4 support
|
||||
* Fixed unneeded reuse of singletons in collaborating containers.
|
||||
Containers now first attempt to autowire components and fallback to collaboration when it fails.
|
||||
[#169](https://github.com/AliSoftware/Dip/issues/169), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
|
||||
## 5.1
|
||||
|
||||
* Dropped Swift 2.3 support.
|
||||
[#150](https://github.com/AliSoftware/Dip/issues/150), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
* Added custom logging function.
|
||||
[#146](https://github.com/AliSoftware/Dip/issues/146), [@Pr0Ger](https://github.com/Pr0Ger)
|
||||
|
||||
#### Fixed
|
||||
|
||||
* Fixed Swift 3.1 warnings.
|
||||
[#145](https://github.com/AliSoftware/Dip/issues/145), [@DenHeadless](https://github.com/DenHeadless)
|
||||
* Fixed collaboration shared references.
|
||||
[#151](https://github.com/AliSoftware/Dip/issues/151), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
* Fixed autowiring when using tags.
|
||||
[#154](https://github.com/AliSoftware/Dip/issues/154), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
|
||||
## 5.0.4
|
||||
|
||||
#### Fixed
|
||||
|
||||
* Fixed broken compatibility for Swift 2.3 API in `resolve(tag:arguments:)` method.
|
||||
[#135](https://github.com/AliSoftware/Dip/issues/135), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
|
||||
## 5.0.3
|
||||
|
||||
* Added Swift 2.3 compatibility. `swift2.3` brunch is no longer maintained.
|
||||
[#127](https://github.com/AliSoftware/Dip/issues/127), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
|
||||
#### Fixed
|
||||
|
||||
* Fixed reusing instances registered with `WeakSingleton` scope
|
||||
[#129](https://github.com/AliSoftware/Dip/issues/129), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
|
||||
## 5.0.2
|
||||
|
||||
#### Fixed
|
||||
|
||||
* Fixed Swift 3 issues related to reflection and IUO
|
||||
[#125](https://github.com/AliSoftware/Dip/issues/125), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
|
||||
## 5.0.1
|
||||
|
||||
This release is the same as 5.0.0 and only fixes CocoaPods speck pushed to trunk without macOS, tvOS and watchOS deployment targets. Please use this release instead of 5.0.0 if you integrate Dip via Cocoapods.
|
||||
|
||||
## 5.0.0
|
||||
|
||||
* Migrated to Swift 3.0
|
||||
[#120](https://github.com/AliSoftware/Dip/issues/120), [@patrick-lind](https://github.com/patrick-lind), [@mark-urbanthings](https://github.com/mark-urbanthings), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
* Renamed `DefinitionOf` to `Definition` and some other source-breaking refactoring.
|
||||
[#113](https://github.com/AliSoftware/Dip/issues/113), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
* Added `invalidType` error when resolved instance does not implement requested type.
|
||||
[#118](https://github.com/AliSoftware/Dip/issues/118), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
* Added optional `type` parameter in register methods to be able to specify type when registering using method literal instead of closure.
|
||||
[#115](https://github.com/AliSoftware/Dip/issues/115), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
* Added `implements` family of methods in to `Definition` to register type-forwarding definitions.
|
||||
[#114](https://github.com/AliSoftware/Dip/issues/114), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
* Shared scope is now the default scope.
|
||||
[#112](https://github.com/AliSoftware/Dip/issues/112), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
* Single target project setup.
|
||||
[#121](https://github.com/AliSoftware/Dip/issues/121), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
* Simplified implementation of auto-wiring.
|
||||
[#117](https://github.com/AliSoftware/Dip/issues/117), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
|
||||
|
||||
#### Fixed
|
||||
* Auto-injected properties inherited from super class are now properly injected when resolving subclass.
|
||||
Added `resolveDependencies(_:DependencyContainer)` method to `Resolvable` protocol to handle inheritance when resolving.
|
||||
[#116](https://github.com/AliSoftware/Dip/issues/116), [@ilyapuchka](https://github.com/ilyapuchka)
|
||||
|
||||
|
||||
## 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.
|
||||
@@ -146,7 +264,7 @@
|
||||
This code:
|
||||
|
||||
```swift
|
||||
container.register("some tag") { SomeClass() as SomeProtocol }
|
||||
container.register(tag: "some tag") { SomeClass() as SomeProtocol }
|
||||
container.resolve("some tag") as SomeProtocol
|
||||
```
|
||||
|
||||
|
||||
+4
-2
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "Dip"
|
||||
s.version = "4.6.0"
|
||||
s.version = "7.1.1"
|
||||
s.summary = "Dependency Injection for Swift made easy."
|
||||
|
||||
s.description = <<-DESC
|
||||
@@ -11,7 +11,7 @@ Pod::Spec.new do |s|
|
||||
|
||||
s.homepage = "https://github.com/AliSoftware/Dip"
|
||||
s.license = 'MIT'
|
||||
s.authors = { "Olivier Halligon" => "olivier@halligon.net", "Ilya Puchka" => "ilya@puchka.me" }
|
||||
s.authors = { "Olivier Halligon" => "olivier@halligon.net", "Ilya Puchka" => "ilyapuchka@gmail.com" }
|
||||
s.source = { :git => "https://github.com/AliSoftware/Dip.git", :tag => s.version.to_s }
|
||||
s.social_media_url = 'https://twitter.com/aligatr'
|
||||
|
||||
@@ -23,4 +23,6 @@ Pod::Spec.new do |s|
|
||||
s.requires_arc = true
|
||||
|
||||
s.source_files = 'Sources/**/*.swift'
|
||||
|
||||
s.swift_version = "5.0", "5.1"
|
||||
end
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
+177
-646
File diff suppressed because it is too large
Load Diff
@@ -1,100 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0710"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0903B3771C1615EC002241C1"
|
||||
BuildableName = "Dip.framework"
|
||||
BlueprintName = "Dip-iOS"
|
||||
ReferencedContainer = "container:Dip.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0903B3801C1615EC002241C1"
|
||||
BuildableName = "Dip-iOSTests.xctest"
|
||||
BlueprintName = "Dip-iOSTests"
|
||||
ReferencedContainer = "container:Dip.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0903B3771C1615EC002241C1"
|
||||
BuildableName = "Dip.framework"
|
||||
BlueprintName = "Dip-iOS"
|
||||
ReferencedContainer = "container:Dip.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0903B3771C1615EC002241C1"
|
||||
BuildableName = "Dip.framework"
|
||||
BlueprintName = "Dip-iOS"
|
||||
ReferencedContainer = "container:Dip.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0903B3771C1615EC002241C1"
|
||||
BuildableName = "Dip.framework"
|
||||
BlueprintName = "Dip-iOS"
|
||||
ReferencedContainer = "container:Dip.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,99 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0710"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0903B3A51C1618AF002241C1"
|
||||
BuildableName = "Dip.framework"
|
||||
BlueprintName = "Dip-tvOS"
|
||||
ReferencedContainer = "container:Dip.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0903B3AE1C1618AF002241C1"
|
||||
BuildableName = "Dip-tvOSTests.xctest"
|
||||
BlueprintName = "Dip-tvOSTests"
|
||||
ReferencedContainer = "container:Dip.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0903B3A51C1618AF002241C1"
|
||||
BuildableName = "Dip.framework"
|
||||
BlueprintName = "Dip-tvOS"
|
||||
ReferencedContainer = "container:Dip.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0903B3A51C1618AF002241C1"
|
||||
BuildableName = "Dip.framework"
|
||||
BlueprintName = "Dip-tvOS"
|
||||
ReferencedContainer = "container:Dip.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0903B3A51C1618AF002241C1"
|
||||
BuildableName = "Dip.framework"
|
||||
BlueprintName = "Dip-tvOS"
|
||||
ReferencedContainer = "container:Dip.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,80 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0710"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0903B4031C162862002241C1"
|
||||
BuildableName = "Dip.framework"
|
||||
BlueprintName = "Dip-watchOS"
|
||||
ReferencedContainer = "container:Dip.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0903B4031C162862002241C1"
|
||||
BuildableName = "Dip.framework"
|
||||
BlueprintName = "Dip-watchOS"
|
||||
ReferencedContainer = "container:Dip.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0903B4031C162862002241C1"
|
||||
BuildableName = "Dip.framework"
|
||||
BlueprintName = "Dip-watchOS"
|
||||
ReferencedContainer = "container:Dip.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
+21
-7
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0710"
|
||||
LastUpgradeVersion = "1020"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -16,7 +16,21 @@
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0903B3571C161543002241C1"
|
||||
BuildableName = "Dip.framework"
|
||||
BlueprintName = "Dip-OSX"
|
||||
BlueprintName = "Dip"
|
||||
ReferencedContainer = "container:Dip.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "NO"
|
||||
buildForProfiling = "NO"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0903B3601C161543002241C1"
|
||||
BuildableName = "DipTests.xctest"
|
||||
BlueprintName = "DipTests"
|
||||
ReferencedContainer = "container:Dip.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
@@ -33,8 +47,8 @@
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0903B3601C161543002241C1"
|
||||
BuildableName = "Dip-OSXTests.xctest"
|
||||
BlueprintName = "Dip-OSXTests"
|
||||
BuildableName = "DipTests.xctest"
|
||||
BlueprintName = "DipTests"
|
||||
ReferencedContainer = "container:Dip.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
@@ -44,7 +58,7 @@
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0903B3571C161543002241C1"
|
||||
BuildableName = "Dip.framework"
|
||||
BlueprintName = "Dip-OSX"
|
||||
BlueprintName = "Dip"
|
||||
ReferencedContainer = "container:Dip.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
@@ -66,7 +80,7 @@
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0903B3571C161543002241C1"
|
||||
BuildableName = "Dip.framework"
|
||||
BlueprintName = "Dip-OSX"
|
||||
BlueprintName = "Dip"
|
||||
ReferencedContainer = "container:Dip.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
@@ -84,7 +98,7 @@
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0903B3571C161543002241C1"
|
||||
BuildableName = "Dip.framework"
|
||||
BlueprintName = "Dip-OSX"
|
||||
BlueprintName = "Dip"
|
||||
ReferencedContainer = "container:Dip.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<string>7.1.1</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<string>7.1.1</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14313.18"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Dip View Controller-->
|
||||
<scene sceneID="adI-oe-5KL">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="DipViewController" id="fzZ-tH-vfC" customClass="DipViewController" customModule="DipTests" sceneMemberID="viewController">
|
||||
<view key="view" id="vso-jO-9Ex">
|
||||
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</view>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="string" keyPath="dipTag" value="vc"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</viewController>
|
||||
<customObject id="wjM-mL-nmG" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="311" y="339"/>
|
||||
</scene>
|
||||
<!--Nil Tag View Controller-->
|
||||
<scene sceneID="OhX-tC-zpS">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="NilTagViewController" id="35S-Ec-qEA" customClass="NilTagViewController" customModule="DipTests" sceneMemberID="viewController">
|
||||
<view key="view" id="y58-K4-cDZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</view>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="nil" keyPath="dipTag"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</viewController>
|
||||
<customObject id="pvf-jv-8Cj" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="874" y="339"/>
|
||||
</scene>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="Rwu-gt-fAa">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="ViewController" id="tne-ER-mvb" sceneMemberID="viewController">
|
||||
<view key="view" id="Kwe-OO-w0D">
|
||||
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<customObject id="0u1-hv-ZtW" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="311" y="729"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -0,0 +1,75 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder.AppleTV.Storyboard" version="3.0" toolsVersion="14313.18" targetRuntime="AppleTV" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES" initialViewController="ctX-Lj-Yrr">
|
||||
<device id="appleTV" orientation="landscape">
|
||||
<adaptation id="light"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.14"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Dip View Controller-->
|
||||
<scene sceneID="0jz-eb-APg">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="DipViewController" id="ctX-Lj-Yrr" customClass="DipViewController" customModule="DipTests" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="TrC-Rh-efi"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="arQ-XW-qWa"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="syn-UA-YGd">
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="100"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
</view>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<size key="freeformSize" width="200" height="100"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="string" keyPath="dipTag" value="vc"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="mq6-CB-g6V" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="563" y="-228"/>
|
||||
</scene>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="gDU-un-krd">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="ViewController" id="UwR-h2-tgS" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="qKG-R0-Nkp"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="vP4-Si-HAL"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="r35-FI-kgS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="100"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
</view>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<size key="freeformSize" width="200" height="100"/>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="AIY-qB-Dbe" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="563" y="-16"/>
|
||||
</scene>
|
||||
<!--Nil Tag View Controller-->
|
||||
<scene sceneID="PiH-4i-Txa">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="NilTagViewController" id="ZLb-1s-1ne" customClass="NilTagViewController" customModule="DipTests" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="arU-Ca-km8"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="jw1-4B-Xlt"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="NMo-Oi-l7H">
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="100"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
</view>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<size key="freeformSize" width="200" height="100"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="nil" keyPath="dipTag"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Cid-Yu-0N6" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="821" y="-228"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -1,252 +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 protocol ForwardedType: class { }
|
||||
private class ServiceImp1: NSObject, Service, ForwardedType { }
|
||||
private class ServiceImp2: NSObject, Service, ForwardedType { }
|
||||
|
||||
class TypeForwardingTests: XCTestCase {
|
||||
|
||||
let container = DependencyContainer()
|
||||
|
||||
#if os(Linux)
|
||||
static var allTests: [(String, TypeForwardingTests -> () throws -> Void)] {
|
||||
return [
|
||||
("testThatItResolvesInstanceByTypeForwarding", testThatItResolvesInstanceByTypeForwarding),
|
||||
("testThatItReusesInstanceResolvedByTypeForwarding", testThatItReusesInstanceResolvedByTypeForwarding),
|
||||
("testThatItDoesNotResolveByTypeForwardingIfRegisteredForAnotherTag", testThatItDoesNotResolveByTypeForwardingIfRegisteredForAnotherTag),
|
||||
("testThatItDoesNotReuseInstanceResolvedByTypeForwardingRegisteredForAnotherTag", testThatItDoesNotReuseInstanceResolvedByTypeForwardingRegisteredForAnotherTag),
|
||||
("testThatItCallsResolvedDependenciesBlockWhenResolvingByTypeForwarding", testThatItCallsResolvedDependenciesBlockWhenResolvingByTypeForwarding),
|
||||
("testThatItFallbackToDefinitionWithNoTagWhenResolvingInstanceByTypeForwarding", testThatItFallbackToDefinitionWithNoTagWhenResolvingInstanceByTypeForwarding),
|
||||
("testThatItThrowsErrorWhenResolvingNotImplementedTypeWithTypeForwarding", testThatItThrowsErrorWhenResolvingNotImplementedTypeWithTypeForwarding),
|
||||
("testThatItOverridesIfSeveralDefinitionsWithTheSameTagForwardTheSameType", testThatItOverridesIfSeveralDefinitionsWithTheSameTagForwardTheSameType)
|
||||
]
|
||||
}
|
||||
|
||||
func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#else
|
||||
override func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#endif
|
||||
|
||||
func testThatItResolvesInstanceByTypeForwarding() {
|
||||
//given
|
||||
let def = container.register { ServiceImp1() as Service }
|
||||
container.register(def, type: ForwardedType.self)
|
||||
container.register(def, type: NSObject.self)
|
||||
|
||||
//when
|
||||
let anotherService = try! container.resolve() as ForwardedType
|
||||
let anyOtherService = try! container.resolve(ForwardedType.self)
|
||||
let object = try! container.resolve() as NSObject
|
||||
let anyObject = try! container.resolve(NSObject.self)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(anotherService is ServiceImp1)
|
||||
XCTAssertTrue(object is ServiceImp1)
|
||||
XCTAssertTrue(anyOtherService is ServiceImp1)
|
||||
XCTAssertTrue(anyObject is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItReusesInstanceResolvedByTypeForwarding() {
|
||||
//given
|
||||
let def = container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
.resolveDependencies { container, resolved in
|
||||
//when
|
||||
let forwardType = try container.resolve() as ForwardedType
|
||||
let anyForwardType = try container.resolve(ForwardedType.self) as! ForwardedType
|
||||
let object = try container.resolve() as NSObject
|
||||
let anyObject = try container.resolve(NSObject.self) as! NSObject
|
||||
let service = try container.resolve() as Service
|
||||
let anyService = try container.resolve(Service.self) as! Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(forwardType === resolved as! ForwardedType)
|
||||
XCTAssertTrue(anyForwardType === resolved as! ForwardedType)
|
||||
XCTAssertTrue(object === resolved as! NSObject)
|
||||
XCTAssertTrue(anyObject === resolved as! NSObject)
|
||||
XCTAssertTrue(service === resolved)
|
||||
XCTAssertTrue(anyService === resolved)
|
||||
}
|
||||
container.register(def, type: ForwardedType.self)
|
||||
container.register(def, type: NSObject.self)
|
||||
|
||||
let _ = try! container.resolve() as Service
|
||||
let _ = try! container.resolve() as ForwardedType
|
||||
let _ = try! container.resolve() as NSObject
|
||||
}
|
||||
|
||||
func testThatItDoesNotResolveByTypeForwardingIfRegisteredForAnotherTag() {
|
||||
//given
|
||||
let def = container.register(tag: "tag") { ServiceImp1() as Service }
|
||||
container.register(def, type: ForwardedType.self, tag: "otherTag")
|
||||
|
||||
//then
|
||||
AssertThrows(expression: try container.resolve(tag: "tag") as ForwardedType)
|
||||
AssertThrows(expression: try container.resolve(ForwardedType.self, tag: "tag"))
|
||||
|
||||
//and given
|
||||
container.register(def, type: ForwardedType.self, tag: "tag")
|
||||
|
||||
//then
|
||||
AssertNoThrow(expression: try container.resolve(tag: "tag") as ForwardedType)
|
||||
AssertNoThrow(expression: try container.resolve(ForwardedType.self, tag: "tag"))
|
||||
}
|
||||
|
||||
func testThatItDoesNotReuseInstanceResolvedByTypeForwardingRegisteredForAnotherTag() {
|
||||
var resolveDependenciesCalled = false
|
||||
//given
|
||||
let def = container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
.resolveDependencies { container, service in
|
||||
guard resolveDependenciesCalled == false else { return }
|
||||
resolveDependenciesCalled = true
|
||||
|
||||
let forwardType = try container.resolve(tag: "tag") as ForwardedType
|
||||
let anyForwardType = try container.resolve(ForwardedType.self, tag: "tag") as! ForwardedType
|
||||
|
||||
let object = try container.resolve() as NSObject
|
||||
let anyObject = try container.resolve(NSObject.self) as! NSObject
|
||||
|
||||
//then
|
||||
XCTAssertFalse(forwardType === service as! ForwardedType)
|
||||
XCTAssertFalse(anyForwardType === service as! ForwardedType)
|
||||
XCTAssertTrue(object === service as! NSObject)
|
||||
XCTAssertTrue(anyObject === service as! NSObject)
|
||||
}
|
||||
container.register(def, type: ForwardedType.self, tag: "tag")
|
||||
container.register(def, type: NSObject.self)
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve() as Service
|
||||
}
|
||||
|
||||
func testThatItCallsResolvedDependenciesBlockWhenResolvingByTypeForwarding() {
|
||||
//given
|
||||
var originalResolveDependenciesCalled = false
|
||||
var resolveDependenciesCalled = false
|
||||
let def = container.register { ServiceImp1() }
|
||||
.resolveDependencies { container, service in
|
||||
originalResolveDependenciesCalled = true
|
||||
}
|
||||
|
||||
container.register(def, type: Service.self)
|
||||
.resolveDependencies { container, object in
|
||||
resolveDependenciesCalled = true
|
||||
}
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve() as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(resolveDependenciesCalled)
|
||||
XCTAssertTrue(originalResolveDependenciesCalled)
|
||||
|
||||
//and when
|
||||
resolveDependenciesCalled = false
|
||||
originalResolveDependenciesCalled = false
|
||||
let _ = try! container.resolve(Service.self)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(resolveDependenciesCalled)
|
||||
XCTAssertTrue(originalResolveDependenciesCalled)
|
||||
}
|
||||
|
||||
func testThatItFallbackToDefinitionWithNoTagWhenResolvingInstanceByTypeForwarding() {
|
||||
let def = container.register { ServiceImp1() as Service }
|
||||
container.register { ServiceImp2() as Service }
|
||||
container.register(def, type: NSObject.self)
|
||||
|
||||
//when
|
||||
let service = try! container.resolve(tag: "tag") as NSObject
|
||||
let anyService = try! container.resolve(NSObject.self, tag: "tag")
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service is ServiceImp1)
|
||||
XCTAssertTrue(anyService is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItFirstUsesTaggedDefinitionWhenResolvingOptional() {
|
||||
let expectedTag: DependencyContainer.Tag = .String("tag")
|
||||
container.register(tag: expectedTag) { ServiceImp1() as Service }
|
||||
.resolveDependencies { container, resolved in
|
||||
XCTAssertEqual(container.context.tag, expectedTag)
|
||||
}
|
||||
container.register { ServiceImp2() as Service }
|
||||
|
||||
//when
|
||||
let service = try! container.resolve(tag: "tag") as Service?
|
||||
let anyService = try! container.resolve((Service?).self, tag: "tag")
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service is ServiceImp1)
|
||||
XCTAssertTrue(anyService is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItThrowsErrorWhenResolvingNotImplementedTypeWithTypeForwarding() {
|
||||
//given
|
||||
let def = container.register { ServiceImp1() as Service }
|
||||
container.register(def, type: NSCoder.self)
|
||||
|
||||
//then
|
||||
AssertThrows(expression: try container.resolve() as NSCoder)
|
||||
AssertThrows(expression: try container.resolve(NSCoder.self))
|
||||
}
|
||||
|
||||
func testThatItOverridesIfSeveralDefinitionsWithTheSameTagForwardTheSameType() {
|
||||
let def1 = container.register { ServiceImp1() as Service }
|
||||
let def2 = container.register { ServiceImp2() as Service }
|
||||
container.register(def1, type: NSObject.self)
|
||||
|
||||
XCTAssertTrue(try! container.resolve() as NSObject is ServiceImp1)
|
||||
XCTAssertTrue(try! container.resolve(NSObject.self) is ServiceImp1)
|
||||
|
||||
//when
|
||||
container.register(def2, type: NSObject.self)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(try! container.resolve() as NSObject is ServiceImp2)
|
||||
XCTAssertTrue(try! container.resolve(NSObject.self) is ServiceImp2)
|
||||
|
||||
//and given
|
||||
container.register(def2, type: NSObject.self, tag: "tag")
|
||||
|
||||
XCTAssertTrue(try! container.resolve(tag: "tag") as NSObject is ServiceImp2)
|
||||
XCTAssertTrue(try! container.resolve(NSObject.self, tag: "tag") is ServiceImp2)
|
||||
|
||||
//when
|
||||
container.register(def1, type: NSObject.self, tag: "tag")
|
||||
|
||||
//then
|
||||
XCTAssertTrue(try! container.resolve(tag: "tag") as NSObject is ServiceImp1)
|
||||
XCTAssertTrue(try! container.resolve(NSObject.self, tag: "tag") is ServiceImp1)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="5AO-J3-7R4">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.14"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="Ole-tM-Q3l">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="ViewController" id="ehZ-7Y-MeO" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="bZ0-tI-Yi9"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="UX3-8G-Z4L"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Jmi-i3-vAY">
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="100"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</view>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<size key="freeformSize" width="200" height="100"/>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="H12-WL-igv" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="646" y="461"/>
|
||||
</scene>
|
||||
<!--Dip View Controller-->
|
||||
<scene sceneID="Lgf-SY-hfd">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="DipViewController" id="5AO-J3-7R4" userLabel="Dip View Controller" customClass="DipViewController" customModule="DipTests" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="JYl-DM-U2W"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="Xg8-gz-BQL"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="aU0-uz-rHf">
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="100"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</view>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<size key="freeformSize" width="200" height="100"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="string" keyPath="dipTag" value="vc"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="4iF-mX-EX6" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="646" y="200"/>
|
||||
</scene>
|
||||
<!--Dip View Controller-->
|
||||
<scene sceneID="AUA-qF-7ky">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="NilTagViewController" id="fFP-hb-OdS" userLabel="Dip View Controller" customClass="NilTagViewController" customModule="DipTests" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Sio-ii-jPl"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="bhs-zK-dln"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="4xq-UV-htt">
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="100"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</view>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<size key="freeformSize" width="200" height="100"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="nil" keyPath="dipTag"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="xvW-k9-I9y" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="898" y="200"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
[
|
||||
]
|
||||
@@ -8,7 +8,7 @@ let container = DependencyContainer()
|
||||
|
||||
### Auto-Injection
|
||||
|
||||
On the previous page you saw how auto-wiring helps us get rid of boilerplate code when registering and resolving components with consturctor injection. Auto-injection solves the same problem for property injection.
|
||||
On the previous page you saw how auto-wiring helps us to get rid of boilerplate code when registering and resolving components with consturctor injection. Auto-injection solves the same problem for property injection.
|
||||
|
||||
Let's say you have following related components:
|
||||
*/
|
||||
@@ -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
|
||||
@@ -47,10 +47,10 @@ With auto-injection your code transforms to this:
|
||||
*/
|
||||
|
||||
class AutoInjectedServiceImp: Service {
|
||||
private var injectedLogger = Injected<Logger>()
|
||||
private let injectedLogger = Injected<Logger>()
|
||||
var logger: Logger? { return injectedLogger.value }
|
||||
|
||||
private var injectedTracker = Injected<Tracker>()
|
||||
private let injectedTracker = Injected<Tracker>()
|
||||
var tracker: Tracker? { return injectedTracker.value }
|
||||
}
|
||||
|
||||
@@ -83,9 +83,7 @@ container.register { ServerWithRequiredClient() }
|
||||
do {
|
||||
let serverWithClient = try container.resolve() as ServerWithRequiredClient
|
||||
}
|
||||
catch {
|
||||
print(error)
|
||||
}
|
||||
catch {}
|
||||
|
||||
/*:
|
||||
You can make auto-injected property optional by passing `false` to `required` parameter of `Injected<T>`/`InjectedWeak<T>` constructor. For such properties container will ignore any errors when it resolves this property (or any of its dependencies).
|
||||
@@ -127,12 +125,12 @@ class ServerClientImp: ServerClient {
|
||||
The standard way to register such components in `DependencyContainer` will lead to such code:
|
||||
*/
|
||||
|
||||
container.register(.ObjectGraph) {
|
||||
container.register {
|
||||
ServerClientImp(server: try container.resolve()) as ServerClient
|
||||
}
|
||||
|
||||
container.register(.ObjectGraph) { ServerImp() as Server }
|
||||
.resolveDependencies { (container: DependencyContainer, server: Server) in
|
||||
container.register { ServerImp() as Server }
|
||||
.resolvingProperties { (container: DependencyContainer, server: Server) in
|
||||
(server as! ServerImp).client = try container.resolve() as ServerClient
|
||||
}
|
||||
|
||||
@@ -153,8 +151,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 { InjectedServerImp() as Server }
|
||||
container.register { InjectedClientImp() as ServerClient }
|
||||
|
||||
let injectedClient = try! container.resolve() as ServerClient
|
||||
injectedClient.server
|
||||
@@ -176,7 +174,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
|
||||
|
||||
@@ -7,7 +7,7 @@ import UIKit
|
||||
|
||||
### Auto-wiring
|
||||
|
||||
Among three main DI patterns - _constructor_, _property_ and _method_ injection - construction injection should be your choise by default. Dip makes use of this pattern very simple.
|
||||
Among three main DI patterns - _constructor_, _property_ and _method_ injection - constructor injection should be your choise by default. Dip makes using this pattern very simple.
|
||||
|
||||
Let's say you have some VIPER module with following components:
|
||||
*/
|
||||
@@ -28,7 +28,7 @@ class View: UIView, ViewOutput {}
|
||||
class ServiceImp: Service {}
|
||||
|
||||
/*:
|
||||
VIPER module by its nature consists of a lot of components, wired up using protocols. Using construction injection you can end up with following constructors for presenter and interactor:
|
||||
VIPER module by its nature consists of a lot of components, wired up using protocols. Using constructor injection you can end up with following constructors for presenter and interactor:
|
||||
*/
|
||||
|
||||
class InteractorImp: Interactor {
|
||||
@@ -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
|
||||
|
||||
/*:
|
||||
@@ -109,7 +109,7 @@ You don't need to call `resolve` in a factory and care about order of arguments
|
||||
|
||||
The only requirement is that all constructor arguments should be registered in the container and there should be no several factories with the same _number_ of arguments registered for the same components.
|
||||
|
||||
In very rare case when you have several different factories with different set of runtime arguments registered for the same component, when you try to resolve it container will try to use these factories one by one until one of them succeeds starting with a factory with most numbers of arguments. If it finds two factories with the same number of arguments it will throw an error.
|
||||
In very rare cases when you have several factories for the same component with different set of runtime arguments, when you try to resolve it container will try to use factory registered for the same type and tag (if provided, otherwise registered without tag) and with the maximum number of runtime arguments. If it finds two factories registered for the same type and tag and with the same number but different types of arguments it will throw an error.
|
||||
|
||||
You can use auto-wiring with tags. The tag that you pass to `resolve` method will be used to resolve each of the constructor arguments.
|
||||
*/
|
||||
|
||||
+11
-10
@@ -1,6 +1,7 @@
|
||||
//: [Previous: Scopes](@previous)
|
||||
|
||||
import Dip
|
||||
import Foundation
|
||||
|
||||
let container = DependencyContainer()
|
||||
|
||||
@@ -25,7 +26,7 @@ class NetworkClientImp: NetworkClient {
|
||||
init() {}
|
||||
}
|
||||
|
||||
class Interactor: NetworkClientDelegate {
|
||||
class Interactor: NSObject, NetworkClientDelegate {
|
||||
let networkClient: NetworkClient
|
||||
init(networkClient: NetworkClient) {
|
||||
self.networkClient = networkClient
|
||||
@@ -43,12 +44,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 {
|
||||
Interactor(networkClient: try container.resolve()) as NetworkClientDelegate
|
||||
}
|
||||
|
||||
container.register(.ObjectGraph) { NetworkClientImp() as NetworkClient }
|
||||
.resolveDependencies { (container, client) -> () in
|
||||
container.register { NetworkClientImp() as NetworkClient }
|
||||
.resolvingProperties { (container, client) -> () in
|
||||
client.delegate = try container.resolve() as NetworkClientDelegate
|
||||
}
|
||||
|
||||
@@ -77,26 +78,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 { NetworkClientImp() as NetworkClient }
|
||||
.resolvingProperties { (container, client) -> () in
|
||||
client.delegate = try container.resolve() as NetworkClientDelegate
|
||||
}
|
||||
|
||||
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Timeline
|
||||
version = "3.0">
|
||||
<TimelineItems>
|
||||
</TimelineItems>
|
||||
</Timeline>
|
||||
+16
-19
@@ -11,10 +11,10 @@ import Dip
|
||||
protocol DataStore {}
|
||||
class CoreDataStore: DataStore {}
|
||||
class AddEventWireframe {
|
||||
var eventsListWireframe: EventsListWireframe!
|
||||
var eventsListWireframe: EventsListWireframe?
|
||||
}
|
||||
class EventsListWireframe {
|
||||
var addEventWireframe: AddEventWireframe!
|
||||
var addEventWireframe: AddEventWireframe?
|
||||
let dataStore: DataStore
|
||||
init(dataStore: DataStore) {
|
||||
self.dataStore = dataStore
|
||||
@@ -22,20 +22,17 @@ class EventsListWireframe {
|
||||
}
|
||||
|
||||
|
||||
let rootContainer = DependencyContainer() { container in
|
||||
container.register(.Singleton) { CoreDataStore() as DataStore }
|
||||
let rootContainer = DependencyContainer()
|
||||
rootContainer.register(.singleton) { CoreDataStore() as DataStore }
|
||||
|
||||
let eventsListModule = DependencyContainer()
|
||||
eventsListModule.register { EventsListWireframe(dataStore: $0) }
|
||||
.resolvingProperties { container, wireframe in
|
||||
wireframe.addEventWireframe = try container.resolve()
|
||||
}
|
||||
|
||||
let eventsListModule = DependencyContainer() { container in
|
||||
container.register(.ObjectGraph) { EventsListWireframe(dataStore: $0) }
|
||||
.resolveDependencies { container, wireframe in
|
||||
wireframe.addEventWireframe = try container.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
let addEventModule = DependencyContainer() { container in
|
||||
container.register { AddEventWireframe() }
|
||||
}
|
||||
let addEventModule = DependencyContainer()
|
||||
addEventModule.register { AddEventWireframe() }
|
||||
|
||||
eventsListModule.collaborate(with: addEventModule, rootContainer)
|
||||
|
||||
@@ -51,13 +48,13 @@ eventsListWireframe.addEventWireframe
|
||||
eventsListModule.reset()
|
||||
addEventModule.reset()
|
||||
|
||||
eventsListModule.register(.ObjectGraph) { EventsListWireframe(dataStore: $0) }
|
||||
.resolveDependencies { container, wireframe in
|
||||
eventsListModule.register { EventsListWireframe(dataStore: $0) }
|
||||
.resolvingProperties { container, wireframe in
|
||||
wireframe.addEventWireframe = try container.resolve()
|
||||
}
|
||||
|
||||
addEventModule.register(.ObjectGraph) { AddEventWireframe() }
|
||||
.resolveDependencies { container, wireframe in
|
||||
addEventModule.register { AddEventWireframe() }
|
||||
.resolvingProperties { container, wireframe in
|
||||
wireframe.eventsListWireframe = try container.resolve()
|
||||
}
|
||||
|
||||
@@ -65,7 +62,7 @@ addEventModule.collaborate(with: eventsListModule)
|
||||
|
||||
eventsListWireframe = try eventsListModule.resolve() as EventsListWireframe
|
||||
eventsListWireframe.addEventWireframe
|
||||
eventsListWireframe.addEventWireframe.eventsListWireframe === eventsListWireframe
|
||||
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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -15,18 +15,15 @@ Note that __types__, __number__ and __order__ of arguments matters and you can r
|
||||
container.register { (url: NSURL, port: Int) in ServiceImp4(name: "1", baseURL: url, port: port) as Service }
|
||||
container.register { (port: Int, url: NSURL) in ServiceImp4(name: "2", baseURL: url, port: port) as Service }
|
||||
container.register { (port: Int, url: NSURL?) in ServiceImp4(name: "3", baseURL: url!, port: port) as Service }
|
||||
container.register { (port: Int, url: NSURL!) in ServiceImp4(name: "4", baseURL: url, port: port) as Service }
|
||||
|
||||
let url: NSURL = NSURL(string: "http://example.com")!
|
||||
let service1 = 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
|
||||
|
||||
(service1 as! ServiceImp4).name
|
||||
(service2 as! ServiceImp4).name
|
||||
(service3 as! ServiceImp4).name
|
||||
(service4 as! ServiceImp4).name
|
||||
|
||||
/*:
|
||||
Note that all of the services were resolved using different factories.
|
||||
@@ -35,12 +32,15 @@ _Dip_ supports up to six runtime arguments. If that is not enougth you can exten
|
||||
*/
|
||||
|
||||
extension DependencyContainer {
|
||||
public func register<T, A, B, C, D, E, F, G>(tag tag: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: (A, B, C, D, E, F, G) throws -> T) -> DefinitionOf<T, (A, B, C, D, E, F, G) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: 7) { container, tag in try factory(container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag))
|
||||
|
||||
@discardableResult
|
||||
public func register<T, A, B, C, D, E, F, G>(_ scope: ComponentScope = .shared, type: T.Type = T.self, tag: DependencyTagConvertible? = nil, factory: @escaping (A, B, C, D, E, F, G) throws -> T) -> Definition<T, (A, B, C, D, E, F, G)> {
|
||||
return register(scope: scope, type: type, tag: tag, 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, 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 {
|
||||
public func resolve<T, A, B, C, D, E, F, G>(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) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,35 +8,35 @@ 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 `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 `EagerSingleton` scope is the same as `Singleton` scope but instances with this scope 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: "shared instance", .Singleton) { ServiceImp3() as Service }
|
||||
container.register(.unique, tag: "prototype") { ServiceImp1() as Service }
|
||||
container.register(.shared, tag: "object graph") { ServiceImp2() as Service }
|
||||
container.register(.singleton, tag: "shared instance") { 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 === anotherPrototypeService // 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 === anotherGraphService // false
|
||||
|
||||
@@ -53,8 +53,8 @@ sharedService as! ServiceImp3 === sameSharedService as! ServiceImp3
|
||||
*/
|
||||
|
||||
var resolvedEagerSingleton = false
|
||||
let definition = container.register(tag: "eager shared instance", .EagerSingleton) { ServiceImp1() as Service }
|
||||
.resolveDependencies { _ in resolvedEagerSingleton = true }
|
||||
let definition = container.register(.eagerSingleton, tag: "eager shared instance") { ServiceImp1() as Service }
|
||||
.resolvingProperties { _ in resolvedEagerSingleton = true }
|
||||
|
||||
try! container.bootstrap()
|
||||
resolvedEagerSingleton
|
||||
|
||||
@@ -33,7 +33,7 @@ class ApiClientSingleton {
|
||||
}
|
||||
|
||||
class MyViewControllerWithSingleton: UIViewController {
|
||||
override func viewDidAppear(amimated: Bool) {
|
||||
override func viewDidAppear(_ amimated: Bool) {
|
||||
super.viewDidAppear(amimated)
|
||||
ApiClientSingleton.sharedInstance.get("/users") { /* refresh your UI */ }
|
||||
}
|
||||
@@ -71,13 +71,13 @@ class MyViewController: UIViewController {
|
||||
var apiClient: ApiClientProtocol!
|
||||
|
||||
override func viewDidAppear(amimated: Bool) {
|
||||
super.viewDidAppear(amimated)
|
||||
super.viewDidAppear(_ amimated)
|
||||
apiClient.get("path") {}
|
||||
}
|
||||
|
||||
convenience init(apiClient: ApiClientProtocol) {
|
||||
self.init()
|
||||
self.apiClient = apiClient
|
||||
self.init()
|
||||
}
|
||||
|
||||
init() {
|
||||
@@ -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,36 +98,16 @@ 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. 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.
|
||||
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 antipattern]((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.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ class FakeService: ServiceType {
|
||||
func configure(container: DependencyContainer) {
|
||||
container.register { RealService() as ServiceType }
|
||||
container.register { Client() }
|
||||
.resolveDependencies { container, client in
|
||||
.resolvingProperties { container, client in
|
||||
client.service = try container.resolve()
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ class MyTests/*: XCTestCase*/ {
|
||||
Reset container configuration to normal state:
|
||||
*/
|
||||
container.reset()
|
||||
configure(container)
|
||||
configure(container: container)
|
||||
}
|
||||
|
||||
func testThatDoSomethingIsCalled() {
|
||||
|
||||
@@ -27,22 +27,22 @@ extension AddPresenter: AddModuleInterface {}
|
||||
We can achieve this result by explicitly rosolving concrete types:
|
||||
*/
|
||||
|
||||
container.register(.ObjectGraph) { ListWireframe(addWireFrame: $0, listPresenter: $1) }
|
||||
container.register(.ObjectGraph) { AddWireframe(addPresenter: $0) }
|
||||
container.register { ListWireframe(addWireFrame: $0, listPresenter: $1) }
|
||||
container.register { AddWireframe(addPresenter: $0) }
|
||||
|
||||
var listInteractorDefinition = container.register(.ObjectGraph) { ListInteractor() }
|
||||
.resolveDependencies { container, interactor in
|
||||
var listInteractorDefinition = container.register { ListInteractor() }
|
||||
.resolvingProperties { container, interactor in
|
||||
interactor.output = try container.resolve() as ListPresenter
|
||||
}
|
||||
|
||||
var listPresenterDefinition = container.register(.ObjectGraph) { ListPresenter() }
|
||||
.resolveDependencies { container, presenter in
|
||||
var listPresenterDefinition = container.register { ListPresenter() }
|
||||
.resolvingProperties { container, presenter in
|
||||
presenter.listInteractor = try container.resolve() as ListInteractor
|
||||
presenter.listWireframe = try container.resolve()
|
||||
}
|
||||
|
||||
var addPresenterDefinition = container.register(.ObjectGraph) { AddPresenter() }
|
||||
.resolveDependencies { container, presenter in
|
||||
var addPresenterDefinition = container.register { AddPresenter() }
|
||||
.resolvingProperties { container, presenter in
|
||||
presenter.addModuleDelegate = try container.resolve() as ListPresenter
|
||||
}
|
||||
|
||||
@@ -54,33 +54,34 @@ var listWireframe = listPresenter.listWireframe
|
||||
listWireframe?.listPresenter === listPresenter
|
||||
|
||||
/*:
|
||||
Alternatively we can use type-forwarding. With type-forwarding we register definition for one (source) type and also for another (forwarded) type. When container will try to resolve forwarded type it will use the same definition as for source type, and (if registered in `ObjectGraph` scope or as a singleton) will reuse the same instance. With that you don't need to resolve concrete types in definitions:
|
||||
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(.ObjectGraph) { ListInteractor() }
|
||||
.resolveDependencies { container, interactor in
|
||||
listInteractorDefinition = container.register { ListInteractor() }
|
||||
.resolvingProperties { container, interactor in
|
||||
interactor.output = try container.resolve()
|
||||
}
|
||||
|
||||
listPresenterDefinition = container.register(.ObjectGraph) { ListPresenter() }
|
||||
.resolveDependencies { container, presenter in
|
||||
listPresenterDefinition = container.register { ListPresenter() }
|
||||
.resolvingProperties { container, presenter in
|
||||
presenter.listInteractor = try container.resolve()
|
||||
presenter.listWireframe = try container.resolve()
|
||||
}
|
||||
|
||||
addPresenterDefinition = container.register(.ObjectGraph) { AddPresenter() }
|
||||
.resolveDependencies { container, presenter in
|
||||
addPresenterDefinition = container.register { 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)
|
||||
listInteractorDefinition
|
||||
.implements(ListInteractorInput.self)
|
||||
listPresenterDefinition
|
||||
.implements(ListInteractorOutput.self)
|
||||
.implements(ListModuleInterface.self)
|
||||
.implements(AddModuleDelegate.self)
|
||||
|
||||
addPresenter = try! container.resolve() as AddPresenter
|
||||
listPresenter = addPresenter.addModuleDelegate as! ListPresenter
|
||||
@@ -97,11 +98,11 @@ listWireframe?.listPresenter === listPresenter
|
||||
You can also provide `resolveDependencies` block for forwarded definition. First container will call `resolveDependencies` block of the source definition, and then of forwarded definition:
|
||||
*/
|
||||
listInteractorDefinition
|
||||
.resolveDependencies { container, interactor in
|
||||
.resolvingProperties { container, interactor in
|
||||
print("resolved ListInteractor")
|
||||
}
|
||||
container.register(listInteractorDefinition, type: ListInteractorInput.self)
|
||||
.resolveDependencies { container, interactor in
|
||||
let _ = container.register(listInteractorDefinition, type: ListInteractorInput.self)
|
||||
.resolvingProperties { container, interactor in
|
||||
print("resolved ListInteractorInput")
|
||||
}
|
||||
addPresenter = try! container.resolve() as AddPresenter
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,76 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.0)
|
||||
activesupport (4.2.11)
|
||||
i18n (~> 0.7)
|
||||
minitest (~> 5.1)
|
||||
thread_safe (~> 0.3, >= 0.3.4)
|
||||
tzinfo (~> 1.1)
|
||||
atomos (0.1.3)
|
||||
claide (1.0.2)
|
||||
cocoapods (1.4.0)
|
||||
activesupport (>= 4.0.2, < 5)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
cocoapods-core (= 1.4.0)
|
||||
cocoapods-deintegrate (>= 1.0.2, < 2.0)
|
||||
cocoapods-downloader (>= 1.1.3, < 2.0)
|
||||
cocoapods-plugins (>= 1.0.0, < 2.0)
|
||||
cocoapods-search (>= 1.0.0, < 2.0)
|
||||
cocoapods-stats (>= 1.0.0, < 2.0)
|
||||
cocoapods-trunk (>= 1.3.0, < 2.0)
|
||||
cocoapods-try (>= 1.1.0, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
escape (~> 0.0.4)
|
||||
fourflusher (~> 2.0.1)
|
||||
gh_inspector (~> 1.0)
|
||||
molinillo (~> 0.6.4)
|
||||
nap (~> 1.0)
|
||||
ruby-macho (~> 1.1)
|
||||
xcodeproj (>= 1.5.4, < 2.0)
|
||||
cocoapods-core (1.4.0)
|
||||
activesupport (>= 4.0.2, < 6)
|
||||
fuzzy_match (~> 2.0.4)
|
||||
nap (~> 1.0)
|
||||
cocoapods-deintegrate (1.0.2)
|
||||
cocoapods-downloader (1.2.2)
|
||||
cocoapods-plugins (1.0.0)
|
||||
nap
|
||||
cocoapods-search (1.0.0)
|
||||
cocoapods-stats (1.0.0)
|
||||
cocoapods-trunk (1.3.1)
|
||||
nap (>= 0.8, < 2.0)
|
||||
netrc (~> 0.11)
|
||||
cocoapods-try (1.1.0)
|
||||
colored2 (3.1.2)
|
||||
concurrent-ruby (1.1.4)
|
||||
escape (0.0.4)
|
||||
fourflusher (2.0.1)
|
||||
fuzzy_match (2.0.4)
|
||||
gh_inspector (1.1.3)
|
||||
i18n (0.9.5)
|
||||
concurrent-ruby (~> 1.0)
|
||||
minitest (5.11.3)
|
||||
molinillo (0.6.6)
|
||||
nanaimo (0.2.6)
|
||||
nap (1.1.0)
|
||||
netrc (0.11.0)
|
||||
ruby-macho (1.3.1)
|
||||
thread_safe (0.3.6)
|
||||
tzinfo (1.2.5)
|
||||
thread_safe (~> 0.1)
|
||||
xcodeproj (1.7.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.2.6)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
cocoapods (= 1.4.0)
|
||||
|
||||
BUNDLED WITH
|
||||
1.16.5
|
||||
@@ -0,0 +1,8 @@
|
||||
import XCTest
|
||||
|
||||
import DipTests
|
||||
|
||||
var tests = [XCTestCaseEntry]()
|
||||
tests += DipTests.__allTests()
|
||||
|
||||
XCTMain(tests)
|
||||
+9
-24
@@ -1,30 +1,15 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// swift-tools-version:5.0
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Dip"
|
||||
name: "Dip",
|
||||
products: [
|
||||
.library(name: "Dip", targets: ["Dip"]),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "Dip", dependencies: [], path: "Sources"),
|
||||
.testTarget(name: "DipTests", dependencies: ["Dip"], path: "Tests"),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Dip
|
||||
|
||||
[](https://travis-ci.org/AliSoftware/Dip)
|
||||
[](https://travis-ci.org/AliSoftware/Dip)
|
||||
[](http://cocoapods.org/pods/Dip)
|
||||
[](https://github.com/Carthage/Carthage)
|
||||
[](http://cocoapods.org/pods/Dip)
|
||||
[](http://cocoapods.org/pods/Dip)
|
||||
[](https://developer.apple.com/swift)
|
||||
[](https://developer.apple.com/swift)
|
||||
[](https://developer.apple.com/swift)
|
||||
[](https://developer.apple.com/swift)
|
||||
|
||||

|
||||
_Photo courtesy of [www.kevinandamanda.com](http://www.kevinandamanda.com/recipes/appetizer/homemade-soft-cinnamon-sugar-pretzel-bites-with-salted-caramel-dipping-sauce.html)_
|
||||
@@ -17,45 +17,12 @@ _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 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 start by creating `let container = DependencyContainer()` and **registering your dependencies, by associating a _protocol_ or _type_ to a `factory`** using `container.register { MyService() as Service }`.
|
||||
* Then you can call `container.resolve() as Service` to **resolve an instance of _protocol_ or _type_** using that `DependencyContainer`.
|
||||
* You can easily use Dip along with **Storyboards and Nibs** . There is also a **[code generator](https://github.com/ilyapuchka/dipgen)** that can help to simplify registering new components.
|
||||
|
||||
> You can easily use Dip along with Storyboards and Nibs - checkout [Dip-UI](https://github.com/AliSoftware/Dip-UI) extensions.
|
||||
|
||||
## Documentation & Usage Examples
|
||||
|
||||
Dip is completely [documented](http://cocoadocs.org/docsets/Dip/4.6.0/) and comes with a Playground that lets you try all its features and become familiar with API. You can find it in `Dip.xcworkspace`.
|
||||
|
||||
> Note: it may happen that you will need to build Dip framework before playground will be able to use it. For that select `Dip-iOS` scheme and build.
|
||||
|
||||
You can find bunch of usage examples in a [wiki](../../wiki).
|
||||
|
||||
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.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **[Scopes](../../wiki/scopes)**. Dip supports 5 different scopes (or life cycle strategies): _Prototype_, _ObjectGraph_, _Singleton_, _EagerSingleton_;
|
||||
- **[Named definitions](../../wiki/named-definitions)**. You can register different factories for the same protocol or type by registering them with [tags]();
|
||||
- **[Runtime arguments](../../wiki/runtime-arguments)**. You can register factories that accept up to 6 runtime arguments;
|
||||
- **[Circular dependencies](../../wiki/circular-dependencies)**. Dip can resolve circular dependencies;
|
||||
- **[Auto-wiring](../../wiki/auto-wiring)** & **[Auto-injection](../../wiki/auto-injection)**. Dip can infer your components' dependencies injected in constructor and automatically resolve them as well as dependencies injected with properties.
|
||||
- **[Type forwarding](../../wiki/type-forwarding)**. You can register the same factory to resolve different types.
|
||||
- **[Storyboards integration](../../wiki/storyboards-integration)**. You can easily use Dip along with storyboards and Xibs without ever referencing container in your view controller's code;
|
||||
- **Weakly typed components**. Dip can resolve weak types when they are unknown at compile time.
|
||||
- **[Easy configuration](../../wiki/configuration)**. No complex container hierarchy, no unneeded functionality;
|
||||
- **Thread safety**. Registering and resolving components is thread safe;
|
||||
- **Helpful error messages and configuration validation**. You can validate your container configuration. If something can not be resolved at runtime Dip throws an error that completely describes the issue;
|
||||
|
||||
## Basic usage
|
||||
<details>
|
||||
<summary>Basic usage</summary>
|
||||
|
||||
```swift
|
||||
import Dip
|
||||
@@ -81,7 +48,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
```
|
||||
|
||||
## More sophisticated example
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>More sophisticated example</summary>
|
||||
|
||||
```swift
|
||||
import Dip
|
||||
@@ -99,8 +69,11 @@ extension DependencyContainer {
|
||||
|
||||
static func configure() -> DependencyContainer {
|
||||
return DependencyContainer { container in
|
||||
unowned let container = container
|
||||
DependencyContainer.uiContainers = [container]
|
||||
|
||||
container.register(tag: "ViewController") { ViewController() }
|
||||
.resolveDependencies { container, controller in
|
||||
.resolvingProperties { container, controller in
|
||||
controller.animationsFactory = try container.resolve() as AnimatonsFactory
|
||||
}
|
||||
|
||||
@@ -136,49 +109,81 @@ class ViewController {
|
||||
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Documentation & Usage Examples
|
||||
|
||||
Dip is completely [documented](http://cocoadocs.org/docsets/Dip/5.0.0/) and comes with a Playground that lets you try all its features and become familiar with API. You can find it in `Dip.xcworkspace`.
|
||||
|
||||
> Note: it may happen that you will need to build Dip framework before playground will be able to use it. For that select `Dip` scheme and build for iPhone Simulator.
|
||||
|
||||
You can find bunch of usage examples and usfull tips 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. Pull requests are warmly welcome too.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- **[Scopes](../../wiki/scopes)**. Dip supports 5 different scopes (or life cycle strategies): _Unique_, _Shared_, _Singleton_, _EagerSingleton_, _WeakSingleton_;
|
||||
- **[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.
|
||||
- **[Resolving optionals](../../wiki/resolving-optionals)**. Dip is able to resolve constructor or property dependencies defined as optionals.
|
||||
- **[Type forwarding](../../wiki/type-forwarding)**. You can register the same factory to resolve different types implemeted by a single class.
|
||||
- **[Circular dependencies](../../wiki/circular-dependencies)**. Dip will be able to resolve circular dependencies if you will follow some simple rules;
|
||||
- **[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;
|
||||
- **[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 (and extend it if you need);
|
||||
- **[Easy configuration](../../wiki/containers-collaboration)** & **Code generation**. No complex containers hierarchy, no unneeded functionality. Tired of writing all registrations by hand? There is a [cool code generator](https://github.com/ilyapuchka/dipgen) that will create them for you. The only thing you need is to annotate your code with some comments.
|
||||
- **Weakly typed components**. Dip can resolve "weak" types when they are unknown at compile time.
|
||||
- **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;
|
||||
|
||||
|
||||
## 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.
|
||||
You can install Dip using your favorite dependency manager:
|
||||
|
||||
Dip is available through [CocoaPods](http://cocoapods.org). To install
|
||||
it, simply add the following line to your Podfile:
|
||||
<details>
|
||||
<summary>CocoaPods</summary>
|
||||
|
||||
```ruby
|
||||
pod "Dip"
|
||||
```
|
||||
`pod "Dip"`
|
||||
|
||||
If you use [Carthage](https://github.com/Carthage/Carthage) add this line to your Cartfile:
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Carthage</summary>
|
||||
|
||||
```
|
||||
github "AliSoftware/Dip"
|
||||
```
|
||||
|
||||
If you use [Swift Package Manager](https://swift.org/package-manager/) add Dip as dependency to you `Package.swift`:
|
||||
To build for Swift 2.3 run Carthage with `--toolchain com.apple.dt.toolchain.Swift_2_3` option.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Swift Package Manager</summary>
|
||||
|
||||
```swift
|
||||
let package = Package(
|
||||
name: "MyPackage",
|
||||
dependencies: [
|
||||
.Package(url: "https://github.com/AliSoftware/Dip.git", "4.6.0")
|
||||
]
|
||||
)
|
||||
.Package(url: "https://github.com/AliSoftware/Dip", majorVersion: 5, minor: 0)
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## 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 and run tests:
|
||||
|
||||
```
|
||||
cd Dip
|
||||
swift build && swift test
|
||||
```
|
||||
|
||||
> 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.
|
||||
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 using this command: `swift build --clean && swift build && swift test`
|
||||
|
||||
## Credits
|
||||
|
||||
This library has been created by [**Olivier Halligon**](olivier@halligon.net).
|
||||
I'd also like to thank [**Ilya Puchka**](https://twitter.com/ilyapuchka) for his big contribution to it, as he added a lot of great features to it.
|
||||
This library has been created by [**Olivier Halligon**](olivier@halligon.net) and is maintained by [**Ilya Puchka**](https://twitter.com/ilyapuchka).
|
||||
|
||||
**Dip** is available under the **MIT license**. See the `LICENSE` file for more info.
|
||||
|
||||
|
||||
@@ -279,7 +279,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0700;
|
||||
LastUpgradeCheck = 0700;
|
||||
LastUpgradeCheck = 1020;
|
||||
ORGANIZATIONNAME = AliSoftware;
|
||||
TargetAttributes = {
|
||||
0990225E1BC123C000E76F43 = {
|
||||
@@ -292,7 +292,7 @@
|
||||
};
|
||||
buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "DipSampleApp" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
@@ -414,6 +414,7 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.alisoftware.DipSampleApp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
@@ -422,17 +423,28 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
@@ -460,6 +472,7 @@
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 4.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -467,17 +480,28 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
@@ -496,6 +520,7 @@
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.3;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_VERSION = 4.0;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
@@ -521,6 +546,7 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0700"
|
||||
LastUpgradeVersion = "1020"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -40,8 +40,8 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
codeCoverageEnabled = "YES"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
|
||||
@@ -16,11 +16,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
private let container = DependencyContainer()
|
||||
|
||||
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
|
||||
// Override point for customization after application launch.
|
||||
|
||||
//This is a composition root where container is configured and all dependencies are resolved
|
||||
configureContainer(container)
|
||||
configure(container: container)
|
||||
|
||||
let personProvider = try! container.resolve() as PersonProviderAPI
|
||||
let starshipProvider = try! container.resolve() as StarshipProviderAPI
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
@@ -31,6 +41,16 @@
|
||||
"size" : "60x60",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "20x20",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "20x20",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
@@ -62,6 +82,16 @@
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-152.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "83.5x83.5",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"size" : "1024x1024",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -12,8 +12,8 @@ protocol BaseCell {
|
||||
static var identifier: String { get }
|
||||
static var nib: UINib? { get }
|
||||
|
||||
static func register(tableView: UITableView)
|
||||
static func dequeueFromTableView(tableView: UITableView, forIndexPath indexPath: NSIndexPath) -> Self
|
||||
static func register(_ tableView: UITableView)
|
||||
static func dequeueFromTableView(_ tableView: UITableView, forIndexPath indexPath: IndexPath) -> Self
|
||||
}
|
||||
|
||||
extension BaseCell where Self : UITableViewCell {
|
||||
@@ -22,16 +22,16 @@ extension BaseCell where Self : UITableViewCell {
|
||||
}
|
||||
static var nib: UINib? { return nil }
|
||||
|
||||
static func register(tableView: UITableView) {
|
||||
static func register(_ tableView: UITableView) {
|
||||
if let cellNib = self.nib {
|
||||
tableView.registerNib(cellNib, forCellReuseIdentifier: identifier)
|
||||
tableView.register(cellNib, forCellReuseIdentifier: identifier)
|
||||
} else {
|
||||
tableView.registerClass(Self.self as AnyClass, forCellReuseIdentifier: identifier)
|
||||
tableView.register(Self.self as AnyClass, forCellReuseIdentifier: identifier)
|
||||
}
|
||||
}
|
||||
|
||||
static func dequeueFromTableView(tableView: UITableView, forIndexPath indexPath: NSIndexPath) -> Self {
|
||||
return tableView.dequeueReusableCellWithIdentifier(identifier, forIndexPath: indexPath) as! Self
|
||||
static func dequeueFromTableView(_ tableView: UITableView, forIndexPath indexPath: IndexPath) -> Self {
|
||||
return tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) as! Self
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,22 +16,22 @@ final class PersonCell : UITableViewCell, FillableCell {
|
||||
@IBOutlet weak var hairLabel: UILabel!
|
||||
@IBOutlet weak var eyesLabel: UILabel!
|
||||
|
||||
let heightFormatter: NSLengthFormatter = {
|
||||
let f = NSLengthFormatter()
|
||||
f.forPersonHeightUse = true
|
||||
let heightFormatter: LengthFormatter = {
|
||||
let f = LengthFormatter()
|
||||
f.isForPersonHeightUse = true
|
||||
return f
|
||||
}()
|
||||
let massFormatter: NSMassFormatter = {
|
||||
let f = NSMassFormatter()
|
||||
f.forPersonMassUse = true
|
||||
let massFormatter: MassFormatter = {
|
||||
let f = MassFormatter()
|
||||
f.isForPersonMassUse = true
|
||||
return f
|
||||
}()
|
||||
|
||||
func fillWithObject(person: Person) {
|
||||
func fillWithObject(object person: Person) {
|
||||
nameLabel.text = person.name
|
||||
genderImageView.image = person.gender.flatMap { UIImage(named: $0.rawValue) }
|
||||
heightLabel.text = heightFormatter.stringFromValue(Double(person.height), unit: .Centimeter)
|
||||
massLabel.text = massFormatter.stringFromValue(Double(person.mass), unit: .Kilogram)
|
||||
heightLabel.text = heightFormatter.string(fromValue: Double(person.height), unit: .centimeter)
|
||||
massLabel.text = massFormatter.string(fromValue: Double(person.mass), unit: .kilogram)
|
||||
hairLabel.text = person.hairColor
|
||||
eyesLabel.text = person.eyeColor
|
||||
}
|
||||
|
||||
@@ -15,13 +15,13 @@ final class StarshipCell : UITableViewCell, FillableCell {
|
||||
@IBOutlet weak var crewLabel: UILabel!
|
||||
@IBOutlet weak var passengersLabel: UILabel!
|
||||
|
||||
let numberFormatter = NSNumberFormatter()
|
||||
let numberFormatter = NumberFormatter()
|
||||
|
||||
func fillWithObject(starship: Starship) {
|
||||
func fillWithObject(object starship: Starship) {
|
||||
nameLabel.text = starship.name
|
||||
modelLabel.text = starship.model
|
||||
manufacturerLabel.text = starship.manufacturer
|
||||
crewLabel.text = numberFormatter.stringFromNumber(starship.crew)
|
||||
passengersLabel.text = numberFormatter.stringFromNumber(starship.passengers)
|
||||
crewLabel.text = numberFormatter.string(from: NSNumber(integerLiteral: starship.crew))
|
||||
passengersLabel.text = numberFormatter.string(from: NSNumber(integerLiteral: starship.passengers))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,17 +23,17 @@ enum DependencyTags: Int, DependencyTagConvertible {
|
||||
}
|
||||
|
||||
// MARK: Dependency Container for Providers
|
||||
func configureContainer(dip: DependencyContainer) {
|
||||
func configure(container dip: DependencyContainer) {
|
||||
|
||||
// Register the NetworkLayer, same for everyone here (but we have the ability to register a different one for a specific WebService if we wanted to)
|
||||
dip.register(.Singleton) { URLSessionNetworkLayer(baseURL: "http://swapi.co/api/")! as NetworkLayer }
|
||||
dip.register(.singleton) { URLSessionNetworkLayer(baseURL: "http://swapi.co/api/")! as NetworkLayer }
|
||||
|
||||
if FAKE_PERSONS {
|
||||
|
||||
// 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 @@
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<string>7.1.1</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
|
||||
@@ -11,12 +11,12 @@ import Foundation
|
||||
///Provides some dummy Person entities
|
||||
struct DummyPilotProvider : PersonProviderAPI {
|
||||
|
||||
func fetchIDs(completion: [Int] -> Void) {
|
||||
func fetchIDs(completion: @escaping ([Int]) -> Void) {
|
||||
completion(Array(0..<5))
|
||||
}
|
||||
|
||||
func fetch(id: Int, completion: Person? -> Void) {
|
||||
completion(dummyPerson(id))
|
||||
func fetch(id: Int, completion: @escaping (Person?) -> Void) {
|
||||
completion(dummyPerson(idx: id))
|
||||
}
|
||||
|
||||
private func dummyPerson(idx: Int) -> Person {
|
||||
@@ -40,9 +40,9 @@ class PlistPersonProvider : PersonProviderAPI {
|
||||
|
||||
init(plist basename: String) {
|
||||
guard
|
||||
let path = NSBundle.mainBundle().pathForResource(basename, ofType: "plist"),
|
||||
let path = Bundle.main.path(forResource: basename, ofType: "plist"),
|
||||
let list = NSArray(contentsOfFile: path),
|
||||
peopleDict = list as? [[String:AnyObject]]
|
||||
let peopleDict = list as? [[String:AnyObject]]
|
||||
else {
|
||||
fatalError("PLIST for \(basename) not found")
|
||||
}
|
||||
@@ -50,11 +50,11 @@ class PlistPersonProvider : PersonProviderAPI {
|
||||
self.people = peopleDict.map(PlistPersonProvider.personFromDict)
|
||||
}
|
||||
|
||||
func fetchIDs(completion: [Int] -> Void) {
|
||||
func fetchIDs(completion: @escaping ([Int]) -> Void) {
|
||||
completion(Array(0..<people.count))
|
||||
}
|
||||
|
||||
func fetch(id: Int, completion: Person? -> Void) {
|
||||
func fetch(id: Int, completion: @escaping (Person?) -> Void) {
|
||||
guard id < people.count else {
|
||||
completion(nil)
|
||||
return
|
||||
@@ -65,12 +65,12 @@ class PlistPersonProvider : PersonProviderAPI {
|
||||
private static func personFromDict(dict: [String:AnyObject]) -> Person {
|
||||
guard
|
||||
let name = dict["name"] as? String,
|
||||
height = dict["height"] as? Int,
|
||||
mass = dict["mass"] as? Int,
|
||||
hairColor = dict["hairColor"] as? String,
|
||||
eyeColor = dict["eyeColor"] as? String,
|
||||
genderStr = dict["gender"] as? String,
|
||||
starshipsIDs = dict["starships"] as? [Int]
|
||||
let height = dict["height"] as? Int,
|
||||
let mass = dict["mass"] as? Int,
|
||||
let hairColor = dict["hairColor"] as? String,
|
||||
let eyeColor = dict["eyeColor"] as? String,
|
||||
let genderStr = dict["gender"] as? String,
|
||||
let starshipsIDs = dict["starships"] as? [Int]
|
||||
else {
|
||||
fatalError("Invalid Plist")
|
||||
}
|
||||
@@ -98,16 +98,16 @@ class FakePersonsProvider: PersonProviderAPI {
|
||||
self.dummyProvider = dummyProvider
|
||||
}
|
||||
|
||||
func fetchIDs(completion: [Int] -> Void) {
|
||||
dummyProvider.fetchIDs(completion)
|
||||
func fetchIDs(completion: @escaping ([Int]) -> Void) {
|
||||
dummyProvider.fetchIDs(completion: completion)
|
||||
}
|
||||
|
||||
func fetch(id: Int, completion: Person? -> Void) {
|
||||
if let plistProvider = plistProvider where id == 0 {
|
||||
plistProvider.fetch(id, completion: completion)
|
||||
func fetch(id: Int, completion: @escaping (Person?) -> Void) {
|
||||
if let plistProvider = plistProvider, id == 0 {
|
||||
plistProvider.fetch(id: id, completion: completion)
|
||||
}
|
||||
else {
|
||||
dummyProvider.fetch(id, completion: completion)
|
||||
dummyProvider.fetch(id: id, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,13 +12,13 @@ import Foundation
|
||||
struct DummyStarshipProvider : StarshipProviderAPI {
|
||||
var pilotName: String
|
||||
|
||||
func fetchIDs(completion: [Int] -> Void) {
|
||||
let nbShips = pilotName.characters.count
|
||||
func fetchIDs(completion: @escaping ([Int]) -> Void) {
|
||||
let nbShips = pilotName.count
|
||||
completion(Array(0..<nbShips))
|
||||
}
|
||||
|
||||
func fetch(id: Int, completion: Starship? -> Void) {
|
||||
completion(dummyStarship(id))
|
||||
func fetch(id: Int, completion: @escaping (Starship?) -> Void) {
|
||||
completion(dummyStarship(idx: id))
|
||||
}
|
||||
|
||||
private func dummyStarship(idx: Int) -> Starship {
|
||||
@@ -42,11 +42,11 @@ class HardCodedStarshipProvider : StarshipProviderAPI {
|
||||
Starship(name: "Third Ship", model: "AwesomeShip Cargo", manufacturer: "HardCoded Inc.", crew: 12, passengers: 150, pilotIDs: [2]),
|
||||
] + Array(4..<75).map { Starship(name: "Ship #\($0)", model: "AwesomeShip Fighter", manufacturer: "HardCoded Inc.", crew: 1, passengers: 2, pilotIDs: [1]) }
|
||||
|
||||
func fetchIDs(completion: [Int] -> Void) {
|
||||
func fetchIDs(completion: @escaping ([Int]) -> Void) {
|
||||
completion(Array(0..<starships.count))
|
||||
}
|
||||
|
||||
func fetch(id: Int, completion: Starship? -> Void) {
|
||||
func fetch(id: Int, completion: @escaping (Starship?) -> Void) {
|
||||
guard id < starships.count else {
|
||||
completion(nil)
|
||||
return
|
||||
@@ -66,16 +66,16 @@ class FakeStarshipProvider: StarshipProviderAPI {
|
||||
self.hardCodedProvider = hardCodedProvider
|
||||
}
|
||||
|
||||
func fetchIDs(completion: [Int] -> Void) {
|
||||
hardCodedProvider.fetchIDs(completion)
|
||||
func fetchIDs(completion: @escaping ([Int]) -> Void) {
|
||||
hardCodedProvider.fetchIDs(completion: completion)
|
||||
}
|
||||
|
||||
func fetch(id: Int, completion: Starship? -> Void) {
|
||||
func fetch(id: Int, completion: @escaping (Starship?) -> Void) {
|
||||
if id == 0 {
|
||||
dummyProvider.fetch(id, completion: completion)
|
||||
dummyProvider.fetch(id: id, completion: completion)
|
||||
}
|
||||
else {
|
||||
hardCodedProvider.fetch(id, completion: completion)
|
||||
hardCodedProvider.fetch(id: id, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,21 +9,21 @@
|
||||
import Foundation
|
||||
|
||||
enum NetworkResponse {
|
||||
case Success(NSData, NSHTTPURLResponse)
|
||||
case Success(Data, HTTPURLResponse)
|
||||
case Error(NSError)
|
||||
|
||||
func unwrap() throws -> (NSData, NSHTTPURLResponse) {
|
||||
func unwrap() throws -> (Data, HTTPURLResponse) {
|
||||
switch self {
|
||||
case Success(let data, let response):
|
||||
case .Success(let data, let response):
|
||||
return (data, response)
|
||||
case Error(let error):
|
||||
case .Error(let error):
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
func json<T>() throws -> T {
|
||||
let (data, _) = try self.unwrap()
|
||||
let obj = try NSJSONSerialization.JSONObjectWithData(data, options: [])
|
||||
let obj = try JSONSerialization.jsonObject(with: data, options: [])
|
||||
guard let json = obj as? T else {
|
||||
throw SWAPIError.InvalidJSON
|
||||
}
|
||||
@@ -32,5 +32,5 @@ enum NetworkResponse {
|
||||
}
|
||||
|
||||
protocol NetworkLayer {
|
||||
func request(path: String, completion: NetworkResponse -> Void)
|
||||
func request(path: String, completion: @escaping (NetworkResponse) -> Void)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
import Foundation
|
||||
|
||||
protocol PersonProviderAPI {
|
||||
func fetchIDs(completion: [Int] -> Void)
|
||||
func fetch(id: Int, completion: Person? -> Void)
|
||||
func fetchIDs(completion: @escaping ([Int]) -> Void)
|
||||
func fetch(id: Int, completion: @escaping (Person?) -> Void)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
import Dip
|
||||
|
||||
enum SWAPIError: ErrorType {
|
||||
enum SWAPIError: Error {
|
||||
case InvalidJSON
|
||||
}
|
||||
|
||||
|
||||
@@ -20,15 +20,15 @@ struct SWAPIPersonProvider : PersonProviderAPI {
|
||||
self.ws = webService
|
||||
}
|
||||
|
||||
func fetchIDs(completion: [Int] -> Void) {
|
||||
ws.request("people") { response in
|
||||
func fetchIDs(completion: @escaping ([Int]) -> Void) {
|
||||
ws.request(path: "people") { response in
|
||||
do {
|
||||
let dict = try response.json() as NSDictionary
|
||||
guard let results = dict["results"] as? [NSDictionary] else { throw SWAPIError.InvalidJSON }
|
||||
|
||||
// Extract URLs (flatten to ignore invalid ones)
|
||||
let urlStrings = results.flatMap({ $0["url"] as? String })
|
||||
let ids = urlStrings.flatMap(idFromURLString)
|
||||
let urlStrings = results.compactMap({ $0["url"] as? String })
|
||||
let ids = urlStrings.compactMap(idFromURLString)
|
||||
|
||||
completion(ids)
|
||||
}
|
||||
@@ -38,14 +38,14 @@ struct SWAPIPersonProvider : PersonProviderAPI {
|
||||
}
|
||||
}
|
||||
|
||||
func fetch(id: Int, completion: Person? -> Void) {
|
||||
ws.request("people/\(id)") { response in
|
||||
func fetch(id: Int, completion: @escaping (Person?) -> Void) {
|
||||
ws.request(path: "people/\(id)") { response in
|
||||
do {
|
||||
let json = try response.json() as NSDictionary
|
||||
guard
|
||||
let name = json["name"] as? String,
|
||||
let heightStr = json["height"] as? String, height = Int(heightStr),
|
||||
let massStr = json["mass"] as? String, mass = Int(massStr),
|
||||
let heightStr = json["height"] as? String, let height = Int(heightStr),
|
||||
let massStr = json["mass"] as? String, let mass = Int(massStr),
|
||||
let hairColor = json["hair_color"] as? String,
|
||||
let eyeColor = json["eye_color"] as? String,
|
||||
let gender = json["gender"] as? String,
|
||||
@@ -61,7 +61,7 @@ struct SWAPIPersonProvider : PersonProviderAPI {
|
||||
hairColor: hairColor,
|
||||
eyeColor: eyeColor,
|
||||
gender: Gender(rawValue: gender),
|
||||
starshipIDs: starshipURLStrings.flatMap(idFromURLString)
|
||||
starshipIDs: starshipURLStrings.compactMap(idFromURLString)
|
||||
)
|
||||
completion(person)
|
||||
}
|
||||
|
||||
@@ -20,15 +20,15 @@ struct SWAPIStarshipProvider : StarshipProviderAPI {
|
||||
self.ws = webService
|
||||
}
|
||||
|
||||
func fetchIDs(completion: [Int] -> Void) {
|
||||
ws.request("starships") { response in
|
||||
func fetchIDs(completion: @escaping ([Int]) -> Void) {
|
||||
ws.request(path: "starships") { response in
|
||||
do {
|
||||
let dict = try response.json() as NSDictionary
|
||||
guard let results = dict["results"] as? [NSDictionary] else { throw SWAPIError.InvalidJSON }
|
||||
|
||||
// Extract URLs (flatten to ignore invalid ones)
|
||||
let urlStrings = results.flatMap({ $0["url"] as? String })
|
||||
let ids = urlStrings.flatMap(idFromURLString)
|
||||
let urlStrings = results.compactMap({ $0["url"] as? String })
|
||||
let ids = urlStrings.compactMap(idFromURLString)
|
||||
|
||||
completion(ids)
|
||||
}
|
||||
@@ -38,16 +38,16 @@ struct SWAPIStarshipProvider : StarshipProviderAPI {
|
||||
}
|
||||
}
|
||||
|
||||
func fetch(id: Int, completion: Starship? -> Void) {
|
||||
ws.request("starships/\(id)") { response in
|
||||
func fetch(id: Int, completion: @escaping (Starship?) -> Void) {
|
||||
ws.request(path: "starships/\(id)") { response in
|
||||
do {
|
||||
let json = try response.json() as NSDictionary
|
||||
guard
|
||||
let name = json["name"] as? String,
|
||||
let model = json["model"] as? String,
|
||||
let manufacturer = json["manufacturer"] as? String,
|
||||
let crewStr = json["crew"] as? String, crew = Int(crewStr),
|
||||
let passengersStr = json["passengers"] as? String, passengers = Int(passengersStr),
|
||||
let crewStr = json["crew"] as? String, let crew = Int(crewStr),
|
||||
let passengersStr = json["passengers"] as? String, let passengers = Int(passengersStr),
|
||||
let pilotIDStrings = json["pilots"] as? [String]
|
||||
else {
|
||||
throw SWAPIError.InvalidJSON
|
||||
@@ -59,7 +59,7 @@ struct SWAPIStarshipProvider : StarshipProviderAPI {
|
||||
manufacturer: manufacturer,
|
||||
crew: crew,
|
||||
passengers: passengers,
|
||||
pilotIDs: pilotIDStrings.flatMap(idFromURLString)
|
||||
pilotIDs: pilotIDStrings.compactMap(idFromURLString)
|
||||
)
|
||||
completion(ship)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
import Foundation
|
||||
|
||||
protocol StarshipProviderAPI {
|
||||
func fetchIDs(completion: [Int] -> Void)
|
||||
func fetch(id: Int, completion: Starship? -> Void)
|
||||
func fetchIDs(completion: @escaping ([Int]) -> Void)
|
||||
func fetch(id: Int, completion: @escaping (Starship?) -> Void)
|
||||
}
|
||||
|
||||
@@ -10,33 +10,33 @@ import Foundation
|
||||
|
||||
///NetworkLayer implementation on top of NSURLSession
|
||||
struct URLSessionNetworkLayer : NetworkLayer {
|
||||
let baseURL: NSURL
|
||||
let session: NSURLSession
|
||||
let responseQueue: dispatch_queue_t
|
||||
let baseURL: URL
|
||||
let session: URLSession
|
||||
let responseQueue: DispatchQueue
|
||||
|
||||
init?(baseURL: String, session: NSURLSession = .sharedSession(), responseQueue: dispatch_queue_t = dispatch_get_main_queue()) {
|
||||
guard let url = NSURL(string: baseURL) else { return nil }
|
||||
init?(baseURL: String, session: URLSession = URLSession.shared, responseQueue: DispatchQueue = DispatchQueue.main) {
|
||||
guard let url = URL(string: baseURL) else { return nil }
|
||||
self.init(baseURL: url, session: session)
|
||||
}
|
||||
|
||||
init(baseURL: NSURL, session: NSURLSession = .sharedSession(), responseQueue: dispatch_queue_t = dispatch_get_main_queue()) {
|
||||
init(baseURL: URL, session: URLSession = URLSession.shared, responseQueue: DispatchQueue = DispatchQueue.main) {
|
||||
self.baseURL = baseURL
|
||||
self.session = session
|
||||
self.responseQueue = responseQueue
|
||||
}
|
||||
|
||||
func request(path: String, completion: NetworkResponse -> Void) {
|
||||
let url = self.baseURL.URLByAppendingPathComponent(path)
|
||||
let task = session.dataTaskWithURL(url) { data, response, error in
|
||||
if let data = data, let response = response as? NSHTTPURLResponse {
|
||||
dispatch_async(self.responseQueue) {
|
||||
func request(path: String, completion: @escaping (NetworkResponse) -> Void) {
|
||||
let url = self.baseURL.appendingPathComponent(path)
|
||||
let task = session.dataTask(with: url) { data, response, error in
|
||||
if let data = data, let response = response as? HTTPURLResponse {
|
||||
self.responseQueue.async() {
|
||||
completion(NetworkResponse.Success(data, response))
|
||||
}
|
||||
}
|
||||
else {
|
||||
let err = error ?? NSError(domain: NSURLErrorDomain, code: NSURLError.Unknown.rawValue, userInfo: nil)
|
||||
dispatch_async(self.responseQueue) {
|
||||
completion(NetworkResponse.Error(err))
|
||||
let err = error ?? NSError(domain: NSURLErrorDomain, code: URLError.unknown.rawValue, userInfo: nil)
|
||||
self.responseQueue.async() {
|
||||
completion(NetworkResponse.Error(err as NSError))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ extension StoryboardScene where Self.RawValue == String {
|
||||
}
|
||||
|
||||
func viewController() -> UIViewController {
|
||||
return Self.storyboard().instantiateViewControllerWithIdentifier(self.rawValue)
|
||||
return Self.storyboard().instantiateViewController(withIdentifier: self.rawValue)
|
||||
}
|
||||
static func viewController(identifier: Self) -> UIViewController {
|
||||
return identifier.viewController()
|
||||
|
||||
@@ -14,8 +14,8 @@ protocol FetchableTrait: class {
|
||||
var batchRequestID: Int { get set }
|
||||
var tableView: UITableView! { get }
|
||||
|
||||
func fetchIDs(completion: [Int] -> Void)
|
||||
func fetchOne(id: Int, completion: ObjectType? -> Void)
|
||||
func fetchIDs(completion: @escaping ([Int]) -> Void)
|
||||
func fetchOne(id: Int, completion: @escaping (ObjectType?) -> Void)
|
||||
var fetchProgress: (current: Int, total: Int?) { get set }
|
||||
}
|
||||
|
||||
@@ -27,10 +27,10 @@ extension FetchableTrait {
|
||||
objects?.removeAll()
|
||||
fetchProgress = (0,objectIDs.count)
|
||||
for objectID in objectIDs {
|
||||
fetchOne(objectID) { (object: ObjectType?) in
|
||||
fetchOne(id: objectID) { (object: ObjectType?) in
|
||||
// Exit if we failed to retrive an object for this ID, or if the request
|
||||
// should be ignored because a new batch request has been started since
|
||||
guard let object = object where batch == self.batchRequestID else { return }
|
||||
guard let object = object, batch == self.batchRequestID else { return }
|
||||
|
||||
if self.objects == nil { self.objects = [] }
|
||||
self.objects?.append(object)
|
||||
@@ -46,7 +46,7 @@ extension FetchableTrait {
|
||||
fetchProgress = (0, nil)
|
||||
fetchIDs() { objectIDs in
|
||||
guard batch == self.batchRequestID else { return }
|
||||
self.loadObjects(objectIDs)
|
||||
self.loadObjects(objectIDs: objectIDs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,8 +63,8 @@ extension FetchableTrait {
|
||||
}
|
||||
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
|
||||
label.text = text
|
||||
label.textColor = .grayColor()
|
||||
label.font = .systemFontOfSize(12)
|
||||
label.textColor = .gray
|
||||
label.font = UIFont.systemFont(ofSize: 12)
|
||||
label.sizeToFit()
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: label)
|
||||
}
|
||||
|
||||
@@ -14,45 +14,46 @@ class PersonListViewController: UITableViewController, FetchableTrait {
|
||||
|
||||
var personProvider: PersonProviderAPI!
|
||||
var starshipProvider: StarshipProviderAPI!
|
||||
|
||||
func fetchIDs(completion: [Int] -> Void) {
|
||||
return personProvider.fetchIDs(completion)
|
||||
|
||||
func fetchIDs(completion: @escaping ([Int]) -> Void) {
|
||||
return personProvider.fetchIDs(completion: completion)
|
||||
}
|
||||
|
||||
func fetchOne(personID: Int, completion: Person? -> Void) {
|
||||
return personProvider.fetch(personID, completion: completion)
|
||||
func fetchOne(id personID: Int, completion: @escaping (Person?) -> Void) {
|
||||
return personProvider.fetch(id: personID, completion: completion)
|
||||
}
|
||||
|
||||
var fetchProgress: (current: Int, total: Int?) = (0, nil) {
|
||||
didSet {
|
||||
displayProgressInNavBar(self.navigationItem)
|
||||
displayProgressInNavBar(navigationItem: self.navigationItem)
|
||||
}
|
||||
}
|
||||
|
||||
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
guard
|
||||
let id = segue.identifier, segueID = UIStoryboard.Segue.Main(rawValue: id)
|
||||
where segueID == .StarshipsSegue,
|
||||
let id = segue.identifier,
|
||||
let segueID = UIStoryboard.Segue.Main(rawValue: id),
|
||||
segueID == .StarshipsSegue,
|
||||
let indexPath = self.tableView.indexPathForSelectedRow,
|
||||
let destVC = segue.destinationViewController as? StarshipListViewController,
|
||||
let destVC = segue.destination as? StarshipListViewController,
|
||||
let person = self.objects?[indexPath.row]
|
||||
else {
|
||||
fatalError()
|
||||
}
|
||||
destVC.starshipProvider = starshipProvider
|
||||
destVC.loadObjects(person.starshipIDs)
|
||||
destVC.loadObjects(objectIDs: person.starshipIDs)
|
||||
}
|
||||
}
|
||||
|
||||
extension PersonListViewController {
|
||||
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return objects?.count ?? 0
|
||||
}
|
||||
|
||||
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
guard let object = self.objects?[indexPath.row] else { fatalError() }
|
||||
let cell = PersonCell.dequeueFromTableView(tableView, forIndexPath: indexPath)
|
||||
cell.fillWithObject(object)
|
||||
cell.fillWithObject(object: object)
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,44 +16,45 @@ class StarshipListViewController : UITableViewController, FetchableTrait {
|
||||
var starshipProvider: StarshipProviderAPI!
|
||||
var personProvider: PersonProviderAPI!
|
||||
|
||||
func fetchIDs(completion: [Int] -> Void) {
|
||||
starshipProvider.fetchIDs(completion)
|
||||
func fetchIDs(completion: @escaping ([Int]) -> Void) {
|
||||
starshipProvider.fetchIDs(completion: completion)
|
||||
}
|
||||
func fetchOne(shipID:Int, completion: Starship? -> Void) {
|
||||
starshipProvider.fetch(shipID, completion: completion)
|
||||
func fetchOne(id shipID:Int, completion: @escaping (Starship?) -> Void) {
|
||||
starshipProvider.fetch(id: shipID, completion: completion)
|
||||
}
|
||||
|
||||
var fetchProgress: (current: Int, total: Int?) = (0, nil) {
|
||||
didSet {
|
||||
displayProgressInNavBar(self.navigationItem)
|
||||
displayProgressInNavBar(navigationItem: self.navigationItem)
|
||||
}
|
||||
}
|
||||
|
||||
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
guard
|
||||
let id = segue.identifier, segueID = UIStoryboard.Segue.Main(rawValue: id)
|
||||
where segueID == .PilotsSegue,
|
||||
let id = segue.identifier,
|
||||
let segueID = UIStoryboard.Segue.Main(rawValue: id),
|
||||
segueID == .PilotsSegue,
|
||||
let indexPath = self.tableView.indexPathForSelectedRow,
|
||||
let destVC = segue.destinationViewController as? PersonListViewController,
|
||||
let destVC = segue.destination as? PersonListViewController,
|
||||
let starship = self.objects?[indexPath.row]
|
||||
else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
destVC.personProvider = personProvider
|
||||
destVC.loadObjects(starship.pilotIDs)
|
||||
destVC.loadObjects(objectIDs: starship.pilotIDs)
|
||||
}
|
||||
}
|
||||
|
||||
extension StarshipListViewController {
|
||||
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return objects?.count ?? 0
|
||||
}
|
||||
|
||||
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
guard let object = self.objects?[indexPath.row] else { fatalError() }
|
||||
let cell = StarshipCell.dequeueFromTableView(tableView, forIndexPath: indexPath)
|
||||
cell.fillWithObject(object)
|
||||
cell.fillWithObject(object: object)
|
||||
return cell
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<string>7.1.1</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
|
||||
@@ -14,24 +14,24 @@ var wsDependencies = DependencyContainer()
|
||||
// MARK: - Mock object used for tests
|
||||
|
||||
struct NetworkMock : NetworkLayer {
|
||||
let fakeData: NSData?
|
||||
let fakeData: Data?
|
||||
|
||||
init(json: AnyObject) {
|
||||
init(json: Any) {
|
||||
do {
|
||||
fakeData = try NSJSONSerialization.dataWithJSONObject(json, options: [])
|
||||
fakeData = try JSONSerialization.data(withJSONObject: json, options: [])
|
||||
} catch {
|
||||
fakeData = nil
|
||||
}
|
||||
}
|
||||
|
||||
func request(path: String, completion: NetworkResponse -> Void) {
|
||||
let fakeURL = NSURL(string: "stub://")!.URLByAppendingPathComponent(path)
|
||||
func request(path: String, completion: @escaping (NetworkResponse) -> Void) {
|
||||
let fakeURL = NSURL(string: "http://stub")?.appendingPathComponent(path)
|
||||
if let data = fakeData {
|
||||
let response = NSHTTPURLResponse(URL: fakeURL, statusCode: 200, HTTPVersion: "1.1", headerFields:nil)!
|
||||
let response = HTTPURLResponse(url: fakeURL!, statusCode: 200, httpVersion: "1.1", headerFields:nil)!
|
||||
completion(.Success(data, response))
|
||||
} else {
|
||||
let response = NSHTTPURLResponse(URL: fakeURL, statusCode: 204, HTTPVersion: "1.1", headerFields:nil)!
|
||||
completion(.Success(NSData(), response))
|
||||
let response = HTTPURLResponse(url: fakeURL!, statusCode: 204, httpVersion: "1.1", headerFields:nil)!
|
||||
completion(.Success(Data(), response))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,10 @@ import XCTest
|
||||
import Dip
|
||||
|
||||
class SWAPIPersonProviderTests: XCTestCase {
|
||||
let fakePerson1 = ["name": "John Doe", "mass": "72", "height": "172", "eye_color": "brown", "hair_color": "black", "gender": "male",
|
||||
"starships": ["stub://starship/7/", "stub://starship/15"], "url": "stub://people/1"]
|
||||
let fakePerson2 = ["name": "Jane Doe", "mass": "63", "height": "167", "eye_color": "blue", "hair_color": "red", "gender": "female",
|
||||
"starships": ["stub://starship/11/"], "url": "stub://people/12"]
|
||||
let fakePerson1: [String : Any] = ["name": "John Doe", "mass": "72", "height": "172", "eye_color": "brown", "hair_color": "black", "gender": "male",
|
||||
"starships": ["http://starship/7/", "http://starship/15"], "url": "http://people/1"]
|
||||
let fakePerson2: [String: Any] = ["name": "Jane Doe", "mass": "63", "height": "167", "eye_color": "blue", "hair_color": "red", "gender": "female",
|
||||
"starships": ["http://starship/11/"], "url": "http://people/12"]
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
@@ -23,7 +23,7 @@ class SWAPIPersonProviderTests: XCTestCase {
|
||||
|
||||
func testFetchPersonIDs() {
|
||||
let mock = NetworkMock(json: ["results": [fakePerson1, fakePerson2]])
|
||||
wsDependencies.register(.Singleton) { mock as NetworkLayer }
|
||||
wsDependencies.register(.singleton) { mock as NetworkLayer }
|
||||
|
||||
let provider = SWAPIPersonProvider(webService: try! wsDependencies.resolve())
|
||||
provider.fetchIDs { personIDs in
|
||||
@@ -37,10 +37,10 @@ class SWAPIPersonProviderTests: XCTestCase {
|
||||
|
||||
func testFetchOnePerson() {
|
||||
let mock = NetworkMock(json: fakePerson1)
|
||||
wsDependencies.register(.Singleton) { mock as NetworkLayer }
|
||||
wsDependencies.register(.singleton) { mock as NetworkLayer }
|
||||
|
||||
let provider = SWAPIPersonProvider(webService: try! wsDependencies.resolve())
|
||||
provider.fetch(1) { person in
|
||||
provider.fetch(id: 1) { person in
|
||||
XCTAssertNotNil(person)
|
||||
XCTAssertEqual(person?.name, "John Doe")
|
||||
XCTAssertEqual(person?.mass, 72)
|
||||
@@ -57,10 +57,10 @@ class SWAPIPersonProviderTests: XCTestCase {
|
||||
func testFetchInvalidPerson() {
|
||||
let json = ["error":"whoops"]
|
||||
let mock = NetworkMock(json: json)
|
||||
wsDependencies.register(.Singleton) { mock as NetworkLayer }
|
||||
wsDependencies.register(.singleton) { mock as NetworkLayer }
|
||||
|
||||
let provider = SWAPIPersonProvider(webService: try! wsDependencies.resolve())
|
||||
provider.fetch(12) { person in
|
||||
provider.fetch(id: 12) { person in
|
||||
XCTAssertNil(person)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,10 @@ import XCTest
|
||||
import Dip
|
||||
|
||||
class SWAPIStarshipProviderTests: XCTestCase {
|
||||
let fakeShip1 = ["name": "Falcon", "model": "Fighter", "manufacturer": "Fake Industries", "crew": "7", "passengers": "15",
|
||||
"pilots": ["stub://people/1/", "stub://people/9"], "url": "stub://starship/4"]
|
||||
let fakeShip2 = ["name": "Voyager", "model": "Cargo", "manufacturer": "Fake Industries", "crew": "18", "passengers": "150",
|
||||
"pilots": ["stub://people/2/", "stub://people/3"], "url": "stub://starship/31"]
|
||||
let fakeShip1: [String: Any] = ["name": "Falcon", "model": "Fighter", "manufacturer": "Fake Industries", "crew": "7", "passengers": "15",
|
||||
"pilots": ["http://people/1/", "http://people/9"], "url": "http://starship/4"]
|
||||
let fakeShip2: [String: Any] = ["name": "Voyager", "model": "Cargo", "manufacturer": "Fake Industries", "crew": "18", "passengers": "150",
|
||||
"pilots": ["http://people/2/", "http://people/3"], "url": "http://starship/31"]
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
@@ -23,7 +23,7 @@ class SWAPIStarshipProviderTests: XCTestCase {
|
||||
|
||||
func testFetchStarshipIDs() {
|
||||
let mock = NetworkMock(json: ["results": [fakeShip1, fakeShip2]])
|
||||
wsDependencies.register(.Singleton) { mock as NetworkLayer }
|
||||
wsDependencies.register(.singleton) { mock as NetworkLayer }
|
||||
|
||||
let provider = SWAPIStarshipProvider(webService: try! wsDependencies.resolve())
|
||||
provider.fetchIDs { shipIDs in
|
||||
@@ -38,10 +38,10 @@ class SWAPIStarshipProviderTests: XCTestCase {
|
||||
func testFetchOneStarship() {
|
||||
|
||||
let mock = NetworkMock(json: fakeShip1)
|
||||
wsDependencies.register(.Singleton) { mock as NetworkLayer }
|
||||
wsDependencies.register(.singleton) { mock as NetworkLayer }
|
||||
|
||||
let provider = SWAPIStarshipProvider(webService: try! wsDependencies.resolve())
|
||||
provider.fetch(1) { starship in
|
||||
provider.fetch(id: 1) { starship in
|
||||
XCTAssertNotNil(starship)
|
||||
XCTAssertEqual(starship?.name, "Falcon")
|
||||
XCTAssertEqual(starship?.model, "Fighter")
|
||||
@@ -57,10 +57,10 @@ class SWAPIStarshipProviderTests: XCTestCase {
|
||||
func testFetchInvalidStarship() {
|
||||
let json = ["error":"whoops"]
|
||||
let mock = NetworkMock(json: json)
|
||||
wsDependencies.register(.Singleton) { mock as NetworkLayer }
|
||||
wsDependencies.register(.singleton) { mock as NetworkLayer }
|
||||
|
||||
let provider = SWAPIStarshipProvider(webService: try! wsDependencies.resolve())
|
||||
provider.fetch(12) { starship in
|
||||
provider.fetch(id: 12) { starship in
|
||||
XCTAssertNil(starship)
|
||||
}
|
||||
}
|
||||
|
||||
+248
-118
@@ -27,16 +27,30 @@ 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)
|
||||
func autoInjectProperties(in instance: Any) throws {
|
||||
let mirror = Mirror(reflecting: instance)
|
||||
|
||||
//mirror only contains class own properties
|
||||
//so we need to walk through super class mirrors
|
||||
//to resolve super class auto-injected properties
|
||||
var superClassMirror = mirror.superclassMirror
|
||||
while superClassMirror != nil {
|
||||
try superClassMirror?.children.forEach(resolveChild)
|
||||
superClassMirror = superClassMirror?.superclassMirror
|
||||
}
|
||||
|
||||
try mirror.children.forEach(resolveChild)
|
||||
}
|
||||
|
||||
private func resolveChild(child: Mirror.Child) throws {
|
||||
//HOTFIX for https://bugs.swift.org/browse/SR-2282
|
||||
guard !String(describing: type(of: child.value)).has(prefix: "ImplicitlyUnwrappedOptional") else { return }
|
||||
guard let injectedPropertyBox = child.value as? AutoInjectedPropertyBox else { return }
|
||||
|
||||
let contextKey = DefinitionKey(protocolType: injectedPropertyBox.dynamicType.wrappedType, argumentsType: Void.self, associatedTag: context.tag)
|
||||
try inContext(contextKey, injectedInProperty: child.label, logErrors: false) {
|
||||
try injectedPropertyBox.resolve(self)
|
||||
let wrappedType = type(of: injectedPropertyBox).wrappedType
|
||||
let contextKey = DefinitionKey(type: wrappedType, typeOfArguments: Void.self, tag: context.tag)
|
||||
try inContext(key:contextKey, injectedInType: context?.resolvingType, injectedInProperty: child.label, logErrors: false) {
|
||||
try injectedPropertyBox.resolve(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +78,7 @@ extension DependencyContainer {
|
||||
```
|
||||
|
||||
*/
|
||||
public protocol AutoInjectedPropertyBox: class {
|
||||
public protocol AutoInjectedPropertyBox {
|
||||
///The type of wrapped property.
|
||||
static var wrappedType: Any.Type { get }
|
||||
|
||||
@@ -76,80 +90,142 @@ public protocol AutoInjectedPropertyBox: class {
|
||||
|
||||
- note: This method is not intended to be called manually, `DependencyContainer` will call it by itself.
|
||||
*/
|
||||
func resolve(container: DependencyContainer) throws
|
||||
func resolve(_ container: DependencyContainer) throws
|
||||
}
|
||||
|
||||
#if swift(>=5.1)
|
||||
/**
|
||||
Use this wrapper to identify _strong_ properties of the instance that should be
|
||||
auto-injected by `DependencyContainer`. Type T can be any type.
|
||||
|
||||
- warning: Do not define this property as optional or container will not be able to inject it.
|
||||
Instead define it with initial value of `Injected<T>()`.
|
||||
|
||||
|
||||
**Example**:
|
||||
|
||||
```swift
|
||||
class ClientImp: Client {
|
||||
@Injected var service: Service?
|
||||
}
|
||||
```
|
||||
|
||||
- seealso: `InjectedWeak`
|
||||
|
||||
*/
|
||||
@propertyWrapper
|
||||
public struct Injected<T>: _InjectedPropertyBox, AutoInjectedPropertyBox {
|
||||
let valueBox: NullableBox<T> = NullableBox(nil)
|
||||
|
||||
///Wrapped value.
|
||||
public var wrappedValue: T? {
|
||||
get {
|
||||
return valueBox.unboxed
|
||||
}
|
||||
set {
|
||||
guard (required && newValue != nil) || !required else {
|
||||
fatalError("Can not set required property to nil.")
|
||||
}
|
||||
valueBox.unboxed = newValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let required: Bool
|
||||
let didInject: (T) -> ()
|
||||
let tag: DependencyContainer.Tag?
|
||||
let overrideTag: Bool
|
||||
|
||||
public init(wrappedValue initialValue: T?) {
|
||||
self.init()
|
||||
}
|
||||
}
|
||||
#else
|
||||
/**
|
||||
Use this wrapper to identify _strong_ properties of the instance that should be
|
||||
auto-injected by `DependencyContainer`. Type T can be any type.
|
||||
|
||||
- warning: Do not define this property as optional or container will not be able to inject it.
|
||||
Instead define it with initial value of `Injected<T>()`.
|
||||
|
||||
**Example**:
|
||||
|
||||
```swift
|
||||
class ClientImp: Client {
|
||||
var service = Injected<Service>()
|
||||
}
|
||||
```
|
||||
|
||||
- seealso: `InjectedWeak`
|
||||
|
||||
*/
|
||||
public final class Injected<T>: _InjectedPropertyBox<T>, AutoInjectedPropertyBox {
|
||||
|
||||
*/
|
||||
public struct Injected<T>: _InjectedPropertyBox, AutoInjectedPropertyBox {
|
||||
let valueBox: NullableBox<T> = NullableBox(nil)
|
||||
|
||||
///The type of wrapped property.
|
||||
public static var wrappedType: Any.Type {
|
||||
return T.self
|
||||
}
|
||||
|
||||
///Wrapped value.
|
||||
public private(set) var value: T? {
|
||||
didSet {
|
||||
if let value = value { didInject(value) }
|
||||
}
|
||||
public var value: T? {
|
||||
return valueBox.unboxed
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a new wrapper for auto-injected property.
|
||||
|
||||
- parameters:
|
||||
- required: Defines if the property is required or not.
|
||||
If container fails to inject required property it will als fail to resolve
|
||||
the instance that defines that property. Default is `true`.
|
||||
- tag: An optional tag to use to lookup definitions when injecting this property. Default is `nil`.
|
||||
- didInject: block that will be called when concrete instance is injected in this property.
|
||||
Similar to `didSet` property observer. Default value does nothing.
|
||||
*/
|
||||
public 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
|
||||
}
|
||||
let required: Bool
|
||||
let didInject: (T) -> ()
|
||||
let tag: DependencyContainer.Tag?
|
||||
let overrideTag: Bool
|
||||
|
||||
/// Returns a new wrapper with provided value.
|
||||
public func setValue(value: T?) -> Injected {
|
||||
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)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public extension Injected {
|
||||
///The type of wrapped property.
|
||||
static var wrappedType: Any.Type {
|
||||
return T.self
|
||||
}
|
||||
|
||||
init(value: T?, required: Bool = true, tag: DependencyTagConvertible?, overrideTag: Bool, didInject: @escaping (T) -> ()) {
|
||||
self.init(required: required, tag: tag, overrideTag: overrideTag, didInject: didInject)
|
||||
self.valueBox.unboxed = value
|
||||
}
|
||||
|
||||
init(required: Bool = true, tag: DependencyTagConvertible?, overrideTag: Bool, didInject: @escaping (T) -> () = { _ in }) {
|
||||
self.required = required
|
||||
self.tag = tag?.dependencyTag
|
||||
self.overrideTag = overrideTag
|
||||
self.didInject = didInject
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a new wrapper for auto-injected property.
|
||||
|
||||
- parameters:
|
||||
- required: Defines if the property is required or not.
|
||||
If container fails to inject required property it will als fail to resolve
|
||||
the instance that defines that property. Default is `true`.
|
||||
- tag: An optional tag to use to lookup definitions when injecting this property. Default is `nil`.
|
||||
- didInject: Block that will be called when concrete instance is injected in this property.
|
||||
Similar to `didSet` property observer. Default value does nothing.
|
||||
*/
|
||||
init(required: Bool = true, didInject: @escaping (T) -> () = { _ in }) {
|
||||
self.init(value: nil, required: required, tag: nil, overrideTag: false, didInject: didInject)
|
||||
}
|
||||
|
||||
init(required: Bool = true, tag: DependencyTagConvertible?, didInject: @escaping (T) -> () = { _ in }) {
|
||||
self.init(value: nil, required: required, tag: tag, overrideTag: true, didInject: didInject)
|
||||
}
|
||||
|
||||
func resolve(_ container: DependencyContainer) throws {
|
||||
let resolved: T? = try self.resolve(with: container, tag: tag, overrideTag: overrideTag, required: required)
|
||||
valueBox.unboxed = resolved
|
||||
if let resolved = resolved {
|
||||
didInject(resolved)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if swift(>=5.1)
|
||||
/**
|
||||
Use this wrapper to identify _weak_ properties of the instance that should be
|
||||
auto-injected by `DependencyContainer`. Type T should be a **class** type.
|
||||
@@ -171,35 +247,122 @@ public final class Injected<T>: _InjectedPropertyBox<T>, AutoInjectedPropertyBox
|
||||
|
||||
```swift
|
||||
class ServiceImp: Service {
|
||||
var client = InjectedWeak<Client>()
|
||||
@InjectedWeak var client: Client?
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
- seealso: `Injected`
|
||||
|
||||
*/
|
||||
public final class InjectedWeak<T>: _InjectedPropertyBox<T>, AutoInjectedPropertyBox {
|
||||
@propertyWrapper
|
||||
public struct InjectedWeak<T>: _InjectedPropertyBox, AutoInjectedPropertyBox {
|
||||
|
||||
//Only classes (means AnyObject) can be used as `weak` properties
|
||||
//but we can not make <T: AnyObject> because that will prevent using protocol as generic type
|
||||
//so we just rely on user reading documentation and passing AnyObject in runtime
|
||||
//also we will throw fatal error if type can not be casted to AnyObject during resolution.
|
||||
|
||||
///The type of wrapped property.
|
||||
public static var wrappedType: Any.Type {
|
||||
return T.self
|
||||
}
|
||||
|
||||
private weak var _value: AnyObject? = nil {
|
||||
didSet {
|
||||
if let value = value { didInject(value) }
|
||||
|
||||
let valueBox: WeakBox<T> = WeakBox(nil)
|
||||
|
||||
///Wrapped value.
|
||||
public var wrappedValue: T? {
|
||||
get {
|
||||
return valueBox.value
|
||||
}
|
||||
set {
|
||||
guard (required && newValue != nil) || !required else {
|
||||
fatalError("Can not set required property to nil.")
|
||||
}
|
||||
|
||||
valueBox.unboxed = newValue as AnyObject
|
||||
}
|
||||
}
|
||||
|
||||
let required: Bool
|
||||
let didInject: (T) -> ()
|
||||
let tag: DependencyContainer.Tag?
|
||||
let overrideTag: Bool
|
||||
|
||||
public init(wrappedValue initialValue: T?) {
|
||||
self.init()
|
||||
}
|
||||
}
|
||||
#else
|
||||
/**
|
||||
Use this wrapper to identify _weak_ properties of the instance that should be
|
||||
auto-injected by `DependencyContainer`. Type T should be a **class** type.
|
||||
Otherwise it will cause runtime exception when container will try to resolve the property.
|
||||
Use this wrapper to define one of two circular dependencies to avoid retain cycle.
|
||||
|
||||
- note: The only difference between `InjectedWeak` and `Injected` is that `InjectedWeak` uses
|
||||
_weak_ reference to store underlying value, when `Injected` uses _strong_ reference.
|
||||
For that reason if you resolve instance that has a _weak_ auto-injected property this property
|
||||
will be released when `resolve` will complete.
|
||||
|
||||
Use `InjectedWeak<T>` to define one of two circular dependecies if another dependency is defined as `Injected<U>`.
|
||||
This will prevent a retain cycle between resolved instances.
|
||||
|
||||
- warning: Do not define this property as optional or container will not be able to inject it.
|
||||
Instead define it with initial value of `InjectedWeak<T>()`.
|
||||
|
||||
**Example**:
|
||||
|
||||
```swift
|
||||
class ServiceImp: Service {
|
||||
var client = InjectedWeak<Client>()
|
||||
}
|
||||
```
|
||||
|
||||
- seealso: `Injected`
|
||||
|
||||
*/
|
||||
public struct InjectedWeak<T>: _InjectedPropertyBox, AutoInjectedPropertyBox {
|
||||
|
||||
//Only classes (means AnyObject) can be used as `weak` properties
|
||||
//but we can not make <T: AnyObject> because that will prevent using protocol as generic type
|
||||
//so we just rely on user reading documentation and passing AnyObject in runtime
|
||||
//also we will throw fatal error if type can not be casted to AnyObject during resolution.
|
||||
|
||||
let valueBox: WeakBox<T> = WeakBox(nil)
|
||||
|
||||
///Wrapped value.
|
||||
public var value: T? {
|
||||
return _value as? T
|
||||
return valueBox.value
|
||||
}
|
||||
|
||||
let required: Bool
|
||||
let didInject: (T) -> ()
|
||||
let tag: DependencyContainer.Tag?
|
||||
let overrideTag: Bool
|
||||
|
||||
|
||||
/// Returns a new wrapper with provided value.
|
||||
func setValue(_ value: T?) -> InjectedWeak {
|
||||
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)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public extension InjectedWeak {
|
||||
///The type of wrapped property.
|
||||
static var wrappedType: Any.Type {
|
||||
return T.self
|
||||
}
|
||||
|
||||
init(value: T?, required: Bool = true, tag: DependencyTagConvertible?, overrideTag: Bool, didInject: @escaping (T) -> ()) {
|
||||
self.init(required: required, tag: tag, overrideTag: overrideTag, didInject: didInject)
|
||||
self.valueBox.unboxed = value as AnyObject
|
||||
}
|
||||
|
||||
init(required: Bool = true, tag: DependencyTagConvertible?, overrideTag: Bool, didInject: @escaping (T) -> () = { _ in }) {
|
||||
self.required = required
|
||||
self.tag = tag?.dependencyTag
|
||||
self.overrideTag = overrideTag
|
||||
self.didInject = didInject
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -210,85 +373,52 @@ public final class InjectedWeak<T>: _InjectedPropertyBox<T>, AutoInjectedPropert
|
||||
If container fails to inject required property it will als fail to resolve
|
||||
the instance that defines that property. Default is `true`.
|
||||
- tag: An optional tag to use to lookup definitions when injecting this property. Default is `nil`.
|
||||
- didInject: block that will be called when concrete instance is injected in this property.
|
||||
- didInject: Block that will be called when concrete instance is injected in this property.
|
||||
Similar to `didSet` property observer. Default value does nothing.
|
||||
*/
|
||||
public convenience init(required: Bool = true, didInject: T -> () = { _ in }) {
|
||||
init(required: Bool = true, didInject: @escaping (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 }) {
|
||||
|
||||
init(required: Bool = true, tag: DependencyTagConvertible?, didInject: @escaping (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) {
|
||||
fatalError("\(T.self) can not be casted to AnyObject. InjectedWeak wrapper should be used to wrap only classes.")
|
||||
}
|
||||
_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.")
|
||||
func resolve(_ container: DependencyContainer) throws {
|
||||
let resolved: T? = try self.resolve(with: container, tag: tag, overrideTag: overrideTag, required: required)
|
||||
valueBox.unboxed = resolved as AnyObject
|
||||
if let resolved = resolved {
|
||||
didInject(resolved)
|
||||
}
|
||||
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> {
|
||||
protocol _InjectedPropertyBox {}
|
||||
|
||||
let required: Bool
|
||||
let didInject: T -> ()
|
||||
let tag: DependencyContainer.Tag?
|
||||
let overrideTag: Bool
|
||||
|
||||
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 tag = overrideTag ? self.tag : container.context.tag
|
||||
extension _InjectedPropertyBox {
|
||||
func resolve<T>(with container: DependencyContainer, tag: DependencyContainer.Tag?, overrideTag: Bool, required: Bool) throws -> T? {
|
||||
let tag = overrideTag ? tag : container.context.tag
|
||||
do {
|
||||
container.context.key = container.context.key.tagged(tag)
|
||||
let key = DefinitionKey(protocolType: T.self, argumentsType: Void.self, associatedTag: tag?.dependencyTag)
|
||||
return try resolve(container, key: key, builder: { factory in try factory() }) as? T
|
||||
container.context.key = container.context.key.tagged(with: tag)
|
||||
let key = DefinitionKey(type: T.self, typeOfArguments: Void.self, tag: tag?.dependencyTag)
|
||||
return try resolve(with: container, key: key, builder: { (factory: (Any) throws -> Any) in try factory(()) }) as? T
|
||||
}
|
||||
catch {
|
||||
let error = DipError.AutoInjectionFailed(label: container.context.injectedInProperty, type: container.context.resolvingType, underlyingError: error)
|
||||
|
||||
let error = DipError.autoInjectionFailed(label: container.context.injectedInProperty, type: container.context.resolvingType, underlyingError: error)
|
||||
if required {
|
||||
throw error
|
||||
}
|
||||
else {
|
||||
log(.Errors, error)
|
||||
log(level: .Errors, error)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
func resolve<U>(with container: DependencyContainer, key: DefinitionKey, builder: ((U) throws -> Any) throws -> Any) throws -> Any {
|
||||
return try container._resolve(key: key, builder: { definition throws -> Any in
|
||||
try builder(definition.weakFactory)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
+42
-102
@@ -22,125 +22,65 @@
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
protocol AutoWiringDefinition: Definition {
|
||||
var numberOfArguments: Int? { get }
|
||||
protocol AutoWiringDefinition: DefinitionType {
|
||||
var numberOfArguments: Int { get }
|
||||
var autoWiringFactory: ((DependencyContainer, DependencyContainer.Tag?) throws -> Any)? { get }
|
||||
}
|
||||
|
||||
extension AutoWiringDefinition {
|
||||
func supportsAutoWiring() -> Bool {
|
||||
return autoWiringFactory != nil && numberOfArguments > 0
|
||||
}
|
||||
}
|
||||
|
||||
extension DependencyContainer {
|
||||
|
||||
/// Tries to resolve instance using auto-wire factories
|
||||
func autowire<T>(key: DefinitionKey) throws -> T {
|
||||
let shouldLogErrors = context.logErrors
|
||||
defer { context.logErrors = shouldLogErrors }
|
||||
context.logErrors = false
|
||||
|
||||
guard key.argumentsType == Void.self else {
|
||||
throw DipError.DefinitionNotFound(key: key)
|
||||
/// Tries to resolve instance using auto-wiring
|
||||
func autowire<T>(key aKey: DefinitionKey) throws -> T {
|
||||
let key = aKey
|
||||
guard key.typeOfArguments == Void.self else {
|
||||
throw DipError.definitionNotFound(key: key)
|
||||
}
|
||||
|
||||
let tag = key.associatedTag
|
||||
let type = key.protocolType
|
||||
let resolved: Any?
|
||||
let autoWiringKey = try autoWiringDefinition(byKey: key).key
|
||||
|
||||
do {
|
||||
let definitions = autoWiringDefinitions(type, tag: tag)
|
||||
resolved = try resolve(enumerating: definitions) { try resolveKey($0, tag: tag, type: type) }
|
||||
let key = autoWiringKey.tagged(with: key.tag ?? context.tag)
|
||||
return try _resolve(key: key) { definition in
|
||||
try definition.autoWiringFactory!(self, key.tag) as! T
|
||||
}
|
||||
}
|
||||
catch {
|
||||
throw DipError.AutoWiringFailed(type: type, underlyingError: error)
|
||||
}
|
||||
|
||||
if let resolved = resolved as? T {
|
||||
return resolved
|
||||
}
|
||||
else {
|
||||
throw DipError.DefinitionNotFound(key: key)
|
||||
throw DipError.autoWiringFailed(type: key.type, underlyingError: error)
|
||||
}
|
||||
}
|
||||
|
||||
private func autoWiringDefinitions(type: Any.Type, tag: DependencyContainer.Tag?) -> [KeyDefinitionPair] {
|
||||
private func autoWiringDefinition(byKey key: DefinitionKey) throws -> KeyDefinitionPair {
|
||||
do {
|
||||
return try autoWiringDefinition(byKey: key, strictByTag: true)
|
||||
} catch {
|
||||
if key.tag != nil {
|
||||
return try autoWiringDefinition(byKey: key, strictByTag: false)
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func autoWiringDefinition(byKey key: DefinitionKey, strictByTag: Bool) throws -> KeyDefinitionPair {
|
||||
var definitions = self.definitions.map({ (key: $0.0, definition: $0.1) })
|
||||
|
||||
//filter definitions
|
||||
definitions = definitions
|
||||
.filter({ $0.definition.supportsAutoWiring() })
|
||||
.sort({ $0.definition.numberOfArguments > $1.definition.numberOfArguments })
|
||||
definitions = filter(definitions: definitions, byKey: key, strictByTag: strictByTag)
|
||||
definitions = definitions.sorted(by: { $0.definition.numberOfArguments > $1.definition.numberOfArguments })
|
||||
|
||||
definitions = filter(definitions, type: type, tag: tag)
|
||||
definitions = order(definitions, tag: tag)
|
||||
|
||||
return definitions
|
||||
}
|
||||
|
||||
/// Enumerates definitions one by one until one of them succeeds, otherwise returns nil
|
||||
private func resolve(enumerating keyDefinitionPairs: [KeyDefinitionPair], @noescape block: (DefinitionKey) throws -> Any?) throws -> Any? {
|
||||
for (index, keyDefinitionPair) in keyDefinitionPairs.enumerate() {
|
||||
//If the next definition matches current definition then they are ambigous
|
||||
if let nextPair = keyDefinitionPairs[next: index], case keyDefinitionPair = nextPair {
|
||||
throw DipError.AmbiguousDefinitions(
|
||||
type: keyDefinitionPair.key.protocolType,
|
||||
definitions: [keyDefinitionPair.definition, nextPair.definition]
|
||||
)
|
||||
}
|
||||
|
||||
if let resolved = try block(keyDefinitionPair.key) {
|
||||
return resolved
|
||||
}
|
||||
guard definitions.count > 0 && definitions[0].definition.numberOfArguments > 0 else {
|
||||
throw DipError.definitionNotFound(key: key)
|
||||
}
|
||||
|
||||
let maximumNumberOfArguments = definitions.first?.definition.numberOfArguments
|
||||
definitions = definitions.filter({ $0.definition.numberOfArguments == maximumNumberOfArguments })
|
||||
|
||||
//when there are several definitions with the same number of arguments but different arguments types
|
||||
if definitions.count > 1 && definitions[0].key.typeOfArguments != definitions[1].key.typeOfArguments {
|
||||
let error = DipError.ambiguousDefinitions(type: key.type, definitions: definitions.map({ $0.definition }))
|
||||
throw DipError.autoWiringFailed(type: key.type, underlyingError: error)
|
||||
} else {
|
||||
return definitions[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func resolveKey(key: DefinitionKey, tag: DependencyContainer.Tag?, type: Any.Type) throws -> Any {
|
||||
let key = key.tagged(tag ?? context.tag)
|
||||
let resolved: Any = try resolveKey(key, builder: { definition in
|
||||
try definition.autoWiringFactory!(self, tag)
|
||||
})
|
||||
return resolved
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension CollectionType where Self.Index: Comparable {
|
||||
subscript(safe index: Index) -> Generator.Element? {
|
||||
guard indices ~= index else { return nil }
|
||||
return self[index]
|
||||
}
|
||||
subscript(next index: Index) -> Generator.Element? {
|
||||
return self[safe: index.advancedBy(1)]
|
||||
}
|
||||
}
|
||||
|
||||
typealias KeyDefinitionPair = (key: DefinitionKey, definition: _Definition)
|
||||
|
||||
/// Definitions are matched if they are registered for the same tag and thier factories accept the same number of runtime arguments.
|
||||
private func ~=(lhs: KeyDefinitionPair, rhs: KeyDefinitionPair) -> Bool {
|
||||
guard lhs.key.protocolType == rhs.key.protocolType else { return false }
|
||||
guard lhs.key.associatedTag == rhs.key.associatedTag else { return false }
|
||||
guard lhs.definition.numberOfArguments == rhs.definition.numberOfArguments else { return false }
|
||||
return true
|
||||
}
|
||||
|
||||
func filter(definitions: [KeyDefinitionPair], type: Any.Type, tag: DependencyContainer.Tag?, argumentsType: Any.Type? = nil) -> [KeyDefinitionPair] {
|
||||
let definitions = definitions
|
||||
.filter({ $0.key.protocolType == type || $0.definition.doesImplements(type) })
|
||||
.filter({ $0.key.associatedTag == tag || $0.key.associatedTag == nil })
|
||||
|
||||
if let argumentsType = argumentsType {
|
||||
return definitions.filter({ $0.key.argumentsType == argumentsType })
|
||||
}
|
||||
return definitions
|
||||
}
|
||||
|
||||
func order(definitions: [KeyDefinitionPair], tag: DependencyContainer.Tag?) -> [KeyDefinitionPair] {
|
||||
return
|
||||
//first will try to use tagged definitions
|
||||
definitions.filter({ $0.key.associatedTag == tag }) +
|
||||
//then will use not tagged definitions
|
||||
definitions.filter({ $0.key.associatedTag != tag })
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
extension String {
|
||||
func has(prefix aPrefix: String) -> Bool {
|
||||
return hasPrefix(aPrefix)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
///Component scope defines a strategy used by the `DependencyContainer` to manage resolved instances life cycle.
|
||||
public enum ComponentScope {
|
||||
|
||||
/**
|
||||
A new instance will be created every time it's resolved.
|
||||
This is a default strategy. Use this strategy when you don't want instances to be shared
|
||||
between different consumers (i.e. if it is not thread safe).
|
||||
|
||||
**Example**:
|
||||
|
||||
```
|
||||
container.register { ServiceImp() as Service }
|
||||
container.register {
|
||||
ServiceConsumerImp(
|
||||
service1: try container.resolve() as Service
|
||||
service2: try container.resolve() as Service
|
||||
) as ServiceConsumer
|
||||
}
|
||||
let consumer = container.resolve() as ServiceConsumer
|
||||
consumer.service1 !== consumer.service2 //true
|
||||
|
||||
```
|
||||
*/
|
||||
case unique
|
||||
|
||||
/**
|
||||
Instance resolved with the same definition will be reused until topmost `resolve(tag:)` method returns.
|
||||
When you resolve the same object graph again the container will create new instances.
|
||||
Use this strategy if you want different object in objects graph to share the same instance.
|
||||
|
||||
- warning: Make sure this component is thread safe or accessed always from the same thread.
|
||||
|
||||
**Example**:
|
||||
|
||||
```
|
||||
container.register { ServiceImp() as Service }
|
||||
container.register {
|
||||
ServiceConsumerImp(
|
||||
service1: try container.resolve() as Service
|
||||
service2: try container.resolve() as Service
|
||||
) as ServiceConsumer
|
||||
}
|
||||
let consumer1 = container.resolve() as ServiceConsumer
|
||||
let consumer2 = container.resolve() as ServiceConsumer
|
||||
consumer1.service1 === consumer1.service2 //true
|
||||
consumer2.service1 === consumer2.service2 //true
|
||||
consumer1.service1 !== consumer2.service1 //true
|
||||
```
|
||||
*/
|
||||
case shared
|
||||
|
||||
/**
|
||||
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 unless they collaborate.
|
||||
|
||||
- warning: Make sure this component is thread safe or accessed always from the same thread.
|
||||
|
||||
- note: When you override or remove definition from the container an instance
|
||||
that was resolved with this definition will be released. When you reset
|
||||
the container it will release all singleton instances.
|
||||
|
||||
**Example**:
|
||||
|
||||
```
|
||||
container.register(.singleton) { ServiceImp() as Service }
|
||||
container.register {
|
||||
ServiceConsumerImp(
|
||||
service1: try container.resolve() as Service
|
||||
service2: try container.resolve() as Service
|
||||
) as ServiceConsumer
|
||||
}
|
||||
let consumer1 = container.resolve() as ServiceConsumer
|
||||
let consumer2 = container.resolve() as ServiceConsumer
|
||||
consumer1.service1 === consumer1.service2 //true
|
||||
consumer2.service1 === consumer2.service2 //true
|
||||
consumer1.service1 === consumer2.service1 //true
|
||||
```
|
||||
*/
|
||||
case singleton
|
||||
|
||||
/**
|
||||
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
|
||||
|
||||
}
|
||||
+170
-179
@@ -23,166 +23,77 @@
|
||||
//
|
||||
|
||||
///A key used to store definitons in a container.
|
||||
public struct DefinitionKey : Hashable, CustomStringConvertible {
|
||||
public let protocolType: Any.Type
|
||||
public let argumentsType: Any.Type
|
||||
public private(set) var associatedTag: DependencyContainer.Tag?
|
||||
|
||||
init(protocolType: Any.Type, argumentsType: Any.Type, associatedTag: DependencyContainer.Tag? = nil) {
|
||||
self.protocolType = protocolType
|
||||
self.argumentsType = argumentsType
|
||||
self.associatedTag = associatedTag
|
||||
public struct DefinitionKey: Hashable, CustomStringConvertible {
|
||||
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)-\(argumentsType)-\(associatedTag)".hashValue
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(ObjectIdentifier(type))
|
||||
hasher.combine(ObjectIdentifier(typeOfArguments))
|
||||
hasher.combine(tag.desc)
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
return "type: \(protocolType), arguments: \(argumentsType), tag: \(associatedTag.desc)"
|
||||
return "type: \(String(reflecting: type)), arguments: \(typeOfArguments), tag: \(tag.desc)"
|
||||
}
|
||||
|
||||
func tagged(tag: DependencyContainer.Tag?) -> DefinitionKey {
|
||||
func tagged(with tag: DependencyContainer.Tag?) -> DefinitionKey {
|
||||
var tagged = self
|
||||
tagged.associatedTag = tag
|
||||
tagged.tag = tag
|
||||
return tagged
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Check two definition keys on equality by comparing their `protocolType`, `factoryType` and `associatedTag` properties.
|
||||
public func ==(lhs: DefinitionKey, rhs: DefinitionKey) -> Bool {
|
||||
return
|
||||
lhs.protocolType == rhs.protocolType &&
|
||||
lhs.argumentsType == rhs.argumentsType &&
|
||||
lhs.associatedTag == rhs.associatedTag
|
||||
}
|
||||
|
||||
///Component scope defines a strategy used by the `DependencyContainer` to manage resolved instances life cycle.
|
||||
public enum ComponentScope {
|
||||
/**
|
||||
A new instance will be created every time it's resolved.
|
||||
This is a default strategy. Use this strategy when you don't want instances to be shared
|
||||
between different consumers (i.e. if it is not thread safe).
|
||||
|
||||
**Example**:
|
||||
|
||||
```
|
||||
container.register { ServiceImp() as Service }
|
||||
container.register {
|
||||
ServiceConsumerImp(
|
||||
service1: try container.resolve() as Service
|
||||
service2: try container.resolve() as Service
|
||||
) as ServiceConsumer
|
||||
}
|
||||
let consumer = container.resolve() as ServiceConsumer
|
||||
consumer.service1 !== consumer.service2 //true
|
||||
|
||||
```
|
||||
*/
|
||||
case Prototype
|
||||
/// Check two definition keys on equality by comparing their `type`, `factoryType` and `tag` properties.
|
||||
public static func ==(lhs: DefinitionKey, rhs: DefinitionKey) -> Bool {
|
||||
return
|
||||
lhs.type == rhs.type &&
|
||||
lhs.typeOfArguments == rhs.typeOfArguments &&
|
||||
lhs.tag.desc == rhs.tag.desc
|
||||
}
|
||||
|
||||
/**
|
||||
Instance resolved with the same definition will be reused until topmost `resolve(tag:)` method returns.
|
||||
When you resolve the same object graph again the container will create new instances.
|
||||
Use this strategy if you want different object in objects graph to share the same instance.
|
||||
|
||||
- warning: Make sure this component is thread safe or accessed always from the same thread.
|
||||
|
||||
**Example**:
|
||||
|
||||
```
|
||||
container.register(.ObjectGraph) { ServiceImp() as Service }
|
||||
container.register {
|
||||
ServiceConsumerImp(
|
||||
service1: try container.resolve() as Service
|
||||
service2: try container.resolve() as Service
|
||||
) as ServiceConsumer
|
||||
}
|
||||
let consumer1 = container.resolve() as ServiceConsumer
|
||||
let consumer2 = container.resolve() as ServiceConsumer
|
||||
consumer1.service1 === consumer1.service2 //true
|
||||
consumer2.service1 === consumer2.service2 //true
|
||||
consumer1.service1 !== consumer2.service1 //true
|
||||
```
|
||||
*/
|
||||
case ObjectGraph
|
||||
|
||||
/**
|
||||
Resolved instance will be retained by the container and always reused.
|
||||
Do not mix this life cycle with _singleton pattern_.
|
||||
Instance will be not shared between different containers unless they collaborate.
|
||||
|
||||
- warning: Make sure this component is thread safe or accessed always from the same thread.
|
||||
|
||||
- note: When you override or remove definition from the container an instance
|
||||
that was resolved with this definition will be released. When you reset
|
||||
the container it will release all singleton instances.
|
||||
|
||||
**Example**:
|
||||
|
||||
```
|
||||
container.register(.Singleton) { ServiceImp() as Service }
|
||||
container.register {
|
||||
ServiceConsumerImp(
|
||||
service1: try container.resolve() as Service
|
||||
service2: try container.resolve() as Service
|
||||
) as ServiceConsumer
|
||||
}
|
||||
let consumer1 = container.resolve() as ServiceConsumer
|
||||
let consumer2 = container.resolve() as ServiceConsumer
|
||||
consumer1.service1 === consumer1.service2 //true
|
||||
consumer2.service1 === consumer2.service2 //true
|
||||
consumer1.service1 === consumer2.service1 //true
|
||||
```
|
||||
*/
|
||||
case Singleton
|
||||
|
||||
/**
|
||||
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 { }
|
||||
public protocol DefinitionType: class { }
|
||||
|
||||
/**
|
||||
`DefinitionOf<T, F>` describes how instances of type `T` should be created when this type is resolved by the `DependencyContainer`.
|
||||
`Definition<T, U>` describes how instances of type `T` should be created when this type is resolved by the `DependencyContainer`.
|
||||
|
||||
- `T` is the type of the instance to resolve
|
||||
- `F` is the type of the factory that will create an instance of T.
|
||||
- `U` is the type of runtime arguments accepted by factory that will create an instance of T.
|
||||
|
||||
For example `DefinitionOf<Service, (String) -> Service>` is the type of definition that will create an instance of type `Service` using factory that accepts `String` argument.
|
||||
For example `Definition<Service, String>` is the type of definition that will create an instance of type `Service` using factory that accepts `String` argument.
|
||||
*/
|
||||
public final class DefinitionOf<T, F>: Definition {
|
||||
|
||||
init(scope: ComponentScope, factory: F) {
|
||||
self.factory = factory
|
||||
self.scope = scope
|
||||
}
|
||||
public final class Definition<T, U>: DefinitionType {
|
||||
public typealias F = (U) throws -> T
|
||||
|
||||
//MARK: - _Definition
|
||||
|
||||
weak var container: DependencyContainer?
|
||||
|
||||
let factory: F
|
||||
let scope: ComponentScope
|
||||
private(set) var weakFactory: (Any throws -> Any)!
|
||||
private(set) var resolveDependenciesBlock: ((DependencyContainer, Any) throws -> ())?
|
||||
var weakFactory: ((Any) throws -> Any)!
|
||||
var resolveProperties: ((DependencyContainer, Any) throws -> ())?
|
||||
var autoInjectProperties: Bool?
|
||||
|
||||
init(scope: ComponentScope, factory: @escaping F) {
|
||||
self.factory = factory
|
||||
self.scope = scope
|
||||
}
|
||||
|
||||
/**
|
||||
Set the block that will be used to resolve dependencies of the instance.
|
||||
This block will be called before `resolve(tag:)` returns.
|
||||
|
||||
- parameter block: The block to use to resolve dependencies of the instance.
|
||||
- parameter block: The block to resolve property dependencies of the instance.
|
||||
|
||||
- returns: modified definition
|
||||
|
||||
@@ -198,103 +109,151 @@ public final class DefinitionOf<T, F>: Definition {
|
||||
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 {
|
||||
let oldBlock = self.resolveDependenciesBlock
|
||||
self.resolveDependenciesBlock = {
|
||||
try oldBlock?($0, $1 as! T)
|
||||
try block($0, $1 as! T)
|
||||
@discardableResult public func resolvingProperties(_ block: @escaping (DependencyContainer, T) throws -> ()) -> Definition {
|
||||
if let oldBlock = self.resolveProperties {
|
||||
self.resolveProperties = {
|
||||
try oldBlock($0, $1 as! T)
|
||||
try block($0, $1 as! T)
|
||||
}
|
||||
}
|
||||
else {
|
||||
self.resolveProperties = { try block($0, $1 as! T) }
|
||||
}
|
||||
return self
|
||||
}
|
||||
|
||||
|
||||
@discardableResult public func resolvingProperty<Root, V>(_ keyPath: ReferenceWritableKeyPath<Root, V>, as type: Any.Type = V.self, tag: DependencyTagConvertible? = nil) -> Definition {
|
||||
return resolvingProperties { (container, instance) in
|
||||
precondition(instance is Root, "Type of resolved instance \(Swift.type(of: instance)) does not match expected type \(Root.self)")
|
||||
let resolved = try container.resolve(type, tag: tag)
|
||||
precondition(resolved is V, "Type of resolved property \(Swift.type(of: resolved)) does not match expected type \(type)")
|
||||
(instance as! Root)[keyPath: keyPath] = resolved as! V
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult public func resolvingProperty<Root, V>(_ keyPath: ReferenceWritableKeyPath<Root, V>, factory: @escaping (DependencyContainer) throws -> V = { try $0.resolve() }) -> Definition {
|
||||
return resolvingProperties { (container, instance) in
|
||||
precondition(instance is Root, "Type of resolved instance \(Swift.type(of: instance)) does not match expected type \(Root.self)")
|
||||
(instance as! Root)[keyPath: keyPath] = try factory(container)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Whether container should perform properties auto-injection when resolving using this definition.
|
||||
If called will override container configuration. Can be called together with `resolvingProperties`
|
||||
to resolve properties that are not automatically injected.
|
||||
|
||||
- parameter shouldAutoInject: Whether container should perform properties auto-injection when resolving using this definition. Default is `true`.
|
||||
*/
|
||||
@discardableResult public func autoInjectingProperties(_ shouldAutoInject: Bool = true) -> Definition {
|
||||
autoInjectProperties = shouldAutoInject
|
||||
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 }
|
||||
if let resolveDependenciesBlock = self.resolveDependenciesBlock {
|
||||
try resolveDependenciesBlock(container, resolvedInstance)
|
||||
func resolveProperties(of instance: Any, container: DependencyContainer) throws {
|
||||
guard let resolvedInstance = instance as? T else { return }
|
||||
if let forwardsTo = forwardsTo {
|
||||
try forwardsTo.resolveProperties(of: resolvedInstance, container: container)
|
||||
}
|
||||
if let resolveProperties = self.resolveProperties {
|
||||
try resolveProperties(container, resolvedInstance)
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - AutoWiringDefinition
|
||||
|
||||
private(set) var autoWiringFactory: ((DependencyContainer, DependencyContainer.Tag?) throws -> Any)?
|
||||
private(set) var numberOfArguments: Int?
|
||||
var autoWiringFactory: ((DependencyContainer, DependencyContainer.Tag?) throws -> Any)?
|
||||
var numberOfArguments: Int = 0
|
||||
|
||||
//MARK: - TypeForwardingDefinition
|
||||
|
||||
/// Types that can be resolved using this definition.
|
||||
private(set) var implementingTypes: [Any.Type] = [(T?).self, (T!).self]
|
||||
private(set) var implementingTypes: [Any.Type] = [(T?).self]
|
||||
|
||||
/// Return `true` if type can be resolved using this definition
|
||||
func doesImplements(type: Any.Type) -> Bool {
|
||||
return implementingTypes.contains({ $0 == type })
|
||||
func doesImplements(type aType: Any.Type) -> Bool {
|
||||
return implementingTypes.contains(where: { $0 == aType })
|
||||
}
|
||||
|
||||
//MARK: - _TypeForwardingDefinition
|
||||
|
||||
/// Adds type as being able to be resolved using this definition
|
||||
private func implements(type: Any.Type) {
|
||||
implements([type])
|
||||
func _implements(type aType: Any.Type) {
|
||||
_implements(types: [aType])
|
||||
}
|
||||
|
||||
/// Adds types as being able to be resolved using this definition
|
||||
private func implements(types: [Any.Type]) {
|
||||
implementingTypes.appendContentsOf(types.filter({ !doesImplements($0) }))
|
||||
func _implements(types aTypes: [Any.Type]) {
|
||||
implementingTypes.append(contentsOf: aTypes.filter({ !doesImplements(type: $0) }))
|
||||
}
|
||||
|
||||
|
||||
/// Definition to which resolution will be forwarded to
|
||||
private weak var forwardsToDefinition: _TypeForwardingDefinition? {
|
||||
weak var forwardsTo: _TypeForwardingDefinition? {
|
||||
didSet {
|
||||
if let forwardsToDefinition = forwardsToDefinition {
|
||||
implements(forwardsToDefinition.type)
|
||||
implements(forwardsToDefinition.implementingTypes)
|
||||
//both definitions (self and forwardsTo) can resolve
|
||||
//each other types and each other implementing types
|
||||
//this relationship can be used to reuse previously resolved instances
|
||||
if let forwardsTo = forwardsTo {
|
||||
_implements(type: forwardsTo.type)
|
||||
_implements(types: forwardsTo.implementingTypes)
|
||||
|
||||
for definition in [forwardsToDefinition] + forwardsToDefinition.forwardsFromDefinitions {
|
||||
definition.implements(type)
|
||||
definition.implements(implementingTypes)
|
||||
//definitions for types that can be resolved by `forwardsTo` definition
|
||||
//can also be used to resolve self type and it's implementing types
|
||||
//this way container properly reuses previosly resolved instances
|
||||
//when there are several forwarded definitions
|
||||
//see testThatItReusesInstanceResolvedByTypeForwarding)
|
||||
for definition in forwardsTo.forwardsFrom {
|
||||
definition._implements(type: type)
|
||||
definition._implements(types: implementingTypes)
|
||||
}
|
||||
forwardsToDefinition.forwardsFromDefinitions.append(self)
|
||||
resolveDependencies({ try forwardsToDefinition.resolveDependenciesOf($1, withContainer: $0) })
|
||||
|
||||
//forwardsTo can be used to resolve self type and it's implementing types
|
||||
forwardsTo._implements(type: type)
|
||||
forwardsTo._implements(types: implementingTypes)
|
||||
forwardsTo.forwardsFrom.append(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Definitions that will forward resolution to this definition
|
||||
private var forwardsFromDefinitions: [_TypeForwardingDefinition] = []
|
||||
var forwardsFrom: [_TypeForwardingDefinition] = []
|
||||
|
||||
}
|
||||
|
||||
//MARK: - _Definition
|
||||
|
||||
protocol _Definition: Definition, AutoWiringDefinition, TypeForwardingDefinition {
|
||||
protocol _Definition: AutoWiringDefinition, TypeForwardingDefinition {
|
||||
var type: Any.Type { get }
|
||||
var scope: ComponentScope { get }
|
||||
var weakFactory: (Any throws -> Any)! { get }
|
||||
func resolveDependenciesOf(resolvedInstance: Any, withContainer container: DependencyContainer) throws
|
||||
var weakFactory: ((Any) throws -> Any)! { get }
|
||||
func resolveProperties(of instance: Any, container: DependencyContainer) throws
|
||||
var container: DependencyContainer? { get set }
|
||||
var autoInjectProperties: Bool? { get }
|
||||
}
|
||||
|
||||
//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])
|
||||
protocol _TypeForwardingDefinition: _Definition {
|
||||
var forwardsTo: _TypeForwardingDefinition? { get }
|
||||
var forwardsFrom: [_TypeForwardingDefinition] { get set }
|
||||
func _implements(type aType: Any.Type)
|
||||
func _implements(types aTypes: [Any.Type])
|
||||
}
|
||||
|
||||
extension DefinitionOf: _TypeForwardingDefinition {
|
||||
extension Definition: _TypeForwardingDefinition {
|
||||
var type: Any.Type {
|
||||
return T.self
|
||||
}
|
||||
}
|
||||
|
||||
extension DefinitionOf: CustomStringConvertible {
|
||||
extension Definition: CustomStringConvertible {
|
||||
public var description: String {
|
||||
return "type: \(T.self), factory: \(F.self), scope: \(scope)"
|
||||
}
|
||||
@@ -303,30 +262,62 @@ 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
|
||||
typealias F = (U) throws -> T
|
||||
|
||||
var scope: ComponentScope!
|
||||
var factory: F!
|
||||
|
||||
var numberOfArguments: Int?
|
||||
var numberOfArguments: Int = 0
|
||||
var autoWiringFactory: ((DependencyContainer, DependencyContainer.Tag?) throws -> T)?
|
||||
|
||||
var forwardsDefinition: _Definition?
|
||||
var forwardsTo: _Definition?
|
||||
|
||||
init(@noescape configure: (DefinitionBuilder -> ())) {
|
||||
init(configure: (DefinitionBuilder) -> ()) {
|
||||
configure(self)
|
||||
}
|
||||
|
||||
func build() -> DefinitionOf<T, F> {
|
||||
let factory = self.factory
|
||||
let definition = DefinitionOf<T, F>(scope: scope, factory: factory)
|
||||
func build() -> Definition<T, U> {
|
||||
let factory = self.factory!
|
||||
let definition = Definition<T, U>(scope: scope, factory: factory)
|
||||
definition.numberOfArguments = numberOfArguments
|
||||
definition.autoWiringFactory = autoWiringFactory
|
||||
definition.weakFactory = { try factory($0 as! U) }
|
||||
definition.forwardsToDefinition = forwardsDefinition as? _TypeForwardingDefinition
|
||||
definition.forwardsTo = forwardsTo as? _TypeForwardingDefinition
|
||||
return definition
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - KeyDefinitionPair
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
/// Returns key-defintion pairs with definitions able to resolve that type (directly or via type forwarding)
|
||||
/// and which tag matches provided key's tag or is nil if strictByTag is false.
|
||||
/// In the end filters defintions by type of runtime arguments.
|
||||
func filter(definitions _definitions: [KeyDefinitionPair], byKey key: DefinitionKey, strictByTag: Bool = false, byTypeOfArguments: Bool = false) -> [KeyDefinitionPair] {
|
||||
let definitions = _definitions
|
||||
.filter({ $0.key.type == key.type || $0.definition.doesImplements(type: key.type) })
|
||||
.filter({ $0.key.tag == key.tag || (!strictByTag && $0.key.tag == nil) })
|
||||
if byTypeOfArguments {
|
||||
return definitions.filter({ $0.key.typeOfArguments == key.typeOfArguments })
|
||||
}
|
||||
else {
|
||||
return definitions
|
||||
}
|
||||
}
|
||||
|
||||
/// Orders key-definition pairs putting first definitions registered for provided tag.
|
||||
func order(definitions _definitions: [KeyDefinitionPair], byTag tag: DependencyContainer.Tag?) -> [KeyDefinitionPair] {
|
||||
return
|
||||
_definitions.filter({ $0.key.tag == tag }) +
|
||||
_definitions.filter({ $0.key.tag != tag })
|
||||
}
|
||||
|
||||
+142
-458
@@ -34,23 +34,25 @@ public final class DependencyContainer {
|
||||
|
||||
- seealso: `DependencyTagConvertible`
|
||||
*/
|
||||
public enum Tag: Equatable {
|
||||
public enum Tag {
|
||||
case String(StringLiteralType)
|
||||
case Int(IntegerLiteralType)
|
||||
}
|
||||
|
||||
|
||||
var autoInjectProperties: Bool
|
||||
var threadSafe: Bool
|
||||
internal(set) public var context: Context!
|
||||
var definitions = [DefinitionKey : _Definition]()
|
||||
private var resolvedInstances = ResolvedInstances()
|
||||
var definitions = [DefinitionKey: _Definition]()
|
||||
var resolvedInstances = ResolvedInstances()
|
||||
private let lock = RecursiveLock()
|
||||
|
||||
private(set) var bootstrapped = false
|
||||
private var bootstrapQueue: [() throws -> ()] = []
|
||||
var bootstrapped = false
|
||||
var bootstrapQueue: [() throws -> ()] = []
|
||||
|
||||
private var _weakCollaborators: [WeakBox<DependencyContainer>] = []
|
||||
private(set) var _collaborators: [DependencyContainer] {
|
||||
var _collaborators: [DependencyContainer] {
|
||||
get {
|
||||
return _weakCollaborators.flatMap({ $0.value })
|
||||
return _weakCollaborators.compactMap({ $0.value })
|
||||
}
|
||||
set {
|
||||
_weakCollaborators = newValue.filter({ $0 !== self }).map(WeakBox.init)
|
||||
@@ -59,16 +61,32 @@ public final class DependencyContainer {
|
||||
|
||||
/**
|
||||
Designated initializer for a DependencyContainer
|
||||
|
||||
- parameter configBlock: A configuration block in which you typically put all you `register` calls.
|
||||
|
||||
- Parameters:
|
||||
- autoInjectProperties: Whether container should perform properties auto-injection. Default is `true`.
|
||||
- threadSafe: Whether container should be thread-safe. Default is `true`. You may want to disable it for better performance.
|
||||
- configBlock: A configuration block in which you typically put all you `register` calls.
|
||||
|
||||
- note: The `configBlock` is simply called at the end of the `init` to let you configure everything.
|
||||
It is only present for convenience to have a cleaner syntax when declaring and initializing
|
||||
your `DependencyContainer` instances.
|
||||
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.
|
||||
*/
|
||||
public init(@noescape configBlock: (DependencyContainer->()) = { _ in }) {
|
||||
public init(autoInjectProperties: Bool = true, threadSafe: Bool = true, configBlock: (DependencyContainer)->() = { _ in }) {
|
||||
self.autoInjectProperties = autoInjectProperties
|
||||
self.threadSafe = threadSafe
|
||||
configBlock(self)
|
||||
}
|
||||
|
||||
@@ -88,11 +106,13 @@ public final class DependencyContainer {
|
||||
}
|
||||
}
|
||||
|
||||
private func threadSafe<T>(@noescape closure: () throws -> T) rethrows -> T {
|
||||
lock.lock()
|
||||
defer {
|
||||
lock.unlock()
|
||||
func threadSafe<T>(_ closure: () throws -> T) rethrows -> T {
|
||||
guard threadSafe else {
|
||||
return try closure()
|
||||
}
|
||||
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
return try closure()
|
||||
}
|
||||
|
||||
@@ -124,7 +144,7 @@ extension DependencyContainer {
|
||||
container.register {
|
||||
//container will pass through the tag ("tag") used to resolve SomeService to resolve $0
|
||||
SomeServiceImp(dependency: $0) as SomeService
|
||||
}.resolveDependencies { container, service in
|
||||
}.resolvingProperties { container, service in
|
||||
//container will use `nil` tag to resolve this dependency
|
||||
self.dependency = try container.resolve() as SomeDependency
|
||||
|
||||
@@ -143,12 +163,12 @@ extension DependencyContainer {
|
||||
|
||||
/// Currently resolving type.
|
||||
public var resolvingType: Any.Type {
|
||||
return key.protocolType
|
||||
return key.type
|
||||
}
|
||||
|
||||
/// The tag used to resolve currently resolving type.
|
||||
public var tag: Tag? {
|
||||
return key.associatedTag
|
||||
return key.tag
|
||||
}
|
||||
|
||||
/// The type that caused currently resolving type to be resolved.
|
||||
@@ -158,12 +178,15 @@ extension DependencyContainer {
|
||||
/// The label of the property where resolved instance will be auto-injected.
|
||||
private(set) public var injectedInProperty: String?
|
||||
|
||||
let inCollaboration: Bool
|
||||
|
||||
var logErrors: Bool = true
|
||||
|
||||
init(key: DefinitionKey, injectedInType: Any.Type?, injectedInProperty: String?) {
|
||||
init(key: DefinitionKey, injectedInType: Any.Type?, injectedInProperty: String?, inCollaboration: Bool) {
|
||||
self.key = key
|
||||
self.injectedInType = injectedInType
|
||||
self.injectedInProperty = injectedInProperty
|
||||
self.inCollaboration = inCollaboration
|
||||
}
|
||||
|
||||
public var debugDescription: String {
|
||||
@@ -171,7 +194,7 @@ extension DependencyContainer {
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
let resolvingDescription = "Resolving type \(key.protocolType) with arguments \(key.argumentsType) tagged with \(key.associatedTag.desc)"
|
||||
let resolvingDescription = "Resolving type \(key.type) with arguments \(key.typeOfArguments) \(key.tag != nil ? "tagged with \(key.tag!)" : "")"
|
||||
if injectedInProperty != nil {
|
||||
return "\(resolvingDescription) while auto-injecting property \(injectedInProperty.desc) of \(injectedInType.desc)"
|
||||
}
|
||||
@@ -186,9 +209,9 @@ extension DependencyContainer {
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
/// When popped to initial (root) context will release all references to resolved instances and call `Resolvable` callbacks.
|
||||
func inContext<T>(key aKey: DefinitionKey, injectedInType: Any.Type?, injectedInProperty: String? = nil, inCollaboration: Bool = false, container: DependencyContainer? = nil, logErrors: Bool! = nil, block: () throws -> T) rethrows -> T {
|
||||
let key = aKey
|
||||
return try threadSafe {
|
||||
let currentContext = self.context
|
||||
|
||||
@@ -198,14 +221,16 @@ extension DependencyContainer {
|
||||
//clean instances pool if it is owned not by other container
|
||||
if context == nil {
|
||||
resolvedInstances.resolvedInstances.removeAll()
|
||||
for (key, instance) in resolvedInstances.sharedWeakSingletons {
|
||||
if resolvedInstances.sharedWeakSingletons[key] is WeakBoxType { continue }
|
||||
resolvedInstances.sharedWeakSingletons[key] = WeakBox(instance)
|
||||
}
|
||||
for (key, instance) in resolvedInstances.weakSingletons {
|
||||
if resolvedInstances.weakSingletons[key] is WeakBoxType { continue }
|
||||
resolvedInstances.weakSingletons[key] = WeakBox(value: instance)
|
||||
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() {
|
||||
for resolvedInstance in resolvedInstances.resolvableInstances.reversed() {
|
||||
resolvedInstance.didResolveDependencies()
|
||||
}
|
||||
resolvedInstances.resolvableInstances.removeAll()
|
||||
@@ -214,8 +239,9 @@ extension DependencyContainer {
|
||||
|
||||
context = Context(
|
||||
key: key,
|
||||
injectedInType: injectedInType ?? currentContext?.resolvingType,
|
||||
injectedInProperty: injectedInProperty
|
||||
injectedInType: injectedInType,
|
||||
injectedInProperty: injectedInProperty,
|
||||
inCollaboration: inCollaboration
|
||||
)
|
||||
context.logErrors = logErrors ?? currentContext?.logErrors ?? true
|
||||
|
||||
@@ -223,7 +249,7 @@ extension DependencyContainer {
|
||||
return try block()
|
||||
}
|
||||
catch {
|
||||
if context.logErrors { log(.Errors, error) }
|
||||
if context.logErrors { log(level: .Errors, error) }
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -231,284 +257,6 @@ extension DependencyContainer {
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Registering definitions
|
||||
|
||||
extension DependencyContainer {
|
||||
|
||||
/**
|
||||
Register factory for type `T` and associate it with an optional tag.
|
||||
|
||||
- parameters:
|
||||
- tag: The arbitrary tag to associate this factory with. Pass `nil` to associate with any tag. Default value is `nil`.
|
||||
- scope: The scope to use for instance created by the factory.
|
||||
- factory: The factory to register.
|
||||
|
||||
- returns: A registered definition.
|
||||
|
||||
- note: You should cast the factory return type to the protocol you want to register it for
|
||||
(unless you want to register concrete type).
|
||||
|
||||
**Example**:
|
||||
```swift
|
||||
container.register { ServiceImp() as Service }
|
||||
container.register(tag: "service") { ServiceImp() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp() as Service }
|
||||
container.register { try ClientImp(service: container.resolve() as Service) as Client }
|
||||
```
|
||||
*/
|
||||
public func register<T>(tag tag: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: () throws -> T) -> DefinitionOf<T, () throws -> T> {
|
||||
let definition = DefinitionBuilder<T, ()> {
|
||||
$0.scope = scope
|
||||
$0.factory = factory
|
||||
}.build()
|
||||
register(definition, forTag: tag)
|
||||
return definition
|
||||
}
|
||||
|
||||
/**
|
||||
Register generic factory and auto-wiring factory and associate it with an optional tag.
|
||||
|
||||
- parameters:
|
||||
- tag: The arbitrary tag to associate this factory with. Pass `nil` to associate with any tag. Default value is `nil`.
|
||||
- scope: The scope to use for instance created by the factory.
|
||||
- factory: The factory to register.
|
||||
- numberOfArguments: The number of factory arguments. Will be used on auto-wiring to sort definitions.
|
||||
- autoWiringFactory: The factory to be used on auto-wiring to resolve component.
|
||||
|
||||
- returns: A registered definition.
|
||||
|
||||
- note: You _should not_ call this method directly, instead call any of other `register` methods.
|
||||
You _should_ use this method only to register dependency with more runtime arguments
|
||||
than _Dip_ supports (currently it's up to six) like in the following example:
|
||||
|
||||
```swift
|
||||
public func register<T, A, B, C, ...>(tag: Tag? = nil, scope: ComponentScope = .Prototype, factory: (A, B, C, ...) throws -> T) -> DefinitionOf<T, (A, B, C, ...) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: ...) { container, tag in
|
||||
try factory(container.resolve(tag: tag), ...)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Though before you do so you should probably review your design and try to reduce number of depnedencies.
|
||||
*/
|
||||
public func registerFactory<T, U>(tag tag: DependencyTagConvertible? = nil, scope: ComponentScope, factory: U throws -> T, numberOfArguments: Int, autoWiringFactory: (DependencyContainer, Tag?) throws -> T) -> DefinitionOf<T, U throws -> T> {
|
||||
let definition = DefinitionBuilder<T, U> {
|
||||
$0.scope = scope
|
||||
$0.factory = factory
|
||||
$0.numberOfArguments = numberOfArguments
|
||||
$0.autoWiringFactory = autoWiringFactory
|
||||
}.build()
|
||||
register(definition, forTag: tag)
|
||||
return definition
|
||||
}
|
||||
|
||||
/**
|
||||
Register definiton in the container and associate it with an optional tag.
|
||||
Will override already registered definition for the same type and factory, associated with the same tag.
|
||||
|
||||
- parameters:
|
||||
- tag: The arbitrary tag to associate this definition with. Pass `nil` to associate with any tag. Default value is `nil`.
|
||||
- definition: The definition to register in the container.
|
||||
|
||||
*/
|
||||
public func register<T, U>(definition: DefinitionOf<T, U throws -> T>, forTag tag: DependencyTagConvertible? = nil) {
|
||||
let key = DefinitionKey(protocolType: T.self, argumentsType: U.self, associatedTag: tag?.dependencyTag)
|
||||
register(definition, forKey: key)
|
||||
|
||||
if case .EagerSingleton = definition.scope {
|
||||
bootstrapQueue.append({ let _ = try self.resolve(tag: tag) as T })
|
||||
}
|
||||
}
|
||||
|
||||
/// Actually register definition
|
||||
func register(definition: _Definition, forKey key: DefinitionKey) {
|
||||
precondition(!bootstrapped, "You can not modify container's definitions after it was bootstrapped.")
|
||||
|
||||
threadSafe {
|
||||
definitions[key] = definition
|
||||
resolvedInstances.singletons[key] = nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Resolve dependencies
|
||||
|
||||
extension DependencyContainer {
|
||||
|
||||
/**
|
||||
Resolve a an instance of type `T`.
|
||||
|
||||
If no matching definition was registered with provided `tag`,
|
||||
container will lookup definition associated with `nil` tag.
|
||||
|
||||
- parameter tag: The arbitrary tag to use to lookup definition.
|
||||
|
||||
- throws: `DipError.DefinitionNotFound`, `DipError.AutoInjectionFailed`, `DipError.AmbiguousDefinitions`
|
||||
|
||||
- returns: An instance of type `T`.
|
||||
|
||||
- seealso: `register(tag:_:factory:)`
|
||||
|
||||
**Example**:
|
||||
```swift
|
||||
let service = try! container.resolve() as Service
|
||||
let service = try! container.resolve(tag: "service") as Service
|
||||
let service: Service = try! container.resolve()
|
||||
```
|
||||
|
||||
*/
|
||||
public func resolve<T>(tag tag: DependencyTagConvertible? = nil) throws -> T {
|
||||
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.
|
||||
|
||||
- parameters:
|
||||
- tag: The arbitrary tag to use to lookup definition.
|
||||
- builder: Generic closure that accepts generic factory and returns inctance created by that factory.
|
||||
|
||||
- throws: `DipError.DefinitionNotFound`, `DipError.AutoInjectionFailed`, `DipError.AmbiguousDefinitions`
|
||||
|
||||
- returns: An instance of type `T`.
|
||||
|
||||
- note: You _should not_ call this method directly, instead call any of other
|
||||
`resolve(tag:)` or `resolve(tag:withArguments:)` methods.
|
||||
You _should_ use this method only to resolve dependency with more runtime arguments than
|
||||
_Dip_ supports (currently it's up to six) like in the following example:
|
||||
|
||||
```swift
|
||||
public func resolve<T, 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, 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(protocolType: type, argumentsType: U.self, associatedTag: tag?.dependencyTag)
|
||||
|
||||
return try inContext(key) {
|
||||
try resolveKey(key, builder: { definition in
|
||||
try builder(definition.weakFactory)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Lookup definition by the key and use it to resolve instance. Fallback to the key with `nil` tag.
|
||||
func resolveKey<T>(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.resolveDependenciesOf(resolvedInstance, withContainer: self)
|
||||
|
||||
log(.Verbose, "Resolved type \(key.protocolType) with \(resolvedInstance)")
|
||||
return resolvedInstance
|
||||
}
|
||||
|
||||
private func previouslyResolved<T>(definition: _Definition, key: DefinitionKey) -> T? {
|
||||
let keys = definition.implementingTypes.map({
|
||||
DefinitionKey(protocolType: $0, argumentsType: key.argumentsType, associatedTag: key.associatedTag)
|
||||
})
|
||||
for key in [key] + keys {
|
||||
if let previouslyResolved = resolvedInstances[forKey: key, inScope: definition.scope] as? T {
|
||||
return previouslyResolved
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Searches for definition that matches provided key
|
||||
private func definition(matching key: DefinitionKey) -> KeyDefinitionPair? {
|
||||
let typeDefinitions = definitions.filter({ $0.0.protocolType == key.protocolType })
|
||||
guard !typeDefinitions.isEmpty else {
|
||||
return typeForwardingDefinition(key)
|
||||
}
|
||||
|
||||
if let definition = (self.definitions[key] ?? self.definitions[key.tagged(nil)]) {
|
||||
return (key, definition)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//MARK: - Collaborating containers
|
||||
|
||||
extension DependencyContainer {
|
||||
@@ -527,20 +275,34 @@ extension DependencyContainer {
|
||||
*/
|
||||
public func collaborate(with containers: [DependencyContainer]) {
|
||||
_collaborators += containers
|
||||
for container in containers {
|
||||
container._collaborators += [self]
|
||||
container.resolvedInstances.sharedSingletonsBox = self.resolvedInstances.sharedSingletonsBox
|
||||
container.resolvedInstances.sharedWeakSingletonsBox = self.resolvedInstances.sharedWeakSingletonsBox
|
||||
updateCollaborationReferences(between: container, and: self)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateCollaborationReferences(between container: DependencyContainer, and collaborator: DependencyContainer) {
|
||||
for container in container._collaborators {
|
||||
guard container.resolvedInstances.sharedSingletonsBox !== collaborator.resolvedInstances.sharedSingletonsBox else { continue }
|
||||
container.resolvedInstances.sharedSingletonsBox = collaborator.resolvedInstances.sharedSingletonsBox
|
||||
container.resolvedInstances.sharedWeakSingletonsBox = collaborator.resolvedInstances.sharedWeakSingletonsBox
|
||||
updateCollaborationReferences(between: container, and: collaborator)
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to resolve key using collaborating containers
|
||||
private func resolveWithCollaborators<T>(key: DefinitionKey, builder: _Definition throws -> T) -> T? {
|
||||
func collaboratingResolve<T>(key aKey: DefinitionKey, builder: (_Definition) throws -> T) -> T? {
|
||||
let key = aKey
|
||||
for collaborator in _collaborators {
|
||||
//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, context.resolvingType == key.type && context.tag == key.tag { continue }
|
||||
|
||||
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.protocolType &&
|
||||
context.tag == key.associatedTag { continue }
|
||||
|
||||
//Pass current container's instances pool to collect instances resolved by collaborator
|
||||
let resolvedInstances = collaborator.resolvedInstances
|
||||
collaborator.resolvedInstances = self.resolvedInstances
|
||||
@@ -548,19 +310,29 @@ extension DependencyContainer {
|
||||
let context = collaborator.context
|
||||
collaborator.context = self.context
|
||||
defer {
|
||||
collaborator.resolvedInstances = resolvedInstances
|
||||
collaborator.context = context
|
||||
collaborator.resolvedInstances = resolvedInstances
|
||||
|
||||
for (key, resolvedSingleton) in self.resolvedInstances.singletons {
|
||||
collaborator.resolvedInstances.singletons[key] = resolvedSingleton
|
||||
}
|
||||
for (key, resolvedSingleton) in self.resolvedInstances.weakSingletons {
|
||||
guard collaborator.definition(matching: key) == nil else { continue }
|
||||
collaborator.resolvedInstances.weakSingletons[key] = resolvedSingleton is WeakBoxType ? resolvedSingleton : WeakBox(resolvedSingleton)
|
||||
}
|
||||
for (key, resolved) in self.resolvedInstances.resolvedInstances {
|
||||
guard collaborator.definition(matching: key) == nil else { continue }
|
||||
collaborator.resolvedInstances.resolvedInstances[key] = resolved
|
||||
}
|
||||
}
|
||||
|
||||
let resolved = try collaborator.inContext(key, injectedInProperty: self.context.injectedInProperty, injectedInType: self.context.injectedInType, logErrors: false) {
|
||||
try collaborator.resolveKey(key, builder: builder)
|
||||
let resolved = try collaborator.inContext(key:key, injectedInType: self.context.injectedInType, injectedInProperty: self.context.injectedInProperty, inCollaboration: true, logErrors: false) {
|
||||
try collaborator._resolve(key: key, builder: builder)
|
||||
}
|
||||
|
||||
return resolved
|
||||
}
|
||||
catch {
|
||||
continue
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -573,22 +345,30 @@ extension DependencyContainer {
|
||||
|
||||
/**
|
||||
Removes definition registered in the container.
|
||||
|
||||
|
||||
- parameters:
|
||||
- tag: The tag used to register definition.
|
||||
- definition: The definition to remove
|
||||
*/
|
||||
public func remove<T, U>(definition: DefinitionOf<T, U>, forTag tag: DependencyTagConvertible? = nil) {
|
||||
let key = DefinitionKey(protocolType: T.self, argumentsType: U.self, associatedTag: tag?.dependencyTag)
|
||||
remove(definitionForKey: key)
|
||||
public func remove<T, U>(_ definition: Definition<T, U>, tag: DependencyTagConvertible? = nil) {
|
||||
_remove(definition: definition, tag: tag)
|
||||
}
|
||||
|
||||
private func remove(definitionForKey key: DefinitionKey) {
|
||||
func _remove<T, U>(definition aDefinition: Definition<T, U>, tag: DependencyTagConvertible? = nil) {
|
||||
let key = DefinitionKey(type: T.self, typeOfArguments: U.self, tag: tag?.dependencyTag)
|
||||
_remove(definitionForKey: key)
|
||||
}
|
||||
|
||||
func _remove(definitionForKey key: DefinitionKey) {
|
||||
precondition(!bootstrapped, "You can not modify container's definitions after it was bootstrapped.")
|
||||
|
||||
threadSafe {
|
||||
definitions[key]?.container = nil
|
||||
definitions[key] = nil
|
||||
resolvedInstances.singletons[key] = nil
|
||||
resolvedInstances.weakSingletons[key] = nil
|
||||
resolvedInstances.sharedSingletons[key] = nil
|
||||
resolvedInstances.sharedWeakSingletons[key] = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -597,14 +377,20 @@ extension DependencyContainer {
|
||||
*/
|
||||
public func reset() {
|
||||
threadSafe {
|
||||
definitions.forEach { $0.1.container = nil }
|
||||
definitions.removeAll()
|
||||
resolvedInstances.singletons.removeAll()
|
||||
resolvedInstances.weakSingletons.removeAll()
|
||||
resolvedInstances.sharedSingletons.removeAll()
|
||||
resolvedInstances.sharedWeakSingletons.removeAll()
|
||||
bootstrapped = false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Validation
|
||||
|
||||
extension DependencyContainer {
|
||||
|
||||
/**
|
||||
@@ -615,15 +401,21 @@ extension DependencyContainer {
|
||||
|
||||
- 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 {
|
||||
*/
|
||||
public func validate(_ arguments: Any...) throws {
|
||||
try _validate(arguments: arguments)
|
||||
}
|
||||
|
||||
func _validate(arguments _arguments: [Any]) throws {
|
||||
let arguments = _arguments
|
||||
validateNextDefinition: for (key, _) in definitions {
|
||||
do {
|
||||
//try to resolve key using provided arguments
|
||||
for argumentsSet in arguments where argumentsSet.dynamicType == key.argumentsType {
|
||||
for argumentsSet in arguments {
|
||||
guard type(of: argumentsSet) == key.typeOfArguments else { continue }
|
||||
do {
|
||||
try inContext(key) {
|
||||
try resolveKey(key, builder: { definition throws -> Any in
|
||||
let _ = try inContext(key:key, injectedInType: nil) {
|
||||
try self._resolve(key: key, builder: { definition throws -> Any in
|
||||
try definition.weakFactory(argumentsSet)
|
||||
})
|
||||
}
|
||||
@@ -633,71 +425,34 @@ extension DependencyContainer {
|
||||
throw error
|
||||
}
|
||||
//ignore other errors
|
||||
catch { log(.Errors, error) }
|
||||
catch { log(level: .Errors, error) }
|
||||
}
|
||||
|
||||
//try to resolve key using auto-wiring
|
||||
do {
|
||||
try self.resolve(key.protocolType, tag: key.associatedTag)
|
||||
let _ = try self.resolve(key.type, tag: key.tag)
|
||||
}
|
||||
catch let error as DipError {
|
||||
throw error
|
||||
}
|
||||
//ignore other errors
|
||||
catch { log(.Errors, error) }
|
||||
catch { log(level: .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 singletons = [DefinitionKey: Any]()
|
||||
var weakSingletons = [DefinitionKey: Any]()
|
||||
var resolvableInstances = [Resolvable]()
|
||||
|
||||
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 .ObjectGraph: return resolvedInstances[key]
|
||||
case .Prototype: return nil
|
||||
}
|
||||
}
|
||||
set {
|
||||
switch scope {
|
||||
case .Singleton, .EagerSingleton: singletons[key] = newValue
|
||||
case .WeakSingleton: weakSingletons[key] = newValue
|
||||
case .ObjectGraph: resolvedInstances[key] = newValue
|
||||
case .Prototype: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension DependencyContainer: CustomStringConvertible {
|
||||
|
||||
public var description: String {
|
||||
return "Definitions: \(definitions.count)\n" + definitions.map({ "\($0.0)" }).joinWithSeparator("\n")
|
||||
return "Definitions: \(definitions.count)\n" + definitions.map({ "\($0.0)" }).joined(separator: "\n")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//MARK: - Resolvable
|
||||
|
||||
/// Conform to this protocol when you need to have a callback when all the dependencies are injected.
|
||||
public protocol Resolvable {
|
||||
/// This method is called by the container when all dependencies of the instance are resolved.
|
||||
func didResolveDependencies()
|
||||
}
|
||||
|
||||
//MARK: - DependencyTagConvertible
|
||||
|
||||
/// Implement this protocol of your type if you want to use its instances as `DependencyContainer`'s tags.
|
||||
/// Implement this protocol on your type if you want to use its instances as `DependencyContainer`'s tags.
|
||||
/// `DependencyContainer.Tag`, `String`, `Int` and any `RawRepresentable` with `RawType` of `String` or `Int` by default confrom to this protocol.
|
||||
public protocol DependencyTagConvertible {
|
||||
var dependencyTag: DependencyContainer.Tag { get }
|
||||
@@ -733,7 +488,7 @@ extension DependencyTagConvertible where Self: RawRepresentable, Self.RawValue =
|
||||
}
|
||||
}
|
||||
|
||||
extension DependencyContainer.Tag: StringLiteralConvertible {
|
||||
extension DependencyContainer.Tag: ExpressibleByStringLiteral {
|
||||
|
||||
public init(stringLiteral value: StringLiteralType) {
|
||||
self = .String(value)
|
||||
@@ -749,7 +504,7 @@ extension DependencyContainer.Tag: StringLiteralConvertible {
|
||||
|
||||
}
|
||||
|
||||
extension DependencyContainer.Tag: IntegerLiteralConvertible {
|
||||
extension DependencyContainer.Tag: ExpressibleByIntegerLiteral {
|
||||
|
||||
public init(integerLiteral value: IntegerLiteralType) {
|
||||
self = .Int(value)
|
||||
@@ -757,88 +512,17 @@ extension DependencyContainer.Tag: IntegerLiteralConvertible {
|
||||
|
||||
}
|
||||
|
||||
public func ==(lhs: DependencyContainer.Tag, rhs: DependencyContainer.Tag) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (.String(lhsString), .String(rhsString)):
|
||||
return lhsString == rhsString
|
||||
case let (.Int(lhsInt), .Int(rhsInt)):
|
||||
return lhsInt == rhsInt
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
extension DependencyContainer.Tag: Equatable {
|
||||
|
||||
//MARK: - DipError
|
||||
|
||||
/**
|
||||
Errors thrown by `DependencyContainer`'s methods.
|
||||
|
||||
- seealso: `resolve(tag:)`
|
||||
*/
|
||||
public enum DipError: ErrorType, CustomStringConvertible {
|
||||
|
||||
/**
|
||||
Thrown by `resolve(tag:)` if no matching definition was registered in container.
|
||||
|
||||
- parameter key: definition key used to lookup matching definition
|
||||
*/
|
||||
case DefinitionNotFound(key: DefinitionKey)
|
||||
|
||||
/**
|
||||
Thrown by `resolve(tag:)` if failed to auto-inject required property.
|
||||
|
||||
- parameters:
|
||||
- label: The name of the property
|
||||
- type: The type of the property
|
||||
- underlyingError: The error that caused auto-injection to fail
|
||||
*/
|
||||
case AutoInjectionFailed(label: String?, type: Any.Type, underlyingError: ErrorType)
|
||||
|
||||
/**
|
||||
Thrown by `resolve(tag:)` if failed to auto-wire a type.
|
||||
|
||||
- parameters:
|
||||
- type: The type that failed to be resolved by auto-wiring
|
||||
- underlyingError: The error that cause auto-wiring to fail
|
||||
*/
|
||||
case AutoWiringFailed(type: Any.Type, underlyingError: ErrorType)
|
||||
|
||||
/**
|
||||
Thrown when auto-wiring type if several definitions with the same number of runtime arguments
|
||||
are registered for that type.
|
||||
|
||||
- parameters:
|
||||
- type: The type that failed to be resolved by auto-wiring
|
||||
- definitions: Ambiguous definitions
|
||||
*/
|
||||
case AmbiguousDefinitions(type: Any.Type, definitions: [Definition])
|
||||
|
||||
public var description: String {
|
||||
switch self {
|
||||
case let .DefinitionNotFound(key):
|
||||
return "No definition registered for \(key).\nCheck the tag, type you try to resolve, number, order and types of runtime arguments passed to `resolve()` and match them with registered factories for type \(key.protocolType)."
|
||||
case let .AutoInjectionFailed(label, type, error):
|
||||
return "Failed to auto-inject property \"\(label.desc)\" of type \(type). \(error)"
|
||||
case let .AutoWiringFailed(type, error):
|
||||
return "Failed to auto-wire type \"\(type)\". \(error)"
|
||||
case let .AmbiguousDefinitions(type, definitions):
|
||||
return "Ambiguous definitions for \(type):\n" +
|
||||
definitions.map({ "\($0)" }).joinWithSeparator(";\n")
|
||||
public static func ==(lhs: DependencyContainer.Tag, rhs: DependencyContainer.Tag) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (.String(lhsString), .String(rhsString)):
|
||||
return lhsString == rhsString
|
||||
case let (.Int(lhsInt), .Int(rhsInt)):
|
||||
return lhsInt == rhsInt
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
/**
|
||||
Errors thrown by `DependencyContainer`'s methods.
|
||||
|
||||
- seealso: `resolve(tag:)`
|
||||
*/
|
||||
public enum DipError: Error, CustomStringConvertible {
|
||||
|
||||
/**
|
||||
Thrown by `resolve(tag:)` if no matching definition was registered in container.
|
||||
|
||||
- parameter key: definition key used to lookup matching definition
|
||||
*/
|
||||
case definitionNotFound(key: DefinitionKey)
|
||||
|
||||
/**
|
||||
Thrown by `resolve(tag:)` if failed to auto-inject required property.
|
||||
|
||||
- parameters:
|
||||
- label: The name of the property
|
||||
- type: The type of the property
|
||||
- underlyingError: The error that caused auto-injection to fail
|
||||
*/
|
||||
case autoInjectionFailed(label: String?, type: Any.Type, underlyingError: Error)
|
||||
|
||||
/**
|
||||
Thrown by `resolve(tag:)` if failed to auto-wire a type.
|
||||
|
||||
- parameters:
|
||||
- type: The type that failed to be resolved by auto-wiring
|
||||
- underlyingError: The error that cause auto-wiring to fail
|
||||
*/
|
||||
case autoWiringFailed(type: Any.Type, underlyingError: Error)
|
||||
|
||||
/**
|
||||
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: [DefinitionType])
|
||||
|
||||
/**
|
||||
Thrown by `resolve(tag:)` if resolved instance does not implemenet resolved type (i.e. when type-forwarding).
|
||||
|
||||
- parameters:
|
||||
- resolved: Resolved instance
|
||||
- key: Definition key used to resolve instance
|
||||
*/
|
||||
case invalidType(resolved: Any?, key: DefinitionKey)
|
||||
|
||||
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 \(String(reflecting: key.type))."
|
||||
case let .autoInjectionFailed(label, type, error):
|
||||
return "Failed to auto-inject property \"\(label.desc)\" of type \(String(reflecting: type)). \(error)"
|
||||
case let .autoWiringFailed(type, error):
|
||||
return "Failed to auto-wire type \"\(String(reflecting: type))\". \(error)"
|
||||
case let .ambiguousDefinitions(type, definitions):
|
||||
return "Ambiguous definitions for \(String(reflecting: type)):\n" +
|
||||
definitions.map({ "\($0)" }).joined(separator: ";\n")
|
||||
case let .invalidType(resolved, key):
|
||||
return "Resolved instance \(resolved ?? "nil") does not implement expected type \(String(reflecting: key.type))."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// Dip
|
||||
//
|
||||
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
extension DependencyContainer {
|
||||
/**
|
||||
Registers definition for passed type.
|
||||
|
||||
If instance created by factory of definition, passed as a first parameter,
|
||||
does not implement type passed in a `type` parameter,
|
||||
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 registered for passed type.
|
||||
*/
|
||||
@discardableResult public func register<T, U, F>(_ definition: Definition<T, U>, type: F.Type, tag: DependencyTagConvertible? = nil) -> Definition<F, U> {
|
||||
return _register(definition: definition, type: type, tag: tag)
|
||||
}
|
||||
|
||||
/**
|
||||
Register definiton in the container and associate it with an optional tag.
|
||||
Will override already registered definition for the same type and factory, associated with the same tag.
|
||||
|
||||
- parameters:
|
||||
- tag: The arbitrary tag to associate this definition with. Pass `nil` to associate with any tag. Default value is `nil`.
|
||||
- definition: The definition to register in the container.
|
||||
|
||||
*/
|
||||
public func register<T, U>(_ definition: Definition<T, U>, tag: DependencyTagConvertible? = nil) {
|
||||
_register(definition: definition, tag: tag)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension DependencyContainer {
|
||||
|
||||
func _register<T, U>(definition aDefinition: Definition<T, U>, tag: DependencyTagConvertible? = nil) {
|
||||
precondition(!bootstrapped, "You can not modify container's definitions after it was bootstrapped.")
|
||||
let definition = aDefinition
|
||||
threadSafe {
|
||||
let key = DefinitionKey(type: T.self, typeOfArguments: U.self, tag: tag?.dependencyTag)
|
||||
if let _ = definitions[key] {
|
||||
_remove(definitionForKey: key)
|
||||
}
|
||||
|
||||
definition.container = self
|
||||
definitions[key] = definition
|
||||
resolvedInstances.singletons[key] = nil
|
||||
resolvedInstances.weakSingletons[key] = nil
|
||||
resolvedInstances.sharedSingletons[key] = nil
|
||||
resolvedInstances.sharedWeakSingletons[key] = nil
|
||||
|
||||
if .eagerSingleton == definition.scope {
|
||||
bootstrapQueue.append({ _ = try self.resolve(tag: tag) as T })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,335 @@
|
||||
//
|
||||
// Dip
|
||||
//
|
||||
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
extension DependencyContainer {
|
||||
|
||||
/**
|
||||
Resolve an instance of type `T`.
|
||||
|
||||
If no matching definition was registered with provided `tag`,
|
||||
container will lookup definition associated with `nil` tag.
|
||||
|
||||
- parameter tag: The arbitrary tag to use to lookup definition.
|
||||
|
||||
- throws: `DipError.DefinitionNotFound`, `DipError.AutoInjectionFailed`, `DipError.AmbiguousDefinitions`, `DipError.InvalidType`
|
||||
|
||||
- returns: An instance of type `T`.
|
||||
|
||||
**Example**:
|
||||
```swift
|
||||
let service = try! container.resolve() as Service
|
||||
let service = try! container.resolve(tag: "service") as Service
|
||||
let service: Service = try! container.resolve()
|
||||
```
|
||||
|
||||
- seealso: `register(_:type:tag:factory:)`
|
||||
*/
|
||||
public func resolve<T>(tag: DependencyTagConvertible? = nil) throws -> T {
|
||||
return try _resolve(tag: tag) { (factory: () throws -> T) in try factory() }
|
||||
}
|
||||
|
||||
/**
|
||||
Resolve an instance of requested 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.
|
||||
|
||||
- parameters:
|
||||
- type: Type to resolve
|
||||
- tag: The arbitrary tag to use to lookup definition.
|
||||
|
||||
- throws: `DipError.DefinitionNotFound`, `DipError.AutoInjectionFailed`, `DipError.AmbiguousDefinitions`, `DipError.InvalidType`
|
||||
|
||||
- returns: An instance of requested type.
|
||||
|
||||
**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(_:type:tag:factory:)`
|
||||
*/
|
||||
public func resolve(_ type: Any.Type, tag: DependencyTagConvertible? = nil) throws -> Any {
|
||||
return try resolve(type, tag: tag) { (factory: () throws -> Any) in try factory() }
|
||||
}
|
||||
|
||||
/**
|
||||
Resolve an instance of type `T` using generic builder closure that accepts generic factory and returns created instance.
|
||||
|
||||
- parameters:
|
||||
- tag: The arbitrary tag to use to lookup definition.
|
||||
- builder: Generic closure that accepts generic factory and returns inctance created by that factory.
|
||||
|
||||
- throws: `DipError.DefinitionNotFound`, `DipError.AutoInjectionFailed`, `DipError.AmbiguousDefinitions`, `DipError.InvalidType`
|
||||
|
||||
- returns: An instance of type `T`.
|
||||
|
||||
- note: You _should not_ call this method directly, instead call any of other
|
||||
`resolve(tag:)` or `resolve(tag:withArguments:)` methods.
|
||||
You _should_ use this method only to resolve dependency with more runtime arguments than
|
||||
_Dip_ supports (currently it's up to six) like in the following example:
|
||||
|
||||
```swift
|
||||
public func resolve<T, A, B, C, ...>(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, U>(tag: DependencyTagConvertible? = nil, builder: ((U) throws -> T) throws -> T) throws -> T {
|
||||
return try _resolve(tag: tag, builder: builder)
|
||||
}
|
||||
|
||||
public func resolve<T>(tag: DependencyTagConvertible? = nil, builder: (() throws -> T) throws -> T) throws -> T {
|
||||
return try _resolve(tag: tag, builder: builder)
|
||||
}
|
||||
|
||||
/**
|
||||
Resolve an instance of provided type using builder closure. Weakly-typed alternative of `resolve(tag:builder:)`
|
||||
|
||||
- seealso: `resolve(tag:builder:)`
|
||||
*/
|
||||
public func resolve<U>(_ type: Any.Type, tag: DependencyTagConvertible? = nil, builder: ((U) throws -> Any) throws -> Any) throws -> Any {
|
||||
return try _resolve(type: type, tag: tag, builder: builder)
|
||||
}
|
||||
|
||||
public func resolve(_ type: Any.Type, tag: DependencyTagConvertible? = nil, builder: (() throws -> Any) throws -> Any) throws -> Any {
|
||||
return try _resolve(type: type, tag: tag, builder: builder)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension DependencyContainer {
|
||||
|
||||
func _resolve<T>(tag aTag: DependencyTagConvertible? = nil, builder: (() throws -> T) throws -> T) throws -> T {
|
||||
return try resolve(T.self, tag: aTag, builder: { factory in
|
||||
try withoutActuallyEscaping(factory, do: { (factory) throws -> T in
|
||||
try builder({ try factory() as! T })
|
||||
})
|
||||
}) as! T
|
||||
}
|
||||
|
||||
func _resolve(type aType: Any.Type, tag: DependencyTagConvertible? = nil, builder: (() throws -> Any) throws -> Any) throws -> Any {
|
||||
let key = DefinitionKey(type: aType, typeOfArguments: Void.self, tag: tag?.dependencyTag)
|
||||
|
||||
return try inContext(key:key, injectedInType: context?.resolvingType) {
|
||||
try self._resolve(key: key, builder: { definition in
|
||||
try builder { try definition.weakFactory(()) }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func _resolve<T, U>(tag aTag: DependencyTagConvertible? = nil, builder: ((U) throws -> T) throws -> T) throws -> T {
|
||||
return try resolve(T.self, tag: aTag, builder: { factory in
|
||||
try withoutActuallyEscaping(factory, do: { (factory) throws -> T in
|
||||
try builder({ try factory($0) as! T })
|
||||
})
|
||||
}) as! T
|
||||
}
|
||||
|
||||
func _resolve<U>(type aType: Any.Type, tag: DependencyTagConvertible? = nil, builder: ((U) throws -> Any) throws -> Any) throws -> Any {
|
||||
let key = DefinitionKey(type: aType, typeOfArguments: U.self, tag: tag?.dependencyTag)
|
||||
|
||||
return try inContext(key:key, injectedInType: context?.resolvingType) {
|
||||
try self._resolve(key: key, builder: { definition in
|
||||
try builder(definition.weakFactory)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Lookup definition by the key and use it to resolve instance. Fallback to the key with `nil` tag.
|
||||
func _resolve<T>(key aKey: DefinitionKey, builder: (_Definition) throws -> T) throws -> T {
|
||||
guard let matching = self.definition(matching: aKey) else {
|
||||
do {
|
||||
return try autowire(key: aKey)
|
||||
} catch {
|
||||
if let resolved = collaboratingResolve(key: aKey, builder: builder) {
|
||||
return resolved
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (key, definition) = matching
|
||||
|
||||
//first search for already resolved instance for this type or any of forwarding types
|
||||
if let previouslyResolved: T = previouslyResolved(for: definition, key: key) {
|
||||
log(level: .Verbose, "Reusing previously resolved instance \(previouslyResolved)")
|
||||
return previouslyResolved
|
||||
}
|
||||
|
||||
if let context = context { log(level: .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, let unboxedAny = box.unboxed, let unboxed = unboxedAny 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(for: definition, key: key) {
|
||||
log(level: .Verbose, "Reusing previously resolved instance \(previouslyResolved)")
|
||||
return previouslyResolved
|
||||
}
|
||||
|
||||
resolvedInstances[key: key, inScope: definition.scope, context: context] = resolvedInstance
|
||||
|
||||
if let resolvable = resolvedInstance as? Resolvable {
|
||||
resolvedInstances.resolvableInstances.append(resolvable)
|
||||
resolvable.resolveDependencies(self)
|
||||
}
|
||||
|
||||
let shouldAutoInject = definition.autoInjectProperties ?? self.autoInjectProperties
|
||||
if shouldAutoInject {
|
||||
try autoInjectProperties(in: resolvedInstance)
|
||||
}
|
||||
try definition.resolveProperties(of: resolvedInstance, container: self)
|
||||
|
||||
log(level: .Verbose, "Resolved type \(key.type) with \(resolvedInstance)")
|
||||
return resolvedInstance
|
||||
}
|
||||
|
||||
private func previouslyResolved<T>(for definition: _Definition, key: DefinitionKey) -> T? {
|
||||
//first check if exact key was already resolved
|
||||
if let previouslyResolved: T = resolvedInstances[key: key, inScope: definition.scope, context: context] {
|
||||
return previouslyResolved
|
||||
}
|
||||
//then check if any related type was already resolved
|
||||
let keys = definition.implementingTypes.map({
|
||||
DefinitionKey(type: $0, typeOfArguments: key.typeOfArguments, tag: key.tag)
|
||||
})
|
||||
for key in keys {
|
||||
if let previouslyResolved: T = resolvedInstances[key: key, inScope: definition.scope, context: context] {
|
||||
return previouslyResolved
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Searches for definition that matches provided key
|
||||
func definition(matching key: DefinitionKey) -> KeyDefinitionPair? {
|
||||
if let definition = (self.definitions[key] ?? self.definitions[key.tagged(with: nil)]) {
|
||||
return (key, definition)
|
||||
}
|
||||
|
||||
//if no definition registered for exact type try to find type-forwarding definition that can resolve the type
|
||||
//that will actually happen only when resolving optionals
|
||||
if definitions.filter({ $0.0.type == key.type }).isEmpty {
|
||||
return typeForwardingDefinition(forKey: key)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
///Pool to hold instances, created during call to `resolve()`.
|
||||
///Before `resolve()` returns pool is drained.
|
||||
class ResolvedInstances {
|
||||
|
||||
var resolvedInstances = [DefinitionKey: Any]()
|
||||
var resolvableInstances = [Resolvable]()
|
||||
|
||||
//singletons are stored using reference type wrapper to be able to share them between containers
|
||||
var sharedSingletonsBox = Box<[DefinitionKey: Any]>([:])
|
||||
var sharedSingletons: [DefinitionKey: Any] {
|
||||
get { return sharedSingletonsBox.unboxed }
|
||||
set { sharedSingletonsBox.unboxed = newValue }
|
||||
}
|
||||
var singletons = [DefinitionKey: Any]()
|
||||
|
||||
var sharedWeakSingletonsBox = Box<[DefinitionKey: Any]>([:])
|
||||
var sharedWeakSingletons: [DefinitionKey: Any] {
|
||||
get { return sharedWeakSingletonsBox.unboxed }
|
||||
set { sharedWeakSingletonsBox.unboxed = newValue }
|
||||
}
|
||||
var weakSingletons = [DefinitionKey: Any]()
|
||||
|
||||
subscript<T>(key key: DefinitionKey, inScope scope: ComponentScope, context context: DependencyContainer.Context) -> T? {
|
||||
get {
|
||||
let instance: Any?
|
||||
switch scope {
|
||||
case .singleton, .eagerSingleton:
|
||||
instance = context.inCollaboration ? sharedSingletons[key] : singletons[key]
|
||||
case .weakSingleton:
|
||||
let singletons = context.inCollaboration ? sharedWeakSingletons : weakSingletons
|
||||
if let boxed = singletons[key] as? WeakBoxType { instance = boxed.unboxed }
|
||||
else { instance = singletons[key] }
|
||||
case .shared:
|
||||
instance = resolvedInstances[key]
|
||||
case .unique:
|
||||
return nil
|
||||
}
|
||||
return instance.flatMap { $0 as? T }
|
||||
}
|
||||
set {
|
||||
switch scope {
|
||||
case .singleton, .eagerSingleton:
|
||||
sharedSingletons[key] = newValue
|
||||
singletons[key] = newValue
|
||||
case .weakSingleton:
|
||||
sharedWeakSingletons[key] = newValue
|
||||
weakSingletons[key] = newValue
|
||||
case .shared:
|
||||
resolvedInstances[key] = newValue
|
||||
case .unique:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//MARK: - Resolvable
|
||||
|
||||
/// Resolvable protocol provides some extension points for resolving dependencies with property injection.
|
||||
public protocol Resolvable {
|
||||
/// This method will be called right after instance is created by the container.
|
||||
func resolveDependencies(_ container: DependencyContainer)
|
||||
/// This method will be called when all dependencies of the instance are resolved.
|
||||
/// When resolving objects graph the last resolved instance will receive this callback first.
|
||||
func didResolveDependencies()
|
||||
}
|
||||
|
||||
extension Resolvable {
|
||||
func resolveDependencies(_ container: DependencyContainer) {}
|
||||
func didResolveDependencies() {}
|
||||
}
|
||||
+156
-71
@@ -22,10 +22,95 @@
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
// MARK: - Register/resolve dependencies with runtime arguments
|
||||
|
||||
extension DependencyContainer {
|
||||
|
||||
/**
|
||||
Register factory for type `T` and associate it with an optional tag.
|
||||
|
||||
- parameters:
|
||||
- scope: The scope to use for instance created by the factory. Default value is `Shared`.
|
||||
- type: Type to register definition for. Default value is return value of factory.
|
||||
- tag: The arbitrary tag to associate this factory with. Pass `nil` to associate with any tag. Default value is `nil`.
|
||||
- factory: The factory that produces instance of `type`. Will be used to resolve instances of `type`.
|
||||
|
||||
- returns: A registered definition.
|
||||
|
||||
- note: You should cast the factory return type to the protocol you want to register it for
|
||||
(unless you want to register concrete type) or provide `type` parameter.
|
||||
|
||||
- seealso: `Definition`, `ComponentScope`, `DependencyTagConvertible`
|
||||
|
||||
**Example**:
|
||||
```swift
|
||||
//Register ServiceImp as Service
|
||||
container.register { ServiceImp() as Service }
|
||||
|
||||
//Register ServiceImp as Service named by "service"
|
||||
container.register(tag: "service") { ServiceImp() as Service }
|
||||
|
||||
//Register unique ServiceImp as Service
|
||||
container.register(.unique) { ServiceImp() as Service }
|
||||
|
||||
//Register ClientImp as Client and resolve it's service dependency
|
||||
container.register { try ClientImp(service: container.resolve() as Service) as Client }
|
||||
|
||||
//Register ServiceImp as concrete type
|
||||
container.register { ServiceImp() }
|
||||
container.register(factory: ServiceImp.init)
|
||||
|
||||
//Register ServiceImp as Service
|
||||
container.register(Service.self, factory: ServiceImp.init)
|
||||
|
||||
//Register ClientImp as Client
|
||||
container.register(Client.self, factory: ClientImp.init(service:))
|
||||
```
|
||||
*/
|
||||
@discardableResult public func register<T>(_ scope: ComponentScope = .shared, type: T.Type = T.self, tag: DependencyTagConvertible? = nil, factory: @escaping (()) throws -> T) -> Definition<T, ()> {
|
||||
let definition = DefinitionBuilder<T, ()> {
|
||||
$0.scope = scope
|
||||
$0.factory = factory
|
||||
}.build()
|
||||
register(definition, tag: tag)
|
||||
return definition
|
||||
}
|
||||
|
||||
/**
|
||||
Register generic factory and auto-wiring factory and associate it with an optional tag.
|
||||
|
||||
- parameters:
|
||||
- tag: The arbitrary tag to associate this factory with. Pass `nil` to associate with any tag. Default value is `nil`.
|
||||
- scope: The scope to use for instance created by the factory.
|
||||
- factory: The factory to register.
|
||||
- numberOfArguments: The number of factory arguments. Will be used on auto-wiring to sort definitions.
|
||||
- autoWiringFactory: The factory to be used on auto-wiring to resolve component.
|
||||
|
||||
- returns: A registered definition.
|
||||
|
||||
- note: You _should not_ call this method directly, instead call any of other `register` methods.
|
||||
You _should_ use this method only to register dependency with more runtime arguments
|
||||
than _Dip_ supports (currently it's up to six) like in the following example:
|
||||
|
||||
```swift
|
||||
public func register<T, A, B, C, ...>(_ scope: ComponentScope = .shared, type: T.Type = T.self, tag: Tag? = nil, factory: (A, B, C, ...) throws -> T) -> Definition<T, (A, B, C, ...)> {
|
||||
return register(scope: scope, type: type, tag: tag, factory: factory, numberOfArguments: ...) { container, tag in
|
||||
try factory(container.resolve(tag: tag), ...)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Though before you do so you should probably review your design and try to reduce number of depnedencies.
|
||||
*/
|
||||
public func register<T, U>(scope: ComponentScope, type: T.Type, tag: DependencyTagConvertible?, factory: @escaping (U) throws -> T, numberOfArguments: Int, autoWiringFactory: @escaping (DependencyContainer, Tag?) throws -> T) -> Definition<T, U> {
|
||||
let definition = DefinitionBuilder<T, U> {
|
||||
$0.scope = scope
|
||||
$0.factory = factory
|
||||
$0.numberOfArguments = numberOfArguments
|
||||
$0.autoWiringFactory = autoWiringFactory
|
||||
}.build()
|
||||
register(definition, tag: tag)
|
||||
return definition
|
||||
}
|
||||
|
||||
// MARK: 1 Runtime Argument
|
||||
|
||||
/**
|
||||
@@ -33,19 +118,19 @@ extension DependencyContainer {
|
||||
|
||||
- note: You can have several factories with different number or types of arguments registered for same type,
|
||||
optionally associated with some tags. When container resolves that type it matches the type,
|
||||
__number__, __types__ and __order__ of runtime arguments and optional tag that you pass to `resolve(tag:withArguments:)` method.
|
||||
__number__, __types__ and __order__ of runtime arguments and optional tag that you pass to `resolve(tag:arguments:)` method.
|
||||
|
||||
- parameters:
|
||||
- tag: The arbitrary tag to associate this factory with. Pass `nil` to associate with any tag. Default value is `nil`.
|
||||
- scope: The scope to use for this component. Default value is `.Prototype`.
|
||||
- scope: The scope to use for this component. Default value is `Shared`.
|
||||
- factory: The factory to register.
|
||||
|
||||
- seealso: `registerFactory(tag:scope:factory:)`
|
||||
- seealso: `register(_:type:tag:factory:)`
|
||||
*/
|
||||
public func register<T, A>(tag tag: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: (A) throws -> T) -> DefinitionOf<T, (A) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: 1) { container, tag in try factory(container.resolve(tag: tag)) }
|
||||
@discardableResult public func register<T, A>(_ scope: ComponentScope = .shared, type: T.Type = T.self, tag: DependencyTagConvertible? = nil, factory: @escaping ((A)) throws -> T) -> Definition<T, A> {
|
||||
return register(scope: scope, type: type, tag: tag, factory: factory, numberOfArguments: 1) { container, tag in try factory(container.resolve(tag: tag)) }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Resolve type `T` using one runtime argument.
|
||||
|
||||
@@ -68,101 +153,101 @@ extension DependencyContainer {
|
||||
|
||||
- returns: An instance of type `T`.
|
||||
|
||||
- seealso: `register(tag:_:factory:)`, `resolve(tag:builder:)`
|
||||
- seealso: `register(_:type:tag:factory:)`, `resolve(tag:builder:)`
|
||||
*/
|
||||
public func resolve<T, A>(tag tag: DependencyTagConvertible? = nil, withArguments arg1: A) throws -> T {
|
||||
public func resolve<T, A>(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, withArguments arg1: A) throws -> Any {
|
||||
///- seealso: `resolve(_:tag:)`, `resolve(tag:arguments:)`
|
||||
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, A, B>(tag tag: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: (A, B) throws -> T) -> DefinitionOf<T, (A, B) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: 2) { container, tag in try factory(container.resolve(tag: tag), container.resolve(tag: tag)) }
|
||||
/// - seealso: `register(_:type:tag:factory:)`
|
||||
@discardableResult public func register<T, A, B>(_ scope: ComponentScope = .shared, type: T.Type = T.self, tag: DependencyTagConvertible? = nil, factory: @escaping ((A, B)
|
||||
) throws -> T) -> Definition<T, (A, B)> {
|
||||
return register(scope: scope, type: type, tag: tag, factory: factory, numberOfArguments: 2) { container, tag in try factory((container.resolve(tag: tag), container.resolve(tag: tag))) }
|
||||
}
|
||||
|
||||
/// - seealso: `resolve(tag:arguments:)`
|
||||
public func resolve<T, A, B>(tag: DependencyTagConvertible? = nil, arguments arg1: A, _ arg2: B) throws -> T {
|
||||
return try resolve(T.self, tag: tag) { factory in try factory((arg1, arg2)) } as! T
|
||||
}
|
||||
|
||||
/// - seealso: `resolve(tag:withArguments:)`
|
||||
public func resolve<T, A, B>(tag tag: DependencyTagConvertible? = nil, withArguments arg1: A, _ arg2: B) throws -> T {
|
||||
return try resolve(tag: tag) { factory in try factory(arg1, arg2) }
|
||||
}
|
||||
|
||||
///- seealso: `resolve(_:tag:)`, `resolve(tag:withArguments:)`
|
||||
public func resolve<A, B>(type: Any.Type, tag: DependencyTagConvertible? = nil, withArguments arg1: A, _ arg2: B) throws -> Any {
|
||||
///- seealso: `resolve(_:tag:)`, `resolve(tag:arguments:)`
|
||||
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, A, B, C>(tag tag: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: (A, B, C) throws -> T) -> DefinitionOf<T, (A, B, C) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: 3) { container, tag in try factory(container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag)) }
|
||||
}
|
||||
|
||||
/// - seealso: `resolve(tag:withArguments:)`
|
||||
public func resolve<T, A, B, C>(tag tag: DependencyTagConvertible? = nil, withArguments arg1: A, _ arg2: B, _ arg3: C) throws -> T {
|
||||
return try resolve(tag: tag) { factory in try factory(arg1, arg2, arg3) }
|
||||
}
|
||||
|
||||
///- seealso: `resolve(_:tag:)`, `resolve(tag:withArguments:)`
|
||||
public func resolve<A, B, C>(type: Any.Type, tag: DependencyTagConvertible? = nil, withArguments arg1: A, _ arg2: B, _ arg3: C) throws -> Any {
|
||||
return try resolve(type, tag: tag) { factory in try factory((arg1, arg2, arg3)) }
|
||||
}
|
||||
|
||||
// MARK: 4 Runtime Arguments
|
||||
|
||||
/// - seealso: `register(tag:scope:factory:)`
|
||||
public func register<T, A, B, C, D>(tag tag: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: (A, B, C, D) throws -> T) -> DefinitionOf<T, (A, B, C, D) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: 4) { container, tag in try factory(container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag)) }
|
||||
}
|
||||
|
||||
/// - seealso: `resolve(tag:withArguments:)`
|
||||
public func resolve<T, A, B, C, D>(tag tag: DependencyTagConvertible? = nil, withArguments arg1: A, _ arg2: B, _ arg3: C, _ arg4: D) throws -> T {
|
||||
return try resolve(tag: tag) { factory in try factory(arg1, arg2, arg3, arg4) }
|
||||
/// - seealso: `register(_:type:tag:factory:)`
|
||||
@discardableResult public func register<T, A, B, C>(_ scope: ComponentScope = .shared, type: T.Type = T.self, tag: DependencyTagConvertible? = nil, factory: @escaping ((A, B, C)) throws -> T) -> Definition<T, (A, B, C)> {
|
||||
return register(scope: scope, type: type, tag: tag, factory: factory, numberOfArguments: 3) { container, tag in try factory((container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag))) }
|
||||
}
|
||||
|
||||
/// - seealso: `resolve(_:tag:)`, `resolve(tag:withArguments:)`
|
||||
public func resolve<A, B, C, D>(type: Any.Type, tag: DependencyTagConvertible? = nil, withArguments arg1: A, _ arg2: B, _ arg3: C, _ arg4: D) throws -> Any {
|
||||
/// - seealso: `resolve(tag:arguments:)`
|
||||
public func resolve<T, A, B, C>(tag: DependencyTagConvertible? = nil, arguments arg1: A, _ arg2: B, _ arg3: C) throws -> T {
|
||||
return try resolve(T.self, tag: tag) { factory in try factory((arg1, arg2, arg3)) } as! T
|
||||
}
|
||||
|
||||
///- seealso: `resolve(_:tag:)`, `resolve(tag:arguments:)`
|
||||
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(_:type:tag:factory:)`
|
||||
@discardableResult public func register<T, A, B, C, D>(_ scope: ComponentScope = .shared, type: T.Type = T.self, tag: DependencyTagConvertible? = nil, factory: @escaping ((A, B, C, D)) throws -> T) -> Definition<T, (A, B, C, D)> {
|
||||
return register(scope: scope, type: type, tag: tag, 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:arguments:)`
|
||||
public func resolve<T, A, B, C, D>(tag: DependencyTagConvertible? = nil, arguments arg1: A, _ arg2: B, _ arg3: C, _ arg4: D) throws -> T {
|
||||
return try resolve(T.self, tag: tag) { factory in try factory((arg1, arg2, arg3, arg4)) } as! T
|
||||
}
|
||||
|
||||
/// - seealso: `resolve(_:tag:)`, `resolve(tag:arguments:)`
|
||||
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, A, B, C, D, E>(tag tag: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: (A, B, C, D, E) throws -> T) -> DefinitionOf<T, (A, B, C, D, E) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: 5) { container, tag in try factory(container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag)) }
|
||||
}
|
||||
|
||||
/// - seealso: `resolve(tag:withArguments:)`
|
||||
public func resolve<T, A, B, C, D, E>(tag tag: DependencyTagConvertible? = nil, withArguments arg1: A, _ arg2: B, _ arg3: C, _ arg4: D, _ arg5: E) throws -> T {
|
||||
return try resolve(tag: tag) { factory in try factory(arg1, arg2, arg3, arg4, arg5) }
|
||||
/// - seealso: `register(_:type:tag:factory:)`
|
||||
@discardableResult public func register<T, A, B, C, D, E>(_ scope: ComponentScope = .shared, type: T.Type = T.self, tag: DependencyTagConvertible? = nil, factory: @escaping ((A, B, C, D, E)) throws -> T) -> Definition<T, (A, B, C, D, E)> {
|
||||
return register(scope: scope, type: type, tag: tag, 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:)`, `resolve(tag:withArguments:)`
|
||||
public func resolve<A, B, C, D, E>(type: Any.Type, tag: DependencyTagConvertible? = nil, withArguments arg1: A, _ arg2: B, _ arg3: C, _ arg4: D, _ arg5: E) throws -> Any {
|
||||
/// - seealso: `resolve(tag:arguments:)`
|
||||
public func resolve<T, A, B, C, D, E>(tag: DependencyTagConvertible? = nil, arguments arg1: A, _ arg2: B, _ arg3: C, _ arg4: D, _ arg5: E) throws -> T {
|
||||
return try resolve(T.self, tag: tag) { factory in try factory((arg1, arg2, arg3, arg4, arg5)) } as! T
|
||||
}
|
||||
|
||||
///- seealso: `resolve(_:tag:)`, `resolve(tag:arguments:)`
|
||||
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, A, B, C, D, E, F>(tag tag: DependencyTagConvertible? = nil, _ scope: ComponentScope = .Prototype, factory: (A, B, C, D, E, F) throws -> T) -> DefinitionOf<T, (A, B, C, D, E, F) throws -> T> {
|
||||
return registerFactory(tag: tag, scope: scope, factory: factory, numberOfArguments: 6) { container, tag in try factory(container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag), container.resolve(tag: tag)) }
|
||||
}
|
||||
|
||||
/// - seealso: `resolve(tag:withArguments:)`
|
||||
public func resolve<T, A, B, C, D, E, F>(tag tag: DependencyTagConvertible? = nil, withArguments arg1: A, _ arg2: B, _ arg3: C, _ arg4: D, _ arg5: E, _ arg6: F) throws -> T {
|
||||
return try resolve(tag: tag) { factory in try factory(arg1, arg2, arg3, arg4, arg5, arg6) }
|
||||
/// - seealso: `register(_:type:tag:factory:)`
|
||||
@discardableResult public func register<T, A, B, C, D, E, F>(_ scope: ComponentScope = .shared, type: T.Type = T.self, tag: DependencyTagConvertible? = nil, factory: @escaping ((A, B, C, D, E, F)) throws -> T) -> Definition<T, (A, B, C, D, E, F)> {
|
||||
return register(scope: scope, type: type, tag: tag, 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:)`, `resolve(tag:withArguments:)`
|
||||
public func resolve<A, B, C, D, E, F>(type: Any.Type, tag: DependencyTagConvertible? = nil, withArguments arg1: A, _ arg2: B, _ arg3: C, _ arg4: D, _ arg5: E, _ arg6: F) throws -> Any {
|
||||
/// - seealso: `resolve(tag:arguments:)`
|
||||
public func resolve<T, A, B, C, D, E, F>(tag: DependencyTagConvertible? = nil, arguments arg1: A, _ arg2: B, _ arg3: C, _ arg4: D, _ arg5: E, _ arg6: F) throws -> T {
|
||||
return try resolve(T.self, tag: tag) { factory in try factory((arg1, arg2, arg3, arg4, arg5, arg6)) } as! T
|
||||
}
|
||||
|
||||
/// - seealso: `resolve(_:tag:)`, `resolve(tag:arguments:)`
|
||||
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)) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
//
|
||||
// DipUI
|
||||
//
|
||||
// Copyright (c) 2016 Ilya Puchka <ilyapuchka@gmail.com>
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#if (canImport(UIKit) || canImport(AppKit) || canImport(WatchKit))
|
||||
|
||||
extension DependencyContainer {
|
||||
///Containers that will be used to resolve dependencies of instances, created by stroyboards.
|
||||
static public var uiContainers: [DependencyContainer] = {
|
||||
#if os(watchOS)
|
||||
swizzleAwakeWithContext
|
||||
#endif
|
||||
return []
|
||||
}()
|
||||
|
||||
/**
|
||||
Resolves dependencies of passed in instance.
|
||||
Use this method to resolve dependencies of object created by storyboard.
|
||||
The type of the instance should be registered in the container.
|
||||
|
||||
You should call this method only from implementation of `didInstantiateFromStoryboard(_:tag:)`
|
||||
of `StoryboardInstantiatable` protocol if you override its default implementation.
|
||||
|
||||
This method will do the same as `resolve(tag:) as T`, but instead of creating
|
||||
a new intance with a registered factory it will use passed in instance as a resolved instance.
|
||||
|
||||
- parameters:
|
||||
- instance: The object which dependencies should be resolved
|
||||
- tag: An optional tag used to register the type (`T`) in the container
|
||||
|
||||
**Example**:
|
||||
|
||||
```swift
|
||||
class ViewController: UIViewController, ServiceDelegate, StoryboardInstantiatable {
|
||||
var service: Service?
|
||||
|
||||
func didInstantiateFromStoryboard(_ container: DependencyContainer, tag: DependencyContainer.Tag?) throws {
|
||||
try container.resolveDependencies(of: self as ServiceDelegate, tag: "vc")
|
||||
}
|
||||
}
|
||||
|
||||
class ServiceImp: Service {
|
||||
weak var delegate: ServiceDelegate?
|
||||
}
|
||||
|
||||
container.register(tag: "vc") { ViewController() }
|
||||
.resolvingProperties { container, controller in
|
||||
controller.service = try container.resolve() as Service
|
||||
controller.service.delegate = controller
|
||||
}
|
||||
|
||||
container.register { ServiceImp() as Service }
|
||||
```
|
||||
|
||||
- seealso: `register(_:type:tag:factory:)`, `didInstantiateFromStoryboard(_:tag:)`
|
||||
|
||||
*/
|
||||
public func resolveDependencies<T>(of instance: T, tag: Tag? = nil) throws {
|
||||
_ = try resolve(tag: tag) { (_: () throws -> T) in instance }
|
||||
}
|
||||
|
||||
/**
|
||||
Register storyboard type `T` which has to conform to `StoryboardInstantiatable` and associate it with an optional tag.
|
||||
|
||||
- parameters:
|
||||
- type: Storyboard type to register definition for.
|
||||
- tag: The arbitrary tag to associate this factory with. Pass `nil` to associate with any tag. Default value is `nil`.
|
||||
|
||||
- returns: A registered definition.
|
||||
|
||||
- note: This method will register concrete types. If you need to register
|
||||
as abstract types you should use standard `register` method from Dip.
|
||||
You should cast the factory return type to the protocol you want to
|
||||
register it for (unless you want to register concrete type) or
|
||||
provide `type` parameter.
|
||||
|
||||
- seealso: `Definition`, `ComponentScope`, `DependencyTagConvertible`
|
||||
|
||||
**Example**:
|
||||
```swift
|
||||
// Register MyViewController
|
||||
container.register(storyboardType: MyViewController.self)
|
||||
// or
|
||||
container.register(tag: "myVC") { MyViewController() as MyViewControllerType }
|
||||
```
|
||||
*/
|
||||
public func register<T: NSObject>(storyboardType type: T.Type, tag: DependencyTagConvertible? = nil) -> Dip.Definition<T, ()> where T: StoryboardInstantiatable {
|
||||
return register(.unique, type: type, tag: tag, factory: { T() })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#if os(watchOS)
|
||||
public protocol StoryboardInstantiatableType {}
|
||||
#else
|
||||
public typealias StoryboardInstantiatableType = NSObjectProtocol
|
||||
#endif
|
||||
|
||||
public protocol StoryboardInstantiatable: StoryboardInstantiatableType {
|
||||
|
||||
/**
|
||||
This method will be called if you set a `dipTag` attirbute on the object in a storyboard
|
||||
that conforms to `StoryboardInstantiatable` protocol.
|
||||
|
||||
- parameters:
|
||||
- tag: The tag value, that was set on the object in a storyboard
|
||||
- container: The `DependencyContainer` associated with storyboards
|
||||
|
||||
The type that implements `StoryboardInstantiatable` protocol should be registered in `UIStoryboard.container`.
|
||||
Default implementation of that method calls `resolveDependenciesOf(_:tag:)`
|
||||
and pass it `self` instance and the tag.
|
||||
|
||||
Usually you will not need to override the default implementation of this method
|
||||
if you registered the type of the instance as a concrete type in the container.
|
||||
Then you only need to add conformance to `StoryboardInstantiatable`.
|
||||
|
||||
You may want to override it if you want to add custom logic before/after resolving dependencies
|
||||
or you want to resolve the instance as implementation of some protocol which it conforms to.
|
||||
|
||||
- warning: This method will be called after `init?(coder:)` but before `awakeFromNib` method of `NSObject`.
|
||||
On watchOS this method will be called before `awakeWithContext(_:)`.
|
||||
|
||||
**Example**:
|
||||
|
||||
```swift
|
||||
extension MyViewController: SomeProtocol { ... }
|
||||
|
||||
extension MyViewController: StoryboardInstantiatable {
|
||||
func didInstantiateFromStoryboard(_ container: DependencyContainer, tag: DependencyContainer.Tag) throws {
|
||||
//resolve dependencies of the instance as SomeProtocol type
|
||||
try container.resolveDependencies(of: self as SomeProtocol, tag: tag)
|
||||
//do some additional setup here
|
||||
}
|
||||
}
|
||||
```
|
||||
*/
|
||||
func didInstantiateFromStoryboard(_ container: DependencyContainer, tag: DependencyContainer.Tag?) throws
|
||||
|
||||
}
|
||||
|
||||
extension StoryboardInstantiatable {
|
||||
public func didInstantiateFromStoryboard(_ container: DependencyContainer, tag: DependencyContainer.Tag?) throws {
|
||||
try container.resolveDependencies(of: self, tag: tag)
|
||||
}
|
||||
}
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(OSX)
|
||||
|
||||
#if os(iOS) || os(tvOS)
|
||||
import UIKit
|
||||
#elseif os(OSX)
|
||||
import AppKit
|
||||
#endif
|
||||
|
||||
let DipTagAssociatedObjectKey = UnsafeMutablePointer<Int8>.allocate(capacity: 1)
|
||||
|
||||
extension NSObject {
|
||||
|
||||
///A string tag that will be used to resolve dependencies of this instance
|
||||
///if it implements `StoryboardInstantiatable` protocol.
|
||||
@objc private(set) public var dipTag: String? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, DipTagAssociatedObjectKey) as? String
|
||||
}
|
||||
set {
|
||||
objc_setAssociatedObject(self, DipTagAssociatedObjectKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
|
||||
guard let instantiatable = self as? StoryboardInstantiatable else { return }
|
||||
|
||||
let tag = dipTag.map(DependencyContainer.Tag.String)
|
||||
|
||||
for (index, container) in DependencyContainer.uiContainers.enumerated() {
|
||||
do {
|
||||
log("Trying to resolve \(type(of: self)) with UI container at index \(index)")
|
||||
try instantiatable.didInstantiateFromStoryboard(container, tag: tag)
|
||||
log("Resolved \(type(of: self))")
|
||||
return
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func log(_ message: Any) {
|
||||
if Dip.LogLevel.Errors.rawValue <= Dip.logLevel.rawValue {
|
||||
Dip.logger(logLevel, message)
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
import WatchKit
|
||||
|
||||
let swizzleAwakeWithContext: Void = {
|
||||
let originalSelector = #selector(WKInterfaceController.awake(withContext:))
|
||||
let swizzledSelector = #selector(WKInterfaceController.dip_awake(withContext:))
|
||||
|
||||
guard let originalMethod = class_getInstanceMethod(WKInterfaceController.self, originalSelector),
|
||||
let swizzledMethod = class_getInstanceMethod(WKInterfaceController.self, swizzledSelector) else { return }
|
||||
|
||||
let didAddMethod = class_addMethod(WKInterfaceController.self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
|
||||
|
||||
if didAddMethod {
|
||||
class_replaceMethod(WKInterfaceController.self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
|
||||
} else {
|
||||
method_exchangeImplementations(originalMethod, swizzledMethod)
|
||||
}
|
||||
}()
|
||||
|
||||
extension WKInterfaceController: StoryboardInstantiatableType {
|
||||
|
||||
@objc func dip_awake(withContext context: AnyObject?) {
|
||||
defer { self.dip_awake(withContext: context) }
|
||||
guard let instantiatable = self as? StoryboardInstantiatable else { return }
|
||||
|
||||
for container in DependencyContainer.uiContainers {
|
||||
guard let _ = try? instantiatable.didInstantiateFromStoryboard(container, tag: nil) else { continue }
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -22,66 +22,122 @@
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
protocol TypeForwardingDefinition: Definition {
|
||||
protocol TypeForwardingDefinition: DefinitionType {
|
||||
var implementingTypes: [Any.Type] { get }
|
||||
func doesImplements(type: Any.Type) -> Bool
|
||||
func doesImplements(type aType: Any.Type) -> Bool
|
||||
}
|
||||
|
||||
extension DependencyContainer {
|
||||
extension Definition {
|
||||
|
||||
/**
|
||||
Registers definition for passed type.
|
||||
|
||||
If instance created by definition factory does not implement registered type
|
||||
If instance created by factory of definition on which method is called
|
||||
does not implement type passed in a `type` parameter,
|
||||
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.
|
||||
- returns: definition on which `implements` was called
|
||||
*/
|
||||
public func register<T, U, F>(definition: DefinitionOf<T, U throws -> T>, type: F.Type, tag: DependencyTagConvertible? = nil) -> DefinitionOf<F, U throws -> F> {
|
||||
let key = DefinitionKey(protocolType: F.self, argumentsType: U.self)
|
||||
@discardableResult public func implements<F>(_ type: F.Type, tag: DependencyTagConvertible? = nil) -> Definition {
|
||||
precondition(container != nil, "Definition should be registered in the container.")
|
||||
|
||||
container!.register(self, type: type, tag: tag)
|
||||
return self
|
||||
}
|
||||
|
||||
/**
|
||||
Registers definition for passed type.
|
||||
|
||||
If instance created by factory of definition on which method is called
|
||||
does not implement type passed in a `type` parameter,
|
||||
container will throw `DipError.DefinitionNotFound` error when trying to resolve that type.
|
||||
|
||||
- parameters:
|
||||
- type: Type to register definition for
|
||||
- tag: Optional tag to associate definition with. Default is `nil`.
|
||||
- resolvingProperties: Optional block to be called to resolve instance property dependencies
|
||||
|
||||
- returns: definition on which `implements` was called
|
||||
*/
|
||||
@discardableResult public func implements<F>(_ type: F.Type, tag: DependencyTagConvertible? = nil, resolvingProperties: @escaping (DependencyContainer, F) throws -> ()) -> Definition {
|
||||
precondition(container != nil, "Definition should be registered in the container.")
|
||||
|
||||
let forwardDefinition = container!.register(self, type: type, tag: tag)
|
||||
forwardDefinition.resolvingProperties(resolvingProperties)
|
||||
return self
|
||||
}
|
||||
|
||||
///Registers definition for types passed as parameters
|
||||
@discardableResult public func implements<A, B>(_ a: A.Type, _ b: B.Type) -> Definition {
|
||||
return implements(a).implements(b)
|
||||
}
|
||||
|
||||
///Registers definition for types passed as parameters
|
||||
@discardableResult public func implements<A, B, C>(_ a: A.Type, _ b: B.Type, _ c: C.Type) -> Definition {
|
||||
return implements(a).implements(b).implements(c)
|
||||
}
|
||||
|
||||
///Registers definition for types passed as parameters
|
||||
@discardableResult public func implements<A, B, C, D>(_ a: A.Type, _ b: B.Type, _ c: C.Type, _ d: D.Type) -> Definition {
|
||||
return implements(a).implements(b).implements(c).implements(d)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension DependencyContainer {
|
||||
|
||||
func _register<T, U, F>(definition aDefinition: Definition<T, U>, type: F.Type, tag: DependencyTagConvertible? = nil) -> Definition<F, U> {
|
||||
let definition = aDefinition
|
||||
precondition(definition.container === self, "Definition should be registered in the container.")
|
||||
|
||||
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))
|
||||
let resolved = try factory($0)
|
||||
if let resolved = resolved as? F {
|
||||
return resolved
|
||||
}
|
||||
else {
|
||||
throw DipError.invalidType(resolved: resolved, key: key.tagged(with: self.context.tag))
|
||||
}
|
||||
return resolved
|
||||
}
|
||||
|
||||
$0.numberOfArguments = definition.numberOfArguments
|
||||
$0.autoWiringFactory = definition.autoWiringFactory.map({ autoWiringFactory in
|
||||
$0.autoWiringFactory = definition.autoWiringFactory.map({ factory in
|
||||
{ [unowned self] in
|
||||
guard let resolved = try autoWiringFactory($0, $1) as? F else {
|
||||
throw DipError.DefinitionNotFound(key: key.tagged(self.context.tag))
|
||||
let resolved = try factory($0, $1)
|
||||
if let resolved = resolved as? F {
|
||||
return resolved
|
||||
}
|
||||
else {
|
||||
throw DipError.invalidType(resolved: resolved, key: key.tagged(with: self.context.tag))
|
||||
}
|
||||
return resolved
|
||||
}
|
||||
})
|
||||
|
||||
$0.forwardsDefinition = definition
|
||||
$0.forwardsTo = definition
|
||||
}.build()
|
||||
|
||||
register(forwardDefinition, forTag: tag)
|
||||
register(forwardDefinition, tag: tag)
|
||||
return forwardDefinition
|
||||
}
|
||||
|
||||
/// Searches for definition that forwards requested type
|
||||
func typeForwardingDefinition(key: DefinitionKey) -> KeyDefinitionPair? {
|
||||
func typeForwardingDefinition(forKey key: DefinitionKey) -> KeyDefinitionPair? {
|
||||
var forwardingDefinitions = self.definitions.map({ (key: $0.0, definition: $0.1) })
|
||||
|
||||
forwardingDefinitions = filter(forwardingDefinitions, type: key.protocolType, tag: key.associatedTag, argumentsType: key.argumentsType)
|
||||
forwardingDefinitions = order(forwardingDefinitions, tag: key.associatedTag)
|
||||
forwardingDefinitions = filter(definitions: forwardingDefinitions, byKey: key, byTypeOfArguments: true)
|
||||
forwardingDefinitions = order(definitions: forwardingDefinitions, byTag: key.tag)
|
||||
|
||||
//we need to carry on original tag
|
||||
return forwardingDefinitions.first.map({ ($0.key.tagged(key.associatedTag), $0.definition) })
|
||||
return forwardingDefinitions.first.map({ ($0.key.tagged(with: key.tag), $0.definition) })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+26
-22
@@ -22,16 +22,19 @@
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
public enum LogLevel {
|
||||
case Verbose
|
||||
case Errors
|
||||
public enum LogLevel: Int {
|
||||
case None
|
||||
case Errors
|
||||
case Verbose
|
||||
}
|
||||
|
||||
public var logLevel: LogLevel = .Errors
|
||||
|
||||
func log(logLevel: LogLevel, _ message: Any) {
|
||||
guard case logLevel = Dip.logLevel else { return }
|
||||
print(message)
|
||||
public var logger: (LogLevel, Any) -> Void = { print($1) }
|
||||
|
||||
func log(level logLevel: LogLevel, _ message: Any) {
|
||||
guard logLevel.rawValue <= Dip.logLevel.rawValue else { return }
|
||||
logger(logLevel, message)
|
||||
}
|
||||
|
||||
///Internal protocol used to unwrap optional values.
|
||||
@@ -41,22 +44,25 @@ protocol BoxType {
|
||||
|
||||
extension Optional: BoxType {
|
||||
var unboxed: Any? {
|
||||
switch self {
|
||||
case let .Some(value): return value
|
||||
default: return nil
|
||||
}
|
||||
return self ?? 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
|
||||
}
|
||||
}
|
||||
|
||||
class NullableBox<T> {
|
||||
var unboxed: T?
|
||||
init(_ value: T?) {
|
||||
self.unboxed = value
|
||||
}
|
||||
}
|
||||
|
||||
protocol WeakBoxType {
|
||||
var unboxed: AnyObject? { get }
|
||||
}
|
||||
@@ -67,10 +73,8 @@ class WeakBox<T>: WeakBoxType {
|
||||
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))")
|
||||
}
|
||||
init(_ value: T?) {
|
||||
weak var value: AnyObject? = value as AnyObject
|
||||
self.unboxed = value
|
||||
}
|
||||
}
|
||||
@@ -94,7 +98,7 @@ extension Optional {
|
||||
}
|
||||
}
|
||||
|
||||
#if os(Linux)
|
||||
#if !_runtime(_ObjC)
|
||||
import Glibc
|
||||
class RecursiveLock {
|
||||
private var _lock = _initializeRecursiveMutex()
|
||||
@@ -124,7 +128,7 @@ extension Optional {
|
||||
|
||||
extension pthread_mutex_t {
|
||||
mutating func lock() {
|
||||
pthread_mutex_trylock(&self)
|
||||
pthread_mutex_lock(&self)
|
||||
}
|
||||
mutating func unlock() {
|
||||
pthread_mutex_unlock(&self)
|
||||
|
||||
@@ -1,463 +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 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(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
|
||||
//when
|
||||
let client = try! container.resolve() as AutoWiredClient
|
||||
|
||||
//then
|
||||
let service1 = client.service1
|
||||
XCTAssertTrue(service1 is ServiceImp1)
|
||||
let service2 = client.service2
|
||||
XCTAssertTrue(service2 is ServiceImp2)
|
||||
|
||||
//when
|
||||
let anyClient = try! container.resolve(AutoWiredClient.self)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(anyClient is AutoWiredClientImp)
|
||||
}
|
||||
|
||||
func testThatItUsesAutoWireFactoryWithMostNumberOfArguments() {
|
||||
//given
|
||||
|
||||
//1 arg
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
|
||||
//1 arg
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: try self.container.resolve(), service2: $0) as AutoWiredClient }
|
||||
|
||||
//2 args
|
||||
var factoryWithMostNumberOfArgumentsCalled = false
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
.resolveDependencies { _ in
|
||||
factoryWithMostNumberOfArgumentsCalled = true
|
||||
}
|
||||
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve() as AutoWiredClient
|
||||
|
||||
//then
|
||||
XCTAssertTrue(factoryWithMostNumberOfArgumentsCalled)
|
||||
}
|
||||
|
||||
func testThatItThrowsAmbiguityErrorWhenUsingAutoWire() {
|
||||
//given
|
||||
|
||||
//1 arg
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
|
||||
//1 arg
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: try self.container.resolve(), service2: $0) as AutoWiredClient }
|
||||
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
//when
|
||||
AssertThrows(expression: try container.resolve() as AutoWiredClient) { error -> Bool in
|
||||
switch error {
|
||||
case let DipError.AutoWiringFailed(_, error):
|
||||
if case DipError.AmbiguousDefinitions = error { return true }
|
||||
else { return false }
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testThatItFirstTriesToUseTaggedFactoriesWhenUsingAutoWire() {
|
||||
//given
|
||||
|
||||
//1 arg
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
|
||||
//1 arg
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: try self.container.resolve(), service2: $0) as AutoWiredClient }
|
||||
|
||||
//2 args
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
|
||||
//1 arg tagged
|
||||
var taggedFactoryWithMostNumberOfArgumentsCalled = false
|
||||
container.register(tag: "tag", .ObjectGraph) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
|
||||
|
||||
//2 arg tagged
|
||||
container.register(tag: "tag", .ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }.resolveDependencies { _ in
|
||||
taggedFactoryWithMostNumberOfArgumentsCalled = true
|
||||
}
|
||||
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve(tag: "tag") as AutoWiredClient
|
||||
|
||||
//then
|
||||
XCTAssertTrue(taggedFactoryWithMostNumberOfArgumentsCalled)
|
||||
}
|
||||
|
||||
func testThatItFallbackToNotTaggedFactoryWhenUsingAutoWire() {
|
||||
//given
|
||||
|
||||
//1 arg
|
||||
var notTaggedFactoryWithMostNumberOfArgumentsCalled = false
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }.resolveDependencies {_ in
|
||||
notTaggedFactoryWithMostNumberOfArgumentsCalled = true
|
||||
}
|
||||
|
||||
//1 arg tagged
|
||||
container.register(tag: "tag", .ObjectGraph) { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
|
||||
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve(tag: "other tag") as AutoWiredClient
|
||||
|
||||
//then
|
||||
XCTAssertTrue(notTaggedFactoryWithMostNumberOfArgumentsCalled)
|
||||
}
|
||||
|
||||
func testThatItDoesNotTryToUseAutoWiringWhenCallingResolveWithArguments() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
//when
|
||||
let service = try! container.resolve() as Service
|
||||
AssertThrows(expression: try container.resolve(withArguments: service) as AutoWiredClient,
|
||||
"Container should not use auto-wiring when resolving with runtime arguments")
|
||||
}
|
||||
|
||||
func testThatItDoesNotUseAutoWiringWhenFailedToResolveLowLevelDependency() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp() as AutoWiredClient }
|
||||
.resolveDependencies { container, resolved in
|
||||
resolved.service1 = try container.resolve() as Service
|
||||
resolved.service2 = try container.resolve() as ServiceImp2
|
||||
|
||||
//simulate that something goes wrong on the way
|
||||
throw DipError.DefinitionNotFound(key: DefinitionKey(protocolType: ServiceImp1.self, argumentsType: Any.self))
|
||||
}
|
||||
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
.resolveDependencies { container, resolved in
|
||||
//auto-wiring should be performed only when definition for type to resolve is not found
|
||||
//but not for any other type along the way in the graph
|
||||
XCTFail("Auto-wiring should not be performed if instance was actually resolved.")
|
||||
}
|
||||
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
//then
|
||||
AssertThrows(expression: try container.resolve() as AutoWiredClient,
|
||||
"Container should not use auto-wiring when definition for resolved type is registered.")
|
||||
}
|
||||
|
||||
func testThatItReusesInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgain() {
|
||||
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
var anotherInstance: AutoWiredClient?
|
||||
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
.resolveDependencies { container, _ in
|
||||
if anotherInstance == nil {
|
||||
anotherInstance = try! container.resolve() as AutoWiredClient
|
||||
}
|
||||
}
|
||||
|
||||
//when
|
||||
let resolved = try! container.resolve() as AutoWiredClient
|
||||
|
||||
//then
|
||||
//when doing another auto-wiring during resolve we should reuse instance
|
||||
XCTAssertTrue((resolved as! AutoWiredClientImp) === (anotherInstance as! AutoWiredClientImp))
|
||||
}
|
||||
|
||||
func testThatItReusesInstancesResolvedWithoutAutoWiringWhenUsingAutoWiringAgain() {
|
||||
|
||||
//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 service1 = try! container.resolve() as Service?
|
||||
let service2 = try! container.resolve() as ServiceImp2
|
||||
let resolved = try! container.resolve(withArguments: 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(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
var anotherInstance: AutoWiredClient?
|
||||
|
||||
container.register(tag: "tag", .ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
.resolveDependencies { container, _ in
|
||||
if anotherInstance == nil {
|
||||
anotherInstance = try! container.resolve(tag: "tag") as AutoWiredClient
|
||||
}
|
||||
}
|
||||
|
||||
//when
|
||||
let resolved = try! container.resolve(tag: "tag") as AutoWiredClient
|
||||
|
||||
//then
|
||||
//when doing another auto-wiring during resolve we should reuse instance
|
||||
XCTAssertTrue((resolved as! AutoWiredClientImp) === (anotherInstance as! AutoWiredClientImp))
|
||||
}
|
||||
|
||||
func testThatItDoesNotReuseInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgainWithNoTag() {
|
||||
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
|
||||
var anotherInstance: AutoWiredClient?
|
||||
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
.resolveDependencies { container, _ in
|
||||
if anotherInstance == nil {
|
||||
anotherInstance = try! container.resolve() as AutoWiredClient
|
||||
}
|
||||
}
|
||||
|
||||
//when
|
||||
let resolved = try! container.resolve(tag: "tag") as AutoWiredClient
|
||||
|
||||
//then
|
||||
//when doing another auto-wiring during resolve we should reuse instance
|
||||
XCTAssertTrue((resolved as! AutoWiredClientImp) !== (anotherInstance as! AutoWiredClientImp))
|
||||
}
|
||||
|
||||
func testThatItUsesTagToResolveDependenciesWithAutoWiringWith1Argument() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(tag: "tag", .ObjectGraph) { ServiceImp2() as Service }
|
||||
|
||||
container.register(.ObjectGraph) { (dep1: Service) -> ServiceImp3 in
|
||||
XCTAssertTrue(dep1 is ServiceImp2)
|
||||
return ServiceImp3()
|
||||
}
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve(tag: "tag") as ServiceImp3
|
||||
}
|
||||
|
||||
func testThatItUsesTagToResolveDependenciesWithAutoWiringWith2Arguments() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(tag: "tag", .ObjectGraph) { ServiceImp2() as Service }
|
||||
|
||||
container.register(.ObjectGraph) { (dep1: Service, dep2: Service) -> ServiceImp3 in
|
||||
XCTAssertTrue(dep1 is ServiceImp2)
|
||||
XCTAssertTrue(dep2 is ServiceImp2)
|
||||
return ServiceImp3()
|
||||
}
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve(tag: "tag") as ServiceImp3
|
||||
}
|
||||
|
||||
func testThatItUsesTagToResolveDependenciesWithAutoWiringWith3Arguments() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(tag: "tag", .ObjectGraph) { ServiceImp2() as Service }
|
||||
|
||||
container.register(.ObjectGraph) { (dep1: Service, dep2: Service, dep3: Service) -> ServiceImp3 in
|
||||
XCTAssertTrue(dep1 is ServiceImp2)
|
||||
XCTAssertTrue(dep2 is ServiceImp2)
|
||||
XCTAssertTrue(dep3 is ServiceImp2)
|
||||
return ServiceImp3()
|
||||
}
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve(tag: "tag") as ServiceImp3
|
||||
}
|
||||
|
||||
func testThatItUsesTagToResolveDependenciesWithAutoWiringWith4Arguments() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(tag: "tag", .ObjectGraph) { ServiceImp2() as Service }
|
||||
|
||||
container.register(.ObjectGraph) { (dep1: Service, dep2: Service, dep3: Service, dep4: Service) -> ServiceImp3 in
|
||||
XCTAssertTrue(dep1 is ServiceImp2)
|
||||
XCTAssertTrue(dep2 is ServiceImp2)
|
||||
XCTAssertTrue(dep3 is ServiceImp2)
|
||||
XCTAssertTrue(dep4 is ServiceImp2)
|
||||
return ServiceImp3()
|
||||
}
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve(tag: "tag") as ServiceImp3
|
||||
}
|
||||
|
||||
func testThatItUsesTagToResolveDependenciesWithAutoWiringWith5Arguments() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(tag: "tag", .ObjectGraph) { ServiceImp2() as Service }
|
||||
|
||||
container.register(.ObjectGraph) { (dep1: Service, dep2: Service, dep3: Service, dep4: Service, dep5: Service) -> ServiceImp3 in
|
||||
XCTAssertTrue(dep1 is ServiceImp2)
|
||||
XCTAssertTrue(dep2 is ServiceImp2)
|
||||
XCTAssertTrue(dep3 is ServiceImp2)
|
||||
XCTAssertTrue(dep4 is ServiceImp2)
|
||||
XCTAssertTrue(dep5 is ServiceImp2)
|
||||
return ServiceImp3()
|
||||
}
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve(tag: "tag") as ServiceImp3
|
||||
}
|
||||
|
||||
func testThatItUsesTagToResolveDependenciesWithAutoWiringWith6Arguments() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(tag: "tag", .ObjectGraph) { ServiceImp2() as Service }
|
||||
|
||||
container.register(.ObjectGraph) { (dep1: Service, dep2: Service, dep3: Service, dep4: Service, dep5: Service, dep6: Service) -> ServiceImp3 in
|
||||
XCTAssertTrue(dep1 is ServiceImp2)
|
||||
XCTAssertTrue(dep2 is ServiceImp2)
|
||||
XCTAssertTrue(dep3 is ServiceImp2)
|
||||
XCTAssertTrue(dep4 is ServiceImp2)
|
||||
XCTAssertTrue(dep5 is ServiceImp2)
|
||||
XCTAssertTrue(dep6 is ServiceImp2)
|
||||
return ServiceImp3()
|
||||
}
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve(tag: "tag") as ServiceImp3
|
||||
}
|
||||
|
||||
func testThatItCanAutoWireOptional() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
container.register(.ObjectGraph) { ServiceImp2() }
|
||||
container.register(.ObjectGraph) { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
|
||||
var resolved: AutoWiredClient?
|
||||
//when
|
||||
AssertNoThrow(expression: resolved = try container.resolve() as AutoWiredClient?)
|
||||
XCTAssertNotNil(resolved)
|
||||
|
||||
//when
|
||||
AssertNoThrow(expression: resolved = try container.resolve() as AutoWiredClient!)
|
||||
XCTAssertNotNil(resolved)
|
||||
|
||||
//when
|
||||
AssertNoThrow(expression: resolved = try container.resolve(tag: "tag") as AutoWiredClient?)
|
||||
XCTAssertNotNil(resolved)
|
||||
|
||||
//when
|
||||
AssertNoThrow(expression: resolved = try container.resolve(tag: "tag") as AutoWiredClient!)
|
||||
XCTAssertNotNil(resolved)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,88 +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
|
||||
|
||||
|
||||
func AssertThrows<T>(file: StaticString = #file, line: UInt = #line, @autoclosure expression: () throws -> T) {
|
||||
AssertThrows(file, line: line, expression: expression, "")
|
||||
}
|
||||
|
||||
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: StaticString = #file, line: UInt = #line, @autoclosure expression: () throws -> T, checkError: ErrorType -> Bool) {
|
||||
AssertThrows(file, line: line, expression: expression, checkError: checkError, "")
|
||||
}
|
||||
|
||||
func AssertThrows<T>(file: StaticString = #file, line: UInt = #line, @autoclosure expression: () throws -> T, checkError: ErrorType -> Bool, _ message: String) {
|
||||
do {
|
||||
try expression()
|
||||
XCTFail(message, file: file, line: line)
|
||||
}
|
||||
catch {
|
||||
XCTAssertTrue(checkError(error), "Thrown unexpected error: \(error)", file: file, line: line)
|
||||
}
|
||||
}
|
||||
|
||||
func AssertNoThrow<T>(file: StaticString = #file, line: UInt = #line, @autoclosure expression: () throws -> T) {
|
||||
AssertNoThrow(file, line: line, expression: expression, "")
|
||||
}
|
||||
|
||||
func AssertNoThrow<T>(file: StaticString = #file, line: UInt = #line, @autoclosure expression: () throws -> T, _ message: String) {
|
||||
do {
|
||||
try expression()
|
||||
}
|
||||
catch {
|
||||
XCTFail(message, file: file, line: line)
|
||||
}
|
||||
}
|
||||
|
||||
#if os(Linux)
|
||||
import Glibc
|
||||
typealias TMain = @convention(c) (UnsafeMutablePointer<Void>) -> UnsafeMutablePointer<Void>
|
||||
|
||||
func dispatch_async(block: TMain) {
|
||||
var pid: pthread_t = 0
|
||||
pthread_create(&pid, nil, block, nil)
|
||||
}
|
||||
|
||||
func dispatch_sync(block: TMain) -> UnsafeMutablePointer<Void> {
|
||||
var pid: pthread_t = 0
|
||||
var result: UnsafeMutablePointer<Void> = nil
|
||||
pthread_create(&pid, nil, block, nil)
|
||||
pthread_join(pid, &result)
|
||||
return result
|
||||
}
|
||||
|
||||
extension pthread_spinlock_t {
|
||||
mutating func lock() {
|
||||
pthread_spin_lock(&self)
|
||||
}
|
||||
mutating func unlock() {
|
||||
pthread_spin_unlock(&self)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -26,27 +26,56 @@ import XCTest
|
||||
@testable import Dip
|
||||
|
||||
private protocol Server: class {
|
||||
weak var client: Client? {get}
|
||||
var anotherClient: Client? {get set}
|
||||
var client: Client! {get}
|
||||
var anotherClient: Client! {get set}
|
||||
}
|
||||
|
||||
private protocol Client: class {
|
||||
var server: Server? {get}
|
||||
var anotherServer: Server? {get set}
|
||||
var anotherServer: Server! {get set}
|
||||
}
|
||||
|
||||
#if swift(>=5.1)
|
||||
private class ServerImp: Server {
|
||||
|
||||
@InjectedWeak(didInject: { _ in
|
||||
AutoInjectionTests.clientDidInjectCalled = true
|
||||
}) var client: Client!
|
||||
|
||||
@InjectedWeak(required: false) var _optionalProperty: AnyObject?
|
||||
|
||||
weak var anotherClient: Client!
|
||||
}
|
||||
|
||||
private class ClientImp: Client {
|
||||
|
||||
@Injected(didInject: { _ in
|
||||
AutoInjectionTests.serverDidInjectCalled = true
|
||||
}) var server: Server
|
||||
|
||||
@Injected(required: false) var _optionalProperty: AnyObject?
|
||||
|
||||
@Injected(tag: "tagged") var taggedServer: Server
|
||||
@Injected(tag: nil) var nilTaggedServer: Server
|
||||
|
||||
var anotherServer: Server!
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
private class ServerImp: Server {
|
||||
|
||||
var _client = InjectedWeak<Client>() { _ in
|
||||
AutoInjectionTests.clientDidInjectCalled = true
|
||||
}
|
||||
var client: Client? {
|
||||
|
||||
var client: Client! {
|
||||
return _client.value
|
||||
}
|
||||
|
||||
weak var anotherClient: Client?
|
||||
weak var anotherClient: Client!
|
||||
|
||||
weak var _optionalProperty = InjectedWeak<AnyObject>(required: false)
|
||||
var _optionalProperty = InjectedWeak<AnyObject>(required: false)
|
||||
}
|
||||
|
||||
private class ClientImp: Client {
|
||||
@@ -54,18 +83,30 @@ private class ClientImp: Client {
|
||||
var _server = Injected<Server>() { _ in
|
||||
AutoInjectionTests.serverDidInjectCalled = true
|
||||
}
|
||||
|
||||
var server: Server? {
|
||||
return _server.value
|
||||
}
|
||||
|
||||
var anotherServer: Server?
|
||||
var anotherServer: Server!
|
||||
|
||||
var _optionalProperty = Injected<AnyObject>(required: false)
|
||||
|
||||
var taggedServer = Injected<Server>(tag: "tagged")
|
||||
var nilTaggedServer = Injected<Server>(tag: nil)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if swift(>=5.1)
|
||||
private class Obj1 {
|
||||
@InjectedWeak var obj2: Obj2?
|
||||
@Injected var obj3: Obj3?
|
||||
}
|
||||
|
||||
private class Obj2 {
|
||||
@Injected var obj1: Obj1?
|
||||
}
|
||||
#else
|
||||
private class Obj1 {
|
||||
let obj2 = InjectedWeak<Obj2>()
|
||||
let obj3 = Injected<Obj3>()
|
||||
@@ -74,6 +115,7 @@ private class Obj1 {
|
||||
private class Obj2 {
|
||||
let obj1 = Injected<Obj1>()
|
||||
}
|
||||
#endif
|
||||
|
||||
private class Obj3 {
|
||||
|
||||
@@ -90,70 +132,78 @@ class AutoInjectionTests: XCTestCase {
|
||||
static var clientDidInjectCalled: Bool = false
|
||||
|
||||
let container = DependencyContainer()
|
||||
|
||||
#if os(Linux)
|
||||
static var allTests: [(String, AutoInjectionTests -> () throws -> Void)] {
|
||||
return [
|
||||
("testThatItResolvesAutoInjectedDependencies", testThatItResolvesAutoInjectedDependencies),
|
||||
("testThatItCanSetInjectedProperty", testThatItCanSetInjectedProperty),
|
||||
("testThatItThrowsErrorIfFailsToAutoInjectDependency", testThatItThrowsErrorIfFailsToAutoInjectDependency),
|
||||
("testThatItResolvesAutoInjectedSingletons", testThatItResolvesAutoInjectedSingletons),
|
||||
("testThatItCallsResolveDependencyBlockWhenAutoInjecting", testThatItCallsResolveDependencyBlockWhenAutoInjecting),
|
||||
("testThatItReusesResolvedAutoInjectedInstances", testThatItReusesResolvedAutoInjectedInstances),
|
||||
("testThatItReusesAutoInjectedInstancesOnNextResolveOrAutoInjection", testThatItReusesAutoInjectedInstancesOnNextResolveOrAutoInjection),
|
||||
("testThatThereIsNoRetainCycleBetweenAutoInjectedCircularDependencies", testThatThereIsNoRetainCycleBetweenAutoInjectedCircularDependencies),
|
||||
("testThatItCallsDidInjectOnAutoInjectedProperty", testThatItCallsDidInjectOnAutoInjectedProperty),
|
||||
("testThatNoErrorThrownWhenOptionalPropertiesAreNotAutoInjected", testThatNoErrorThrownWhenOptionalPropertiesAreNotAutoInjected),
|
||||
("testThatItResolvesTaggedAutoInjectedProperties", testThatItResolvesTaggedAutoInjectedProperties),
|
||||
("testThatItPassesTagToAutoInjectedProperty", testThatItPassesTagToAutoInjectedProperty),
|
||||
("testThatItDoesNotPassTagToAutoInjectedPropertyWithExplicitTag", testThatItDoesNotPassTagToAutoInjectedPropertyWithExplicitTag)
|
||||
]
|
||||
}
|
||||
|
||||
func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#else
|
||||
override func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#endif
|
||||
|
||||
func testThatItResolvesAutoInjectedDependencies() {
|
||||
container.register(.ObjectGraph) { ServerImp() as Server }
|
||||
container.register(.ObjectGraph) { ClientImp() as Client }
|
||||
container.register { ServerImp() as Server }
|
||||
container.register { ClientImp() as Client }
|
||||
|
||||
let client = try! container.resolve() as Client
|
||||
let server = client.server
|
||||
XCTAssertTrue(client === server?.client)
|
||||
}
|
||||
|
||||
func testThatItResolvesInheritedDependencies() {
|
||||
class ServerImp2: ServerImp {
|
||||
#if swift(>=5.1)
|
||||
@InjectedWeak(didInject: { _ in
|
||||
XCTAssertTrue(AutoInjectionTests.serverDidInjectCalled, "Inherited properties should be resolved first")
|
||||
}) var client2: Client?
|
||||
#else
|
||||
var _client2 = InjectedWeak<Client>() { _ in
|
||||
XCTAssertTrue(AutoInjectionTests.serverDidInjectCalled, "Inherited properties should be resolved first")
|
||||
}
|
||||
var client2: Client? {
|
||||
return _client2.value
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
container.register { ServerImp2() as Server }
|
||||
container.register { ClientImp() as Client }
|
||||
|
||||
//when
|
||||
let client = try! container.resolve() as Client
|
||||
let server = client.server as? ServerImp2
|
||||
XCTAssertTrue(client === server?.client)
|
||||
XCTAssertTrue(client === server?.client2)
|
||||
}
|
||||
|
||||
func testThatItCanSetInjectedProperty() {
|
||||
container.register(.ObjectGraph) { ServerImp() as Server }
|
||||
container.register(.ObjectGraph) { ClientImp() as Client }
|
||||
container.register { ServerImp() as Server }
|
||||
container.register { ClientImp() as Client }
|
||||
|
||||
let client = (try! container.resolve() as Client) as! ClientImp
|
||||
let server = client.server as! ServerImp
|
||||
|
||||
let newServer = ServerImp()
|
||||
let newClient = ClientImp()
|
||||
#if swift(>=5.1)
|
||||
client.server = newServer
|
||||
server.client = newClient
|
||||
#else
|
||||
client._server = client._server.setValue(newServer)
|
||||
server._client = server._client.setValue(newClient)
|
||||
#endif
|
||||
|
||||
XCTAssertTrue(client.server === newServer)
|
||||
XCTAssertTrue(server.client === newClient)
|
||||
}
|
||||
|
||||
func testThatItThrowsErrorIfFailsToAutoInjectDependency() {
|
||||
container.register(.ObjectGraph) { ClientImp() as Client }
|
||||
|
||||
AssertThrows(expression: try container.resolve() as Client)
|
||||
|
||||
func testThatItThrowsErrorIfFailsToAutoInjectDependency() {
|
||||
container.register { ClientImp() as Client }
|
||||
|
||||
XCTAssertThrowsError(try self.container.resolve() as Client)
|
||||
}
|
||||
|
||||
func testThatItResolvesAutoInjectedSingletons() {
|
||||
//given
|
||||
container.register(.Singleton) { ServerImp() as Server }
|
||||
container.register(.Singleton) { ClientImp() as Client }
|
||||
container.register(.singleton) { ServerImp() as Server }
|
||||
container.register(.singleton) { ClientImp() as Client }
|
||||
|
||||
//when
|
||||
let sharedClient = try! container.resolve() as Client
|
||||
@@ -172,34 +222,34 @@ class AutoInjectionTests: XCTestCase {
|
||||
var serverBlockWasCalled = false
|
||||
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServerImp() as Server }
|
||||
.resolveDependencies { (container, server) -> () in
|
||||
container.register { ServerImp() as Server }
|
||||
.resolvingProperties { (container, server) -> () in
|
||||
serverBlockWasCalled = true
|
||||
}
|
||||
|
||||
var clientBlockWasCalled = false
|
||||
container.register(.ObjectGraph) { ClientImp() as Client }
|
||||
.resolveDependencies { (container, client) -> () in
|
||||
container.register { ClientImp() as Client }
|
||||
.resolvingProperties { (container, client) -> () in
|
||||
clientBlockWasCalled = true
|
||||
}
|
||||
|
||||
//when
|
||||
try! container.resolve() as Client
|
||||
let _ = try! container.resolve() as Client
|
||||
XCTAssertTrue(serverBlockWasCalled)
|
||||
|
||||
try! container.resolve() as Server
|
||||
let _ = try! container.resolve() as Server
|
||||
XCTAssertTrue(clientBlockWasCalled)
|
||||
}
|
||||
|
||||
func testThatItReusesResolvedAutoInjectedInstances() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServerImp() as Server }
|
||||
.resolveDependencies { (container, server) -> () in
|
||||
container.register { 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 { ClientImp() as Client }
|
||||
.resolvingProperties { (container, client) -> () in
|
||||
client.anotherServer = try! container.resolve() as Server
|
||||
}
|
||||
|
||||
@@ -221,25 +271,33 @@ 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 { Obj1() }
|
||||
container.register { Obj2() }
|
||||
container.register { Obj3(obj: try self.container.resolve()) }
|
||||
|
||||
//when
|
||||
let obj2 = try! container.resolve() as Obj2
|
||||
|
||||
//then
|
||||
XCTAssertTrue(obj2 === obj2.obj1.value!.obj2.value!,
|
||||
#if swift(>=5.1)
|
||||
XCTAssertTrue(obj2 === obj2.obj1!.obj2!,
|
||||
"Auto-injected instance should be reused on next auto-injection")
|
||||
|
||||
XCTAssertTrue(obj2.obj1.value! === obj2.obj1.value!.obj3.value!.obj1,
|
||||
XCTAssertTrue(obj2.obj1! === obj2.obj1!.obj3!.obj1,
|
||||
"Auto-injected instance should be reused on next resolve")
|
||||
#else
|
||||
XCTAssertTrue(obj2 === obj2.obj1.value!.obj2.value!,
|
||||
"Auto-injected instance should be reused on next auto-injection")
|
||||
|
||||
XCTAssertTrue(obj2.obj1.value! === obj2.obj1.value!.obj3.value!.obj1,
|
||||
"Auto-injected instance should be reused on next resolve")
|
||||
#endif
|
||||
}
|
||||
|
||||
func testThatThereIsNoRetainCycleBetweenAutoInjectedCircularDependencies() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServerImp() as Server }
|
||||
container.register(.ObjectGraph) { ClientImp() as Client }
|
||||
container.register { ServerImp() as Server }
|
||||
container.register { ClientImp() as Client }
|
||||
|
||||
//when
|
||||
var client: Client? = try! container.resolve() as Client
|
||||
@@ -262,11 +320,11 @@ class AutoInjectionTests: XCTestCase {
|
||||
AutoInjectionTests.serverDidInjectCalled = false
|
||||
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServerImp() as Server }
|
||||
container.register(.ObjectGraph) { ClientImp() as Client }
|
||||
container.register { ServerImp() as Server }
|
||||
container.register { ClientImp() as Client }
|
||||
|
||||
//when
|
||||
try! container.resolve() as Client
|
||||
let _ = try! container.resolve() as Client
|
||||
|
||||
//then
|
||||
XCTAssertTrue(AutoInjectionTests.clientDidInjectCalled)
|
||||
@@ -275,23 +333,30 @@ class AutoInjectionTests: XCTestCase {
|
||||
|
||||
func testThatNoErrorThrownWhenOptionalPropertiesAreNotAutoInjected() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServerImp() as Server }
|
||||
container.register(.ObjectGraph) { ClientImp() as Client }
|
||||
container.register { ServerImp() as Server }
|
||||
container.register { ClientImp() as Client }
|
||||
|
||||
AssertNoThrow(expression: try container.resolve() as Client, "Container should not throw error if failed to resolve optional auto-injected properties.")
|
||||
XCTAssertNoThrow(
|
||||
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 { ServerImp() as Server }
|
||||
container.register(tag: "tagged") { ServerImp() as Server }
|
||||
container.register { ClientImp() as Client }
|
||||
|
||||
//when
|
||||
let client = try! container.resolve() as Client
|
||||
|
||||
//then
|
||||
#if swift(>=5.1)
|
||||
let taggedServer = (client as! ClientImp).taggedServer!
|
||||
#else
|
||||
let taggedServer = (client as! ClientImp).taggedServer.value!
|
||||
#endif
|
||||
let server = client.server!
|
||||
|
||||
//server and tagged server should be resolved as different instances
|
||||
@@ -302,15 +367,19 @@ class AutoInjectionTests: XCTestCase {
|
||||
|
||||
func testThatItPassesTagToAutoInjectedProperty() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServerImp() as Server }
|
||||
container.register(tag: "tagged", .ObjectGraph) { ServerImp() as Server }
|
||||
container.register(.ObjectGraph) { ClientImp() as Client }
|
||||
container.register { ServerImp() as Server }
|
||||
container.register(tag: "tagged") { ServerImp() as Server }
|
||||
container.register { ClientImp() as Client }
|
||||
|
||||
//when
|
||||
let client = try! container.resolve(tag: "tagged") as Client
|
||||
|
||||
//then
|
||||
#if swift(>=5.1)
|
||||
let taggedServer = (client as! ClientImp).taggedServer!
|
||||
#else
|
||||
let taggedServer = (client as! ClientImp).taggedServer.value!
|
||||
#endif
|
||||
let server = client.server!
|
||||
|
||||
//server and tagged server should be resolved as the same instance
|
||||
@@ -319,11 +388,11 @@ class AutoInjectionTests: XCTestCase {
|
||||
|
||||
func testThatItDoesNotPassTagToAutoInjectedPropertyWithExplicitTag() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { ServerImp() as Server }
|
||||
container.register(tag: "tagged", .ObjectGraph) { ServerImp() as Server }
|
||||
container.register { ServerImp() as Server }
|
||||
container.register(tag: "tagged") { ServerImp() as Server }
|
||||
|
||||
container.register(.ObjectGraph) { ClientImp() as Client }
|
||||
.resolveDependencies { (container, client) -> () in
|
||||
container.register { ClientImp() as Client }
|
||||
.resolvingProperties { (container, client) -> () in
|
||||
client.anotherServer = try! container.resolve() as Server
|
||||
}
|
||||
|
||||
@@ -331,8 +400,13 @@ class AutoInjectionTests: XCTestCase {
|
||||
let client = try! container.resolve(tag: "otherTag") as Client
|
||||
|
||||
//then
|
||||
#if swift(>=5.1)
|
||||
let taggedServer = (client as! ClientImp).taggedServer!
|
||||
let nilTaggedServer = (client as! ClientImp).nilTaggedServer!
|
||||
#else
|
||||
let taggedServer = (client as! ClientImp).taggedServer.value!
|
||||
let nilTaggedServer = (client as! ClientImp).nilTaggedServer.value!
|
||||
#endif
|
||||
let server = client.server!
|
||||
|
||||
//server and tagged server should be resolved as different instances
|
||||
@@ -343,19 +417,89 @@ class AutoInjectionTests: XCTestCase {
|
||||
XCTAssertNotNil(taggedServer)
|
||||
XCTAssertNotNil(nilTaggedServer)
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct Foo
|
||||
{
|
||||
struct Bar
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
struct Baz
|
||||
{
|
||||
struct Bar
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func testScopedTypes() {
|
||||
let key1 = DefinitionKey(type: Baz.Bar.self, typeOfArguments: Void.self)
|
||||
let key2 = DefinitionKey(type: Foo.Bar.self, typeOfArguments: Void.self)
|
||||
|
||||
XCTAssertNotEqual(key1, key2)
|
||||
XCTAssertNotEqual(key1.hashValue, key2.hashValue)
|
||||
|
||||
container.register { Baz.Bar() }
|
||||
|
||||
XCTAssertNotNil(try? container.resolve() as Baz.Bar)
|
||||
XCTAssertThrowsError(try container.resolve() as Foo.Bar)
|
||||
|
||||
container.register { Foo.Bar() }
|
||||
|
||||
XCTAssertNotNil(try? container.resolve() as Foo.Bar)
|
||||
}
|
||||
|
||||
func testThatItAutoInjectsPropertyWithCollaboratingContainer() {
|
||||
let collaborator = DependencyContainer()
|
||||
collaborator.register(.ObjectGraph) { ServerImp() as Server }
|
||||
container.register(.ObjectGraph) { ClientImp() as Client }
|
||||
|
||||
collaborator.register { ServerImp() as Server }
|
||||
container.register { 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)
|
||||
}
|
||||
|
||||
|
||||
func testThatItDoesNotAutoInjectIfDisabledInDefinition() {
|
||||
container.register { ServerImp() as Server }
|
||||
container.register { ClientImp() as Client }
|
||||
.autoInjectingProperties(false)
|
||||
|
||||
let client = try! container.resolve() as Client
|
||||
let server = client.server
|
||||
|
||||
XCTAssertNil(server)
|
||||
}
|
||||
|
||||
func testThatItDoesNotAutoInjectIfDisabledInContainer() {
|
||||
let container = DependencyContainer(autoInjectProperties: false)
|
||||
container.register { ServerImp() as Server }
|
||||
container.register { ClientImp() as Client }
|
||||
|
||||
let client = try! container.resolve() as Client
|
||||
let server = client.server
|
||||
|
||||
XCTAssertNil(server)
|
||||
}
|
||||
|
||||
func testThatItAutoInjectsWhenOverridenInDefinition() {
|
||||
let container = DependencyContainer(autoInjectProperties: false)
|
||||
container.register { ServerImp() as Server }
|
||||
container.register { ClientImp() as Client }
|
||||
.autoInjectingProperties(true)
|
||||
|
||||
let client = try! container.resolve() as Client
|
||||
let server = client.server
|
||||
|
||||
XCTAssertNotNil(server)
|
||||
XCTAssertNil(server?.client)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
override func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
|
||||
func testThatItCanResolveWithAutoWiring() {
|
||||
//given
|
||||
container.register { ServiceImp1() as Service }
|
||||
container.register { ServiceImp2() }
|
||||
|
||||
container.register { 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 { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
|
||||
//1 arg
|
||||
container.register { AutoWiredClientImp(service1: try self.container.resolve(), service2: $0) as AutoWiredClient }
|
||||
|
||||
//2 args
|
||||
var factoryWithMostNumberOfArgumentsCalled = false
|
||||
container.register { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
.resolvingProperties { _,_ in
|
||||
factoryWithMostNumberOfArgumentsCalled = true
|
||||
}
|
||||
|
||||
container.register { ServiceImp1() as Service }
|
||||
container.register { ServiceImp2() }
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve() as AutoWiredClient
|
||||
|
||||
//then
|
||||
XCTAssertTrue(factoryWithMostNumberOfArgumentsCalled)
|
||||
}
|
||||
|
||||
func testThatItThrowsAmbiguityErrorWhenUsingAutoWire() {
|
||||
//given
|
||||
|
||||
//1 arg
|
||||
container.register { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
|
||||
//1 arg
|
||||
container.register { AutoWiredClientImp(service1: try self.container.resolve(), service2: $0) as AutoWiredClient }
|
||||
|
||||
container.register { ServiceImp1() as Service }
|
||||
container.register { ServiceImp2() }
|
||||
|
||||
//when
|
||||
XCTAssertThrowsError(try self.container.resolve() as AutoWiredClient) { (error) in
|
||||
guard case DipError.autoWiringFailed(_, DipError.ambiguousDefinitions) = error else {
|
||||
XCTFail("Thrown unexpected error: \(error)")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testThatItPrefersTaggedFactoryWithDifferentNumberOfArgumentsWhenUsingAutoWire() {
|
||||
//given
|
||||
|
||||
//1 arg
|
||||
container.register { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
|
||||
//1 arg
|
||||
container.register { AutoWiredClientImp(service1: try self.container.resolve(), service2: $0) as AutoWiredClient }
|
||||
|
||||
//2 args
|
||||
container.register { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
|
||||
//1 arg tagged
|
||||
var taggedFactoryWithMostNumberOfArgumentsCalled = false
|
||||
container.register(tag: "tag") { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
|
||||
|
||||
//2 arg tagged
|
||||
container.register(tag: "tag") { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }.resolvingProperties { _,_ in
|
||||
taggedFactoryWithMostNumberOfArgumentsCalled = true
|
||||
}
|
||||
|
||||
container.register() { ServiceImp1() as Service }
|
||||
container.register { ServiceImp2() }
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve(tag: "tag") as AutoWiredClient
|
||||
|
||||
//then
|
||||
XCTAssertTrue(taggedFactoryWithMostNumberOfArgumentsCalled)
|
||||
}
|
||||
|
||||
func testThatItPrefersTaggedFactoryWithDifferentTypesOfArgumentsWhenUsingAutoWire() {
|
||||
//given
|
||||
|
||||
//1 arg
|
||||
container.register { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
|
||||
|
||||
//2 args
|
||||
container.register { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
|
||||
//1 arg tagged
|
||||
var taggedFactoryCalled = false
|
||||
container.register(tag: "tag") { AutoWiredClientImp(service1: try self.container.resolve(), service2: $0) as AutoWiredClient }.resolvingProperties { _,_ in
|
||||
taggedFactoryCalled = true
|
||||
}
|
||||
|
||||
container.register() { ServiceImp1() as Service }
|
||||
container.register { ServiceImp2() }
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve(tag: "tag") as AutoWiredClient
|
||||
|
||||
//then
|
||||
XCTAssertTrue(taggedFactoryCalled)
|
||||
}
|
||||
|
||||
func testThatItFallbackToNotTaggedFactoryWhenUsingAutoWire() {
|
||||
//given
|
||||
|
||||
//1 arg
|
||||
var notTaggedFactoryWithMostNumberOfArgumentsCalled = false
|
||||
container.register { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }.resolvingProperties { _,_ in
|
||||
notTaggedFactoryWithMostNumberOfArgumentsCalled = true
|
||||
}
|
||||
|
||||
//1 arg tagged
|
||||
container.register(tag: "tag") { AutoWiredClientImp(service1: $0, service2: try self.container.resolve()) as AutoWiredClient }
|
||||
|
||||
container.register { ServiceImp1() as Service }
|
||||
container.register { ServiceImp2() }
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve(tag: "other tag") as AutoWiredClient
|
||||
|
||||
//then
|
||||
XCTAssertTrue(notTaggedFactoryWithMostNumberOfArgumentsCalled)
|
||||
}
|
||||
|
||||
func testThatItDoesNotTryToUseAutoWiringWhenCallingResolveWithArguments() {
|
||||
//given
|
||||
container.register { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
container.register { ServiceImp1() as Service }
|
||||
container.register { ServiceImp2() }
|
||||
|
||||
//when
|
||||
let service = try! container.resolve() as Service
|
||||
|
||||
// then
|
||||
XCTAssertThrowsError(
|
||||
try self.container.resolve(arguments: service) as AutoWiredClient,
|
||||
"Container should not use auto-wiring when resolving with runtime arguments"
|
||||
)
|
||||
}
|
||||
|
||||
func testThatItDoesNotUseAutoWiringWhenFailedToResolveLowLevelDependency() {
|
||||
//given
|
||||
container.register { 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 { 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 { ServiceImp1() as Service }
|
||||
container.register { ServiceImp2() }
|
||||
|
||||
//then
|
||||
XCTAssertThrowsError(
|
||||
try self.container.resolve() as AutoWiredClient,
|
||||
"Container should not use auto-wiring when definition for resolved type is registered."
|
||||
)
|
||||
}
|
||||
|
||||
func testThatItReusesInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgain() {
|
||||
//given
|
||||
container.register { ServiceImp1() as Service }
|
||||
container.register { ServiceImp2() }
|
||||
|
||||
var anotherInstance: AutoWiredClient?
|
||||
|
||||
container.register { 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
|
||||
XCTAssertTrue(
|
||||
(resolved as! AutoWiredClientImp) === (anotherInstance as! AutoWiredClientImp),
|
||||
"when doing another auto-wiring during resolve we should reuse instance"
|
||||
)
|
||||
}
|
||||
|
||||
func testThatItReusesInstancesResolvedWithoutAutoWiringWhenUsingAutoWiringAgain() {
|
||||
|
||||
//given
|
||||
container.register { ServiceImp1() as Service }
|
||||
container.register { ServiceImp2() }
|
||||
|
||||
var anotherInstance: AutoWiredClient?
|
||||
|
||||
container.register { 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
|
||||
XCTAssertTrue(
|
||||
(resolved as! AutoWiredClientImp) === (anotherInstance as! AutoWiredClientImp),
|
||||
"when doing another auto-wiring during resolve we should reuse instance"
|
||||
)
|
||||
}
|
||||
|
||||
func testThatItReusesInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgainWithTheSameTag() {
|
||||
|
||||
//given
|
||||
container.register { ServiceImp1() as Service }
|
||||
container.register { ServiceImp2() }
|
||||
|
||||
var anotherInstance: AutoWiredClient?
|
||||
|
||||
container.register(tag: "tag") { 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
|
||||
XCTAssertTrue(
|
||||
(resolved as! AutoWiredClientImp) === (anotherInstance as! AutoWiredClientImp),
|
||||
"when doing another auto-wiring during resolve we should reuse instance"
|
||||
)
|
||||
}
|
||||
|
||||
func testThatItDoesNotReuseInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgainWithAnotherTag() {
|
||||
|
||||
//given
|
||||
container.register { ServiceImp1() as Service }
|
||||
container.register { ServiceImp2() }
|
||||
|
||||
var anotherInstance: AutoWiredClient?
|
||||
|
||||
container.register { 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
|
||||
XCTAssertTrue(
|
||||
(resolved as! AutoWiredClientImp) !== (anotherInstance as! AutoWiredClientImp),
|
||||
"when doing another auto-wiring during resolve we should reuse instance"
|
||||
)
|
||||
}
|
||||
|
||||
func testThatItUsesTagToResolveDependenciesWithAutoWiringWith1Argument() {
|
||||
//given
|
||||
container.register { ServiceImp1() as Service }
|
||||
container.register(tag: "tag") { ServiceImp2() as Service }
|
||||
|
||||
container.register { (dep1: Service) -> ServiceImp3 in
|
||||
XCTAssertTrue(dep1 is ServiceImp2)
|
||||
return ServiceImp3()
|
||||
}
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve(tag: "tag") as ServiceImp3
|
||||
}
|
||||
|
||||
func testThatItUsesTagToResolveDependenciesWithAutoWiringWith2Arguments() {
|
||||
//given
|
||||
container.register { ServiceImp1() as Service }
|
||||
container.register(tag: "tag") { ServiceImp2() as Service }
|
||||
|
||||
container.register { (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 { ServiceImp1() as Service }
|
||||
container.register(tag: "tag") { ServiceImp2() as Service }
|
||||
|
||||
container.register { (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 { ServiceImp1() as Service }
|
||||
container.register(tag: "tag") { ServiceImp2() as Service }
|
||||
|
||||
container.register { (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 { ServiceImp1() as Service }
|
||||
container.register(tag: "tag") { ServiceImp2() as Service }
|
||||
|
||||
container.register { (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 { ServiceImp1() as Service }
|
||||
container.register(tag: "tag") { ServiceImp2() as Service }
|
||||
|
||||
container.register { (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 { ServiceImp1() as Service }
|
||||
container.register { ServiceImp2() }
|
||||
container.register { AutoWiredClientImp(service1: $0, service2: $1) as AutoWiredClient }
|
||||
|
||||
var resolved: AutoWiredClient?
|
||||
//when
|
||||
XCTAssertNoThrow(resolved = try self.container.resolve() as AutoWiredClient?)
|
||||
XCTAssertNotNil(resolved)
|
||||
|
||||
//when
|
||||
XCTAssertNoThrow(resolved = try self.container.resolve(tag: "tag") as AutoWiredClient?)
|
||||
XCTAssertNotNil(resolved)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -47,47 +47,21 @@ class ComponentScopeTests: XCTestCase {
|
||||
|
||||
let container = DependencyContainer()
|
||||
|
||||
#if os(Linux)
|
||||
static var allTests: [(String, ComponentScopeTests -> () throws -> Void)] {
|
||||
return [
|
||||
("testThatPrototypeIsDefaultScope", testThatPrototypeIsDefaultScope),
|
||||
("testThatScopeCanBeChanged", testThatScopeCanBeChanged),
|
||||
("testThatItResolvesTypeAsNewInstanceForPrototypeScope", testThatItResolvesTypeAsNewInstanceForPrototypeScope),
|
||||
("testThatItReusesInstanceForSingletonScope", testThatItReusesInstanceForSingletonScope),
|
||||
("testThatSingletonIsNotReusedAcrossContainers", testThatSingletonIsNotReusedAcrossContainers),
|
||||
("testThatSingletonIsReleasedWhenDefinitionIsRemoved", testThatSingletonIsReleasedWhenDefinitionIsRemoved),
|
||||
("testThatSingletonIsReleasedWhenDefinitionIsOverridden", testThatSingletonIsReleasedWhenDefinitionIsOverridden),
|
||||
("testThatSingletonIsReleasedWhenContainerIsReset", testThatSingletonIsReleasedWhenContainerIsReset),
|
||||
("testThatItReusesInstanceInObjectGraphScopeDuringResolve", testThatItReusesInstanceInObjectGraphScopeDuringResolve),
|
||||
("testThatItDoesNotReuseInstanceInObjectGraphScopeInNextResolve", testThatItDoesNotReuseInstanceInObjectGraphScopeInNextResolve),
|
||||
("testThatItDoesNotReuseInstanceInObjectGraphScopeResolvedForNilTag", testThatItDoesNotReuseInstanceInObjectGraphScopeResolvedForNilTagWhenResolvingForAnotherTag),
|
||||
("testThatItReusesInstanceInObjectGraphScopeResolvedForNilTag", testThatItReusesInstanceInObjectGraphScopeResolvedForNilTag),
|
||||
("testThatItReusesResolvedInstanceWhenResolvingOptional", testThatItReusesResolvedInstanceWhenResolvingOptional),
|
||||
("testThatItHoldsWeakReferenceToWeakSingletonInstance",
|
||||
testThatItHoldsWeakReferenceToWeakSingletonInstance)
|
||||
]
|
||||
}
|
||||
|
||||
func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#else
|
||||
override func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#endif
|
||||
|
||||
func testThatPrototypeIsDefaultScope() {
|
||||
func testThatSharedIsDefaultScope() {
|
||||
let def = container.register { ServiceImp1() as Service }
|
||||
XCTAssertEqual(def.scope, ComponentScope.Prototype)
|
||||
XCTAssertEqual(def.scope, ComponentScope.shared)
|
||||
}
|
||||
|
||||
func testThatScopeCanBeChanged() {
|
||||
let def = container.register(.Singleton) { ServiceImp1() as Service }
|
||||
XCTAssertEqual(def.scope, ComponentScope.Singleton)
|
||||
let def = container.register(.singleton) { ServiceImp1() as Service }
|
||||
XCTAssertEqual(def.scope, ComponentScope.singleton)
|
||||
}
|
||||
|
||||
func testThatItResolvesTypeAsNewInstanceForPrototypeScope() {
|
||||
func testThatItResolvesTypeAsNewInstanceForUniqueScope() {
|
||||
//given
|
||||
container.register { ServiceImp1() as Service }
|
||||
|
||||
@@ -100,7 +74,7 @@ class ComponentScopeTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testThatItReusesInstanceForSingletonScope() {
|
||||
func test(scope: ComponentScope) {
|
||||
func test(_ scope: ComponentScope, line: UInt = #line) {
|
||||
//given
|
||||
container.register(scope) { ServiceImp1() as Service }
|
||||
|
||||
@@ -109,94 +83,99 @@ class ComponentScopeTests: XCTestCase {
|
||||
let service2 = try! container.resolve() as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service1 === service2)
|
||||
XCTAssertTrue(service1 === service2, line: line)
|
||||
}
|
||||
|
||||
test(.Singleton)
|
||||
test(.EagerSingleton)
|
||||
test(.singleton)
|
||||
test(.weakSingleton)
|
||||
test(.eagerSingleton)
|
||||
}
|
||||
|
||||
func testThatSingletonIsNotReusedAcrossContainers() {
|
||||
func test(scope: ComponentScope) {
|
||||
func test(_ scope: ComponentScope, line: UInt = #line) {
|
||||
//given
|
||||
let def = container.register(.Singleton) { ServiceImp1() as Service }
|
||||
let def = container.register(scope) { ServiceImp1() as Service }
|
||||
let secondContainer = DependencyContainer()
|
||||
secondContainer.register(def, forTag: nil)
|
||||
secondContainer.register(def, tag: 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")
|
||||
XCTAssertTrue(service1 !== service2, "Singleton instances should not be reused across containers", line: line)
|
||||
}
|
||||
|
||||
test(.Singleton)
|
||||
test(.EagerSingleton)
|
||||
test(.singleton)
|
||||
test(.weakSingleton)
|
||||
test(.eagerSingleton)
|
||||
}
|
||||
|
||||
func testThatSingletonIsReleasedWhenDefinitionIsRemoved() {
|
||||
func test(scope: ComponentScope) {
|
||||
func test(_ scope: ComponentScope, line: UInt = #line) {
|
||||
//given
|
||||
let def = container.register(.Singleton) { ServiceImp1() as Service }
|
||||
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)
|
||||
container.remove(def, tag: nil)
|
||||
container.register(def, tag: nil)
|
||||
|
||||
//then
|
||||
let service2 = try! container.resolve() as Service
|
||||
XCTAssertTrue(service1 !== service2, "Singleton instances should be released when definition is removed from the container")
|
||||
XCTAssertTrue(service1 !== service2, "Singleton instances should be released when definition is removed from the container", line: line)
|
||||
}
|
||||
|
||||
test(.Singleton)
|
||||
test(.EagerSingleton)
|
||||
test(.singleton)
|
||||
test(.weakSingleton)
|
||||
test(.eagerSingleton)
|
||||
}
|
||||
|
||||
func testThatSingletonIsReleasedWhenDefinitionIsOverridden() {
|
||||
func test(scope: ComponentScope) {
|
||||
func test(_ scope: ComponentScope, line: UInt = #line) {
|
||||
//given
|
||||
let def = container.register(.Singleton) { ServiceImp1() as Service }
|
||||
let def = container.register(scope) { ServiceImp1() as Service }
|
||||
let service1 = try! container.resolve() as Service
|
||||
|
||||
//when
|
||||
container.register(def, forTag: nil)
|
||||
container.register(def, tag: nil)
|
||||
|
||||
//then
|
||||
let service2 = try! container.resolve() as Service
|
||||
XCTAssertTrue(service1 !== service2, "Singleton instances should be released when definition is overridden")
|
||||
XCTAssertTrue(service1 !== service2, "Singleton instances should be released when definition is overridden", line: line)
|
||||
}
|
||||
|
||||
test(.Singleton)
|
||||
test(.EagerSingleton)
|
||||
test(.singleton)
|
||||
test(.weakSingleton)
|
||||
test(.eagerSingleton)
|
||||
}
|
||||
|
||||
func testThatSingletonIsReleasedWhenContainerIsReset() {
|
||||
func test(scope: ComponentScope) {
|
||||
func test(_ scope: ComponentScope, line: UInt = #line) {
|
||||
//given
|
||||
let def = container.register(.Singleton) { ServiceImp1() as Service }
|
||||
let def = container.register(scope) { ServiceImp1() as Service }
|
||||
let service1 = try! container.resolve() as Service
|
||||
|
||||
//when
|
||||
container.reset()
|
||||
container.register(def, forTag: nil)
|
||||
container.register(def, tag: nil)
|
||||
|
||||
//then
|
||||
let service2 = try! container.resolve() as Service
|
||||
XCTAssertTrue(service1 !== service2, "Singleton instances should be released when container is reset")
|
||||
XCTAssertTrue(service1 !== service2, "Singleton instances should be released when container is reset", line: line)
|
||||
}
|
||||
|
||||
test(.Singleton)
|
||||
test(.EagerSingleton)
|
||||
test(.singleton)
|
||||
test(.weakSingleton)
|
||||
test(.eagerSingleton)
|
||||
}
|
||||
|
||||
func testThatItReusesInstanceInObjectGraphScopeDuringResolve() {
|
||||
func testThatItReusesInstanceInSharedScopeDuringResolve() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { Client(server: try self.container.resolve()) as Client }
|
||||
container.register { Client(server: try self.container.resolve()) as Client }
|
||||
|
||||
container.register(.ObjectGraph) { Server() as Server }
|
||||
.resolveDependencies { container, server in
|
||||
container.register { Server() as Server }
|
||||
.resolvingProperties { container, server in
|
||||
server.client = try container.resolve() as Client
|
||||
}
|
||||
|
||||
@@ -208,11 +187,11 @@ class ComponentScopeTests: XCTestCase {
|
||||
XCTAssertTrue(server.client === client)
|
||||
}
|
||||
|
||||
func testThatItDoesNotReuseInstanceInObjectGraphScopeInNextResolve() {
|
||||
func testThatItDoesNotReuseInstanceInSharedScopeInNextResolve() {
|
||||
//given
|
||||
container.register(.ObjectGraph) { Client(server: try self.container.resolve()) as Client }
|
||||
container.register(.ObjectGraph) { Server() as Server }
|
||||
.resolveDependencies { container, server in
|
||||
container.register { Client(server: try self.container.resolve()) as Client }
|
||||
container.register { Server() as Server }
|
||||
.resolvingProperties { container, server in
|
||||
server.client = try container.resolve() as Client
|
||||
}
|
||||
|
||||
@@ -228,18 +207,18 @@ class ComponentScopeTests: XCTestCase {
|
||||
XCTAssertFalse(client === anotherClient)
|
||||
}
|
||||
|
||||
func testThatItDoesNotReuseInstanceInObjectGraphScopeResolvedForNilTagWhenResolvingForAnotherTag() {
|
||||
func testThatItDoesNotReuseInstanceInSharedScopeResolvedForNilTagWhenResolvingForAnotherTag() {
|
||||
//given
|
||||
var service2: Service?
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
.resolveDependencies { (c, _) in
|
||||
container.register { 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", .ObjectGraph) { ServiceImp2() as Service}
|
||||
container.register(tag: "service") { ServiceImp2() as Service}
|
||||
|
||||
//when
|
||||
let service1 = try! container.resolve(tag: "tag") as Service
|
||||
@@ -248,11 +227,11 @@ class ComponentScopeTests: XCTestCase {
|
||||
XCTAssertTrue(service1 is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItReusesInstanceInObjectGraphScopeResolvedForNilTag() {
|
||||
func testThatItReusesInstanceInSharedScopeResolvedForNilTag() {
|
||||
//given
|
||||
var service2: Service?
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
.resolveDependencies { (c, service1) in
|
||||
container.register { 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
|
||||
@@ -274,17 +253,17 @@ class ComponentScopeTests: XCTestCase {
|
||||
//given
|
||||
var eagerSingletonResolved = false
|
||||
|
||||
container.register(tag: "eager", .EagerSingleton) { ServiceImp1() as Service }
|
||||
.resolveDependencies { container, service in eagerSingletonResolved = true }
|
||||
container.register(.eagerSingleton, tag: "eager") { ServiceImp1() as Service }
|
||||
.resolvingProperties { container, service in eagerSingletonResolved = true }
|
||||
|
||||
container.register(tag: "singleton", .Singleton) { ServiceImp1() as Service }
|
||||
.resolveDependencies { container, service in XCTFail() }
|
||||
container.register(.singleton, tag: "singleton") { ServiceImp1() as Service }
|
||||
.resolvingProperties { container, service in XCTFail() }
|
||||
|
||||
container.register(tag: "prototype", .Prototype) { ServiceImp1() as Service }
|
||||
.resolveDependencies { container, service in XCTFail() }
|
||||
container.register(.unique, tag: "prototype") { ServiceImp1() as Service }
|
||||
.resolvingProperties { container, service in XCTFail() }
|
||||
|
||||
container.register(tag: "graph", .ObjectGraph) { ServiceImp1() as Service }
|
||||
.resolveDependencies { container, service in XCTFail() }
|
||||
container.register(.shared, tag: "graph") { ServiceImp1() as Service }
|
||||
.resolvingProperties { container, service in XCTFail() }
|
||||
|
||||
//when
|
||||
try! container.bootstrap()
|
||||
@@ -301,28 +280,22 @@ class ComponentScopeTests: XCTestCase {
|
||||
|
||||
func testThatItReusesResolvedInstanceWhenResolvingOptional() {
|
||||
var otherService: Service!
|
||||
var impOtherService: Service!
|
||||
var anyOtherService: Any!
|
||||
var anyImpOtherService: Any!
|
||||
|
||||
container.register(.ObjectGraph) { ServiceImp1() as Service }
|
||||
.resolveDependencies { container, service in
|
||||
|
||||
container.register { 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 }
|
||||
container.register(.weakSingleton) { ServiceImp1() as Service }
|
||||
var strongSingleton: Service? = try! container.resolve() as Service
|
||||
weak var weakSingleton = try! container.resolve() as Service
|
||||
|
||||
@@ -335,5 +308,46 @@ class ComponentScopeTests: XCTestCase {
|
||||
//then
|
||||
XCTAssertNil(weakSingleton)
|
||||
}
|
||||
|
||||
func testThatItResolvesWeakSingletonAgainAfterItWasReleased() {
|
||||
Dip.logLevel = .Verbose
|
||||
//given
|
||||
let service = container.register(.weakSingleton) { ServiceImp1() }
|
||||
container.register(service, type: Service.self)
|
||||
|
||||
//when
|
||||
//resolve and realease reight away
|
||||
_ = try? container.resolve() as ServiceImp1
|
||||
|
||||
//then
|
||||
XCTAssertNoThrow(
|
||||
try container.resolve() as Service,
|
||||
"Weak singleton should be resolved again."
|
||||
)
|
||||
}
|
||||
|
||||
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, "\(scope) should be reused when first resolved from another container", line: line)
|
||||
}
|
||||
|
||||
test(.singleton)
|
||||
test(.weakSingleton)
|
||||
test(.eagerSingleton)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -39,35 +39,17 @@ class ContextTests: XCTestCase {
|
||||
|
||||
let container = DependencyContainer()
|
||||
|
||||
#if os(Linux)
|
||||
static var allTests: [(String, ContextTests -> () throws -> Void)] {
|
||||
return [
|
||||
("testThatContextStoresCurrentlyResolvedType", testThatContextStoresCurrentlyResolvedType),
|
||||
("testThatContextStoresInjectedInType", testThatContextStoresInjectedInType),
|
||||
("testThatContextStoresTheTagPassedToResolve", testThatContextStoresTheTagPassedToResolve),
|
||||
("testThatContextStoresTheTagPassedToResolveWhenAutoInjecting", testThatContextStoresTheTagPassedToResolveWhenAutoInjecting),
|
||||
("testThatContextStoresTheTagPassedToResolveWhenAutoWiring", testThatContextStoresTheTagPassedToResolveWhenAutoWiring),
|
||||
("testThatContextDoesNotOverrideNilTagPassedToResolve", testThatContextDoesNotOverrideNilTagPassedToResolve),
|
||||
("testThatContextStoresNameOfAutoInjectedProperty", testThatContextStoresNameOfAutoInjectedProperty)
|
||||
]
|
||||
}
|
||||
|
||||
func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#else
|
||||
override func setUp() {
|
||||
container.reset()
|
||||
container.register { ServiceImp2() }
|
||||
}
|
||||
#endif
|
||||
|
||||
func testThatContextStoresCurrentlyResolvedType() {
|
||||
container.register { () -> Service in
|
||||
XCTAssertTrue(self.container.context.resolvingType == Service.self)
|
||||
let _ = try self.container.resolve() as ServiceImp1
|
||||
return ServiceImp1() as Service
|
||||
}.resolveDependencies { _ in
|
||||
}.resolvingProperties { _,_ in
|
||||
XCTAssertTrue(self.container.context.resolvingType == Service.self)
|
||||
let _ = try self.container.resolve() as ServiceImp1
|
||||
}
|
||||
@@ -75,7 +57,7 @@ class ContextTests: XCTestCase {
|
||||
container.register { () -> ServiceImp1 in
|
||||
XCTAssertTrue(self.container.context.resolvingType == ServiceImp1.self)
|
||||
return ServiceImp1()
|
||||
}.resolveDependencies { _ in
|
||||
}.resolvingProperties { _,_ in
|
||||
XCTAssertTrue(self.container.context.resolvingType == ServiceImp1.self)
|
||||
}
|
||||
|
||||
@@ -87,7 +69,7 @@ class ContextTests: XCTestCase {
|
||||
XCTAssertNil(self.container.context.injectedInType)
|
||||
let _ = try self.container.resolve() as ServiceImp1
|
||||
return ServiceImp1() as Service
|
||||
}.resolveDependencies { _ in
|
||||
}.resolvingProperties { _,_ in
|
||||
XCTAssertNil(self.container.context.injectedInType)
|
||||
let _ = try self.container.resolve() as ServiceImp1
|
||||
}
|
||||
@@ -95,7 +77,7 @@ class ContextTests: XCTestCase {
|
||||
container.register { () -> ServiceImp1 in
|
||||
XCTAssertTrue(self.container.context.injectedInType == Service.self)
|
||||
return ServiceImp1()
|
||||
}.resolveDependencies { _ in
|
||||
}.resolvingProperties { _,_ in
|
||||
XCTAssertTrue(self.container.context.injectedInType == Service.self)
|
||||
}
|
||||
|
||||
@@ -108,7 +90,7 @@ class ContextTests: XCTestCase {
|
||||
XCTAssertTrue(DependencyContainer.Tag.String("tag") ~= self.container.context.tag!)
|
||||
let _ = try self.container.resolve(tag: "otherTag") as ServiceImp1
|
||||
return ServiceImp1() as Service
|
||||
}.resolveDependencies { _ in
|
||||
}.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
|
||||
@@ -118,7 +100,7 @@ class ContextTests: XCTestCase {
|
||||
XCTAssertNotNil(self.container.context.tag)
|
||||
XCTAssertTrue(DependencyContainer.Tag.String("otherTag") ~= self.container.context.tag!)
|
||||
return ServiceImp1()
|
||||
}.resolveDependencies { _ in
|
||||
}.resolvingProperties { _,_ in
|
||||
XCTAssertNotNil(self.container.context.tag)
|
||||
XCTAssertTrue(DependencyContainer.Tag.String("otherTag") ~= self.container.context.tag!)
|
||||
}
|
||||
@@ -130,7 +112,7 @@ class ContextTests: XCTestCase {
|
||||
container.register { ServiceImp1() as Service }
|
||||
container.register { ServiceImp1() }
|
||||
|
||||
container.register() { () -> ServiceImp2 in
|
||||
container.register { () -> ServiceImp2 in
|
||||
if self.container.context.injectedInProperty == "injectedNilTag" {
|
||||
XCTAssertNil(self.container.context.tag)
|
||||
}
|
||||
@@ -139,7 +121,7 @@ class ContextTests: XCTestCase {
|
||||
XCTAssertTrue(DependencyContainer.Tag.String("injectedTag") ~= self.container.context.tag!)
|
||||
}
|
||||
return ServiceImp2()
|
||||
}.resolveDependencies { _ in
|
||||
}.resolvingProperties { _,_ in
|
||||
if self.container.context.injectedInProperty == "injectedNilTag" {
|
||||
XCTAssertNil(self.container.context.tag)
|
||||
}
|
||||
@@ -153,7 +135,7 @@ class ContextTests: XCTestCase {
|
||||
XCTAssertNotNil(self.container.context.tag)
|
||||
XCTAssertTrue(DependencyContainer.Tag.String("tag") ~= self.container.context.tag!)
|
||||
return ServiceImp2()
|
||||
}.resolveDependencies { _ in
|
||||
}.resolvingProperties { _,_ in
|
||||
XCTAssertNotNil(self.container.context.tag)
|
||||
XCTAssertTrue(DependencyContainer.Tag.String("tag") ~= self.container.context.tag!)
|
||||
}
|
||||
@@ -164,14 +146,14 @@ class ContextTests: XCTestCase {
|
||||
func testThatContextStoresTheTagPassedToResolveWhenAutoWiring() {
|
||||
container.register { (_: ServiceImp1) -> Service in
|
||||
return ServiceImp1() as Service
|
||||
}.resolveDependencies { _ in
|
||||
}.resolvingProperties { _,_ in
|
||||
}
|
||||
|
||||
container.register { () -> ServiceImp1 in
|
||||
XCTAssertNotNil(self.container.context.tag)
|
||||
XCTAssertTrue(DependencyContainer.Tag.String("tag") ~= self.container.context.tag!)
|
||||
return ServiceImp1()
|
||||
}.resolveDependencies { _ in
|
||||
}.resolvingProperties { _,_ in
|
||||
XCTAssertNotNil(self.container.context.tag)
|
||||
XCTAssertTrue(DependencyContainer.Tag.String("tag") ~= self.container.context.tag!)
|
||||
}
|
||||
@@ -185,7 +167,7 @@ class ContextTests: XCTestCase {
|
||||
XCTAssertTrue(DependencyContainer.Tag.String("tag") ~= self.container.context.tag!)
|
||||
let _ = try self.container.resolve() as ServiceImp1
|
||||
return ServiceImp1() as Service
|
||||
}.resolveDependencies { _ in
|
||||
}.resolvingProperties { _,_ in
|
||||
XCTAssertNotNil(self.container.context.tag)
|
||||
XCTAssertTrue(DependencyContainer.Tag.String("tag") ~= self.container.context.tag!)
|
||||
let _ = try self.container.resolve() as ServiceImp1
|
||||
@@ -194,7 +176,7 @@ class ContextTests: XCTestCase {
|
||||
container.register { () -> ServiceImp1 in
|
||||
XCTAssertNil(self.container.context.tag)
|
||||
return ServiceImp1()
|
||||
}.resolveDependencies { _ in
|
||||
}.resolvingProperties { _,_ in
|
||||
XCTAssertNil(self.container.context.tag)
|
||||
}
|
||||
|
||||
@@ -207,11 +189,11 @@ class ContextTests: XCTestCase {
|
||||
|
||||
let names = ["injected", "injectedWeak", "taggedInjected", "taggedInjectedWeak", "injectedNilTag"]
|
||||
|
||||
container.register() { () -> ServiceImp2 in
|
||||
container.register { () -> ServiceImp2 in
|
||||
XCTAssertNotNil(self.container.context.injectedInProperty)
|
||||
XCTAssertTrue(names.contains(self.container.context.injectedInProperty!))
|
||||
return ServiceImp2()
|
||||
}.resolveDependencies { _ in
|
||||
}.resolvingProperties { _,_ in
|
||||
XCTAssertNotNil(self.container.context.injectedInProperty)
|
||||
XCTAssertTrue(names.contains(self.container.context.injectedInProperty!))
|
||||
}
|
||||
@@ -219,4 +201,47 @@ class ContextTests: XCTestCase {
|
||||
let _ = try! container.resolve() as Service
|
||||
}
|
||||
|
||||
func testThatItDoesNotSetInjectedInTypeWhenResolvingWithCollaboration() {
|
||||
let collaborator = DependencyContainer()
|
||||
|
||||
collaborator.register { () -> ServiceImp1 in
|
||||
unowned let collaborator = collaborator
|
||||
XCTAssertNil(collaborator.context.injectedInType)
|
||||
return ServiceImp1()
|
||||
}.resolvingProperties { collaborator, _ in
|
||||
XCTAssertNil(collaborator.context.injectedInType)
|
||||
}
|
||||
|
||||
container.collaborate(with: collaborator)
|
||||
collaborator.collaborate(with: container)
|
||||
|
||||
let _ = try! container.resolve() as ServiceImp1
|
||||
}
|
||||
|
||||
func testThatContextIsPreservedWhenResolvingWithCollaboration() {
|
||||
let collaborator = DependencyContainer()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
collaborator.register { () -> ServiceImp1 in
|
||||
XCTAssertTrue(collaborator.context.resolvingType == ServiceImp1.self)
|
||||
return ServiceImp1()
|
||||
}.resolvingProperties { _,_ in
|
||||
XCTAssertTrue(collaborator.context.resolvingType == ServiceImp1.self)
|
||||
}
|
||||
|
||||
container.collaborate(with: collaborator)
|
||||
collaborator.collaborate(with: container)
|
||||
let _ = try! container.resolve() as Service
|
||||
|
||||
collaborator.reset()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,47 +36,33 @@ class DefinitionTests: XCTestCase {
|
||||
let tag1 = DependencyContainer.Tag.String("tag1")
|
||||
let tag2 = DependencyContainer.Tag.String("tag2")
|
||||
|
||||
#if os(Linux)
|
||||
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),
|
||||
("testThatItRegisteresOptionalTypesAsForwardedTypes", testThatItRegisteresOptionalTypesAsForwardedTypes)
|
||||
]
|
||||
}
|
||||
#endif
|
||||
|
||||
func testThatDefinitionKeyIsEqualBy_Type_Factory_Tag() {
|
||||
let equalKey1 = DefinitionKey(protocolType: Service.self, argumentsType: F1.self, associatedTag: tag1)
|
||||
let equalKey2 = DefinitionKey(protocolType: Service.self, argumentsType: 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, argumentsType: F1.self, associatedTag: nil)
|
||||
let keyWithDifferentType2 = DefinitionKey(protocolType: AnyObject.self, argumentsType: 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, argumentsType: F1.self, associatedTag: nil)
|
||||
let keyWithDifferentFactory2 = DefinitionKey(protocolType: Service.self, argumentsType: 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, argumentsType: F1.self, associatedTag: tag1)
|
||||
let keyWithDifferentTag2 = DefinitionKey(protocolType: Service.self, argumentsType: 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)
|
||||
@@ -86,13 +72,13 @@ class DefinitionTests: XCTestCase {
|
||||
var blockCalled = false
|
||||
|
||||
//given
|
||||
let def = DefinitionOf<Service, () -> Service>(scope: .Prototype) { ServiceImp() as Service }
|
||||
.resolveDependencies { container, service in
|
||||
let def = Definition<Service, ()>(scope: .unique) { ServiceImp() as Service }
|
||||
.resolvingProperties { container, service in
|
||||
blockCalled = true
|
||||
}
|
||||
|
||||
//when
|
||||
try! def.resolveDependenciesOf(ServiceImp(), withContainer: DependencyContainer())
|
||||
try! def.resolveProperties(of: ServiceImp(), container: DependencyContainer())
|
||||
|
||||
//then
|
||||
XCTAssertTrue(blockCalled)
|
||||
@@ -102,23 +88,22 @@ class DefinitionTests: XCTestCase {
|
||||
var blockCalled = false
|
||||
|
||||
//given
|
||||
let def = DefinitionOf<Service, () -> Service>(scope: .Prototype) { ServiceImp() as Service }
|
||||
.resolveDependencies { container, service in
|
||||
let def = Definition<Service, ()>(scope: .unique) { ServiceImp() as Service }
|
||||
.resolvingProperties { container, service in
|
||||
blockCalled = true
|
||||
}
|
||||
|
||||
//when
|
||||
try! def.resolveDependenciesOf(String(), withContainer: DependencyContainer())
|
||||
try! def.resolveProperties(of: String(), container: DependencyContainer())
|
||||
|
||||
//then
|
||||
XCTAssertFalse(blockCalled)
|
||||
}
|
||||
|
||||
func testThatItRegisteresOptionalTypesAsForwardedTypes() {
|
||||
let def = DefinitionOf<Service, () -> Service>(scope: .Prototype) { ServiceImp() as Service }
|
||||
let def = Definition<Service, ()>(scope: .unique) { ServiceImp() as Service }
|
||||
|
||||
XCTAssertTrue(def.implementingTypes.contains({ $0 == Service?.self }))
|
||||
XCTAssertTrue(def.implementingTypes.contains({ $0 == Service!.self }))
|
||||
XCTAssertTrue(def.implementingTypes.contains(where: { $0 == Service?.self }))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -30,10 +30,10 @@ private class ServiceImp1: Service { }
|
||||
private class ServiceImp2: Service { }
|
||||
|
||||
private protocol Server: class {
|
||||
weak var client: Client? { get }
|
||||
var client: Client! { get }
|
||||
}
|
||||
private protocol Client: class {
|
||||
var server: Server? { get }
|
||||
var server: Server! { get }
|
||||
}
|
||||
|
||||
class ResolvableService: Service, Resolvable {
|
||||
@@ -49,34 +49,34 @@ class DipTests: XCTestCase {
|
||||
|
||||
let container = DependencyContainer()
|
||||
|
||||
#if os(Linux)
|
||||
static var allTests: [(String, DipTests -> () throws -> Void)] {
|
||||
return [
|
||||
("testThatItResolvesInstanceRegisteredWithoutTag", testThatItResolvesInstanceRegisteredWithoutTag),
|
||||
("testThatItResolvesInstanceRegisteredWithTag", testThatItResolvesInstanceRegisteredWithTag),
|
||||
("testThatItResolvesDifferentInstancesRegisteredForDifferentTags", testThatItResolvesDifferentInstancesRegisteredForDifferentTags),
|
||||
("testThatNewRegistrationOverridesPreviousRegistration", testThatNewRegistrationOverridesPreviousRegistration),
|
||||
("testThatItCallsResolveDependenciesOnDefinition", testThatItCallsResolveDependenciesOnDefinition),
|
||||
("testThatItThrowsErrorIfCanNotFindDefinitionForType", testThatItThrowsErrorIfCanNotFindDefinitionForType),
|
||||
("testThatItThrowsErrorIfCanNotFindDefinitionForTag", testThatItThrowsErrorIfCanNotFindDefinitionForTag),
|
||||
("testThatItThrowsErrorIfCanNotFindDefinitionForFactoryWithArguments", testThatItThrowsErrorIfCanNotFindDefinitionForFactoryWithArguments),
|
||||
("testThatItThrowsErrorIfConstructorThrows", testThatItThrowsErrorIfConstructorThrows),
|
||||
("testThatItThrowsErrorIfFailsToResolveDependency", testThatItThrowsErrorIfFailsToResolveDependency),
|
||||
("testThatItCallsDidResolveDependenciesOnResolvableIntance", testThatItCallsDidResolveDependenciesOnResolvableIntance),
|
||||
("testThatItCallsDidResolveDependenciesInReverseOrder", testThatItCallsDidResolveDependenciesInReverseOrder),
|
||||
("testThatItResolvesCircularDependencies", testThatItResolvesCircularDependencies),
|
||||
("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
|
||||
@@ -99,12 +99,6 @@ class DipTests: XCTestCase {
|
||||
|
||||
//then
|
||||
XCTAssertTrue(optService is ServiceImp1)
|
||||
|
||||
//and when
|
||||
let impService = try! container.resolve((Service!).self)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(impService is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItResolvesInstanceRegisteredWithTag() {
|
||||
@@ -128,12 +122,6 @@ class DipTests: XCTestCase {
|
||||
|
||||
//then
|
||||
XCTAssertTrue(optService is ServiceImp1)
|
||||
|
||||
//and when
|
||||
let impService = try! container.resolve((Service!).self, tag: "service")
|
||||
|
||||
//then
|
||||
XCTAssertTrue(impService is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItResolvesDifferentInstancesRegisteredForDifferentTags() {
|
||||
@@ -164,14 +152,6 @@ class DipTests: XCTestCase {
|
||||
//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() {
|
||||
@@ -191,19 +171,19 @@ class DipTests: XCTestCase {
|
||||
func testThatItCallsResolveDependenciesOnDefinition() {
|
||||
//given
|
||||
var resolveDependenciesCalled = false
|
||||
container.register { ServiceImp1() as Service }.resolveDependencies { (c, s) in
|
||||
container.register { ServiceImp1() as Service }.resolvingProperties { (c, s) in
|
||||
resolveDependenciesCalled = true
|
||||
}
|
||||
|
||||
//when
|
||||
try! container.resolve() as Service
|
||||
let _ = try! container.resolve() as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(resolveDependenciesCalled)
|
||||
resolveDependenciesCalled = false
|
||||
|
||||
//and when
|
||||
try! container.resolve(Service.self)
|
||||
let _ = try! container.resolve(Service.self)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(resolveDependenciesCalled)
|
||||
@@ -211,44 +191,40 @@ class DipTests: XCTestCase {
|
||||
resolveDependenciesCalled = false
|
||||
|
||||
//and when
|
||||
try! container.resolve((Service?).self)
|
||||
let _ = 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 }
|
||||
XCTAssertThrowsError(try self.container.resolve() as Service) { error in
|
||||
guard case let DipError.definitionNotFound(key) = error else {
|
||||
XCTFail("Thrown unexpected error: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
//then
|
||||
let expectedKey = DefinitionKey(protocolType: Service.self, argumentsType: Void.self, associatedTag: nil)
|
||||
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 }
|
||||
XCTAssertThrowsError(try self.container.resolve(Service.self)) { error in
|
||||
guard case let DipError.definitionNotFound(key) = error else {
|
||||
XCTFail("Thrown unexpected error: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
//then
|
||||
let expectedKey = DefinitionKey(protocolType: Service.self, argumentsType: Void.self, associatedTag: nil)
|
||||
let expectedKey = DefinitionKey(type: Service.self, typeOfArguments: Void.self, tag: nil)
|
||||
XCTAssertEqual(key, expectedKey)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,25 +233,27 @@ class DipTests: XCTestCase {
|
||||
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 }
|
||||
XCTAssertThrowsError(try self.container.resolve(tag: "other tag") as Service) { error in
|
||||
guard case let DipError.definitionNotFound(key) = error else {
|
||||
XCTFail("Thrown unexpected error: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
//then
|
||||
let expectedKey = DefinitionKey(protocolType: Service.self, argumentsType: Void.self, associatedTag: "other tag")
|
||||
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 }
|
||||
XCTAssertThrowsError(try self.container.resolve(Service.self, tag: "other tag")) { error in
|
||||
guard case let DipError.definitionNotFound(key) = error else {
|
||||
XCTFail("Thrown unexpected error: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
//then
|
||||
let expectedKey = DefinitionKey(protocolType: Service.self, argumentsType: Void.self, associatedTag: "other tag")
|
||||
let expectedKey = DefinitionKey(type: Service.self, typeOfArguments: Void.self, tag: "other tag")
|
||||
XCTAssertEqual(key, expectedKey)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,74 +262,76 @@ class DipTests: XCTestCase {
|
||||
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 }
|
||||
XCTAssertThrowsError(try self.container.resolve(arguments: "some string") as Service) { error in
|
||||
guard case let DipError.definitionNotFound(key) = error else {
|
||||
XCTFail("Thrown unexpected error: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
//then
|
||||
let expectedKey = DefinitionKey(protocolType: Service.self, argumentsType: String.self, associatedTag: nil)
|
||||
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, withArguments: "some string")) { error in
|
||||
guard case let DipError.DefinitionNotFound(key) = error else { return false }
|
||||
XCTAssertThrowsError(try self.container.resolve(Service.self, arguments: "some string")) { error in
|
||||
guard case let DipError.definitionNotFound(key) = error else {
|
||||
XCTFail("Thrown unexpected error: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
//then
|
||||
let expectedKey = DefinitionKey(protocolType: Service.self, argumentsType: String.self, associatedTag: nil)
|
||||
let expectedKey = DefinitionKey(type: Service.self, typeOfArguments: String.self, tag: nil)
|
||||
XCTAssertEqual(key, expectedKey)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func testThatItThrowsErrorIfConstructorThrows() {
|
||||
//given
|
||||
let failedKey = DefinitionKey(protocolType: Any.self, argumentsType: Any.self)
|
||||
let expectedError = DipError.DefinitionNotFound(key: failedKey)
|
||||
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
|
||||
XCTAssertThrowsError(try self.container.resolve() as Service) { error in
|
||||
guard case let DipError.definitionNotFound(key) = error, key == failedKey else {
|
||||
XCTFail("Thrown unexpected error: \(error)")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//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
|
||||
XCTAssertThrowsError(try self.container.resolve(Service.self)) { error in
|
||||
guard case let DipError.definitionNotFound(key) = error, key == failedKey else {
|
||||
XCTFail("Thrown unexpected error: \(error)")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testThatItThrowsErrorIfFailsToResolveDependency() {
|
||||
//given
|
||||
let failedKey = DefinitionKey(protocolType: Any.self, argumentsType: Any.self)
|
||||
let expectedError = DipError.DefinitionNotFound(key: failedKey)
|
||||
let failedKey = DefinitionKey(type: Any.self, typeOfArguments: Any.self)
|
||||
let expectedError = DipError.definitionNotFound(key: failedKey)
|
||||
container.register { ServiceImp1() as Service }
|
||||
.resolveDependencies { container, service in
|
||||
.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
|
||||
XCTAssertThrowsError(try self.container.resolve() as Service) { error in
|
||||
guard case let DipError.definitionNotFound(key) = error, key == failedKey else {
|
||||
XCTFail("Thrown unexpected error: \(error)")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//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
|
||||
XCTAssertThrowsError(try self.container.resolve(Service.self)) { error in
|
||||
guard case let DipError.definitionNotFound(key) = error, key == failedKey else {
|
||||
XCTFail("Thrown unexpected error: \(error)")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -359,19 +339,19 @@ class DipTests: XCTestCase {
|
||||
func testThatItCallsDidResolveDependenciesOnResolvableIntance() {
|
||||
//given
|
||||
container.register { ResolvableService() as Service }
|
||||
.resolveDependencies { _, service in
|
||||
.resolvingProperties { _, 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
|
||||
container.register(tag: "graph") { 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 }
|
||||
.resolveDependencies { _, service in
|
||||
container.register(.singleton, tag: "singleton") { ResolvableService() as Service }
|
||||
.resolvingProperties { _, service in
|
||||
XCTAssertFalse((service as! ResolvableService).didResolveDependenciesCalled, "didResolveDependencies should not be called yet")
|
||||
return
|
||||
}
|
||||
@@ -410,7 +390,7 @@ class DipTests: XCTestCase {
|
||||
var resolveDependenciesCalled = false
|
||||
var service2: Service!
|
||||
container.register { ResolvableService() as Service }
|
||||
.resolveDependencies { _, service in
|
||||
.resolvingProperties { _, service in
|
||||
if !resolveDependenciesCalled {
|
||||
resolveDependenciesCalled = true
|
||||
service2 = try self.container.resolve() as Service
|
||||
@@ -426,11 +406,41 @@ class DipTests: XCTestCase {
|
||||
XCTAssertTrue(ResolvableService.resolved.last === service1)
|
||||
}
|
||||
|
||||
func testItCallsResolveDependenciesOnResolableInstance() {
|
||||
|
||||
class Class: Resolvable {
|
||||
var resolveDependenciesCalled = false
|
||||
|
||||
func resolveDependencies(_ container: DependencyContainer) {
|
||||
resolveDependenciesCalled = true
|
||||
}
|
||||
}
|
||||
|
||||
class SubClass: Class {
|
||||
override func resolveDependencies(_ container: DependencyContainer) {
|
||||
super.resolveDependencies(container)
|
||||
}
|
||||
}
|
||||
|
||||
container.register { Class() }
|
||||
.resolvingProperties { _, instance in
|
||||
XCTAssertTrue(instance.resolveDependenciesCalled)
|
||||
}
|
||||
|
||||
container.register { SubClass() }
|
||||
.resolvingProperties { _, instance in
|
||||
XCTAssertTrue(instance.resolveDependenciesCalled)
|
||||
}
|
||||
|
||||
let _ = try! container.resolve() as Class
|
||||
let _ = try! container.resolve() as SubClass
|
||||
}
|
||||
|
||||
func testThatItResolvesCircularDependencies() {
|
||||
|
||||
class ResolvableServer: Server, Resolvable {
|
||||
weak var client: Client?
|
||||
weak var secondClient: Client?
|
||||
weak var client: Client!
|
||||
weak var secondClient: Client!
|
||||
|
||||
init(client: Client) {
|
||||
self.client = client
|
||||
@@ -452,10 +462,8 @@ class DipTests: XCTestCase {
|
||||
}
|
||||
|
||||
class ResolvableClient: Client, Resolvable {
|
||||
var server: Server?
|
||||
var secondServer: Server?
|
||||
|
||||
init() {}
|
||||
var server: Server!
|
||||
var secondServer: Server!
|
||||
|
||||
var didResolveDependenciesCalled = false
|
||||
|
||||
@@ -468,22 +476,15 @@ class DipTests: XCTestCase {
|
||||
XCTAssertNotNil(self.server?.client)
|
||||
XCTAssertNotNil(self.secondServer?.client)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//given
|
||||
container.register(.ObjectGraph) { try ResolvableServer(client: self.container.resolve()) as Server }
|
||||
.resolveDependencies { (container: DependencyContainer, server: Server) in
|
||||
let server = server as! ResolvableServer
|
||||
server.secondClient = try container.resolve() as Client
|
||||
}
|
||||
container.register { try ResolvableServer(client: self.container.resolve()) as Server }
|
||||
.resolvingProperty(\ResolvableServer.secondClient, as: Client.self)
|
||||
|
||||
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
|
||||
}
|
||||
container.register { ResolvableClient() as Client }
|
||||
.resolvingProperty(\ResolvableClient.server, factory: { try $0.resolve() })
|
||||
.resolvingProperty(\ResolvableClient.secondServer)
|
||||
|
||||
//when
|
||||
let client = (try! container.resolve() as Client) as! ResolvableClient
|
||||
@@ -510,29 +511,28 @@ class DipTests: XCTestCase {
|
||||
var createdService3 = false
|
||||
var createdService = false
|
||||
|
||||
let service = container.register { ServiceImp1() }
|
||||
.resolveDependencies { container, _ in
|
||||
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)
|
||||
}.implements(Service.self)
|
||||
|
||||
container.register(tag: "tag") { ServiceImp2() as Service }
|
||||
.resolveDependencies { _ in
|
||||
.resolvingProperties { _,_ in
|
||||
createdService2 = true
|
||||
}
|
||||
|
||||
container.register() { (arg: String) in ServiceImp1() }
|
||||
.resolveDependencies { _ in
|
||||
container.register { (arg: String) in ServiceImp1() }
|
||||
.resolvingProperties { _,_ in
|
||||
createdService3 = true
|
||||
}
|
||||
|
||||
//then
|
||||
AssertNoThrow(expression: try container.validate("arg"))
|
||||
XCTAssertNoThrow(try self.container.validate("arg"))
|
||||
XCTAssertTrue(createdService1)
|
||||
XCTAssertTrue(createdService2)
|
||||
XCTAssertTrue(createdService3)
|
||||
@@ -555,8 +555,8 @@ class DipTests: XCTestCase {
|
||||
}
|
||||
|
||||
//then
|
||||
AssertNoThrow(expression:
|
||||
try container.validate(
|
||||
XCTAssertNoThrow(
|
||||
try self.container.validate(
|
||||
"1",
|
||||
expectedIntArgument,
|
||||
"x",
|
||||
@@ -571,29 +571,43 @@ class DipTests: XCTestCase {
|
||||
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 }
|
||||
XCTAssertThrowsError(try self.container.validate()) { error in
|
||||
guard error is DipError else {
|
||||
XCTFail("Thrown unexpected error: \(error)")
|
||||
return
|
||||
}
|
||||
}
|
||||
XCTAssertThrowsError(try self.container.validate("1")) { error in
|
||||
guard error is DipError else {
|
||||
XCTFail("Thrown unexpected error: \(error)")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testThatItFailsValidationOnlyForDipErrors() {
|
||||
//given
|
||||
enum SomeError: Error { case error }
|
||||
|
||||
container.register { () -> Service in
|
||||
throw NSError(domain: "", code: 0, userInfo: nil)
|
||||
throw SomeError.error
|
||||
}
|
||||
|
||||
//then
|
||||
AssertNoThrow(expression: try container.validate())
|
||||
XCTAssertNoThrow(try self.container.validate())
|
||||
|
||||
//given
|
||||
let key = DefinitionKey(protocolType: Service.self, argumentsType: Void.self, associatedTag: nil)
|
||||
let key = DefinitionKey(type: Service.self, typeOfArguments: Void.self, tag: nil)
|
||||
container.register { () -> Service in
|
||||
throw DipError.DefinitionNotFound(key: key)
|
||||
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 }
|
||||
XCTAssertThrowsError(try self.container.validate()) { error in
|
||||
guard case let DipError.definitionNotFound(_key) = error, _key == key else {
|
||||
XCTFail("Thrown unexpected error: \(error)")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -605,20 +619,22 @@ extension DipTests {
|
||||
//given
|
||||
let collaborator = DependencyContainer()
|
||||
collaborator.register { ResolvableService() as Service }
|
||||
container.register { "something" }
|
||||
|
||||
//when
|
||||
container.collaborate(with: collaborator)
|
||||
|
||||
//then
|
||||
AssertNoThrow(expression: try container.resolve() as Service)
|
||||
AssertNoThrow(expression: try container.resolve(Service.self))
|
||||
XCTAssertNoThrow(try self.container.resolve() as Service)
|
||||
XCTAssertNoThrow(try self.container.resolve(Service.self))
|
||||
XCTAssertNoThrow(try collaborator.resolve() as String)
|
||||
XCTAssertNoThrow(try collaborator.resolve(String.self))
|
||||
}
|
||||
|
||||
func testThatCollaboratingWithSelfIsIgnored() {
|
||||
let collaborator = DependencyContainer()
|
||||
collaborator.collaborate(with: collaborator)
|
||||
XCTAssertTrue(collaborator._collaborators.isEmpty, "Container should not collaborate with itself")
|
||||
|
||||
}
|
||||
|
||||
func testThatCollaboratingContainersAreWeakReferences() {
|
||||
@@ -637,25 +653,24 @@ extension DipTests {
|
||||
func testThatCollaboratingContainersReuseInstancesResolvedByAnotherContainer() {
|
||||
//given
|
||||
class ServerImp: Server {
|
||||
weak var client: Client?
|
||||
weak var client: Client!
|
||||
init(client: Client) { self.client = client }
|
||||
}
|
||||
|
||||
class ClientImp: Client {
|
||||
var server: Server?
|
||||
var anotherServer: Server?
|
||||
init() {}
|
||||
var server: Server!
|
||||
var anotherServer: Server!
|
||||
}
|
||||
|
||||
let serverContainer = DependencyContainer() { container in
|
||||
container.register(.ObjectGraph) { ServerImp(client: $0) as Server }
|
||||
}
|
||||
let clientContainer = DependencyContainer() { container in
|
||||
container.register(.ObjectGraph) { ClientImp() as Client }
|
||||
.resolveDependencies { container, client in
|
||||
let client = client as! ClientImp
|
||||
client.server = try container.resolve() as Server
|
||||
client.anotherServer = try container.resolve() as Server
|
||||
}
|
||||
let serverContainer = DependencyContainer()
|
||||
serverContainer.register { ServerImp(client: $0) as Server }
|
||||
|
||||
let clientContainer = DependencyContainer()
|
||||
clientContainer.register { 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
|
||||
@@ -678,4 +693,188 @@ extension DipTests {
|
||||
XCTAssertTrue(client?.server === (client as? ClientImp)?.anotherServer)
|
||||
}
|
||||
|
||||
func testThatCollaborationReferencesAreRecursivelyUpdate() {
|
||||
let container = DependencyContainer()
|
||||
container.register(.singleton){ ResolvableService() as Service }
|
||||
|
||||
//when
|
||||
let collaborator1 = DependencyContainer()
|
||||
let collaborator2 = DependencyContainer()
|
||||
let collaborator3 = DependencyContainer()
|
||||
let collaborator4 = DependencyContainer()
|
||||
|
||||
collaborator1.collaborate(with: container)
|
||||
XCTAssertTrue(collaborator1.resolvedInstances.sharedSingletonsBox === container.resolvedInstances.sharedSingletonsBox)
|
||||
|
||||
collaborator2.collaborate(with: container)
|
||||
XCTAssertTrue(collaborator2.resolvedInstances.sharedSingletonsBox === container.resolvedInstances.sharedSingletonsBox)
|
||||
|
||||
collaborator3.collaborate(with: collaborator1)
|
||||
XCTAssertTrue(collaborator3.resolvedInstances.sharedSingletonsBox === container.resolvedInstances.sharedSingletonsBox)
|
||||
|
||||
collaborator4.collaborate(with: collaborator2)
|
||||
XCTAssertTrue(collaborator4.resolvedInstances.sharedSingletonsBox === container.resolvedInstances.sharedSingletonsBox)
|
||||
|
||||
let service1 = try! collaborator1.resolve() as Service
|
||||
let service2 = try! collaborator2.resolve() as Service
|
||||
let service3 = try! collaborator3.resolve() as Service
|
||||
let service4 = try! collaborator4.resolve() as Service
|
||||
let serviceRoot = try! container.resolve() as Service
|
||||
|
||||
XCTAssertTrue(service1 === service2)
|
||||
XCTAssertTrue(service1 === service3)
|
||||
XCTAssertTrue(service1 === service4)
|
||||
|
||||
XCTAssertTrue(service1 === serviceRoot)
|
||||
XCTAssertTrue(service2 === serviceRoot)
|
||||
XCTAssertTrue(service3 === serviceRoot)
|
||||
XCTAssertTrue(service4 === serviceRoot)
|
||||
}
|
||||
|
||||
class RootService {}
|
||||
class ServiceClient {
|
||||
let name: String
|
||||
let service: RootService
|
||||
init(name: String, service: RootService) {
|
||||
self.name = name
|
||||
self.service = service
|
||||
}
|
||||
}
|
||||
|
||||
func testThatContainersShareTheirSingletonsOnlyWithCollaborators() {
|
||||
let container = DependencyContainer()
|
||||
container.register(.singleton) { RootService() }
|
||||
|
||||
let collaborator1 = DependencyContainer()
|
||||
collaborator1.register(.singleton) {
|
||||
ServiceClient(name: "1", service: $0)
|
||||
}
|
||||
|
||||
let collaborator2 = DependencyContainer()
|
||||
collaborator2.register(.singleton) {
|
||||
ServiceClient(name: "2", service: $0)
|
||||
}
|
||||
|
||||
collaborator1.collaborate(with: container)
|
||||
collaborator2.collaborate(with: container)
|
||||
|
||||
let client2 = try! collaborator2.resolve() as ServiceClient
|
||||
let client1 = try! collaborator1.resolve() as ServiceClient
|
||||
|
||||
XCTAssertEqual(client1.name, "1")
|
||||
XCTAssertEqual(client2.name, "2")
|
||||
XCTAssertTrue(client1.service === client2.service)
|
||||
}
|
||||
|
||||
func testThatContainerAutowireBeforeCollaboration() {
|
||||
let container = DependencyContainer()
|
||||
container.register(.singleton) { RootService() }
|
||||
|
||||
let collaborator1 = DependencyContainer()
|
||||
collaborator1.register(.singleton) {
|
||||
ServiceClient(name: "1", service: $0)
|
||||
}
|
||||
|
||||
let collaborator2 = DependencyContainer()
|
||||
collaborator2.register(.singleton) {
|
||||
ServiceClient(name: "2", service: $0)
|
||||
}
|
||||
|
||||
collaborator1.collaborate(with: container, collaborator2)
|
||||
collaborator2.collaborate(with: container, collaborator1)
|
||||
|
||||
let client2 = try! collaborator2.resolve() as ServiceClient
|
||||
let client1 = try! collaborator1.resolve() as ServiceClient
|
||||
|
||||
XCTAssertEqual(client1.name, "1")
|
||||
XCTAssertEqual(client2.name, "2")
|
||||
XCTAssertTrue(client1.service === client2.service)
|
||||
}
|
||||
}
|
||||
|
||||
class Manager {}
|
||||
class AnotherManager {}
|
||||
|
||||
class Object {
|
||||
let manager: Manager?
|
||||
|
||||
init(with container: DependencyContainer) {
|
||||
self.manager = try? container.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
class Owner {
|
||||
var manager: Manager?
|
||||
}
|
||||
|
||||
extension DipTests {
|
||||
func testThatItCanHandleSeparateContainersAndTheirCollaboration() {
|
||||
let container = self.container
|
||||
|
||||
let anotherContainer = DependencyContainer()
|
||||
anotherContainer.register { Object(with: anotherContainer) }
|
||||
|
||||
container.collaborate(with: anotherContainer)
|
||||
|
||||
container
|
||||
.register { Owner() }
|
||||
.resolvingProperties { $1.manager = try $0.resolve() }
|
||||
|
||||
container.register(.singleton) { AnotherManager() }
|
||||
container.register(.singleton) { Manager() }
|
||||
|
||||
let manager: Manager? = try? container.resolve()
|
||||
let another: AnotherManager? = try? container.resolve()
|
||||
var owner: Owner? = try? container.resolve(arguments: 1, "")
|
||||
|
||||
let object: Object? = try? container.resolve()
|
||||
owner = try? container.resolve()
|
||||
|
||||
let nonNilValues: [Any?] = [another, manager, owner, object, object?.manager]
|
||||
nonNilValues.forEach { XCTAssertNotNil($0) }
|
||||
|
||||
XCTAssertTrue(
|
||||
owner?.manager
|
||||
.flatMap { value in
|
||||
manager.flatMap { $0 === value }
|
||||
}
|
||||
?? false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension DipTests {
|
||||
// https://bugs.swift.org/browse/SR-8878
|
||||
func test_weak_mirror_regression() {
|
||||
class A {
|
||||
static var released = false
|
||||
deinit {
|
||||
A.released = true
|
||||
}
|
||||
}
|
||||
class B {
|
||||
static var released = false
|
||||
weak var a: A?
|
||||
init(a: A) {
|
||||
self.a = a
|
||||
}
|
||||
deinit {
|
||||
B.released = true
|
||||
}
|
||||
}
|
||||
let container = DependencyContainer()
|
||||
let tag = "my_tag"
|
||||
container.register(.unique, tag: tag, factory: B.init(a:))
|
||||
do {
|
||||
let a0 = A()
|
||||
let _: B = try container.resolve(tag: tag, arguments: a0)
|
||||
|
||||
|
||||
XCTAssertTrue(B.released)
|
||||
// Due to regression in swift 4.2 Mirror retains weak children
|
||||
// https://bugs.swift.org/browse/SR-8878
|
||||
XCTAssertFalse(A.released)
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
//
|
||||
// DipUI
|
||||
//
|
||||
// Copyright (c) 2016 Ilya Puchka <ilyapuchka@gmail.com>
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
|
||||
#if (canImport(UIKit) || canImport(AppKit)) && !SWIFT_PACKAGE
|
||||
|
||||
import XCTest
|
||||
@testable import Dip
|
||||
|
||||
#if canImport(UIKit)
|
||||
import UIKit
|
||||
typealias Storyboard = UIStoryboard
|
||||
typealias ViewController = UIViewController
|
||||
typealias StoryboardName = String
|
||||
|
||||
extension UIStoryboard {
|
||||
@nonobjc
|
||||
@discardableResult func instantiateViewControllerWithIdentifier(_ identifier: String) -> UIViewController {
|
||||
return instantiateViewController(withIdentifier: identifier)
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
import AppKit
|
||||
typealias Storyboard = NSStoryboard
|
||||
typealias ViewController = NSViewController
|
||||
typealias StoryboardName = NSStoryboard.Name
|
||||
|
||||
extension NSStoryboard {
|
||||
@discardableResult func instantiateViewControllerWithIdentifier(_ identifier: String) -> NSViewController {
|
||||
return instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(identifier)) as! NSViewController
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if os(iOS)
|
||||
let storyboardName: StoryboardName = "UIStoryboard"
|
||||
#elseif os(tvOS)
|
||||
let storyboardName: StoryboardName = "TVStoryboard"
|
||||
#else
|
||||
let storyboardName: StoryboardName = StoryboardName("NSStoryboard")
|
||||
#endif
|
||||
|
||||
class DipViewController: ViewController, StoryboardInstantiatable {}
|
||||
class NilTagViewController: ViewController, StoryboardInstantiatable {}
|
||||
|
||||
class DipUITests: XCTestCase {
|
||||
|
||||
let storyboard: Storyboard = {
|
||||
let bundle = Bundle(for: DipUITests.self)
|
||||
return Storyboard(name: storyboardName, bundle: bundle)
|
||||
}()
|
||||
|
||||
func testThatViewControllerHasDipTagProperty() {
|
||||
let viewController = storyboard.instantiateViewControllerWithIdentifier("DipViewController")
|
||||
XCTAssertEqual(viewController.dipTag, "vc")
|
||||
}
|
||||
|
||||
func testThatItDoesNotResolveIfContainerIsNotSet() {
|
||||
let container = DependencyContainer()
|
||||
container.register(tag: "vc") { ViewController() }
|
||||
.resolvingProperties { _, _ in
|
||||
XCTFail("Should not resolve when container is not set.")
|
||||
}
|
||||
|
||||
storyboard.instantiateViewControllerWithIdentifier("DipViewController")
|
||||
}
|
||||
|
||||
func testThatItDoesNotResolveIfTagIsNotSet() {
|
||||
let container = DependencyContainer()
|
||||
container.register(tag: "vc") { ViewController() }
|
||||
.resolvingProperties { _, _ in
|
||||
XCTFail("Should not resolve when container is not set.")
|
||||
}
|
||||
|
||||
DependencyContainer.uiContainers = [container]
|
||||
storyboard.instantiateViewControllerWithIdentifier("ViewController")
|
||||
}
|
||||
|
||||
func testThatItResolvesIfContainerAndStringTagAreSet() {
|
||||
var resolved = false
|
||||
let container = DependencyContainer()
|
||||
container.register(storyboardType: DipViewController.self, tag: "vc")
|
||||
.resolvingProperties { _, _ in
|
||||
resolved = true
|
||||
}
|
||||
|
||||
DependencyContainer.uiContainers = [container]
|
||||
storyboard.instantiateViewControllerWithIdentifier("DipViewController")
|
||||
XCTAssertTrue(resolved, "Should resolve when container and tag are set.")
|
||||
}
|
||||
|
||||
func testThatItResolvesIfContainerAndNilTagAreSet() {
|
||||
var resolved = false
|
||||
let container = DependencyContainer()
|
||||
container.register(storyboardType: NilTagViewController.self)
|
||||
.resolvingProperties { _, _ in
|
||||
resolved = true
|
||||
}
|
||||
|
||||
DependencyContainer.uiContainers = [container]
|
||||
storyboard.instantiateViewControllerWithIdentifier("NilTagViewController")
|
||||
XCTAssertTrue(resolved, "Should resolve when container and nil tag are set.")
|
||||
}
|
||||
|
||||
func testThatItDoesNotResolveIfTagDoesNotMatch() {
|
||||
let container = DependencyContainer()
|
||||
container.register(storyboardType: DipViewController.self, tag: "wrong tag")
|
||||
.resolvingProperties { _, _ in
|
||||
XCTFail("Should not resolve when container is not set.")
|
||||
}
|
||||
|
||||
DependencyContainer.uiContainers = [container]
|
||||
storyboard.instantiateViewControllerWithIdentifier("DipViewController")
|
||||
}
|
||||
|
||||
func testThatItResolvesWithDefinitionWithNoTag() {
|
||||
var resolved = false
|
||||
let container = DependencyContainer()
|
||||
container.register(storyboardType: DipViewController.self)
|
||||
.resolvingProperties { _, _ in
|
||||
resolved = true
|
||||
}
|
||||
|
||||
DependencyContainer.uiContainers = [container]
|
||||
storyboard.instantiateViewControllerWithIdentifier("DipViewController")
|
||||
XCTAssertTrue(resolved, "Should fallback to definition with no tag.")
|
||||
}
|
||||
|
||||
func testThatItIteratesUIContainers() {
|
||||
var resolved = false
|
||||
let container1 = DependencyContainer()
|
||||
let container2 = DependencyContainer()
|
||||
container2.register(storyboardType: DipViewController.self, tag: "vc")
|
||||
.resolvingProperties { container, _ in
|
||||
XCTAssertTrue(container === container2)
|
||||
resolved = true
|
||||
}
|
||||
|
||||
DependencyContainer.uiContainers = [container1, container2]
|
||||
storyboard.instantiateViewControllerWithIdentifier("DipViewController")
|
||||
XCTAssertTrue(resolved, "Should resolve using second container")
|
||||
}
|
||||
}
|
||||
|
||||
protocol SomeService: class {
|
||||
var delegate: SomeServiceDelegate? { get set }
|
||||
}
|
||||
protocol SomeServiceDelegate: class { }
|
||||
class SomeServiceImp: SomeService {
|
||||
weak var delegate: SomeServiceDelegate?
|
||||
init(delegate: SomeServiceDelegate) {
|
||||
self.delegate = delegate
|
||||
}
|
||||
init(){}
|
||||
}
|
||||
|
||||
protocol OtherService: class {
|
||||
var delegate: OtherServiceDelegate? { get set }
|
||||
}
|
||||
protocol OtherServiceDelegate: class {}
|
||||
class OtherServiceImp: OtherService {
|
||||
weak var delegate: OtherServiceDelegate?
|
||||
init(delegate: OtherServiceDelegate){
|
||||
self.delegate = delegate
|
||||
}
|
||||
init(){}
|
||||
}
|
||||
|
||||
|
||||
protocol SomeScreen: class {
|
||||
var someService: SomeService? { get set }
|
||||
var otherService: OtherService? { get set }
|
||||
}
|
||||
|
||||
class ViewControllerImp: SomeScreen, SomeServiceDelegate, OtherServiceDelegate {
|
||||
var someService: SomeService?
|
||||
var otherService: OtherService?
|
||||
init(){}
|
||||
}
|
||||
|
||||
extension DipUITests {
|
||||
|
||||
func testThatItDoesNotCreateNewInstanceWhenResolvingDependenciesOfExternalInstance() {
|
||||
let container = DependencyContainer()
|
||||
|
||||
//given
|
||||
var factoryCalled = false
|
||||
container.register(.shared) { () -> SomeScreen in
|
||||
factoryCalled = true
|
||||
return ViewControllerImp() as SomeScreen
|
||||
}
|
||||
|
||||
//when
|
||||
let screen = ViewControllerImp()
|
||||
try! container.resolveDependencies(of: screen as SomeScreen)
|
||||
|
||||
//then
|
||||
XCTAssertFalse(factoryCalled, "Container should not create new instance when resolving dependencies of external instance.")
|
||||
}
|
||||
|
||||
func testThatItResolvesInstanceThatImplementsSeveralProtocols() {
|
||||
let container = DependencyContainer()
|
||||
|
||||
//given
|
||||
container.register(.shared) { ViewControllerImp() as SomeScreen }
|
||||
.resolvingProperties { container, resolved in
|
||||
|
||||
//manually provide resolved instance for the delegate properties
|
||||
resolved.someService = try container.resolve() as SomeService
|
||||
resolved.someService?.delegate = resolved as? SomeServiceDelegate
|
||||
resolved.otherService = try container.resolve(arguments: resolved as! OtherServiceDelegate) as OtherService
|
||||
}
|
||||
|
||||
container.register(.shared) { SomeServiceImp() as SomeService }
|
||||
container.register(.shared) { OtherServiceImp(delegate: $0) as OtherService }
|
||||
|
||||
//when
|
||||
let screen = try! container.resolve() as SomeScreen
|
||||
|
||||
//then
|
||||
XCTAssertNotNil(screen.someService)
|
||||
XCTAssertNotNil(screen.otherService)
|
||||
|
||||
XCTAssertTrue(screen.someService?.delegate === screen)
|
||||
XCTAssertTrue(screen.otherService?.delegate === screen)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
+34
-48
@@ -51,31 +51,9 @@ class RuntimeArgumentsTests: XCTestCase {
|
||||
|
||||
let container = DependencyContainer()
|
||||
|
||||
#if os(Linux)
|
||||
static var allTests: [(String, RuntimeArgumentsTests -> () throws -> Void)] {
|
||||
return [
|
||||
("testThatItResolvesInstanceWithOneArgument", testThatItResolvesInstanceWithOneArgument),
|
||||
("testThatItResolvesInstanceWithTwoArguments", testThatItResolvesInstanceWithTwoArguments),
|
||||
("testThatItResolvesInstanceWithThreeArguments", testThatItResolvesInstanceWithThreeArguments),
|
||||
("testThatItResolvesInstanceWithFourArguments", testThatItResolvesInstanceWithFourArguments),
|
||||
("testThatItResolvesInstanceWithFiveArguments", testThatItResolvesInstanceWithFiveArguments),
|
||||
("testThatItResolvesInstanceWithSixArguments", testThatItResolvesInstanceWithSixArguments),
|
||||
("testThatItRegistersDifferentFactoriesForDifferentNumberOfArguments", testThatItRegistersDifferentFactoriesForDifferentNumberOfArguments),
|
||||
("testThatItRegistersDifferentFactoriesForDifferentTypesOfArguments", testThatItRegistersDifferentFactoriesForDifferentTypesOfArguments),
|
||||
("testThatItRegistersDifferentFactoriesForDifferentOrderOfArguments", testThatItRegistersDifferentFactoriesForDifferentOrderOfArguments),
|
||||
("testThatNewRegistrationWithSameArgumentsOverridesPreviousRegistration", testThatNewRegistrationWithSameArgumentsOverridesPreviousRegistration),
|
||||
("testThatDifferentFactoriesRegisteredIfArgumentIsOptional", testThatDifferentFactoriesRegisteredIfArgumentIsOptional)
|
||||
]
|
||||
}
|
||||
|
||||
func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#else
|
||||
override func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
#endif
|
||||
|
||||
func testThatItResolvesInstanceWithOneArgument() {
|
||||
//given
|
||||
@@ -86,13 +64,13 @@ 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, withArguments: arg1)
|
||||
let anyService = try! container.resolve(Service.self, arguments: arg1)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(anyService is ServiceImp1)
|
||||
@@ -108,13 +86,13 @@ 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, withArguments: arg1, arg2)
|
||||
let anyService = try! container.resolve(Service.self, arguments: arg1, arg2)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(anyService is ServiceImp1)
|
||||
@@ -130,13 +108,13 @@ 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, withArguments: arg1, arg2, arg3)
|
||||
let anyService = try! container.resolve(Service.self, arguments: arg1, arg2, arg3)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(anyService is ServiceImp1)
|
||||
@@ -153,13 +131,13 @@ 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, withArguments: arg1, arg2, arg3, arg4)
|
||||
let anyService = try! container.resolve(Service.self, arguments: arg1, arg2, arg3, arg4)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(anyService is ServiceImp1)
|
||||
@@ -177,13 +155,13 @@ 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, withArguments: arg1, arg2, arg3, arg4, arg5)
|
||||
let anyService = try! container.resolve(Service.self, arguments: arg1, arg2, arg3, arg4, arg5)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(anyService is ServiceImp1)
|
||||
@@ -202,13 +180,13 @@ 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, withArguments: arg1, arg2, arg3, arg4, arg5, arg6)
|
||||
let anyService = try! container.resolve(Service.self, arguments: arg1, arg2, arg3, arg4, arg5, arg6)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(anyService is ServiceImp1)
|
||||
@@ -221,8 +199,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)
|
||||
@@ -236,8 +214,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)
|
||||
@@ -251,8 +229,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)
|
||||
@@ -263,11 +241,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)
|
||||
@@ -276,20 +254,28 @@ class RuntimeArgumentsTests: XCTestCase {
|
||||
|
||||
func testThatDifferentFactoriesRegisteredIfArgumentIsOptional() {
|
||||
//given
|
||||
let name1 = "1", name2 = "2", name3 = "3"
|
||||
let name1 = "1", name2 = "2"
|
||||
container.register { (port: Int, url: String) in ServiceImp(name: name1, baseURL: url, port: port) as Service }
|
||||
container.register { (port: Int, url: String?) in ServiceImp(name: name2, baseURL: url!, port: port) as Service }
|
||||
container.register { (port: Int, url: String!) in ServiceImp(name: name3, baseURL: url, port: port) as Service }
|
||||
|
||||
//when
|
||||
let service1 = try! container.resolve(withArguments: 80, "http://example.com") as Service
|
||||
let service2 = try! container.resolve(withArguments: 80, "http://example.com" as String?) as Service
|
||||
let service3 = try! container.resolve(withArguments: 80, "http://example.com" as String!) as Service
|
||||
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
|
||||
|
||||
//then
|
||||
XCTAssertEqual(service1.name, name1)
|
||||
XCTAssertEqual(service2.name, name2)
|
||||
XCTAssertEqual(service3.name, name3)
|
||||
|
||||
//Due to incomplete implementation of SE-0054 (bug: https://bugs.swift.org/browse/SR-2143)
|
||||
//registering definition with T? and T! arguments types will produce two different definitions
|
||||
//but when argement of T! will be passed to `resolve` method it will be transformed to T?
|
||||
//and wrong definition will be used
|
||||
//When fixed using T? and T! should not register two different definitions
|
||||
|
||||
// let name3 = "3"
|
||||
// container.register { (port: Int, url: String!) in ServiceImp(name: name3, baseURL: url, port: port) as Service }
|
||||
// let service3 = try! container.resolve(arguments: 80, "http://example.com" as String!) as Service
|
||||
// XCTAssertEqual(service3.name, name3)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,11 +22,12 @@
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
#if canImport(ObjectiveC)
|
||||
import XCTest
|
||||
@testable import Dip
|
||||
|
||||
private protocol Server: class {
|
||||
var client: Client? { get set }
|
||||
var client: Client! { get set }
|
||||
}
|
||||
|
||||
private protocol Client: class {
|
||||
@@ -45,11 +46,11 @@ private func ==<T: ClientImp>(lhs: T, rhs: T) -> Bool {
|
||||
}
|
||||
|
||||
private class ServerImp: Server, Hashable {
|
||||
weak var client: Client?
|
||||
weak var client: Client!
|
||||
init() {}
|
||||
|
||||
var hashValue: Int {
|
||||
return unsafeAddressOf(self).hashValue
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(ObjectIdentifier(self))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,37 +63,21 @@ private var resolvedClients = Array<ClientImp>()
|
||||
|
||||
private var container: DependencyContainer!
|
||||
|
||||
#if os(Linux)
|
||||
import Glibc
|
||||
private var lock: pthread_spinlock_t = 0
|
||||
|
||||
private let resolveClientSync: () -> Client? = {
|
||||
var clientPointer: UnsafeMutablePointer<Void> = nil
|
||||
clientPointer = dispatch_sync { _ in
|
||||
let resolved = try! container.resolve() as Client
|
||||
return UnsafeMutablePointer(Unmanaged.passUnretained(resolved as! ClientImp).toOpaque())
|
||||
}
|
||||
return Unmanaged<ClientImp>.fromOpaque(COpaquePointer(clientPointer)).takeUnretainedValue()
|
||||
}
|
||||
|
||||
#else
|
||||
let queue = NSOperationQueue()
|
||||
let lock = NSRecursiveLock()
|
||||
let queue = OperationQueue()
|
||||
let lock = RecursiveLock()
|
||||
|
||||
private let resolveClientSync: () -> Client? = {
|
||||
var client: Client?
|
||||
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
|
||||
DispatchQueue.global(qos: .default).sync() {
|
||||
client = try! container.resolve() as Client
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
let resolveServerAsync = {
|
||||
let service = try! container.resolve() as Server
|
||||
let server = try! container.resolve() as Server
|
||||
lock.lock()
|
||||
resolvedServers.insert(service as! ServerImp)
|
||||
resolvedServers.insert(server as! ServerImp)
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
@@ -105,29 +90,8 @@ let resolveClientAsync = {
|
||||
|
||||
class ThreadSafetyTests: XCTestCase {
|
||||
|
||||
#if os(Linux)
|
||||
required init(name: String, testClosure: XCTestCase throws -> Void) {
|
||||
pthread_spin_init(&lock, 0)
|
||||
}
|
||||
|
||||
static var allTests: [(String, ThreadSafetyTests -> () throws -> Void)] {
|
||||
return [
|
||||
("testSingletonThreadSafety", testSingletonThreadSafety),
|
||||
("testFactoryThreadSafety", testFactoryThreadSafety),
|
||||
("testCircularReferenceThreadSafety", testCircularReferenceThreadSafety)
|
||||
]
|
||||
}
|
||||
|
||||
func setUp() {
|
||||
container = DependencyContainer()
|
||||
}
|
||||
|
||||
func tearDown() {
|
||||
resolvedServers.removeAll()
|
||||
resolvedClients.removeAll()
|
||||
}
|
||||
#else
|
||||
override func setUp() {
|
||||
Dip.logLevel = .Verbose
|
||||
container = DependencyContainer()
|
||||
}
|
||||
|
||||
@@ -135,27 +99,15 @@ class ThreadSafetyTests: XCTestCase {
|
||||
resolvedServers.removeAll()
|
||||
resolvedClients.removeAll()
|
||||
}
|
||||
#endif
|
||||
|
||||
func testSingletonThreadSafety() {
|
||||
container.register(.Singleton) { ServerImp() as Server }
|
||||
container.register(.singleton) { ServerImp() as Server }
|
||||
|
||||
for _ in 0..<100 {
|
||||
#if os(Linux)
|
||||
dispatch_async({ _ in
|
||||
resolveServerAsync()
|
||||
return nil
|
||||
})
|
||||
#else
|
||||
queue.addOperationWithBlock(resolveServerAsync)
|
||||
#endif
|
||||
queue.addOperation(resolveServerAsync)
|
||||
}
|
||||
|
||||
#if os(Linux)
|
||||
sleep(1)
|
||||
#else
|
||||
queue.waitUntilAllOperationsAreFinished()
|
||||
#endif
|
||||
|
||||
XCTAssertEqual(resolvedServers.count, 1, "Should create only one instance")
|
||||
}
|
||||
@@ -165,52 +117,30 @@ class ThreadSafetyTests: XCTestCase {
|
||||
container.register { ServerImp() as Server }
|
||||
|
||||
for _ in 0..<100 {
|
||||
#if os(Linux)
|
||||
dispatch_async({ _ in
|
||||
resolveServerAsync()
|
||||
return nil
|
||||
})
|
||||
#else
|
||||
queue.addOperationWithBlock(resolveServerAsync)
|
||||
#endif
|
||||
queue.addOperation(resolveServerAsync)
|
||||
}
|
||||
|
||||
#if os(Linux)
|
||||
sleep(1)
|
||||
#else
|
||||
queue.waitUntilAllOperationsAreFinished()
|
||||
#endif
|
||||
|
||||
XCTAssertEqual(resolvedServers.count, 100, "All instances should be different")
|
||||
}
|
||||
|
||||
|
||||
func testCircularReferenceThreadSafety() {
|
||||
container.register(.ObjectGraph) {
|
||||
container.register {
|
||||
ClientImp(server: try container.resolve()) as Client
|
||||
}
|
||||
|
||||
container.register(.ObjectGraph) { ServerImp() as Server }
|
||||
.resolveDependencies { container, server in
|
||||
container.register { ServerImp() as Server }
|
||||
.resolvingProperties { container, server in
|
||||
server.client = resolveClientSync()
|
||||
}
|
||||
|
||||
for _ in 0..<100 {
|
||||
#if os(Linux)
|
||||
dispatch_async({ _ in
|
||||
resolveClientAsync()
|
||||
return nil
|
||||
})
|
||||
#else
|
||||
queue.addOperationWithBlock(resolveClientAsync)
|
||||
#endif
|
||||
queue.addOperation(resolveClientAsync)
|
||||
}
|
||||
|
||||
#if os(Linux)
|
||||
sleep(1)
|
||||
#else
|
||||
queue.waitUntilAllOperationsAreFinished()
|
||||
#endif
|
||||
|
||||
XCTAssertEqual(resolvedClients.count, 100, "Instances should be not reused in different object graphs")
|
||||
for client in resolvedClients {
|
||||
@@ -221,5 +151,4 @@ class ThreadSafetyTests: XCTestCase {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,385 @@
|
||||
//
|
||||
// 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 { }
|
||||
#if os(Linux)
|
||||
private class ServiceImp1: Service, ForwardedType { }
|
||||
private class ServiceImp2: Service, ForwardedType { }
|
||||
#else
|
||||
private class ServiceImp1: NSObject, Service, ForwardedType { }
|
||||
private class ServiceImp2: NSObject, Service, ForwardedType { }
|
||||
#endif
|
||||
|
||||
private protocol Dependency {}
|
||||
private struct DependencyImpl: Dependency {}
|
||||
private class DependencyRefImpl: Dependency {}
|
||||
|
||||
private struct DependencyClient {
|
||||
let dep: Dependency
|
||||
init(dependency: Dependency) {
|
||||
self.dep = dependency
|
||||
}
|
||||
}
|
||||
|
||||
private struct OptionalDependencyClient {
|
||||
let dep: Dependency?
|
||||
init(dependency: Dependency?) {
|
||||
self.dep = dependency
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TypeForwardingTests: XCTestCase {
|
||||
|
||||
let container = DependencyContainer()
|
||||
|
||||
override func setUp() {
|
||||
container.reset()
|
||||
}
|
||||
|
||||
func testThatItResolvesInstanceByTypeForwarding() {
|
||||
//given
|
||||
container.register { ServiceImp1() as Service }
|
||||
.implements(ForwardedType.self)
|
||||
.implements(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
|
||||
container.register() { ServiceImp1() as Service }
|
||||
.resolvingProperties { container, resolved in
|
||||
//when
|
||||
//resolving forwarded type
|
||||
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)
|
||||
}
|
||||
.implements(ForwardedType.self)
|
||||
.implements(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 }
|
||||
def.implements(ForwardedType.self, tag: "otherTag")
|
||||
|
||||
//then
|
||||
XCTAssertThrowsError(try self.container.resolve(tag: "tag") as ForwardedType)
|
||||
XCTAssertThrowsError(try self.container.resolve(ForwardedType.self, tag: "tag"))
|
||||
|
||||
//and given
|
||||
def.implements(ForwardedType.self, tag: "tag")
|
||||
|
||||
//then
|
||||
XCTAssertNoThrow(try self.container.resolve(tag: "tag") as ForwardedType)
|
||||
XCTAssertNoThrow(try self.container.resolve(ForwardedType.self, tag: "tag"))
|
||||
}
|
||||
|
||||
func testThatItDoesNotReuseInstanceResolvedByTypeForwardingRegisteredForAnotherTag() {
|
||||
var resolveDependenciesCalled = false
|
||||
//given
|
||||
container.register() { ServiceImp1() as Service }
|
||||
.resolvingProperties { container, service in
|
||||
guard resolveDependenciesCalled == false else { return }
|
||||
resolveDependenciesCalled = true
|
||||
|
||||
//when
|
||||
//resolving via type-forawrding for tag different then tag for original definition
|
||||
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)
|
||||
}
|
||||
.implements(ForwardedType.self, tag: "tag")
|
||||
.implements(NSObject.self)
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve() as Service
|
||||
}
|
||||
|
||||
func testThatItCallsResolvedDependenciesBlockWhenResolvingByTypeForwarding() {
|
||||
//given
|
||||
var originalResolvingPropertiesCalled = false
|
||||
var resolvingPropertiesCalled = false
|
||||
container.register { ServiceImp1() }
|
||||
.resolvingProperties { _,_ in
|
||||
originalResolvingPropertiesCalled = true
|
||||
}.implements(Service.self) { _,_ in
|
||||
resolvingPropertiesCalled = true
|
||||
}
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve() as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(resolvingPropertiesCalled)
|
||||
XCTAssertTrue(originalResolvingPropertiesCalled)
|
||||
|
||||
//and when
|
||||
resolvingPropertiesCalled = false
|
||||
originalResolvingPropertiesCalled = false
|
||||
let _ = try! container.resolve(Service.self)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(resolvingPropertiesCalled)
|
||||
XCTAssertTrue(originalResolvingPropertiesCalled)
|
||||
}
|
||||
|
||||
func testThatItCallsResolvedDependenciesBlockProvidedAfterRegistrationWhenResolvingByTypeForwarding() {
|
||||
//given
|
||||
//resolvingProperties provided after registering definition
|
||||
var originalResolvingPropertiesCalled = false
|
||||
var resolvingPropertiesCalled = false
|
||||
container.reset()
|
||||
let definition = container.register { ServiceImp1() }
|
||||
.implements(Service.self) { _,_ in
|
||||
resolvingPropertiesCalled = true
|
||||
}
|
||||
|
||||
definition.resolvingProperties { _,_ in
|
||||
originalResolvingPropertiesCalled = true
|
||||
}
|
||||
|
||||
//when
|
||||
let _ = try! container.resolve() as Service
|
||||
|
||||
//then
|
||||
XCTAssertTrue(resolvingPropertiesCalled)
|
||||
XCTAssertTrue(originalResolvingPropertiesCalled)
|
||||
|
||||
//and when
|
||||
resolvingPropertiesCalled = false
|
||||
originalResolvingPropertiesCalled = false
|
||||
let _ = try! container.resolve(Service.self)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(resolvingPropertiesCalled)
|
||||
XCTAssertTrue(originalResolvingPropertiesCalled)
|
||||
}
|
||||
|
||||
func testThatItFallbackToDefinitionWithNoTagWhenResolvingInstanceByTypeForwarding() {
|
||||
//given
|
||||
container.register { ServiceImp1() as Service }
|
||||
.implements(ForwardedType.self)
|
||||
|
||||
//when
|
||||
let service = try! container.resolve(tag: "tag") as ForwardedType
|
||||
let anyService = try! container.resolve(ForwardedType.self, tag: "tag")
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service is ServiceImp1)
|
||||
XCTAssertTrue(anyService is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItCanResolveOptional() {
|
||||
container.register { ServiceImp1() as Service }
|
||||
.implements(ForwardedType.self)
|
||||
.implements(NSObject.self)
|
||||
|
||||
//when
|
||||
let service = try! container.resolve() as Service?
|
||||
let anyService = try! container.resolve((Service?).self)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(service is ServiceImp1)
|
||||
XCTAssertTrue(anyService is ServiceImp1)
|
||||
|
||||
//when
|
||||
let forwardedType = try! container.resolve() as ForwardedType?
|
||||
let anyForwardedType = try! container.resolve((ForwardedType?).self)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(forwardedType is ServiceImp1)
|
||||
XCTAssertTrue(anyForwardedType is ServiceImp1)
|
||||
|
||||
//when
|
||||
let object = try! container.resolve() as NSObject?
|
||||
let anyObject = try! container.resolve((NSObject?).self)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(object is ServiceImp1)
|
||||
XCTAssertTrue(anyObject is ServiceImp1)
|
||||
}
|
||||
|
||||
func testThatItReusesInstancesResolvedForOptionalType() {
|
||||
var alreadyResolved = false
|
||||
container.register(.singleton) { () -> Dependency in
|
||||
XCTAssertFalse(alreadyResolved)
|
||||
return DependencyImpl() as Dependency
|
||||
}
|
||||
container.register() { DependencyClient(dependency: try! self.container.resolve()) }
|
||||
container.register() { OptionalDependencyClient(dependency: try! self.container.resolve()) }
|
||||
|
||||
let _ = try! container.resolve() as OptionalDependencyClient
|
||||
let _ = try! container.resolve() as DependencyClient
|
||||
|
||||
alreadyResolved = false
|
||||
|
||||
let _ = try! container.resolve() as DependencyClient
|
||||
let _ = try! container.resolve() as OptionalDependencyClient
|
||||
|
||||
container.register(.singleton) { () -> Dependency in
|
||||
XCTAssertFalse(alreadyResolved)
|
||||
return DependencyRefImpl() as Dependency
|
||||
}
|
||||
|
||||
let client1 = try! container.resolve() as DependencyClient
|
||||
let client2 = try! container.resolve() as OptionalDependencyClient
|
||||
|
||||
XCTAssertTrue(client1.dep as! DependencyRefImpl === client2.dep as! DependencyRefImpl)
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
container.register { ServiceImp1() as Service }
|
||||
.implements(ServiceImp2.self)
|
||||
|
||||
//then
|
||||
XCTAssertThrowsError(try self.container.resolve() as ServiceImp2) { error in
|
||||
guard case let DipError.invalidType(_, key) = error else {
|
||||
XCTFail("Thrown unexpected error: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
let expectedKey = DefinitionKey(type: ServiceImp2.self, typeOfArguments: Void.self, tag: nil)
|
||||
XCTAssertEqual(key, expectedKey)
|
||||
}
|
||||
XCTAssertThrowsError(try self.container.resolve(ServiceImp2.self)) { error in
|
||||
guard case let DipError.invalidType(_, key) = error else {
|
||||
XCTFail("Thrown unexpected error: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
let expectedKey = DefinitionKey(type: ServiceImp2.self, typeOfArguments: Void.self, tag: nil)
|
||||
XCTAssertEqual(key, expectedKey)
|
||||
}
|
||||
}
|
||||
|
||||
func testThatItOverridesIfSeveralDefinitionsWithTheSameTagForwardTheSameType() {
|
||||
let def1 = container.register { ServiceImp1() }
|
||||
let def2 = container.register { ServiceImp2() }
|
||||
|
||||
//when
|
||||
def1.implements(ForwardedType.self)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(try! container.resolve() as ForwardedType is ServiceImp1)
|
||||
XCTAssertTrue(try! container.resolve(ForwardedType.self) is ServiceImp1)
|
||||
|
||||
//when
|
||||
//another definition implements the same type
|
||||
def2.implements(ForwardedType.self)
|
||||
|
||||
//then
|
||||
XCTAssertTrue(try! container.resolve() as ForwardedType is ServiceImp2)
|
||||
XCTAssertTrue(try! container.resolve(ForwardedType.self) is ServiceImp2)
|
||||
|
||||
//when
|
||||
def1.implements(ForwardedType.self, tag: "tag")
|
||||
|
||||
//then
|
||||
XCTAssertTrue(try! container.resolve(tag: "tag") as ForwardedType is ServiceImp1)
|
||||
XCTAssertTrue(try! container.resolve(ForwardedType.self, tag: "tag") is ServiceImp1)
|
||||
|
||||
//when
|
||||
//another definition implements the same type for the same tag
|
||||
def2.implements(ForwardedType.self, tag: "tag")
|
||||
|
||||
//then
|
||||
XCTAssertTrue(try! container.resolve(tag: "tag") as ForwardedType is ServiceImp2)
|
||||
XCTAssertTrue(try! container.resolve(ForwardedType.self, tag: "tag") is ServiceImp2)
|
||||
|
||||
}
|
||||
|
||||
func testThatItDoesNotOverrideIfDefinitionForwardsTheSameTypeWithDifferentTag() {
|
||||
let def1 = container.register { ServiceImp1() }
|
||||
let def2 = container.register { ServiceImp2() }
|
||||
|
||||
//when
|
||||
def1.implements(ForwardedType.self, tag: "tag")
|
||||
//another definition implements the same type for a different tag
|
||||
def2.implements(ForwardedType.self, tag: "anotherTag")
|
||||
|
||||
//then
|
||||
XCTAssertTrue(try! container.resolve(tag: "tag") as ForwardedType is ServiceImp1)
|
||||
XCTAssertTrue(try! container.resolve(ForwardedType.self, tag: "tag") is ServiceImp1)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// Dip
|
||||
//
|
||||
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
#if os(Linux)
|
||||
typealias NSObject = AnyObject
|
||||
#endif
|
||||
|
||||
#if os(Linux)
|
||||
import Glibc
|
||||
typealias TMain = @convention(c) (UnsafeMutableRawPointer?) -> UnsafeMutableRawPointer?
|
||||
|
||||
private func startThread(_ block: @escaping TMain) -> pthread_t {
|
||||
var pid: pthread_t = 0
|
||||
pthread_create(&pid, nil, block, nil)
|
||||
return pid
|
||||
}
|
||||
|
||||
func dispatch_async(block: @escaping TMain) -> pthread_t {
|
||||
return startThread(block)
|
||||
}
|
||||
|
||||
func dispatch_sync(block: @escaping TMain) -> UnsafeMutableRawPointer? {
|
||||
var result: UnsafeMutableRawPointer? = UnsafeMutableRawPointer.allocate(byteCount: 1, alignment: 0)
|
||||
let pid = startThread(block)
|
||||
pthread_join(pid, &result)
|
||||
return result
|
||||
}
|
||||
|
||||
extension pthread_spinlock_t {
|
||||
mutating func lock() {
|
||||
pthread_spin_lock(&self)
|
||||
}
|
||||
mutating func unlock() {
|
||||
pthread_spin_unlock(&self)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,13 +0,0 @@
|
||||
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)
|
||||
])
|
||||
@@ -0,0 +1,202 @@
|
||||
#if !canImport(ObjectiveC)
|
||||
import XCTest
|
||||
|
||||
extension AutoInjectionTests {
|
||||
// DO NOT MODIFY: This is autogenerated, use:
|
||||
// `swift test --generate-linuxmain`
|
||||
// to regenerate.
|
||||
static let __allTests__AutoInjectionTests = [
|
||||
("testThatItAutoInjectsPropertyWithCollaboratingContainer", testThatItAutoInjectsPropertyWithCollaboratingContainer),
|
||||
("testThatItAutoInjectsWhenOverridenInDefinition", testThatItAutoInjectsWhenOverridenInDefinition),
|
||||
("testThatItCallsDidInjectOnAutoInjectedProperty", testThatItCallsDidInjectOnAutoInjectedProperty),
|
||||
("testThatItCallsResolveDependencyBlockWhenAutoInjecting", testThatItCallsResolveDependencyBlockWhenAutoInjecting),
|
||||
("testThatItDoesNotAutoInjectIfDisabledInContainer", testThatItDoesNotAutoInjectIfDisabledInContainer),
|
||||
("testThatItDoesNotAutoInjectIfDisabledInDefinition", testThatItDoesNotAutoInjectIfDisabledInDefinition),
|
||||
("testThatItDoesNotPassTagToAutoInjectedPropertyWithExplicitTag", testThatItDoesNotPassTagToAutoInjectedPropertyWithExplicitTag),
|
||||
("testThatItPassesTagToAutoInjectedProperty", testThatItPassesTagToAutoInjectedProperty),
|
||||
("testThatItResolvesAutoInjectedDependencies", testThatItResolvesAutoInjectedDependencies),
|
||||
("testThatItResolvesAutoInjectedSingletons", testThatItResolvesAutoInjectedSingletons),
|
||||
("testThatItResolvesInheritedDependencies", testThatItResolvesInheritedDependencies),
|
||||
("testThatItResolvesTaggedAutoInjectedProperties", testThatItResolvesTaggedAutoInjectedProperties),
|
||||
("testThatItReusesAutoInjectedInstancesOnNextResolveOrAutoInjection", testThatItReusesAutoInjectedInstancesOnNextResolveOrAutoInjection),
|
||||
("testThatItReusesResolvedAutoInjectedInstances", testThatItReusesResolvedAutoInjectedInstances),
|
||||
("testThatItThrowsErrorIfFailsToAutoInjectDependency", testThatItThrowsErrorIfFailsToAutoInjectDependency),
|
||||
("testThatNoErrorThrownWhenOptionalPropertiesAreNotAutoInjected", testThatNoErrorThrownWhenOptionalPropertiesAreNotAutoInjected),
|
||||
("testThatThereIsNoRetainCycleBetweenAutoInjectedCircularDependencies", testThatThereIsNoRetainCycleBetweenAutoInjectedCircularDependencies),
|
||||
]
|
||||
}
|
||||
|
||||
extension AutoWiringTests {
|
||||
// DO NOT MODIFY: This is autogenerated, use:
|
||||
// `swift test --generate-linuxmain`
|
||||
// to regenerate.
|
||||
static let __allTests__AutoWiringTests = [
|
||||
("testThatItCanAutoWireOptional", testThatItCanAutoWireOptional),
|
||||
("testThatItCanResolveWithAutoWiring", testThatItCanResolveWithAutoWiring),
|
||||
("testThatItDoesNotReuseInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgainWithAnotherTag", testThatItDoesNotReuseInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgainWithAnotherTag),
|
||||
("testThatItDoesNotTryToUseAutoWiringWhenCallingResolveWithArguments", testThatItDoesNotTryToUseAutoWiringWhenCallingResolveWithArguments),
|
||||
("testThatItDoesNotUseAutoWiringWhenFailedToResolveLowLevelDependency", testThatItDoesNotUseAutoWiringWhenFailedToResolveLowLevelDependency),
|
||||
("testThatItFallbackToNotTaggedFactoryWhenUsingAutoWire", testThatItFallbackToNotTaggedFactoryWhenUsingAutoWire),
|
||||
("testThatItPrefersTaggedFactoryWithDifferentNumberOfArgumentsWhenUsingAutoWire", testThatItPrefersTaggedFactoryWithDifferentNumberOfArgumentsWhenUsingAutoWire),
|
||||
("testThatItPrefersTaggedFactoryWithDifferentTypesOfArgumentsWhenUsingAutoWire", testThatItPrefersTaggedFactoryWithDifferentTypesOfArgumentsWhenUsingAutoWire),
|
||||
("testThatItReusesInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgain", testThatItReusesInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgain),
|
||||
("testThatItReusesInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgainWithTheSameTag", testThatItReusesInstancesResolvedWithAutoWiringWhenUsingAutoWiringAgainWithTheSameTag),
|
||||
("testThatItReusesInstancesResolvedWithoutAutoWiringWhenUsingAutoWiringAgain", testThatItReusesInstancesResolvedWithoutAutoWiringWhenUsingAutoWiringAgain),
|
||||
("testThatItThrowsAmbiguityErrorWhenUsingAutoWire", testThatItThrowsAmbiguityErrorWhenUsingAutoWire),
|
||||
("testThatItUsesAutoWireFactoryWithMostNumberOfArguments", testThatItUsesAutoWireFactoryWithMostNumberOfArguments),
|
||||
("testThatItUsesTagToResolveDependenciesWithAutoWiringWith1Argument", testThatItUsesTagToResolveDependenciesWithAutoWiringWith1Argument),
|
||||
("testThatItUsesTagToResolveDependenciesWithAutoWiringWith2Arguments", testThatItUsesTagToResolveDependenciesWithAutoWiringWith2Arguments),
|
||||
("testThatItUsesTagToResolveDependenciesWithAutoWiringWith3Arguments", testThatItUsesTagToResolveDependenciesWithAutoWiringWith3Arguments),
|
||||
("testThatItUsesTagToResolveDependenciesWithAutoWiringWith4Arguments", testThatItUsesTagToResolveDependenciesWithAutoWiringWith4Arguments),
|
||||
("testThatItUsesTagToResolveDependenciesWithAutoWiringWith5Arguments", testThatItUsesTagToResolveDependenciesWithAutoWiringWith5Arguments),
|
||||
("testThatItUsesTagToResolveDependenciesWithAutoWiringWith6Arguments", testThatItUsesTagToResolveDependenciesWithAutoWiringWith6Arguments),
|
||||
]
|
||||
}
|
||||
|
||||
extension ComponentScopeTests {
|
||||
// DO NOT MODIFY: This is autogenerated, use:
|
||||
// `swift test --generate-linuxmain`
|
||||
// to regenerate.
|
||||
static let __allTests__ComponentScopeTests = [
|
||||
("testThatCollaboratingContainersReuseSingletonsResolvedByAnotherContainer", testThatCollaboratingContainersReuseSingletonsResolvedByAnotherContainer),
|
||||
("testThatContainerCanBeBootstrappedAgainAfterReset", testThatContainerCanBeBootstrappedAgainAfterReset),
|
||||
("testThatItDoesNotReuseInstanceInSharedScopeInNextResolve", testThatItDoesNotReuseInstanceInSharedScopeInNextResolve),
|
||||
("testThatItDoesNotReuseInstanceInSharedScopeResolvedForNilTagWhenResolvingForAnotherTag", testThatItDoesNotReuseInstanceInSharedScopeResolvedForNilTagWhenResolvingForAnotherTag),
|
||||
("testThatItHoldsWeakReferenceToWeakSingletonInstance", testThatItHoldsWeakReferenceToWeakSingletonInstance),
|
||||
("testThatItResolvesTypeAsNewInstanceForUniqueScope", testThatItResolvesTypeAsNewInstanceForUniqueScope),
|
||||
("testThatItResolvesWeakSingletonAgainAfterItWasReleased", testThatItResolvesWeakSingletonAgainAfterItWasReleased),
|
||||
("testThatItReusesInstanceForSingletonScope", testThatItReusesInstanceForSingletonScope),
|
||||
("testThatItReusesInstanceInSharedScopeDuringResolve", testThatItReusesInstanceInSharedScopeDuringResolve),
|
||||
("testThatItReusesInstanceInSharedScopeResolvedForNilTag", testThatItReusesInstanceInSharedScopeResolvedForNilTag),
|
||||
("testThatItReusesResolvedInstanceWhenResolvingOptional", testThatItReusesResolvedInstanceWhenResolvingOptional),
|
||||
("testThatOnlyEagerSingletonIsCreatedWhenContainerIsBootsrapped", testThatOnlyEagerSingletonIsCreatedWhenContainerIsBootsrapped),
|
||||
("testThatScopeCanBeChanged", testThatScopeCanBeChanged),
|
||||
("testThatSharedIsDefaultScope", testThatSharedIsDefaultScope),
|
||||
("testThatSingletonIsNotReusedAcrossContainers", testThatSingletonIsNotReusedAcrossContainers),
|
||||
("testThatSingletonIsReleasedWhenContainerIsReset", testThatSingletonIsReleasedWhenContainerIsReset),
|
||||
("testThatSingletonIsReleasedWhenDefinitionIsOverridden", testThatSingletonIsReleasedWhenDefinitionIsOverridden),
|
||||
("testThatSingletonIsReleasedWhenDefinitionIsRemoved", testThatSingletonIsReleasedWhenDefinitionIsRemoved),
|
||||
]
|
||||
}
|
||||
|
||||
extension ContextTests {
|
||||
// DO NOT MODIFY: This is autogenerated, use:
|
||||
// `swift test --generate-linuxmain`
|
||||
// to regenerate.
|
||||
static let __allTests__ContextTests = [
|
||||
("testThatContextDoesNotOverrideNilTagPassedToResolve", testThatContextDoesNotOverrideNilTagPassedToResolve),
|
||||
("testThatContextIsPreservedWhenResolvingWithCollaboration", testThatContextIsPreservedWhenResolvingWithCollaboration),
|
||||
("testThatContextStoresCurrentlyResolvedType", testThatContextStoresCurrentlyResolvedType),
|
||||
("testThatContextStoresInjectedInType", testThatContextStoresInjectedInType),
|
||||
("testThatContextStoresNameOfAutoInjectedProperty", testThatContextStoresNameOfAutoInjectedProperty),
|
||||
("testThatContextStoresTheTagPassedToResolve", testThatContextStoresTheTagPassedToResolve),
|
||||
("testThatContextStoresTheTagPassedToResolveWhenAutoInjecting", testThatContextStoresTheTagPassedToResolveWhenAutoInjecting),
|
||||
("testThatContextStoresTheTagPassedToResolveWhenAutoWiring", testThatContextStoresTheTagPassedToResolveWhenAutoWiring),
|
||||
("testThatItDoesNotSetInjectedInTypeWhenResolvingWithCollaboration", testThatItDoesNotSetInjectedInTypeWhenResolvingWithCollaboration),
|
||||
]
|
||||
}
|
||||
|
||||
extension DefinitionTests {
|
||||
// DO NOT MODIFY: This is autogenerated, use:
|
||||
// `swift test --generate-linuxmain`
|
||||
// to regenerate.
|
||||
static let __allTests__DefinitionTests = [
|
||||
("testThatDefinitionKeyIsEqualBy_Type_Factory_Tag", testThatDefinitionKeyIsEqualBy_Type_Factory_Tag),
|
||||
("testThatDefinitionKeysWithDifferentFactoriesAreNotEqual", testThatDefinitionKeysWithDifferentFactoriesAreNotEqual),
|
||||
("testThatDefinitionKeysWithDifferentTagsAreNotEqual", testThatDefinitionKeysWithDifferentTagsAreNotEqual),
|
||||
("testThatDefinitionKeysWithDifferentTypesAreNotEqual", testThatDefinitionKeysWithDifferentTypesAreNotEqual),
|
||||
("testThatItRegisteresOptionalTypesAsForwardedTypes", testThatItRegisteresOptionalTypesAsForwardedTypes),
|
||||
("testThatResolveDependenciesBlockIsNotCalledWhenPassedWrongInstance", testThatResolveDependenciesBlockIsNotCalledWhenPassedWrongInstance),
|
||||
("testThatResolveDependenciesCallsResolveDependenciesBlock", testThatResolveDependenciesCallsResolveDependenciesBlock),
|
||||
]
|
||||
}
|
||||
|
||||
extension DipTests {
|
||||
// DO NOT MODIFY: This is autogenerated, use:
|
||||
// `swift test --generate-linuxmain`
|
||||
// to regenerate.
|
||||
static let __allTests__DipTests = [
|
||||
("test_weak_mirror_regression", test_weak_mirror_regression),
|
||||
("testItCallsResolveDependenciesOnResolableInstance", testItCallsResolveDependenciesOnResolableInstance),
|
||||
("testThatCollaboratingContainersAreWeakReferences", testThatCollaboratingContainersAreWeakReferences),
|
||||
("testThatCollaboratingContainersReuseInstancesResolvedByAnotherContainer", testThatCollaboratingContainersReuseInstancesResolvedByAnotherContainer),
|
||||
("testThatCollaboratingWithSelfIsIgnored", testThatCollaboratingWithSelfIsIgnored),
|
||||
("testThatCollaborationReferencesAreRecursivelyUpdate", testThatCollaborationReferencesAreRecursivelyUpdate),
|
||||
("testThatContainerAutowireBeforeCollaboration", testThatContainerAutowireBeforeCollaboration),
|
||||
("testThatContainersShareTheirSingletonsOnlyWithCollaborators", testThatContainersShareTheirSingletonsOnlyWithCollaborators),
|
||||
("testThatCreatingContainerWithConfigBlockDoesNotCreateRetainCycle", testThatCreatingContainerWithConfigBlockDoesNotCreateRetainCycle),
|
||||
("testThatItCallsDidResolveDependenciesInReverseOrder", testThatItCallsDidResolveDependenciesInReverseOrder),
|
||||
("testThatItCallsDidResolveDependenciesOnResolvableIntance", testThatItCallsDidResolveDependenciesOnResolvableIntance),
|
||||
("testThatItCallsResolveDependenciesOnDefinition", testThatItCallsResolveDependenciesOnDefinition),
|
||||
("testThatItCanHandleSeparateContainersAndTheirCollaboration", testThatItCanHandleSeparateContainersAndTheirCollaboration),
|
||||
("testThatItCanResolveUsingContainersCollaboration", testThatItCanResolveUsingContainersCollaboration),
|
||||
("testThatItFailsValidationIfNoMatchingArgumentsFound", testThatItFailsValidationIfNoMatchingArgumentsFound),
|
||||
("testThatItFailsValidationOnlyForDipErrors", testThatItFailsValidationOnlyForDipErrors),
|
||||
("testThatItPicksRuntimeArgumentsWhenValidatingConfiguration", testThatItPicksRuntimeArgumentsWhenValidatingConfiguration),
|
||||
("testThatItResolvesCircularDependencies", testThatItResolvesCircularDependencies),
|
||||
("testThatItResolvesDifferentInstancesRegisteredForDifferentTags", testThatItResolvesDifferentInstancesRegisteredForDifferentTags),
|
||||
("testThatItResolvesInstanceRegisteredWithoutTag", testThatItResolvesInstanceRegisteredWithoutTag),
|
||||
("testThatItResolvesInstanceRegisteredWithTag", testThatItResolvesInstanceRegisteredWithTag),
|
||||
("testThatItThrowsErrorIfCanNotFindDefinitionForFactoryWithArguments", testThatItThrowsErrorIfCanNotFindDefinitionForFactoryWithArguments),
|
||||
("testThatItThrowsErrorIfCanNotFindDefinitionForTag", testThatItThrowsErrorIfCanNotFindDefinitionForTag),
|
||||
("testThatItThrowsErrorIfCanNotFindDefinitionForType", testThatItThrowsErrorIfCanNotFindDefinitionForType),
|
||||
("testThatItThrowsErrorIfConstructorThrows", testThatItThrowsErrorIfConstructorThrows),
|
||||
("testThatItThrowsErrorIfFailsToResolveDependency", testThatItThrowsErrorIfFailsToResolveDependency),
|
||||
("testThatItValidatesConfiguration", testThatItValidatesConfiguration),
|
||||
("testThatNewRegistrationOverridesPreviousRegistration", testThatNewRegistrationOverridesPreviousRegistration),
|
||||
]
|
||||
}
|
||||
|
||||
extension RuntimeArgumentsTests {
|
||||
// DO NOT MODIFY: This is autogenerated, use:
|
||||
// `swift test --generate-linuxmain`
|
||||
// to regenerate.
|
||||
static let __allTests__RuntimeArgumentsTests = [
|
||||
("testThatDifferentFactoriesRegisteredIfArgumentIsOptional", testThatDifferentFactoriesRegisteredIfArgumentIsOptional),
|
||||
("testThatItRegistersDifferentFactoriesForDifferentNumberOfArguments", testThatItRegistersDifferentFactoriesForDifferentNumberOfArguments),
|
||||
("testThatItRegistersDifferentFactoriesForDifferentOrderOfArguments", testThatItRegistersDifferentFactoriesForDifferentOrderOfArguments),
|
||||
("testThatItRegistersDifferentFactoriesForDifferentTypesOfArguments", testThatItRegistersDifferentFactoriesForDifferentTypesOfArguments),
|
||||
("testThatItResolvesInstanceWithFiveArguments", testThatItResolvesInstanceWithFiveArguments),
|
||||
("testThatItResolvesInstanceWithFourArguments", testThatItResolvesInstanceWithFourArguments),
|
||||
("testThatItResolvesInstanceWithOneArgument", testThatItResolvesInstanceWithOneArgument),
|
||||
("testThatItResolvesInstanceWithSixArguments", testThatItResolvesInstanceWithSixArguments),
|
||||
("testThatItResolvesInstanceWithThreeArguments", testThatItResolvesInstanceWithThreeArguments),
|
||||
("testThatItResolvesInstanceWithTwoArguments", testThatItResolvesInstanceWithTwoArguments),
|
||||
("testThatNewRegistrationWithSameArgumentsOverridesPreviousRegistration", testThatNewRegistrationWithSameArgumentsOverridesPreviousRegistration),
|
||||
]
|
||||
}
|
||||
|
||||
extension TypeForwardingTests {
|
||||
// DO NOT MODIFY: This is autogenerated, use:
|
||||
// `swift test --generate-linuxmain`
|
||||
// to regenerate.
|
||||
static let __allTests__TypeForwardingTests = [
|
||||
("testThatItCallsResolvedDependenciesBlockProvidedAfterRegistrationWhenResolvingByTypeForwarding", testThatItCallsResolvedDependenciesBlockProvidedAfterRegistrationWhenResolvingByTypeForwarding),
|
||||
("testThatItCallsResolvedDependenciesBlockWhenResolvingByTypeForwarding", testThatItCallsResolvedDependenciesBlockWhenResolvingByTypeForwarding),
|
||||
("testThatItCanResolveOptional", testThatItCanResolveOptional),
|
||||
("testThatItDoesNotOverrideIfDefinitionForwardsTheSameTypeWithDifferentTag", testThatItDoesNotOverrideIfDefinitionForwardsTheSameTypeWithDifferentTag),
|
||||
("testThatItDoesNotResolveByTypeForwardingIfRegisteredForAnotherTag", testThatItDoesNotResolveByTypeForwardingIfRegisteredForAnotherTag),
|
||||
("testThatItDoesNotReuseInstanceResolvedByTypeForwardingRegisteredForAnotherTag", testThatItDoesNotReuseInstanceResolvedByTypeForwardingRegisteredForAnotherTag),
|
||||
("testThatItFallbackToDefinitionWithNoTagWhenResolvingInstanceByTypeForwarding", testThatItFallbackToDefinitionWithNoTagWhenResolvingInstanceByTypeForwarding),
|
||||
("testThatItFirstUsesTaggedDefinitionWhenResolvingOptional", testThatItFirstUsesTaggedDefinitionWhenResolvingOptional),
|
||||
("testThatItOverridesIfSeveralDefinitionsWithTheSameTagForwardTheSameType", testThatItOverridesIfSeveralDefinitionsWithTheSameTagForwardTheSameType),
|
||||
("testThatItResolvesInstanceByTypeForwarding", testThatItResolvesInstanceByTypeForwarding),
|
||||
("testThatItReusesInstanceResolvedByTypeForwarding", testThatItReusesInstanceResolvedByTypeForwarding),
|
||||
("testThatItReusesInstancesResolvedForOptionalType", testThatItReusesInstancesResolvedForOptionalType),
|
||||
("testThatItThrowsErrorWhenResolvingNotImplementedTypeWithTypeForwarding", testThatItThrowsErrorWhenResolvingNotImplementedTypeWithTypeForwarding),
|
||||
]
|
||||
}
|
||||
|
||||
public func __allTests() -> [XCTestCaseEntry] {
|
||||
return [
|
||||
testCase(AutoInjectionTests.__allTests__AutoInjectionTests),
|
||||
testCase(AutoWiringTests.__allTests__AutoWiringTests),
|
||||
testCase(ComponentScopeTests.__allTests__ComponentScopeTests),
|
||||
testCase(ContextTests.__allTests__ContextTests),
|
||||
testCase(DefinitionTests.__allTests__DefinitionTests),
|
||||
testCase(DipTests.__allTests__DipTests),
|
||||
testCase(RuntimeArgumentsTests.__allTests__RuntimeArgumentsTests),
|
||||
testCase(TypeForwardingTests.__allTests__TypeForwardingTests),
|
||||
]
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user