Compare commits

...

385 Commits

Author SHA1 Message Date
Ilya Puchka 142a348b20 removed unneded builds from travis 2018-04-28 20:05:53 +01:00
Ilya Puchka 88f101639d updated travis config 2018-04-28 19:48:03 +01:00
Ilya Puchka 5e70633ece bumped swift version to 4.1 2018-04-28 19:43:49 +01:00
Ilya Puchka af57aa26ed bumped version to 6.1 2018-04-28 19:41:45 +01:00
Ilya Puchka 6d6d77d603 fixed swift 4.1 warnings 2018-04-28 19:39:46 +01:00
Ilya Puchka 8d2f6ab8fb Revert #171 (#189)
* Revert "fix merge conflict"

This reverts commit aff01c541b.

* Revert "Merge branch 'develop' into develop"

This reverts commit a8dd47cee5, reversing
changes made to 9e7bd51bcd.
2018-04-28 19:21:05 +01:00
Ilya Puchka 2cc5310d4b Merge pull request #182 from tapsandswipes/develop
Fix crash with colaborator containers and weak singletons
2018-02-26 13:25:17 +00:00
Antonio Cabezuelo Vivo 5cb03a11bb Fix crash with colaborator containers and weak singletons
Signed-off-by: Antonio Cabezuelo Vivo <antonio@tapsandswipes.com>
2018-02-12 14:17:31 +01:00
Ilya Puchka aff01c541b fix merge conflict 2017-12-12 16:56:23 +01:00
Ilya Puchka a8dd47cee5 Merge branch 'develop' into develop 2017-12-12 16:51:32 +01:00
Ilya Puchka 12738a665f Merge pull request #171 from creditkarma/parent_child_containers
Parent child container support for Dip
2017-12-12 16:33:32 +01:00
Oleksa 'trimm' Korin 9e7bd51bcd Restructured tests, fixed crash 2017-10-30 23:49:44 +02:00
Oleksa 'trimm' Korin ce394e7d2b Added reproduction test 2017-10-30 23:26:36 +02:00
Oleksa 'trimm' Korin cd2c66f4a8 Initial try for collaboration reproduction crash 2017-10-30 20:45:51 +02:00
Ilya Puchka 5464f00bad Merge pull request #177 from AliSoftware/release/6.0
Release 6.0
2017-10-09 18:12:39 +02:00
Ilya Puchka 5b9cc50190 Merge branch 'master' into release/6.0 2017-09-27 00:35:49 +02:00
Ilya Puchka a7c8616300 updated changelog 2017-09-27 00:34:57 +02:00
Ilya Puchka 8349fbd8d8 bump version to 6.0 2017-09-26 23:51:08 +02:00
Ilya Puchka 4b1ecbcb8e removed previously deprecated method 2017-09-26 23:50:56 +02:00
Ilya Puchka 2f0e1fdb10 Merge pull request #176 from AliSoftware/swift4
Swift 4 migration
2017-09-26 23:45:25 +02:00
Ilya Puchka 4a8a1daeca swift version settings update 2017-09-21 22:26:19 +02:00
Ilya Puchka 56554147cb travis update 2017-09-16 19:36:07 +02:00
Ilya Puchka a554454afe fixed failing tests 2017-09-16 19:24:15 +02:00
Ilya Puchka dde2e98953 project settings updated and warnings fixed 2017-09-16 18:52:58 +02:00
Ilya Puchka a2c04ed61e migrate tests 2017-09-16 18:39:12 +02:00
Ilya Puchka c64cf720b8 swift 4 migration 2017-09-16 18:04:36 +02:00
John Twigg 3429c6ae0b Parent child container support for Dip 2017-08-02 10:41:03 -07:00
Ilya Puchka 451fb03bc8 updated changelog 2017-07-30 12:31:06 +02:00
Ilya Puchka b1ad2a65b2 Merge pull request #169 from AliSoftware/fix-collaboration-sharing-singletons
Fix: containers with common collaborator share resolved instances for their own definitions
2017-07-30 12:19:34 +02:00
Ilya Puchka 3f8b83b87c addet test for peforming autowiring before collaboration 2017-07-29 18:16:54 +02:00
Ilya Puchka b7bf4904e9 fixed sharing singletons during collaboration 2017-07-29 18:04:28 +02:00
Ilya Puchka 199b1aec8f Merge pull request #156 from kandelvijaya/improvement/equatableIntoItsExtension
Added Equatable conformance on the site of declaration
2017-04-14 23:06:30 +02:00
Ilya Puchka 22d19697f1 Merge pull request #155 from kandelvijaya/patch-1
Playgrounds typo fixes
2017-04-14 23:05:22 +02:00
vkandel d809177273 moved equatable conformance into Hashable declaration site for DefinationKey 2017-04-09 21:41:10 +02:00
vkandel 420c866726 Added Equatable conformance on the site of declaration 2017-04-09 21:32:31 +02:00
Vijaya Prakash Kandel 6e6cc0b1df Merge pull request #1 from kandelvijaya/patch-2
Update Contents.swift
2017-04-09 21:04:19 +02:00
Vijaya Prakash Kandel b71f50cef5 Update Contents.swift 2017-04-09 21:03:56 +02:00
Vijaya Prakash Kandel 462d528474 Update Contents.swift 2017-04-09 21:02:00 +02:00
Ilya Puchka 01c318bd5c Merge branch 'release/5.1' into develop 2017-04-09 13:20:40 +02:00
Ilya Puchka a0c890c931 Merge pull request #152 from AliSoftware/release/5.1
Release 5.1
2017-04-09 13:18:56 +02:00
Ilya Puchka 9b2a7918d8 Merge pull request #154 from AliSoftware/auto-wiring-tag-fix
Fixed auto-wiring with tagged definitions
2017-04-09 13:08:04 +02:00
Ilya Puchka 218b5a4e9a disable running tests on watchos on Travis
https://github.com/travis-ci/travis-ci/issues/7580
2017-04-09 12:56:22 +02:00
Ilya Puchka 0b221f3368 fixed auto-wiring with tagged definitions 2017-04-09 12:08:48 +02:00
Ilya Puchka 3b943c10ef Merge branch 'master' into release/5.1 2017-04-06 22:51:21 +02:00
Ilya Puchka a1da60bd14 updated CHANGELOG, bumped version to 5.1 2017-04-06 22:49:05 +02:00
Ilya Puchka 1825cd7d4c Merge pull request #151 from AliSoftware/recursive-collaboration
Fixed collaboration shared references
2017-04-06 22:40:08 +02:00
Ilya Puchka e7d8fb41e1 fixed collaboration references, now collaboration is bidirectional 2017-04-06 21:46:08 +02:00
Ilya Puchka 1c398defeb Merge pull request #150 from AliSoftware/drop-swift2.3
Drop Swift 2.3
2017-04-06 10:49:51 +02:00
Ilya Puchka ab561546f0 allow spec warnings 2017-04-06 00:43:48 +02:00
Ilya Puchka c0fb925ab8 updated travis config to build for 3.0 and 3.1 on Linux 2017-04-06 00:00:22 +02:00
Ilya Puchka 80ee4865ce drop swift 2.3 support 2017-04-01 00:44:10 +02:00
Ilya Puchka d95df1343e minor changes for loging function 2017-03-30 19:43:11 +02:00
Ilya Puchka a83d866cbd Merge pull request #146 from Pr0Ger/develop
Enable use of custom or no logging function
2017-03-30 19:42:25 +02:00
Ilya Puchka cc1dcba4b9 Merge pull request #145 from DenHeadless/patch-1
Fix swift 3.1 warning
2017-03-30 19:15:19 +02:00
Sergey Petrov 0ceb6a3503 Enable use of custom or no logging function 2017-03-30 17:02:11 +03:00
Denys Telezhkin 4e641a6465 fix swift 3.1 warning 2017-03-30 15:14:27 +03:00
Ilya Puchka 35b6da8556 fixed method name inconsistency 2017-03-10 00:01:37 +01:00
Ilya Puchka 47bc1913e3 Merge pull request #141 from AliSoftware/swift-3.0.2
Swift 3.0.2
2017-01-23 23:24:02 +01:00
Ilya Puchka 24d341503e swift 3.0.2 2017-01-21 23:04:50 +01:00
Ilya Puchka 3c1331089b Merge branch 'hotfix/swift2.3-api-diff' into develop 2016-11-01 17:24:21 +03:00
Ilya Puchka 7af8957a01 Merge pull request #135 from AliSoftware/hotfix/swift2.3-api-diff
Hotfix - swift2.3 api diff
2016-11-01 18:23:04 +04:00
Ilya Puchka 6b68bea55d bumped version to 5.0.4 2016-11-01 14:52:37 +03:00
Ilya Puchka 5b2168ecef fixed broken swift 2.3 api 2016-11-01 14:47:33 +03:00
Ilya Puchka 5eb0eece56 Merge branch 'release/5.0.3' into develop 2016-10-23 23:54:58 +03:00
Ilya Puchka bdf4477774 Merge pull request #131 from AliSoftware/release/5.0.3
Release 5.0.3
2016-10-24 00:53:32 +04:00
Ilya Puchka 84573d967b fixed typo 2016-10-23 22:19:19 +03:00
Ilya Puchka 83511d601b Merge branch 'master' into release/5.0.3 2016-10-21 22:01:23 +04:00
Ilya Puchka b24734e1c9 fixed access levels 2016-10-13 00:04:31 +02:00
Ilya Puchka ab9abbe7ab bumped version to 5.0.3 2016-10-12 23:54:30 +02:00
Ilya Puchka 298cf83be3 updated README and CHANGELOG 2016-10-12 23:54:30 +02:00
Ilya Puchka 455456e817 Merge pull request #127 from AliSoftware/feature/swift-compatibility
Swift 2.3 compatibility
2016-10-12 22:59:02 +02:00
Ilya Puchka 905cbdadb0 swift 2.3 compatibility 2016-10-12 20:47:33 +02:00
Ilya Puchka 945caa451a sligtly improved logging 2016-10-11 13:40:10 +02:00
Ilya Puchka bfacca6fd0 Merge pull request #129 from AliSoftware/features/fixed-reusing-released-weak-singletons
Fixed reusing instances for weak singletons
2016-10-10 15:08:03 +02:00
Ilya Puchka 21673d1f4d added some tests 2016-10-10 15:07:22 +02:00
Ilya Puchka 65175fa372 fixed reusing instances for weak singletons when underlying instance was already released 2016-10-10 14:02:11 +02:00
Ilya Puchka 99fb4ea081 Merge branch 'release/5.0.2' into develop 2016-10-09 11:34:29 +02:00
Ilya Puchka a1ece4b0ab Merge pull request #126 from AliSoftware/release/5.0.2
Release 5.0.2
2016-10-09 11:33:26 +02:00
Ilya Puchka 0b00e13e00 added hasPrefix method for Linux 2016-10-09 00:34:11 +02:00
Ilya Puchka 4aec626a1f bumped version to 5.0.2 2016-10-08 00:28:05 +02:00
Ilya Puchka d477392deb building against 3.0-RELEASE on Travis on Linux 2016-10-08 00:27:51 +02:00
Ilya Puchka 3b23dc1a6f updated README and CHANGELOG 2016-10-08 00:27:08 +02:00
Ilya Puchka 1d7d0052bf fixed logging 2016-10-07 22:47:07 +02:00
Ilya Puchka b1fee5c1db Merge pull request #125 from AliSoftware/features/swift-3-fixes
Fixed Swift 3 issues
2016-10-07 22:13:41 +02:00
Ilya Puchka e19b65d9c2 fixed issues with reflection of objectes with IUO properties 2016-10-07 19:19:05 +02:00
Ilya Puchka 86bb28da14 Merge branch 'hotfix/pod-fix' into develop 2016-09-16 12:13:30 +02:00
Ilya Puchka e4545e3ed3 Merge pull request #124 from AliSoftware/hotfix/pod-fix
Pod hotfix
2016-09-16 12:13:03 +02:00
Ilya Puchka 52455e663c bumped version to 5.0.1 2016-09-15 01:37:25 +02:00
Ilya Puchka 7a10d1e46e fixed linting podspec 2016-09-15 01:00:34 +02:00
Ilya Puchka e95df85503 Update README.md 2016-09-11 21:18:03 +02:00
Ilya Puchka a028c05271 Merge branch 'release/5.0.0' into develop 2016-09-11 20:48:56 +02:00
Ilya Puchka e18a4b131f Merge pull request #123 from AliSoftware/release/5.0.0
Release 5.0.0
2016-09-11 20:46:31 +02:00
Ilya Puchka e2799279ab Updated README 2016-09-11 20:23:40 +02:00
Ilya Puchka ffb61be63c Merge branch 'master' into release/5.0.0 2016-09-11 19:29:20 +02:00
Ilya Puchka 0070522341 bumped version to 5.0.0 2016-09-11 19:12:14 +02:00
Ilya Puchka 6842a016bf updated CHANGELOG 2016-09-11 19:12:14 +02:00
Ilya Puchka 6a6c4a358e lowercased DipError enum cases 2016-09-11 11:29:44 +02:00
Ilya Puchka 734e67f124 playground fix 2016-09-11 11:15:28 +02:00
Ilya Puchka aa46ef79b9 removed deprecated APIs 2016-09-11 11:12:14 +02:00
Ilya Puchka 2d177b7aea lowercased ComponentScope enum cases 2016-09-11 11:07:01 +02:00
Ilya Puchka 3ada18d756 Swift 3 (#120)
Swift 3 migration
2016-09-11 11:01:02 +02:00
Ilya Puchka 2441f096d6 Merge branch 'feature/single_target' into develop 2016-09-11 02:28:52 +02:00
Ilya Puchka f9f2777474 single target 2016-09-11 02:25:27 +02:00
Ilya Puchka 1dca889730 Merge pull request #121 from AliSoftware/feature/public-methods-renamed
Some public methods renamed
2016-09-11 02:05:15 +02:00
Ilya Puchka 86b23d7260 some public methods renamed 2016-09-11 01:48:57 +02:00
Ilya Puchka 85897e562d minor playground docs fixes 2016-09-11 00:38:15 +02:00
Ilya Puchka cdb5f1b901 Merge pull request #118 from AliSoftware/feature/invalid-type-error
Added error when resolved instance has mismatched type
2016-09-10 00:10:26 +02:00
Ilya Puchka 03903931f2 Merge pull request #117 from AliSoftware/feature/simplified-auto-wiring
Simplified auto-wiring
2016-09-06 23:23:23 +02:00
Ilya Puchka 5016ce860c added error for resolving instance with mismatched type 2016-09-06 01:25:16 +02:00
Ilya Puchka 8da6be8cad simplified auto-wiring 2016-09-06 00:40:59 +02:00
Ilya Puchka 873347aed0 fixed constructing context when resolving with containers collaboration 2016-08-29 23:25:02 +02:00
Ilya Puchka 637ff1f916 made Resolvable extension public 2016-08-29 00:41:14 +02:00
Ilya Puchka c8ddfad188 removed unneeded print 2016-08-27 22:35:19 +02:00
Ilya Puchka 22d73210d0 Merge pull request #116 from AliSoftware/feature/inheritance
Properly handling inheritance
2016-08-27 22:31:16 +02:00
Ilya Puchka feeb7b424a added resolveDependencies(_:DependencyContainer) method in Resolvable 2016-08-27 22:05:21 +02:00
Ilya Puchka a6d2a1b1ab fixed resolving inherited auto-injected dependencies 2016-08-27 21:47:10 +02:00
Ilya Puchka fa400b9a50 added missing precondition in implements method 2016-08-27 19:36:27 +02:00
Ilya Puchka 771c993d78 Merge pull request #114 from AliSoftware/feature/type-forwarding-improvements
Improved type forwarding
2016-08-27 00:25:59 +02:00
Ilya Puchka 10d2560b57 Merge pull request #115 from AliSoftware/feature/registering-explicit-type
Optional type parameter in register methods
2016-08-26 22:53:17 +02:00
Ilya Puchka 2887b78944 added optional type parameter to register methods 2016-08-26 21:40:33 +02:00
Ilya Puchka ed6d45e372 Update README.md 2016-08-26 01:39:25 +02:00
Ilya Puchka 7654737a34 Update README.md 2016-08-26 01:35:47 +02:00
Ilya Puchka f1d35a6a96 improved type forwarding
- removed deprecation annotation for register(_:type:tag:)
- added removing previously registered definition when overriding (for proper cleanup)
- fixed calling resolvingProperties set after registering type-forwarding
- improved tests and code comments
2016-08-26 00:38:01 +02:00
Ilya Puchka 0b355e0fbe some internal methods renaming 2016-08-26 00:34:09 +02:00
Ilya Puchka 9a8e189ce7 fixed playground 2016-08-21 17:44:44 +02:00
Ilya Puchka 53ff00f00d Merge pull request #113 from AliSoftware/feature/definition-refactoring
Definition refactoring
2016-08-21 00:47:05 +02:00
Ilya Puchka 1c18eab371 type forwarding with implements method 2016-08-21 00:11:10 +02:00
Ilya Puchka cd47d888aa renamed DefinitionOf to Definition and changed generic parameter type 2016-08-21 00:11:10 +02:00
Ilya Puchka 499b0c1e38 Merge pull request #112 from ilyapuchka/feature/shared-scope-as-default
Made shared scope a default argument value for register methods
2016-08-21 00:09:28 +02:00
Ilya Puchka 6536a2182d Made shared scope a default argument value for register methods 2016-08-20 23:41:13 +02:00
Ilya Puchka e2ae164d3e fixed crashing in DipUI (SR-680) 2016-08-20 02:12:31 +02:00
Ilya Puchka 092afd8a8b Update README.md 2016-08-20 00:58:06 +02:00
Ilya Puchka a7985f949a Merge branch 'release/4.6.1' into develop 2016-08-14 22:32:17 +02:00
Ilya Puchka 967e1a43cb Merge pull request #111 from AliSoftware/release/4.6.1
Release 4.6.1
2016-08-14 22:31:48 +02:00
Ilya Puchka 73225ddd10 bumped version to 4.6.1, updated README and CHANGELOG 2016-08-14 21:41:45 +02:00
Ilya Puchka 2d238265ca Merge pull request #105 from AliSoftware/feature/grand-renaming
Grand renaming
2016-08-14 20:56:26 +02:00
Ilya Puchka cdfbf3dbfe Update README.md 2016-08-07 00:26:51 +02:00
Ilya Puchka 21a3b43d9b renamed resolveDependencies to resolingProperities 2016-07-28 23:58:24 +02:00
Ilya Puchka 8f0f49e790 renamed withArguments to arguments 2016-07-28 23:35:26 +02:00
Ilya Puchka 7c5966ab61 switched tag and scope parameters in register method 2016-07-28 23:35:26 +02:00
Ilya Puchka c41696cd23 some internal methods renaming 2016-07-28 23:35:26 +02:00
Ilya Puchka 6e7a168fb7 renamed properties of DefinitionKey 2016-07-28 23:35:26 +02:00
Ilya Puchka a2efe0abff renamed Prototype and ObjectGraph scopes 2016-07-28 23:35:26 +02:00
Ilya Puchka d876389c19 Update README.md 2016-07-27 23:05:43 +02:00
Ilya Puchka 5c54e07219 Merge pull request #104 from AliSoftware/feature/config-retain-cycle
Some notes about avoiding retain cycle when configuring container
2016-07-27 22:29:48 +02:00
Ilya Puchka 0e32a79d8c added some notes about avoiding retain cycle when configuring container 2016-07-27 22:02:52 +02:00
Ilya Puchka 37c8b3d03d Merge pull request #103 from AliSoftware/feature/shared-singletons
Sharing singletons between collaborating containers
2016-07-27 21:14:54 +02:00
Ilya Puchka 21ea965607 fixed releasing weak singletons when resetting definition 2016-07-27 20:41:37 +02:00
Ilya Puchka 80d8b1ba99 fixed sharing singletons between collaborators 2016-07-27 20:41:37 +02:00
Ilya Puchka 975041933e Update README.md 2016-07-27 02:18:36 +02:00
Ilya Puchka 404b65b633 Update README.md 2016-07-27 02:16:07 +02:00
Ilya Puchka 73a78ed5a0 Merge branch 'release/4.6.0' into develop 2016-07-17 21:59:32 +02:00
Ilya Puchka 2371338e9b Merge pull request #100 from AliSoftware/release/4.6.0
Release 4.6.0
2016-07-17 21:58:33 +02:00
Ilya Puchka 65f4b0a4ab bumped version to 4.6.0 2016-07-17 21:48:13 +02:00
Ilya Puchka 2c7444fef8 updated CHANGELOG and README 2016-07-17 21:47:32 +02:00
Ilya Puchka daad001e17 fixed building on linux 2016-07-17 15:38:07 +02:00
Ilya Puchka 11ed3c975a Merge pull request #96 from AliSoftware/feature/weak-singletons
WeakSingleton scope
2016-07-17 15:36:38 +02:00
Ilya Puchka 02e0877fcb weak singleton scope 2016-07-17 15:24:05 +02:00
Ilya Puchka 54844f8459 improved comment explaining need of manual unboxing of optionals 2016-07-17 15:23:21 +02:00
Ilya Puchka fe006c4f13 Merge pull request #99 from AliSoftware/feature/improved-logging
Improved logging
2016-07-17 15:22:54 +02:00
Ilya Puchka 8e0e165aa4 Merge pull request #98 from AliSoftware/feature/fix-updating-context
Fix updating context
2016-07-17 15:07:57 +02:00
Ilya Puchka 0319bcff97 improved logging 2016-07-17 15:00:36 +02:00
Ilya Puchka ffdfdb0826 fix build error 2016-07-17 14:53:33 +02:00
Ilya Puchka c0884deddd simplified resolving with collaborator 2016-07-17 14:11:56 +02:00
Ilya Puchka 23318bbb7b Merge pull request #97 from AliSoftware/feature/auto-injecting-properties-first
Auto-injecting properties before calling resolveDependencies block
2016-07-17 13:04:50 +02:00
Ilya Puchka 2800c84553 fixed updating context when auto-injecting and resolving with collaborator 2016-07-17 12:51:13 +02:00
Ilya Puchka 18087d67c9 auto-injecting properties before calling resolve dependencies block 2016-07-17 12:27:20 +02:00
Ilya Puchka ab235c23e9 accessing previously resolved instances with subscript 2016-07-17 12:26:54 +02:00
Ilya Puchka c82dfb3dbe Merge pull request #95 from AliSoftware/feature/collaborating-containers
Containers collaboration
2016-07-12 00:07:09 +02:00
Ilya Puchka 2865a07e5a removed underscore prefix from internal methods names 2016-07-11 01:54:13 +02:00
Ilya Puchka ac5999011d filter matching definitions by arguments type 2016-07-11 01:54:13 +02:00
Ilya Puchka cd92deeccd added playground page for containers collaboration 2016-07-11 01:54:13 +02:00
Ilya Puchka 4f1dbd1e48 added context property to disable logging 2016-07-11 01:54:13 +02:00
Ilya Puchka 51e95fe73c improved collaborating containers implementation 2016-07-11 01:54:13 +02:00
Ilya Puchka aaab404d45 containers collaboration 2016-07-04 15:13:10 +02:00
Ilya Puchka 55da6f3707 Merge pull request #94 from AliSoftware/feature/logging
Logging all errors
2016-07-04 10:45:49 +02:00
Ilya Puchka e24d4cb577 logging all errors 2016-07-04 10:30:54 +02:00
Ilya Puchka b733c74e34 Fixed wiki links 2016-07-04 02:19:58 +02:00
Ilya Puchka 5927ab9471 Added link to VIPER example using Dip in README 2016-07-02 20:38:45 +02:00
Ilya Puchka 86a7289d2c Merge pull request #92 from mwoollard/develop
Enable the 'allow app extension api only' option
2016-06-13 00:15:44 +04:00
Mark Woollard 2c85a01f0d Enable the 'allow app extension api only' option
Dip doesn't appear to use any API calls that are unavailable to app extensions / watch kit. Enabling this flag removes warnings in extension / watch kit projects that link against dip.
2016-06-10 08:07:50 +01:00
Ilya Puchka bb85e27db8 Merge branch 'release/4.5.0' into develop 2016-06-08 23:58:37 +03:00
Ilya Puchka eadd87a5bb Merge pull request #90 from AliSoftware/release/4.5.0
Release 4.5.0
2016-06-08 22:57:58 +02:00
Ilya Puchka 4c4b9ef57a bumped version to 4.5.0 2016-06-08 23:45:01 +03:00
Ilya Puchka ee99cf4fdc updated CHANGELOG and README 2016-06-08 23:44:50 +03:00
Ilya Puchka 40dfd6419f improved tests playground page 2016-06-08 23:44:25 +03:00
Ilya Puchka 88fa94ed30 Merge pull request #89 from AliSoftware/feature/type-forwarding
Type forwarding
2016-06-08 21:21:12 +02:00
Ilya Puchka 58d1813681 sources layout fixed 2016-06-05 21:39:46 +02:00
Ilya Puchka 7277b940ca type forwarding playground page 2016-06-05 21:39:46 +02:00
Ilya Puchka 581c241fd1 improved type-forwarding implementation 2016-06-05 17:23:45 +02:00
Ilya Puchka 3c9d2a6db8 type forwarding 2016-05-22 17:17:57 +02:00
Ilya Puchka 10309c1d5d incremented swift snapshot version 2016-05-22 17:17:36 +02:00
Ilya Puchka 2a27152fa1 skip loading SPM 2016-05-22 17:16:57 +02:00
Ilya Puchka e04a741479 fixed travis script 2016-05-22 16:23:15 +02:00
Ilya Puchka dbaba50c88 fixed travis script indentation 2016-05-22 16:18:03 +02:00
Ilya Puchka 032db2d2b1 fixed travis scrip 2016-05-22 16:16:56 +02:00
Ilya Puchka b2456e0430 fixed cloning spm in travis script 2016-05-22 16:08:31 +02:00
Ilya Puchka fd17075a8a some minor docs changes 2016-05-22 15:55:31 +02:00
Ilya Puchka 2d9be9acc0 Merge pull request #87 from AliSoftware/feature/validate-container
Validating container
2016-05-13 16:16:30 +02:00
Ilya Puchka efd279b26b validate method 2016-05-12 22:28:29 +02:00
Ilya Puchka b5cb796f5f Merge pull request #88 from AliSoftware/linux-test-update
Linux test update
2016-05-06 00:20:41 +02:00
Ilya Puchka e1fd904842 added linux step on travis 2016-05-05 21:26:39 +02:00
Ilya Puchka c65ae6cedd adopted xctest api changes 2016-05-05 17:27:15 +02:00
Ilya Puchka 21aedcb655 minor fix 2016-05-05 16:43:12 +02:00
Ilya Puchka 37d0e4750f Merge pull request #86 from phatblat/phatblat-patch-1
Fix typo on readme
2016-05-01 18:51:25 +02:00
Ben Chatelain 95157a99f6 Fix typo on readme 2016-05-01 09:47:20 -06:00
Ilya Puchka 1ad3aa2c18 Merge pull request #85 from AliSoftware/feature/auto-wiring-error
Auto wiring error
2016-05-01 15:10:27 +02:00
Ilya Puchka 1c916cbafc Added AutoWiringFailed error 2016-05-01 14:31:27 +02:00
Ilya Puchka 86b6afe5d9 Merge pull request #84 from AliSoftware/feature/resolving-optionals
Resolving optionals
2016-05-01 14:22:56 +02:00
Ilya Puchka a1e7a1f958 resolving optionals 2016-05-01 12:39:02 +02:00
Ilya Puchka c414882c6a Merge pull request #83 from AliSoftware/feature/container-context
Container context
2016-04-29 10:22:00 +02:00
Ilya Puchka f8cd1c4dc3 container context implementation 2016-04-27 14:59:23 +02:00
Ilya Puchka 3e68acd37b settable auto-injected property without calling didInject observer 2016-04-25 22:51:34 +02:00
Ilya Puchka 9b047e4145 Merge pull request #81 from AliSoftware/feature/settable-auto-injected-properties
Settable auto-injected properties
2016-04-25 22:43:28 +02:00
Ilya Puchka f31236688d settable auto-injected properties 2016-04-25 14:10:25 +02:00
Ilya Puchka af61fea49e Merge pull request #79 from AliSoftware/feature/weakly-typed-resolve
Weakly typed resolve
2016-04-21 22:42:14 +02:00
Ilya Puchka 8c9172aac9 Merge pull request #74 from AliSoftware/docs
Cleaned up README
2016-04-21 22:41:59 +02:00
Ilya Puchka 5495bd2b97 removed description that encouraged service locator anti-pattern from podspec 2016-04-21 22:20:43 +02:00
Ilya Puchka b3e6c3c61a Cleaned up README 2016-04-21 22:19:54 +02:00
Ilya Puchka be86b30da3 weakly typed resolve 2016-04-20 01:54:25 +02:00
Ilya Puchka 26ba11dcbf Merge branch 'release/4.4.0' into develop 2016-03-31 22:57:29 +02:00
Ilya Puchka 1356a8056f Merge pull request #70 from ilyapuchka/release/4.4.0
Release 4.4.0
2016-03-31 22:55:38 +02:00
Ilya Puchka 0ddb37bea3 Bumped version to 4.4.0 2016-03-31 22:39:00 +02:00
Ilya Puchka 537cad5923 updated docs 2016-03-31 22:37:53 +02:00
Ilya Puchka 0c4ce2213b bootstrap method can throw 2016-03-31 22:19:29 +02:00
Ilya Puchka a91dacb29c Merge pull request #65 from ilyapuchka/feature/eager-singleton-scope
EagerSingleton scope and bootstrap method
2016-03-31 10:03:59 +02:00
Ilya Puchka 895a6f2583 Merge pull request #67 from ilyapuchka/feature/resolvable-calls-order
Reversed order of Resolvable callback calls
2016-03-31 10:03:52 +02:00
Ilya Puchka 73f71a99b2 Reversed order of Resolvable callback calls 2016-03-30 21:56:45 +02:00
Ilya Puchka 41664914f4 EagerSingleton scope and bootstrap method 2016-03-25 10:24:49 +01:00
Ilya Puchka 53bc97ba63 Update README.md
Added some fancy badges
2016-03-24 14:34:38 +01:00
Ilya Puchka c321189b66 Merge branch 'release/4.3.1' into develop 2016-03-24 12:24:05 +01:00
Ilya Puchka 317d67ca90 Merge pull request #64 from AliSoftware/release/4.3.1
Release 4.3.1
2016-03-24 12:23:17 +01:00
Ilya Puchka cbcef835f7 Bumped version to 4.3.1 2016-03-24 12:10:21 +01:00
Ilya Puchka 47a1870de1 Fixed sample app warnings 2016-03-24 12:02:50 +01:00
Ilya Puchka da2197c909 updated travis script to use Xcode 7.3 2016-03-24 11:48:41 +01:00
Ilya Puchka 3ee8b04118 Merge pull request #62 from mwoollard/develop
Fix build warnings / issues from Swift 2.2
2016-03-24 10:50:45 +01:00
Mark Woollard 29c1a3805f Fix build warnings / issues from Swift 2.2 2016-03-22 13:32:37 +00:00
Ilya Puchka 4dc11a6e2d Merge branch 'hotfix/pods' into develop 2016-03-19 17:53:09 +01:00
Ilya Puchka 7a4d9c554c Merge pull request #60 from AliSoftware/hotfix/pods
Pods hotfix
2016-03-19 17:52:46 +01:00
Ilya Puchka cf4bb0352a pods hotfix 2016-03-19 17:40:59 +01:00
Ilya Puchka 8c6a822eca Merge pull request #59 from AliSoftware/release/4.3.0
Release 4.3.0
2016-03-19 17:09:18 +01:00
Ilya Puchka 4c6718efb3 Merge branch 'release/4.3.0' into develop 2016-03-19 17:06:41 +01:00
Ilya Puchka 1306f12804 Merge branch 'master' into release/4.3.0 2016-03-19 17:02:06 +01:00
Ilya Puchka 764cb98a59 bumped version to 4.3.0 2016-03-19 16:54:42 +01:00
Ilya Puchka 130d71ce3c Added missing tests for Linux 2016-03-19 16:46:17 +01:00
Ilya Puchka 490c2a4d56 Updated CHANGELOG 2016-03-19 16:00:03 +01:00
Ilya Puchka 6e98f270bf Merge pull request #58 from AliSoftware/remove-resolution-failed-error
Removed ResolutionFailed error
2016-03-19 15:44:34 +01:00
Ilya Puchka b3d090c3fd Call didResolveDependencies when graph is complete 2016-03-19 15:33:10 +01:00
Ilya Puchka 4e5cf238c9 removed ResolutionFailed error 2016-03-19 15:33:10 +01:00
Ilya Puchka f4f660d81d Minor DependencyTagConvertible refactoring 2016-03-19 15:33:10 +01:00
Ilya Puchka 211b2fce97 Merge pull request #50 from gavrix/develop
DependencyTagConvertible for better typed Tags
2016-03-19 11:50:19 +01:00
Ilya Puchka 3aa79d1c69 Merge branch 'gavrix-develop' into develop
# Conflicts:
#	Sources/Dip.swift
#	Sources/RuntimeArguments.swift
2016-03-19 11:48:57 +01:00
Sergey Gavrilyuk 4d085d1329 DependencyTag reverted back to DependencyCotnainer.Tag 2016-03-19 11:39:13 +01:00
Ilya Puchka 84845c9b17 Merge pull request #57 from AliSoftware/feature/resolve-dependencies-callback
Added Resolvable protocol
2016-03-19 00:06:34 +01:00
Ilya Puchka f4c38f281e Merge pull request #55 from AliSoftware/feature/auto-wiring
Auto-wiring
2016-03-19 00:05:55 +01:00
Ilya Puchka 99315e32eb Added Resolvable protocol 2016-03-18 23:49:04 +01:00
Ilya Puchka b48fe9840a Moved numberOfArguments property to Definition 2016-03-18 23:40:04 +01:00
Ilya Puchka 6a6fbf906b added playground page, changelog and readmy entry, improved docs 2016-03-18 23:39:33 +01:00
Sergey Gavrilyuk 7e5a0c8ada DependencyTag reverted back to DependencyCotnainer.Tag 2016-03-16 14:43:32 -04:00
Ilya Puchka 1f7ce50035 auto-wiring 2016-03-07 23:59:34 +01:00
Ilya Puchka 6656116e12 Merge pull request #54 from AliSoftware/release/4.2.0
Release/4.2.0 to master
2016-02-27 15:27:14 +01:00
Ilya Puchka 3700f687a2 Merge branch 'release/4.2.0' into develop 2016-02-27 15:00:34 +01:00
Ilya Puchka 92499c9561 fixed auto-injection error description 2016-02-27 14:55:16 +01:00
Ilya Puchka 7c5ea83dc4 Fixed usage of Tag in README 2016-02-27 13:37:51 +01:00
Ilya Puchka 4793232d8c bumped version to 4.2.0 2016-02-25 18:44:21 +01:00
Ilya Puchka 2c09b4d6d1 Merge pull request #46 from AliSoftware/feature/linux-tests
Building tests on linux with spm
2016-02-25 18:32:56 +01:00
Ilya Puchka 34e920a348 building tests on linux with spm 2016-02-25 18:08:06 +01:00
Ilya Puchka 3357630deb updated README and CHANGELOG 2016-02-25 00:26:39 +01:00
Ilya Puchka f23492ee40 Improved auto-injection docs 2016-02-25 00:26:20 +01:00
Sergey Gavrilyuk c817980dd8 DependencyTagConvertible introduced 2016-02-23 11:39:55 -05:00
AliSoftware 328d1b98a5 Merge pull request #49 from AliSoftware/public-autoinjectedpropertybox-protocol
Public AutoInjectedPropertyBox protocol
2016-02-22 20:21:26 +01:00
Ilya Puchka 9f77677014 public AutoInjectedPropertyBox protocol 2016-02-22 15:13:06 +03:00
Ilya Puchka b13a38cf9e clear singleton instances on definition override 2016-02-12 19:50:08 +01:00
Ilya Puchka edb0d3e576 Merge pull request #42 from AliSoftware/feature/pthread
Implemented recursive lock using pthread mutex
2016-02-09 22:34:01 +01:00
Ilya Puchka bd8a503085 Merge pull request #43 from AliSoftware/feature/singleton-scope-fixed
Singleton scope fixed
2016-02-08 11:13:10 +01:00
Ilya Puchka 6af8f18a63 fixed typo 2016-02-08 10:55:53 +01:00
Ilya Puchka bcf2329312 added unit tests 2016-02-08 10:37:25 +01:00
Ilya Puchka b01b7fb055 added thread safety tests for Linux 2016-02-08 01:32:12 +01:00
Ilya Puchka 0515e7358d fixed getting lock 2016-02-07 21:06:26 +01:00
Ilya Puchka 463f9a37a3 added examples to component scope cases 2016-02-07 14:30:16 +01:00
Ilya Puchka 6c802d2c39 fixed releasing singleton instances when definition is removed or container is reset 2016-02-07 14:29:36 +01:00
Ilya Puchka 967a2342b4 storing singleton instances in container instead of definition 2016-02-07 14:00:39 +01:00
Ilya Puchka d7b3db1476 minor refactoring of internal methods 2016-02-07 14:00:39 +01:00
Ilya Puchka f65949bafc removed Foundation import 2016-02-06 20:41:15 +01:00
Ilya Puchka ba420c27a1 implemented recursive lock using pthread mutex 2016-02-06 20:40:04 +01:00
Ilya Puchka ef66212ba3 Merge pull request #41 from AliSoftware/feature/spm
Swift package manager support
2016-02-06 20:39:04 +01:00
Ilya Puchka 1d71eb08ca moved source files to Sources instead of symlink 2016-02-06 16:12:04 +01:00
Ilya Puchka 689ff2ed29 swift package manager support 2016-02-06 15:47:32 +01:00
Olivier Halligon 126c5d3191 Merge branch 'release/4.1.0' 2016-02-04 21:07:06 +01:00
AliSoftware 3b4dab4c97 Merge pull request #40 from AliSoftware/release/4.1.0
Release 4.1.0
2016-02-04 21:03:36 +01:00
Olivier Halligon 7de69e1fa0 Updated the framework's targets version too 2016-02-04 20:48:03 +01:00
Ilya Puchka a44d11b72f updated CHANGELOG and pod spec 2016-02-03 22:28:57 +01:00
AliSoftware 7352769a47 Merge pull request #37 from AliSoftware/feature/sample-app-refined
Refined sample app and readme
2016-02-01 11:24:58 +01:00
Ilya Puchka 5be2995ef1 added comments on the type of di patterns used, added illustration of tagged definitions, property injection and using runtime arguments 2016-02-01 11:04:30 +01:00
AliSoftware 306a52f9d5 Merge pull request #35 from AliSoftware/feature/auto-injection-safety-improvements
Auto-injection improvements
2016-02-01 10:32:23 +01:00
Ilya Puchka 764024700f updated docs and playground 2016-02-01 01:59:00 +01:00
Ilya Puchka ee6c9b6115 removed resolveDependenciesOf method
it's moved to DipUI for less confusion
2016-02-01 00:59:02 +01:00
Ilya Puchka da5cc8f1a7 improved documentation 2016-01-31 15:38:04 +01:00
Ilya Puchka b7b586cba1 rearranged methods of DependencyContainer by extensions 2016-01-31 15:07:15 +01:00
Ilya Puchka b50691205a added ResolutionFailed error
refactored definitions lookup in a separate method
2016-01-31 15:00:20 +01:00
Ilya Puchka d3d1f3cbf9 resolve dependencies of external instances 2016-01-31 14:54:07 +01:00
Ilya Puchka 8d4c71fe5a tagged auto-injected properties 2016-01-31 13:59:18 +01:00
Ilya Puchka 591c1bfefc extracted base class for auto-injection wrappers 2016-01-31 13:59:18 +01:00
Ilya Puchka 786b56b5ea added separate error for auto-injection 2016-01-31 13:59:18 +01:00
Ilya Puchka 4938023a60 reimplemented auto-injection without additional definitions 2016-01-31 13:59:18 +01:00
Ilya Puchka 0db1d2433d implemented optional auto-injected properties using required property 2016-01-31 13:58:26 +01:00
Ilya Puchka fbb1bcc94f bunch of refactoring 2016-01-31 13:58:26 +01:00
Ilya Puchka f41d28382d made auto-injected properties required, enabled code coverage, added few tests 2016-01-31 13:58:25 +01:00
Ilya Puchka 7a242a7656 made value property of auto-injection wrappers readonly for safer property injection, added injected values observers 2016-01-31 13:58:25 +01:00
Ilya Puchka 1c7e8419c7 some code and docs improvements 2016-01-31 13:58:25 +01:00
Ilya Puchka 03cf8f7b32 fixed remove/register methods and DefinitionKey properties access level
DefinitionKey init was never public and was never returned by container, so there is actually no way for clients to use those methods anyway
and it's safe to move them from public scope to internal
2016-01-31 13:40:08 +01:00
Ilya Puchka a36efc1c5e moved thread safety tests specific code to corresponding file 2016-01-30 23:46:41 +01:00
Ilya Puchka 19d1d38d63 removed public extension for Optional description 2016-01-30 23:46:41 +01:00
Ilya Puchka 2bae6bce60 noescape closure in threadSafe method 2016-01-30 23:46:40 +01:00
Ilya Puchka 66bca3d5b6 fix for clearing instances pool in case of error 2016-01-30 23:46:14 +01:00
Ilya Puchka 4dcdabc8ae refined sample app 2016-01-28 00:27:11 +01:00
Ilya Puchka d8859fb44a Refined README 2016-01-28 00:27:11 +01:00
AliSoftware fef5eb10ac Update README.md
Fix links in README
2016-01-26 20:56:56 +01:00
Ilya Puchka fc337f0fe3 fixed reuse on auto-injection 2016-01-22 01:16:58 +01:00
Ilya Puchka d3759baff6 fixed regex 2016-01-22 01:14:20 +01:00
Ilya Puchka bd45b97400 Merge pull request #36 from AliSoftware/mwoollard-develop
Add thread safety with NSRecursiveLock
2016-01-20 10:26:03 +01:00
Mark Woollard 4479b58ace Add thread safety with NSRecursiveLock 2016-01-19 23:23:09 +01:00
AliSoftware 47043a213e Merge pull request #13 from AliSoftware/feature/auto-injection
Auto-injection
2016-01-14 15:43:48 +01:00
Ilya Puchka cc399031e7 removed forced try in playground 2016-01-10 19:48:26 +01:00
Ilya Puchka d50ca57d49 updated CHANGELOG 2016-01-10 19:43:18 +01:00
Ilya Puchka 6f5ab994a0 fixed typos 2016-01-10 19:43:18 +01:00
Ilya Puchka 33601418f3 added throws to auto injection factories 2016-01-10 19:43:18 +01:00
Ilya Puchka 691762242f auto injection playground page and updated documentation 2016-01-10 19:43:18 +01:00
Ilya Puchka 345b6e164a minor internal protocols refactoring 2016-01-10 19:41:38 +01:00
Ilya Puchka 4b0554b539 auto-injection 2016-01-10 19:41:38 +01:00
AliSoftware d786eb17dd Merge pull request #32 from AliSoftware/feature/throwing-factories
Throwing factories and resolve dependencies block
2016-01-10 16:39:27 +01:00
Ilya Puchka d4d275cda6 changed throw to rethrows in private _resolve method 2016-01-10 15:39:05 +01:00
AliSoftware cdf9f43c9b Merge pull request #33 from ilyapuchka/develop
Added reference to Mark Seemann's book in playground and README
2016-01-10 15:37:52 +01:00
Ilya Puchka f8a4b40281 added reference to Mark Seemann's book in playground and README 2016-01-10 14:05:45 +01:00
Ilya Puchka 3803a72548 throwing factories and resolve dependencies block 2016-01-10 13:41:18 +01:00
Olivier Halligon 8f3fad759e Merge branch 'release/4.0.0' 2015-12-12 22:24:29 +01:00
Olivier Halligon c6bf18181f Merge branch 'release/4.0.0' into develop 2015-12-12 22:24:29 +01:00
Ilya Puchka d6908e1feb Update CHANGELOG.md 2015-12-12 21:16:59 +01:00
Ilya Puchka 288673ecb3 Update README.md 2015-12-12 17:54:40 +01:00
Ilya Puchka 14c13d7f35 Update CHANGELOG.md 2015-12-12 17:54:19 +01:00
Ilya Puchka 11a9c7fb70 added note on migration to 4.0.0 in CHANGELOG 2015-12-12 11:56:57 +01:00
Ilya Puchka c670a220d5 added remark about throwing errors in README 2015-12-12 11:51:17 +01:00
Ilya Puchka 049737dd2f bumped version to 4.0.0 2015-12-12 11:25:05 +01:00
Ilya Puchka 79adaed6d3 made DefinitionKey public 2015-12-12 11:23:48 +01:00
AliSoftware c7f9c82f81 Merge pull request #26 from AliSoftware/feature/targets
Added targets for OS X, tvOS and watchOS2
2015-12-12 01:19:00 +01:00
Ilya Puchka ad9c095041 make travis build with carthage 2015-12-11 22:19:43 +01:00
Ilya Puchka 409f0f1990 sudo installer on travis 2015-12-11 22:05:28 +01:00
Ilya Puchka 685809e6ec install carthage on travis 2015-12-11 22:00:24 +01:00
Ilya Puchka dcea032624 use specific tvOS and watchOS simulators on travis 2015-12-11 21:49:12 +01:00
Ilya Puchka ace73a2672 use xcode7.2 on travis 2015-12-11 21:37:34 +01:00
Ilya Puchka ff6645d012 added build for tvOS and watchOS to travis script 2015-12-11 21:30:46 +01:00
Ilya Puchka e3448d9148 minor CHANGELOG fixes 2015-12-11 00:55:57 +01:00
Olivier Halligon b14c4aeb10 Fix CodeSigning DevelopmentTeam to None
To be able to build in Release on any computer
2015-12-11 00:35:05 +01:00
Ilya Puchka 0adc671bd1 updated CHANGELOG 2015-12-08 01:15:54 +01:00
Ilya Puchka 1d7533a516 added targets for OS X, tvOS, watchOS2 2015-12-08 01:06:51 +01:00
Ilya Puchka 6ad4b82ec0 container made final class 2015-12-07 20:15:39 +01:00
Ilya Puchka 6bc96bc8af fixed tests 2015-12-02 22:09:27 +01:00
Ilya Puchka 8b9879a9a2 removed usage of unowned container 2015-12-02 20:58:09 +01:00
Ilya Puchka 406e47206a Merge pull request #22 from ilyapuchka/feature/throwing-errors
Throwing errors
2015-12-02 20:56:28 +01:00
Ilya Puchka 69e8eef0f1 updated docs 2015-11-30 22:59:53 +01:00
Ilya Puchka 78e789cbce enabled code coverage 2015-11-30 15:20:47 +01:00
Ilya Puchka ebbe9a1513 added test on throwing errors and keys eqaulity 2015-11-30 15:20:47 +01:00
Ilya Puchka bc0511261f throwing errors 2015-11-30 15:20:47 +01:00
Ilya Puchka 6a0fa295cb removed deprecated methods and foundation imports 2015-11-30 13:28:46 +01:00
Ilya Puchka 73d8990afa CHANGELOG minor update 2015-11-30 13:28:46 +01:00
Ilya Puchka d7d8f28a36 definition and container description 2015-11-30 13:28:46 +01:00
Ilya Puchka e97883efb0 reverted DefinitionOf back to final class 2015-11-30 13:28:46 +01:00
Olivier Halligon 342fdf9c92 Add @ilyapuchka as author in the Podspec 2015-11-30 12:42:28 +01:00
Ilya Puchka 9778236416 Merge branch 'release/3.1.0' into develop 2015-11-30 11:02:00 +01:00
Olivier Halligon b50046c071 Merge branch 'hotfix/migration-note' into develop 2015-11-30 00:00:10 +01:00
Ilya Puchka 0c93e868f0 Added documentation note on definition generic parameters 2015-11-22 21:04:00 +01:00
Olivier Halligon ada995590f Fix depreciation warnings in Sample App 2015-11-22 19:02:04 +01:00
Olivier Halligon c0e5df3443 Merge branch 'feature/circular-dependencies' into develop 2015-11-22 16:52:08 +01:00
Olivier Halligon 37d42281c1 CHANGELOG 2015-11-22 16:51:32 +01:00
Olivier Halligon 6c56e12eeb Including whole MIT licence in comment headers
As license says: "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software." ;-)
2015-11-22 16:49:57 +01:00
Olivier Halligon 948b3cd780 [Playground] Nitpicking on the Circular Dependencies page 2015-11-22 16:49:57 +01:00
Ilya Puchka adde50a3d0 added references to articles about service locator 2015-11-22 16:49:57 +01:00
Ilya Puchka 450a2f8a72 updated playground and added page about circular dependencies 2015-11-22 16:49:57 +01:00
Ilya Puchka 59719a0c37 updated documentation 2015-11-22 16:48:44 +01:00
Ilya Puchka f6afdfd08e added tag property on DefinitionOf 2015-11-22 16:47:14 +01:00
Ilya Puchka abf2202eeb not mutating resolveDependencies method 2015-11-22 16:47:14 +01:00
Ilya Puchka 938108f2ab converted DefinitionOf to struct 2015-11-22 16:47:14 +01:00
Ilya Puchka 9d619d6a25 added note on thread safety in readme 2015-11-22 16:47:14 +01:00
Ilya Puchka 58efc586db added scoped function to increment/decrement recursion depth 2015-11-22 16:44:39 +01:00
Ilya Puchka 3a2aecbe48 circular dependencies 2015-11-22 16:44:39 +01:00
96 changed files with 8042 additions and 1752 deletions
+4
View File
@@ -32,3 +32,7 @@ Carthage
# `pod install` in .travis.yml
#
# Pods/
# SPM
.build/
Packages
+1
View File
@@ -0,0 +1 @@
4.1
+32 -14
View File
@@ -1,15 +1,33 @@
language: objective-c
osx_image: xcode7
# cache: cocoapods
# before_install:
# - gem install cocoapods # Since Travis is not always on latest version
install:
- gem install xcpretty --no-rdoc --no-ri --no-document --quiet
script:
- set -o pipefail && xcodebuild test -workspace Dip.xcworkspace -scheme Dip -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 6,OS=latest' ONLY_ACTIVE_ARCH=NO | xcpretty -c
- set -o pipefail && xcodebuild test -workspace Dip.xcworkspace -scheme DipSampleApp -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 6,OS=latest' ONLY_ACTIVE_ARCH=NO | xcpretty -c
- pod lib lint --quick
matrix:
allow_failures:
- os: linux
include:
- script:
- set -o pipefail && xcodebuild test -workspace Dip.xcworkspace -scheme Dip -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 6,OS=latest' ONLY_ACTIVE_ARCH=NO | xcpretty -c
- set -o pipefail && xcodebuild test -workspace Dip.xcworkspace -scheme Dip -sdk macosx -destination 'platform=macOS,arch=x86_64' ONLY_ACTIVE_ARCH=NO | xcpretty -c
- pod spec lint --allow-warnings
- carthage build --no-skip-current
os: osx
osx_image: xcode9.3
language: objective-c
before_install:
- gem install cocoapods --version 1.1.0.rc.2 --no-document
- 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-4.1-RELEASE
- wget https://swift.org/builds/swift-4.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}"
- cd Dip
- script:
- swift package clean && swift build && swift test
notifications:
email: false
+212 -1
View File
@@ -1,5 +1,216 @@
# CHANGELOG
## 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.
[#95](https://github.com/AliSoftware/Dip/pull/95), [@ilyapuchka](https://github.com/ilyapuchka)
* Added WeakSingleton scope.
[#96](https://github.com/AliSoftware/Dip/pull/96), [@ilyapuchka](https://github.com/ilyapuchka)
* Properties Auto-injection now is performed before calling `resolveDependencies` block
[#97](https://github.com/AliSoftware/Dip/pull/97), [@ilyapuchka](https://github.com/ilyapuchka)
* Fixed updating container's context when resolving properties with auto-injection.
[#98](https://github.com/AliSoftware/Dip/pull/98), [@ilyapuchka](https://github.com/ilyapuchka)
* Improved logging.
[#94](https://github.com/AliSoftware/Dip/pull/94), [#99](https://github.com/AliSoftware/Dip/pull/99), [@ilyapuchka](https://github.com/ilyapuchka)
* Fixed warning about using only extensions api.
[#92](https://github.com/AliSoftware/Dip/pull/92), [@mwoollard](https://github.com/mwoollard)
## 4.5.0
* Added weakly-typed API to resolve components when exact type is unknown during compile time.
[#79](https://github.com/AliSoftware/Dip/pull/79), [@ilyapuchka](https://github.com/ilyapuchka)
* Added type forwarding feature. You can register the same factory to resolve different types.
[#89](https://github.com/AliSoftware/Dip/pull/89), [@ilyapuchka](https://github.com/ilyapuchka)
* Container now can resolve optional types :tada:
[#84](https://github.com/AliSoftware/Dip/pull/84), [@ilyapuchka](https://github.com/ilyapuchka)
* Added container context that provides contextual information during graph resolution process.
[#83](https://github.com/AliSoftware/Dip/pull/83), [@ilyapuchka](https://github.com/ilyapuchka)
* Added method to validate container configuration.
[#87](https://github.com/AliSoftware/Dip/pull/87), [@ilyapuchka](https://github.com/ilyapuchka)
* Added method to manually set value wrapped by auto-injection wrappers.
[#81](https://github.com/AliSoftware/Dip/pull/81), [@ilyapuchka](https://github.com/ilyapuchka)
* Added separate error type for failures during auto-wiring.
[#85](https://github.com/AliSoftware/Dip/pull/85), [@ilyapuchka](https://github.com/ilyapuchka)
## 4.4.0
* Added `.EagerSingleton` scope for objectes requiring early instantiation and `bootstrap()` method on `DepenencyContainer`.
[#65](https://github.com/AliSoftware/Dip/pull/65), [@ilyapuchka](https://github.com/ilyapuchka)
* Reverted order of `Resolvable` callbacks.
[#67](https://github.com/AliSoftware/Dip/pull/67), [@ilyapuchka](https://github.com/ilyapuchka)
## 4.3.1
* Fix Swift 2.2 compile errors in tests.
[#62](https://github.com/AliSoftware/Dip/pull/62), [@mwoollard](https://github.com/mwoollard)
## 4.3.0
* Added `DependencyTagConvertible` protocol for better typed tags.
[#50](https://github.com/AliSoftware/Dip/pull/50), [@gavrix](https://github.com/gavrix)
* Auto-wiring. `DependencyContainer` resolves constructor arguments automatically.
[#55](https://github.com/AliSoftware/Dip/pull/55), [@ilyapuchka](https://github.com/ilyapuchka)
* Added `Resolvable` protocol to get a callback when dependencies graph is complete.
[#57](https://github.com/AliSoftware/Dip/pull/57), [@ilyapuchka](https://github.com/ilyapuchka)
* Removed `DipError.ResolutionFailed` error for better consistency.
[#58](https://github.com/AliSoftware/Dip/pull/58), [@ilyapuchka](https://github.com/ilyapuchka)
## 4.2.0
* Added support for Swift Package Manager.
[#41](https://github.com/AliSoftware/Dip/pull/41), [@ilyapuchka](https://github.com/ilyapuchka)
* Added Linux support.
[#42](https://github.com/AliSoftware/Dip/pull/42), [#46](https://github.com/AliSoftware/Dip/pull/46), [@ilyapuchka](https://github.com/ilyapuchka)
* Fixed the issue that could cause singleton instances to be reused between different containers.
[#43](https://github.com/AliSoftware/Dip/pull/43), [@ilyapuchka](https://github.com/ilyapuchka)
* Added public `AutoInjectedPropertyBox` protocol for user-defined auto-injected property wrappers.
[#49](https://github.com/AliSoftware/Dip/pull/49), [@ilyapuchka](https://github.com/ilyapuchka)
## 4.1.0
#### New features
* Added auto-injection feature.
[#13](https://github.com/AliSoftware/Dip/pull/13), [@ilyapuchka](https://github.com/ilyapuchka)
* Factories and `resolveDependencies` blocks of `DefinitionOf` are now allowed to `throw`. Improved errors handling.
[#32](https://github.com/AliSoftware/Dip/pull/32), [@ilyapuchka](https://github.com/ilyapuchka)
* Thread safety reimplemented with support for recursive methods calls.
[#31](https://github.com/AliSoftware/Dip/pull/31), [@mwoollard](https://github.com/mwoollard)
## 4.0.0
#### New Features
* Added support for circular dependencies:
* Added `ObjectGraph` scope to reuse resolved instances
* Added `resolveDependencies` method on `DefinitionOf` class to resolve dependencies of resolved instance.
[#11](https://github.com/AliSoftware/Dip/pull/11), [@ilyapuchka](https://github.com/ilyapuchka)
* Added methods to register/remove individual definitions.
[#11](https://github.com/AliSoftware/Dip/pull/11), [@ilyapuchka](https://github.com/ilyapuchka)
* All `resolve` methods now can throw error if type can not be resolved.
[#15](https://github.com/AliSoftware/Dip/issues/15), [@ilyapuchka](https://github.com/ilyapuchka)
* `DependencyContainer` is marked as `final`.
* Added support for OSX, tvOS and watchOS2.
[#26](https://github.com/AliSoftware/Dip/pull/26), [@ilyapuchka](https://github.com/ilyapuchka)
#### Breaking Changes
* Removed container thread-safety to enable recursion calls to `resolve`.
**Access to container from multiple threads should be handled by clients** from now on.
* All `resolve` methods now can throw.
### Note on migration from 3.x to 4.0.0:
* Errors
In 4.0.0 each `resolve` method can throw `DefinitionNotFound(DefinitionKey)` error, so you need to call it using `try!` or `try?`, or catch the error if it's appropriate for your case. See [#15](https://github.com/AliSoftware/Dip/issues/15) for rationale of this change.
* Thread safety
In 4.0.0 `DependencyContainer` drops any guarantee of thread safety. From now on code that uses Dip must ensure that it's methods are called from a single thread. For example if you have registered type as a singleton and later two threads try to resolve it at the same time you can have two different instances of type instead of one as expected. This change was required to enable recursive calls of `resolve` method to resolve circular dependencies.
* Removed methods
Methods deprecated in 3.1.0 are now removed.
## 3.1.0
#### New
@@ -28,7 +239,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
```
+13 -17
View File
@@ -1,30 +1,26 @@
Pod::Spec.new do |s|
s.name = "Dip"
s.version = "3.1.0"
s.summary = "A simple Dependency Resolver: Dependency Injection using Protocol resolution."
s.version = "6.1"
s.summary = "Dependency Injection for Swift made easy."
s.description = <<-DESC
Dip is a Swift framework to manage your Dependencies between your classes
in your app using Dependency Injection.
It's aimed to be very simple to use while improving testability
of your app by allowing you to get rid of those sharedInstances and instead
inject values based on protocol resolution.
Define your API using a protocol, then ask Dip to resolve this protocol into
an instance dynamically in your classes. Then your App and your Tests can be
configured to resolve the protocol using a different instance or class so this
improve testability by decoupling the API and the concrete class used to implement it.
DESC
Dip is a Swift Dependency Injection Container.
It provides reusable functionality for managing dependencies of your types
and will help you to wire up different parts of your app.
DESC
s.homepage = "https://github.com/AliSoftware/Dip"
s.license = 'MIT'
s.author = { "Olivier Halligon" => "olivier@halligon.net" }
s.authors = { "Olivier Halligon" => "olivier@halligon.net", "Ilya Puchka" => "ilya@puchka.me" }
s.source = { :git => "https://github.com/AliSoftware/Dip.git", :tag => s.version.to_s }
s.social_media_url = 'https://twitter.com/aligatr'
s.platform = :ios, '8.0'
s.ios.deployment_target = '8.0'
s.osx.deployment_target = '10.9'
s.tvos.deployment_target = '9.0'
s.watchos.deployment_target = '2.0'
s.requires_arc = true
s.source_files = 'Dip/Dip/**/*'
s.source_files = 'Sources/**/*.swift'
end
@@ -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>
+286 -168
View File
@@ -7,62 +7,135 @@
objects = {
/* Begin PBXBuildFile section */
094526951BEA1CFF0034E72A /* Dip.h in Headers */ = {isa = PBXBuildFile; fileRef = 094526941BEA1CFF0034E72A /* Dip.h */; settings = {ATTRIBUTES = (Public, ); }; };
0945269C1BEA1CFF0034E72A /* Dip.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 094526911BEA1CFF0034E72A /* Dip.framework */; };
094526A11BEA1CFF0034E72A /* DipTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094526A01BEA1CFF0034E72A /* DipTests.swift */; };
094526AC1BEA1D200034E72A /* Dip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094526AB1BEA1D200034E72A /* Dip.swift */; };
094526B41BEA51540034E72A /* RuntimeArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094526B31BEA51540034E72A /* RuntimeArguments.swift */; };
094526B61BEA520B0034E72A /* Definition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094526B51BEA520B0034E72A /* Definition.swift */; };
094526B81BEA536A0034E72A /* RuntimeArgumentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094526B71BEA536A0034E72A /* RuntimeArgumentsTests.swift */; };
0903B3621C161544002241C1 /* Dip.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0903B3581C161543002241C1 /* Dip.framework */; };
0919F4D31C16417B00DC3B10 /* Dip.h in Headers */ = {isa = PBXBuildFile; fileRef = 0919F4C91C16417000DC3B10 /* Dip.h */; settings = {ATTRIBUTES = (Public, ); }; };
0919F4D41C16417B00DC3B10 /* Dip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919F4CA1C16417000DC3B10 /* Dip.swift */; };
0919F4D51C16417B00DC3B10 /* Definition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919F4C81C16417000DC3B10 /* Definition.swift */; };
0919F4D61C16417B00DC3B10 /* RuntimeArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919F4CC1C16417000DC3B10 /* RuntimeArguments.swift */; };
095F829C1D043B41008CD706 /* TypeForwarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095F829B1D043B41008CD706 /* TypeForwarding.swift */; };
0982AF0C1C5183A000B62463 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0982AF0B1C5183A000B62463 /* Utils.swift */; };
09871B411DAA6BF300B40B91 /* Compatibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09871B401DAA6BF300B40B91 /* Compatibility.swift */; };
09873F561C1E0237000C02F6 /* AutoInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09873F551C1E0237000C02F6 /* AutoInjection.swift */; };
09B036001C5D2B83001EA5B7 /* AutoWiring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09B035FF1C5D2B83001EA5B7 /* AutoWiring.swift */; };
09BD350E1D84E30D00B33E53 /* AutoInjectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BD35041D84E30D00B33E53 /* AutoInjectionTests.swift */; };
09BD350F1D84E30D00B33E53 /* AutoWiringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BD35051D84E30D00B33E53 /* AutoWiringTests.swift */; };
09BD35101D84E30D00B33E53 /* ComponentScopeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BD35061D84E30D00B33E53 /* ComponentScopeTests.swift */; };
09BD35111D84E30D00B33E53 /* ContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BD35071D84E30D00B33E53 /* ContextTests.swift */; };
09BD35121D84E30D00B33E53 /* DefinitionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BD35081D84E30D00B33E53 /* DefinitionTests.swift */; };
09BD35131D84E30D00B33E53 /* DipTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BD35091D84E30D00B33E53 /* DipTests.swift */; };
09BD35141D84E30D00B33E53 /* RuntimeArgumentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BD350A1D84E30D00B33E53 /* RuntimeArgumentsTests.swift */; };
09BD35151D84E30D00B33E53 /* ThreadSafetyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BD350B1D84E30D00B33E53 /* ThreadSafetyTests.swift */; };
09BD35161D84E30D00B33E53 /* TypeForwardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BD350C1D84E30D00B33E53 /* TypeForwardingTests.swift */; };
09BD35171D84E30D00B33E53 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BD350D1D84E30D00B33E53 /* Utils.swift */; };
09FC48071DAA9AC700566AA8 /* Resolve.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09FC48051DAA9AC700566AA8 /* Resolve.swift */; };
09FC480F1DAA9CAF00566AA8 /* Register.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09FC480C1DAA9CAF00566AA8 /* Register.swift */; };
09FC48181DAAA53100566AA8 /* DipError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09FC48161DAAA53100566AA8 /* DipError.swift */; };
09FC481E1DAAA8F900566AA8 /* ComponentScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09FC481C1DAAA8F900566AA8 /* ComponentScope.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
0945269D1BEA1CFF0034E72A /* PBXContainerItemProxy */ = {
0903B3631C161544002241C1 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 094526881BEA1CFF0034E72A /* Project object */;
proxyType = 1;
remoteGlobalIDString = 094526901BEA1CFF0034E72A;
remoteInfo = Dip;
remoteGlobalIDString = 0903B3571C161543002241C1;
remoteInfo = "Dip-OSX";
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
094526911BEA1CFF0034E72A /* Dip.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Dip.framework; sourceTree = BUILT_PRODUCTS_DIR; };
094526941BEA1CFF0034E72A /* Dip.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Dip.h; sourceTree = "<group>"; };
094526961BEA1CFF0034E72A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
0945269B1BEA1CFF0034E72A /* DipTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DipTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
094526A01BEA1CFF0034E72A /* DipTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DipTests.swift; sourceTree = "<group>"; };
094526A21BEA1CFF0034E72A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
094526AB1BEA1D200034E72A /* Dip.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Dip.swift; sourceTree = "<group>"; };
094526B31BEA51540034E72A /* RuntimeArguments.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = RuntimeArguments.swift; sourceTree = "<group>"; tabWidth = 2; };
094526B51BEA520B0034E72A /* Definition.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = Definition.swift; sourceTree = "<group>"; tabWidth = 2; };
094526B71BEA536A0034E72A /* RuntimeArgumentsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuntimeArgumentsTests.swift; sourceTree = "<group>"; };
0903B3581C161543002241C1 /* Dip.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Dip.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0903B3611C161543002241C1 /* DipTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DipTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
0919F4C81C16417000DC3B10 /* Definition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Definition.swift; path = ../../Sources/Definition.swift; sourceTree = "<group>"; };
0919F4C91C16417000DC3B10 /* Dip.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Dip.h; sourceTree = "<group>"; };
0919F4CA1C16417000DC3B10 /* Dip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Dip.swift; path = ../../Sources/Dip.swift; sourceTree = "<group>"; };
0919F4CB1C16417000DC3B10 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
0919F4CC1C16417000DC3B10 /* RuntimeArguments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RuntimeArguments.swift; path = ../../Sources/RuntimeArguments.swift; sourceTree = "<group>"; };
0919F4D11C16417000DC3B10 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
095F829B1D043B41008CD706 /* TypeForwarding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TypeForwarding.swift; path = ../../Sources/TypeForwarding.swift; sourceTree = "<group>"; };
0982AF0B1C5183A000B62463 /* Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Utils.swift; path = ../../Sources/Utils.swift; sourceTree = "<group>"; };
09871B401DAA6BF300B40B91 /* Compatibility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Compatibility.swift; path = ../../Sources/Compatibility.swift; sourceTree = "<group>"; };
09873F551C1E0237000C02F6 /* AutoInjection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AutoInjection.swift; path = ../../Sources/AutoInjection.swift; sourceTree = "<group>"; };
09B035FF1C5D2B83001EA5B7 /* AutoWiring.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AutoWiring.swift; path = ../../Sources/AutoWiring.swift; sourceTree = "<group>"; };
09BD35041D84E30D00B33E53 /* AutoInjectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AutoInjectionTests.swift; path = ../../Tests/DipTests/AutoInjectionTests.swift; sourceTree = "<group>"; };
09BD35051D84E30D00B33E53 /* AutoWiringTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AutoWiringTests.swift; path = ../../Tests/DipTests/AutoWiringTests.swift; sourceTree = "<group>"; };
09BD35061D84E30D00B33E53 /* ComponentScopeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ComponentScopeTests.swift; path = ../../Tests/DipTests/ComponentScopeTests.swift; sourceTree = "<group>"; };
09BD35071D84E30D00B33E53 /* ContextTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ContextTests.swift; path = ../../Tests/DipTests/ContextTests.swift; sourceTree = "<group>"; };
09BD35081D84E30D00B33E53 /* DefinitionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DefinitionTests.swift; path = ../../Tests/DipTests/DefinitionTests.swift; sourceTree = "<group>"; };
09BD35091D84E30D00B33E53 /* DipTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DipTests.swift; path = ../../Tests/DipTests/DipTests.swift; sourceTree = "<group>"; };
09BD350A1D84E30D00B33E53 /* RuntimeArgumentsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RuntimeArgumentsTests.swift; path = ../../Tests/DipTests/RuntimeArgumentsTests.swift; sourceTree = "<group>"; };
09BD350B1D84E30D00B33E53 /* ThreadSafetyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ThreadSafetyTests.swift; path = ../../Tests/DipTests/ThreadSafetyTests.swift; sourceTree = "<group>"; };
09BD350C1D84E30D00B33E53 /* TypeForwardingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TypeForwardingTests.swift; path = ../../Tests/DipTests/TypeForwardingTests.swift; sourceTree = "<group>"; };
09BD350D1D84E30D00B33E53 /* Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Utils.swift; path = ../../Tests/DipTests/Utils.swift; sourceTree = "<group>"; };
09FC48051DAA9AC700566AA8 /* Resolve.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Resolve.swift; path = ../../Sources/Resolve.swift; sourceTree = "<group>"; };
09FC480C1DAA9CAF00566AA8 /* Register.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Register.swift; path = ../../Sources/Register.swift; sourceTree = "<group>"; };
09FC48161DAAA53100566AA8 /* DipError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DipError.swift; path = ../../Sources/DipError.swift; sourceTree = "<group>"; };
09FC481C1DAAA8F900566AA8 /* ComponentScope.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ComponentScope.swift; path = ../../Sources/ComponentScope.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
0945268D1BEA1CFF0034E72A /* Frameworks */ = {
0903B3541C161543002241C1 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
094526981BEA1CFF0034E72A /* Frameworks */ = {
0903B35E1C161543002241C1 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
0945269C1BEA1CFF0034E72A /* Dip.framework in Frameworks */,
0903B3621C161544002241C1 /* Dip.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
0919F4C71C16417000DC3B10 /* Dip */ = {
isa = PBXGroup;
children = (
0919F4C91C16417000DC3B10 /* Dip.h */,
0919F4CA1C16417000DC3B10 /* Dip.swift */,
09FC48161DAAA53100566AA8 /* DipError.swift */,
09FC480C1DAA9CAF00566AA8 /* Register.swift */,
09FC48051DAA9AC700566AA8 /* Resolve.swift */,
0919F4C81C16417000DC3B10 /* Definition.swift */,
09FC481C1DAAA8F900566AA8 /* ComponentScope.swift */,
0919F4CC1C16417000DC3B10 /* RuntimeArguments.swift */,
09873F551C1E0237000C02F6 /* AutoInjection.swift */,
09B035FF1C5D2B83001EA5B7 /* AutoWiring.swift */,
095F829B1D043B41008CD706 /* TypeForwarding.swift */,
0982AF0B1C5183A000B62463 /* Utils.swift */,
09871B401DAA6BF300B40B91 /* Compatibility.swift */,
0919F4CB1C16417000DC3B10 /* Info.plist */,
);
path = Dip;
sourceTree = "<group>";
};
0919F4CD1C16417000DC3B10 /* DipTests */ = {
isa = PBXGroup;
children = (
09BD35041D84E30D00B33E53 /* AutoInjectionTests.swift */,
09BD35051D84E30D00B33E53 /* AutoWiringTests.swift */,
09BD35061D84E30D00B33E53 /* ComponentScopeTests.swift */,
09BD35071D84E30D00B33E53 /* ContextTests.swift */,
09BD35081D84E30D00B33E53 /* DefinitionTests.swift */,
09BD35091D84E30D00B33E53 /* DipTests.swift */,
09BD350A1D84E30D00B33E53 /* RuntimeArgumentsTests.swift */,
09BD350B1D84E30D00B33E53 /* ThreadSafetyTests.swift */,
09BD350C1D84E30D00B33E53 /* TypeForwardingTests.swift */,
09BD350D1D84E30D00B33E53 /* Utils.swift */,
0919F4D11C16417000DC3B10 /* Info.plist */,
);
path = DipTests;
sourceTree = "<group>";
};
094526871BEA1CFF0034E72A = {
isa = PBXGroup;
children = (
094526931BEA1CFF0034E72A /* Dip */,
0945269F1BEA1CFF0034E72A /* DipTests */,
0919F4C71C16417000DC3B10 /* Dip */,
0919F4CD1C16417000DC3B10 /* DipTests */,
094526921BEA1CFF0034E72A /* Products */,
);
indentWidth = 2;
@@ -72,82 +145,60 @@
094526921BEA1CFF0034E72A /* Products */ = {
isa = PBXGroup;
children = (
094526911BEA1CFF0034E72A /* Dip.framework */,
0945269B1BEA1CFF0034E72A /* DipTests.xctest */,
0903B3581C161543002241C1 /* Dip.framework */,
0903B3611C161543002241C1 /* DipTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
094526931BEA1CFF0034E72A /* Dip */ = {
isa = PBXGroup;
children = (
094526941BEA1CFF0034E72A /* Dip.h */,
094526AB1BEA1D200034E72A /* Dip.swift */,
094526B31BEA51540034E72A /* RuntimeArguments.swift */,
094526B51BEA520B0034E72A /* Definition.swift */,
094526961BEA1CFF0034E72A /* Info.plist */,
);
path = Dip;
sourceTree = "<group>";
};
0945269F1BEA1CFF0034E72A /* DipTests */ = {
isa = PBXGroup;
children = (
094526A01BEA1CFF0034E72A /* DipTests.swift */,
094526B71BEA536A0034E72A /* RuntimeArgumentsTests.swift */,
094526A21BEA1CFF0034E72A /* Info.plist */,
);
path = DipTests;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
0945268E1BEA1CFF0034E72A /* Headers */ = {
0903B3551C161543002241C1 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
094526951BEA1CFF0034E72A /* Dip.h in Headers */,
0919F4D31C16417B00DC3B10 /* Dip.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
094526901BEA1CFF0034E72A /* Dip */ = {
0903B3571C161543002241C1 /* Dip */ = {
isa = PBXNativeTarget;
buildConfigurationList = 094526A51BEA1CFF0034E72A /* Build configuration list for PBXNativeTarget "Dip" */;
buildConfigurationList = 0903B36D1C161544002241C1 /* Build configuration list for PBXNativeTarget "Dip" */;
buildPhases = (
0945268C1BEA1CFF0034E72A /* Sources */,
0945268D1BEA1CFF0034E72A /* Frameworks */,
0945268E1BEA1CFF0034E72A /* Headers */,
0945268F1BEA1CFF0034E72A /* Resources */,
0903B3531C161543002241C1 /* Sources */,
0903B3541C161543002241C1 /* Frameworks */,
0903B3551C161543002241C1 /* Headers */,
0903B3561C161543002241C1 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = Dip;
productName = Dip;
productReference = 094526911BEA1CFF0034E72A /* Dip.framework */;
productName = "Dip-OSX";
productReference = 0903B3581C161543002241C1 /* Dip.framework */;
productType = "com.apple.product-type.framework";
};
0945269A1BEA1CFF0034E72A /* DipTests */ = {
0903B3601C161543002241C1 /* DipTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 094526A81BEA1CFF0034E72A /* Build configuration list for PBXNativeTarget "DipTests" */;
buildConfigurationList = 0903B36E1C161544002241C1 /* Build configuration list for PBXNativeTarget "DipTests" */;
buildPhases = (
094526971BEA1CFF0034E72A /* Sources */,
094526981BEA1CFF0034E72A /* Frameworks */,
094526991BEA1CFF0034E72A /* Resources */,
0903B35D1C161543002241C1 /* Sources */,
0903B35E1C161543002241C1 /* Frameworks */,
0903B35F1C161543002241C1 /* Resources */,
);
buildRules = (
);
dependencies = (
0945269E1BEA1CFF0034E72A /* PBXTargetDependency */,
0903B3641C161544002241C1 /* PBXTargetDependency */,
);
name = DipTests;
productName = DipTests;
productReference = 0945269B1BEA1CFF0034E72A /* DipTests.xctest */;
productName = "Dip-OSXTests";
productReference = 0903B3611C161543002241C1 /* DipTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
@@ -156,15 +207,17 @@
094526881BEA1CFF0034E72A /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0710;
LastUpgradeCheck = 0710;
LastSwiftUpdateCheck = 0730;
LastUpgradeCheck = 0930;
ORGANIZATIONNAME = AliSoftware;
TargetAttributes = {
094526901BEA1CFF0034E72A = {
CreatedOnToolsVersion = 7.1;
0903B3571C161543002241C1 = {
CreatedOnToolsVersion = 7.1.1;
LastSwiftMigration = 0800;
};
0945269A1BEA1CFF0034E72A = {
CreatedOnToolsVersion = 7.1;
0903B3601C161543002241C1 = {
CreatedOnToolsVersion = 7.1.1;
LastSwiftMigration = 0800;
};
};
};
@@ -180,21 +233,21 @@
projectDirPath = "";
projectRoot = "";
targets = (
094526901BEA1CFF0034E72A /* Dip */,
0945269A1BEA1CFF0034E72A /* DipTests */,
0903B3571C161543002241C1 /* Dip */,
0903B3601C161543002241C1 /* DipTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
0945268F1BEA1CFF0034E72A /* Resources */ = {
0903B3561C161543002241C1 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
094526991BEA1CFF0034E72A /* Resources */ = {
0903B35F1C161543002241C1 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -204,36 +257,127 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
0945268C1BEA1CFF0034E72A /* Sources */ = {
0903B3531C161543002241C1 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
094526AC1BEA1D200034E72A /* Dip.swift in Sources */,
094526B61BEA520B0034E72A /* Definition.swift in Sources */,
094526B41BEA51540034E72A /* RuntimeArguments.swift in Sources */,
09871B411DAA6BF300B40B91 /* Compatibility.swift in Sources */,
0982AF0C1C5183A000B62463 /* Utils.swift in Sources */,
0919F4D51C16417B00DC3B10 /* Definition.swift in Sources */,
09FC481E1DAAA8F900566AA8 /* ComponentScope.swift in Sources */,
09B036001C5D2B83001EA5B7 /* AutoWiring.swift in Sources */,
0919F4D41C16417B00DC3B10 /* Dip.swift in Sources */,
09FC480F1DAA9CAF00566AA8 /* Register.swift in Sources */,
09FC48071DAA9AC700566AA8 /* Resolve.swift in Sources */,
095F829C1D043B41008CD706 /* TypeForwarding.swift in Sources */,
09873F561C1E0237000C02F6 /* AutoInjection.swift in Sources */,
09FC48181DAAA53100566AA8 /* DipError.swift in Sources */,
0919F4D61C16417B00DC3B10 /* RuntimeArguments.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
094526971BEA1CFF0034E72A /* Sources */ = {
0903B35D1C161543002241C1 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
094526A11BEA1CFF0034E72A /* DipTests.swift in Sources */,
094526B81BEA536A0034E72A /* RuntimeArgumentsTests.swift in Sources */,
09BD35141D84E30D00B33E53 /* RuntimeArgumentsTests.swift in Sources */,
09BD35151D84E30D00B33E53 /* ThreadSafetyTests.swift in Sources */,
09BD35121D84E30D00B33E53 /* DefinitionTests.swift in Sources */,
09BD350F1D84E30D00B33E53 /* AutoWiringTests.swift in Sources */,
09BD35111D84E30D00B33E53 /* ContextTests.swift in Sources */,
09BD35171D84E30D00B33E53 /* Utils.swift in Sources */,
09BD35131D84E30D00B33E53 /* DipTests.swift in Sources */,
09BD35101D84E30D00B33E53 /* ComponentScopeTests.swift in Sources */,
09BD350E1D84E30D00B33E53 /* AutoInjectionTests.swift in Sources */,
09BD35161D84E30D00B33E53 /* TypeForwardingTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
0945269E1BEA1CFF0034E72A /* PBXTargetDependency */ = {
0903B3641C161544002241C1 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 094526901BEA1CFF0034E72A /* Dip */;
targetProxy = 0945269D1BEA1CFF0034E72A /* PBXContainerItemProxy */;
target = 0903B3571C161543002241C1 /* Dip */;
targetProxy = 0903B3631C161544002241C1 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
0903B3691C161544002241C1 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
"CODE_SIGN_IDENTITY[sdk=*]" = "";
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_VERSION = A;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
"iOS=1",
);
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.alisoftware.Dip;
PRODUCT_NAME = Dip;
SKIP_INSTALL = YES;
};
name = Debug;
};
0903B36A1C161544002241C1 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = YES;
"CODE_SIGN_IDENTITY[sdk=*]" = "";
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_VERSION = A;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.alisoftware.Dip;
PRODUCT_NAME = Dip;
SKIP_INSTALL = YES;
};
name = Release;
};
0903B36B1C161544002241C1 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
"CODE_SIGN_IDENTITY[sdk=*]" = "";
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = DipTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks @loader_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.10;
PRODUCT_BUNDLE_IDENTIFIER = com.alisoftware.DipTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
0903B36C1C161544002241C1 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
"CODE_SIGN_IDENTITY[sdk=*]" = "";
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = DipTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks @loader_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.10;
PRODUCT_BUNDLE_IDENTIFIER = com.alisoftware.DipTests;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
094526A31BEA1CFF0034E72A /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -242,18 +386,28 @@
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";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 3.1.0;
CURRENT_PROJECT_VERSION = 6.1;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
@@ -271,14 +425,19 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = Dip/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MACOSX_DEPLOYMENT_TARGET = 10.9;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = "macosx watchsimulator iphonesimulator appletvsimulator watchos appletvos iphoneos";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
TARGETED_DEVICE_FAMILY = "1,2";
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2,3,4";
TVOS_DEPLOYMENT_TARGET = 9.0;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
WATCHOS_DEPLOYMENT_TARGET = 2.0;
};
name = Debug;
};
@@ -290,18 +449,28 @@
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";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 3.1.0;
CURRENT_PROJECT_VERSION = 6.1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -313,76 +482,43 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = Dip/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MACOSX_DEPLOYMENT_TARGET = 10.9;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
SUPPORTED_PLATFORMS = "macosx watchsimulator iphonesimulator appletvsimulator watchos appletvos iphoneos";
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2,3,4";
TVOS_DEPLOYMENT_TARGET = 9.0;
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
094526A61BEA1CFF0034E72A /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Dip/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.alisoftware.Dip;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
094526A71BEA1CFF0034E72A /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = Dip/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.alisoftware.Dip;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Release;
};
094526A91BEA1CFF0034E72A /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
INFOPLIST_FILE = DipTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.alisoftware.DipTests;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
094526AA1BEA1CFF0034E72A /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
INFOPLIST_FILE = DipTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.alisoftware.DipTests;
PRODUCT_NAME = "$(TARGET_NAME)";
WATCHOS_DEPLOYMENT_TARGET = 2.0;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
0903B36D1C161544002241C1 /* Build configuration list for PBXNativeTarget "Dip" */ = {
isa = XCConfigurationList;
buildConfigurations = (
0903B3691C161544002241C1 /* Debug */,
0903B36A1C161544002241C1 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
0903B36E1C161544002241C1 /* Build configuration list for PBXNativeTarget "DipTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
0903B36B1C161544002241C1 /* Debug */,
0903B36C1C161544002241C1 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
0945268B1BEA1CFF0034E72A /* Build configuration list for PBXProject "Dip" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@@ -392,24 +528,6 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
094526A51BEA1CFF0034E72A /* Build configuration list for PBXNativeTarget "Dip" */ = {
isa = XCConfigurationList;
buildConfigurations = (
094526A61BEA1CFF0034E72A /* Debug */,
094526A71BEA1CFF0034E72A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
094526A81BEA1CFF0034E72A /* Build configuration list for PBXNativeTarget "DipTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
094526A91BEA1CFF0034E72A /* Debug */,
094526AA1BEA1CFF0034E72A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 094526881BEA1CFF0034E72A /* Project object */;
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0710"
LastUpgradeVersion = "0930"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -14,25 +14,40 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "094526901BEA1CFF0034E72A"
BlueprintIdentifier = "0903B3571C161543002241C1"
BuildableName = "Dip.framework"
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>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
codeCoverageEnabled = "YES"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0945269A1BEA1CFF0034E72A"
BlueprintIdentifier = "0903B3601C161543002241C1"
BuildableName = "DipTests.xctest"
BlueprintName = "DipTests"
ReferencedContainer = "container:Dip.xcodeproj">
@@ -42,7 +57,7 @@
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "094526901BEA1CFF0034E72A"
BlueprintIdentifier = "0903B3571C161543002241C1"
BuildableName = "Dip.framework"
BlueprintName = "Dip"
ReferencedContainer = "container:Dip.xcodeproj">
@@ -64,7 +79,7 @@
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "094526901BEA1CFF0034E72A"
BlueprintIdentifier = "0903B3571C161543002241C1"
BuildableName = "Dip.framework"
BlueprintName = "Dip"
ReferencedContainer = "container:Dip.xcodeproj">
@@ -82,7 +97,7 @@
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "094526901BEA1CFF0034E72A"
BlueprintIdentifier = "0903B3571C161543002241C1"
BuildableName = "Dip.framework"
BlueprintName = "Dip"
ReferencedContainer = "container:Dip.xcodeproj">
-82
View File
@@ -1,82 +0,0 @@
//
// Dip
//
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
///Internal representation of a key used to associate definitons and factories by tag, type and factory.
struct DefinitionKey : Hashable, Equatable, CustomDebugStringConvertible {
var protocolType: Any.Type
var factoryType: Any.Type
var associatedTag: DependencyContainer.Tag?
var hashValue: Int {
return "\(protocolType)-\(factoryType)-\(associatedTag)".hashValue
}
var debugDescription: String {
return "type: \(protocolType), factory: \(factoryType), tag: \(associatedTag)"
}
}
func ==(lhs: DefinitionKey, rhs: DefinitionKey) -> Bool {
return
lhs.protocolType == rhs.protocolType &&
lhs.factoryType == rhs.factoryType &&
lhs.associatedTag == rhs.associatedTag
}
///Describes the lifecycle of instances created by container.
public enum ComponentScope {
/// Indicates that a new instance of the component will be created each time it's resolved.
case Prototype
/// Indicates that resolved component should be retained by container and always reused.
case Singleton
}
///Definition of type T describes how instances of this type should be created when they are resolved by container.
public final class DefinitionOf<T>: Definition {
let factory: Any
let scope: ComponentScope
init(factory: Any, scope: ComponentScope = .Prototype) {
self.factory = factory
self.scope = scope
}
var resolvedInstance: T? {
get {
guard scope == .Singleton else { return nil }
return _resolvedInstance
}
set {
guard scope == .Singleton else { return }
_resolvedInstance = newValue
}
}
private var _resolvedInstance: T?
}
///Dummy protocol to store definitions for different types in collection
protocol Definition {}
+20 -4
View File
@@ -1,9 +1,25 @@
//
// Dip.h
// Dip
// Dip
//
// Created by Ilya Puchka on 04.11.15.
// Copyright © 2015 AliSoftware. All rights reserved.
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#import <Foundation/Foundation.h>
-240
View File
@@ -1,240 +0,0 @@
//
// Dip
//
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
// MARK: - DependencyContainer
/**
* _Dip_'s Dependency Containers allow you to do very simple **Dependency Injection**
* by associating `protocols` to concrete implementations
*/
public class DependencyContainer {
/**
Use a tag in case you need to register multiple instances or factories
with the same protocol, to differentiate them. Tags can be either String
or Int, to your convenience.
*/
public enum Tag: Equatable {
case String(StringLiteralType)
case Int(IntegerLiteralType)
}
private var dependencies = [DefinitionKey : Definition]()
private var lock: OSSpinLock = OS_SPINLOCK_INIT
/**
Designated initializer for a DependencyContainer
- parameter configBlock: A configuration block in which you typically put all you `register` calls.
- note: The `configBlock` is simply called at the end of the `init` to let you configure everything.
It is only present for convenience to have a cleaner syntax when declaring and initializing
your `DependencyContainer` instances.
- returns: A new DependencyContainer.
*/
public init(@noescape configBlock: (DependencyContainer->()) = { _ in }) {
configBlock(self)
}
// MARK: - Reset all dependencies
/**
Clear all the previously registered dependencies on this container.
*/
public func reset() {
lockAndDo {
dependencies.removeAll()
}
}
// MARK: Register dependencies
/**
Register a Void->T factory associated with optional tag.
- parameter tag: The arbitrary tag to associate this factory with when registering with that protocol. Pass `nil` to associate with any tag. Default value is `nil`.
- parameter factory: The factory to register, with return type of protocol you want to register it for
- note: You must cast the factory return type to the protocol you want to register it with (e.g `MyClass() as MyAPI`)
*/
public func register<T>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: ()->T) -> DefinitionOf<T> {
return register(tag: tag, factory: factory, scope: scope) as DefinitionOf<T>
}
/**
Register a Singleton instance associated with optional tag.
- parameter tag: The arbitrary tag to associate this instance with when registering with that protocol. `nil` to associate with any tag.
- parameter instance: The instance to register, with return type of protocol you want to register it for
- note: You must cast the instance to the protocol you want to register it with (e.g `MyClass() as MyAPI`)
**Deprecated**: Use `register(.Singleton){}` method instead to define singleton scope.
*/
@available(*, deprecated, message="Use `register(.Singleton){}` method instead to define singleton scope.")
public func register<T>(tag tag: Tag? = nil, @autoclosure(escaping) instance factory: ()->T) -> DefinitionOf<T> {
return register(tag: tag, factory: { factory() }, scope: .Singleton)
}
/**
Register generic factory associated with optional tag.
- parameter tag: The arbitrary tag to look for when resolving this protocol.
- parameter factory: generic factory that should be used to create concrete instance of type
- parameter scope: scope of the component. Default value is `Prototype`
- note: You should not call this method directly, instead call any of other `register` methods.
You _should_ use this method only to register dependency with more runtime arguments
than _Dip_ supports (currently it's up to six) like in this example:
```swift
public func register<T, Arg1, Arg2, Arg3, ...>(tag: Tag? = nil, factory: (Arg1, Arg2, Arg3, ...) -> T) -> DefinitionOf<T> {
return register(tag: tag, factory: factory, scope: .Prototype) as DefinitionOf<T>
}
```
Though before you do that you should probably review your design and try to reduce number of depnedencies.
*/
public func register<T, F>(tag tag: Tag? = nil, factory: F, scope: ComponentScope) -> DefinitionOf<T> {
let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag)
let definition = DefinitionOf<T>(factory: factory, scope: scope)
lockAndDo {
dependencies[key] = definition
}
return definition
}
// MARK: Resolve dependencies
/**
Resolve a dependency.
If no instance/factory was registered with this `tag` for this `protocol`, it will try to resolve the instance/factory associated with `nil` (no tag).
- parameter tag: The arbitrary tag to look for when resolving this protocol.
*/
public func resolve<T>(tag tag: Tag? = nil) -> T {
return resolve(tag: tag) { (factory: ()->T) in factory() }
}
/**
Resolve a dependency using generic builder closure that accepts generic factory and returns created instance.
- parameter tag: The arbitrary tag to look for when resolving this protocol.
- parameter builder: Generic closure that accepts generic factory and returns inctance produced by that factory
- note: You should not call this method directly, instead call any of other `resolve` methods. (see `RuntimeArguments.swift`).
You _should_ use this method only to resolve dependency with more runtime arguments than _Dip_ supports
(currently it's up to six) like in this example:
```swift
public func resolve<T, Arg1, Arg2, Arg3, ...>(tag tag: Tag? = nil, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, ...) -> T {
return resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, ...) -> T) in factory(arg1, arg2, arg3, ...) }
}
```
Though before you do that you should probably review your design and try to reduce the number of dependencies.
*/
public func resolve<T, F>(tag tag: Tag? = nil, builder: F->T) -> T {
let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag)
let nilTagKey = tag.map { _ in DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: nil) }
var resolved: T!
lockAndDo { [unowned self] in
resolved = self._resolve(key, nilTagKey: nilTagKey, builder: builder)
}
return resolved
}
/// Actually resolve dependency
private func _resolve<T, F>(key: DefinitionKey, nilTagKey: DefinitionKey?, builder: F->T) -> T {
guard let definition = (self.dependencies[key] ?? self.dependencies[nilTagKey]) as? DefinitionOf<T> else {
fatalError("No instance factory registered with \(key) or \(nilTagKey)")
}
if let resolvedInstance = definition.resolvedInstance {
return resolvedInstance
}
else {
let resolved = builder(definition.factory as! F)
definition.resolvedInstance = resolved
return resolved
}
}
// MARK: - Private
private func lockAndDo(@noescape block: Void->Void) {
OSSpinLockLock(&lock)
defer { OSSpinLockUnlock(&lock) }
block()
}
}
extension DependencyContainer.Tag: IntegerLiteralConvertible {
public init(integerLiteral value: IntegerLiteralType) {
self = .Int(value)
}
}
extension DependencyContainer.Tag: StringLiteralConvertible {
public typealias ExtendedGraphemeClusterLiteralType = StringLiteralType
public typealias UnicodeScalarLiteralType = StringLiteralType
public init(stringLiteral value: StringLiteralType) {
self = .String(value)
}
public init(unicodeScalarLiteral value: UnicodeScalarLiteralType) {
self.init(stringLiteral: value)
}
public init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) {
self.init(stringLiteral: value)
}
}
public func ==(lhs: DependencyContainer.Tag, rhs: DependencyContainer.Tag) -> Bool {
switch (lhs, rhs) {
case let (.String(lhsString), .String(rhsString)):
return lhsString == rhsString
case let (.Int(lhsInt), .Int(rhsInt)):
return lhsInt == rhsInt
default:
return false
}
}
extension Dictionary {
subscript(key: Key?) -> Value! {
guard let key = key else { return nil }
return self[key]
}
}
+1 -1
View File
@@ -9,7 +9,7 @@
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<string>6.1</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
-154
View File
@@ -1,154 +0,0 @@
//
// Dip
//
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
// MARK: - Register/resolve dependencies with runtime arguments
extension DependencyContainer {
// MARK: 1 Runtime Argument
/**
Registers factory that accepts one runtime argument. You can use up to six runtime arguments.
- parameter tag: The arbitrary tag to associate this factory with when registering with that protocol.
Pass `nil` to associate with any tag. Default value is `nil`.
- parameter factory: The factory to register, with return type of protocol you want to register it for
- note: You can have several factories with different number or types of arguments registered to for same type.
When you resolve it container will match the type and tag as well as __number__, __types__ and __order__
of runtime arguments that you pass to `resolve` method.
- seealso: `register(tag:factory:scope:)`
*/
public func register<T, Arg1>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1) -> T) -> DefinitionOf<T> {
return register(tag: tag, factory: factory, scope: scope) as DefinitionOf<T>
}
/**
Resolve a dependency with runtime argument. Factories will be matched by tag and the type to resolve as well
as __number__, __types__ and __order__ of runtime arguments that you pass to this method.
- parameter tag: The arbitrary tag to look for when resolving this protocol.
- parameter arg1: First argument to be passed to factory
- seealso: `resolve(tag:)`
*/
public func resolve<T, Arg1>(tag tag: Tag? = nil, withArguments arg1: Arg1) -> T {
return resolve(tag: tag) { (factory: (Arg1) -> T) in factory(arg1) }
}
///**Deprecated** Use `resolve(tag:withArguments:)` method instead.
@available(*, deprecated, message="Use `resolve(tag:withArguments:)` method instead.")
public func resolve<T, Arg1>(tag tag: Tag? = nil, _ arg1: Arg1) -> T {
return resolve(tag: tag) { (factory: (Arg1) -> T) in factory(arg1) }
}
// MARK: 2 Runtime Arguments
/// - seealso: `register(:factory:scope:)`
public func register<T, Arg1, Arg2>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2) -> T) -> DefinitionOf<T> {
return register(tag: tag, factory: factory, scope: scope) as DefinitionOf<T>
}
/// - seealso: `resolve(tag:_:)`
public func resolve<T, Arg1, Arg2>(tag tag: Tag? = nil, withArguments arg1: Arg1, _ arg2: Arg2) -> T {
return resolve(tag: tag) { (factory: (Arg1, Arg2) -> T) in factory(arg1, arg2) }
}
///**Deprecated** Use `resolve(tag:withArguments:_:)` method instead.
@available(*, deprecated, message="Use `resolve(tag:withArguments:_:)` method instead.")
public func resolve<T, Arg1, Arg2>(tag tag: Tag? = nil, _ arg1: Arg1, _ arg2: Arg2) -> T {
return resolve(tag: tag) { (factory: (Arg1, Arg2) -> T) in factory(arg1, arg2) }
}
// MARK: 3 Runtime Arguments
public func register<T, Arg1, Arg2, Arg3>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3) -> T) -> DefinitionOf<T> {
return register(tag: tag, factory: factory, scope: scope) as DefinitionOf<T>
}
/// - seealso: `resolve(tag:withArguments:)`
public func resolve<T, Arg1, Arg2, Arg3>(tag tag: Tag? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3) -> T {
return resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3) -> T) in factory(arg1, arg2, arg3) }
}
///**Deprecated** Use `resolve(tag:withArguments:_:_:)` method instead.
@available(*, deprecated, message="Use `resolve(tag:withArguments:_:_:)` method instead.")
public func resolve<T, Arg1, Arg2, Arg3>(tag tag: Tag? = nil, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3) -> T {
return resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3) -> T) in factory(arg1, arg2, arg3) }
}
// MARK: 4 Runtime Arguments
public func register<T, Arg1, Arg2, Arg3, Arg4>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4) -> T) -> DefinitionOf<T> {
return register(tag: tag, factory: factory, scope: scope) as DefinitionOf<T>
}
/// - seealso: `resolve(tag:withArguments:)`
public func resolve<T, Arg1, Arg2, Arg3, Arg4>(tag tag: Tag? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4) -> T {
return resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4) -> T) in factory(arg1, arg2, arg3, arg4) }
}
///**Deprecated** Use `resolve(tag:withArguments:_:_:_:)` method instead.
@available(*, deprecated, message="Use `resolve(tag:withArguments:_:_:_:)` method instead.")
public func resolve<T, Arg1, Arg2, Arg3, Arg4>(tag tag: Tag? = nil, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4) -> T {
return resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4) -> T) in factory(arg1, arg2, arg3, arg4) }
}
// MARK: 5 Runtime Arguments
public func register<T, Arg1, Arg2, Arg3, Arg4, Arg5>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4, Arg5) -> T) -> DefinitionOf<T> {
return register(tag: tag, factory: factory, scope: scope) as DefinitionOf<T>
}
/// - seealso: `resolve(tag:withArguments:)`
public func resolve<T, Arg1, Arg2, Arg3, Arg4, Arg5>(tag tag: Tag? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5) -> T {
return resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5) -> T) in factory(arg1, arg2, arg3, arg4, arg5) }
}
///**Deprecated** Use `resolve(tag:withArguments:_:_:_:_:)` method instead.
@available(*, deprecated, message="Use `resolve(tag:withArguments:_:_:_:_:)` method instead.")
public func resolve<T, Arg1, Arg2, Arg3, Arg4, Arg5>(tag tag: Tag? = nil, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5) -> T {
return resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5) -> T) in factory(arg1, arg2, arg3, arg4, arg5) }
}
// MARK: 6 Runtime Arguments
public func register<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> T) -> DefinitionOf<T> {
return register(tag: tag, factory: factory, scope: scope) as DefinitionOf<T>
}
/// - seealso: `resolve(tag:withArguments:)`
public func resolve<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6>(tag tag: Tag? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6) -> T {
return resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> T) in factory(arg1, arg2, arg3, arg4, arg5, arg6) }
}
///**Deprecated** Use `resolve(tag:withArguments:_:_:_:_:_:)` method instead.
@available(*, deprecated, message="Use `resolve(tag:withArguments:_:_:_:_:_:)` method instead.")
public func resolve<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6>(tag tag: Tag? = nil, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6) -> T {
return resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> T) in factory(arg1, arg2, arg3, arg4, arg5, arg6) }
}
}
-111
View File
@@ -1,111 +0,0 @@
//
// DipTests.swift
// DipTests
//
// Created by Ilya Puchka on 04.11.15.
// Copyright © 2015 AliSoftware. All rights reserved.
//
import XCTest
@testable import Dip
protocol Service {
func getServiceName() -> String
}
class ServiceImp1: Service {
func getServiceName() -> String {
return "ServiceImp1"
}
}
class ServiceImp2: Service {
func getServiceName() -> String {
return "ServiceImp2"
}
}
class DipTests: XCTestCase {
let container = DependencyContainer()
override func setUp() {
super.setUp()
container.reset()
}
func testThatItResolvesInstanceRegisteredWithoutTag() {
//given
container.register { ServiceImp1() as Service }
//when
let serviceInstance = container.resolve() as Service
//then
XCTAssertTrue(serviceInstance is ServiceImp1)
}
func testThatItResolvesInstanceRegisteredWithTag() {
//given
container.register(tag: "service") { ServiceImp1() as Service }
//when
let serviceInstance = container.resolve(tag: "service") as Service
//then
XCTAssertTrue(serviceInstance is ServiceImp1)
}
func testThatItResolvesDifferentInstancesRegisteredForDifferentTags() {
//given
container.register(tag: "service1") { ServiceImp1() as Service }
container.register(tag: "service2") { ServiceImp2() as Service }
//when
let service1Instance = container.resolve(tag: "service1") as Service
let service2Instance = container.resolve(tag: "service2") as Service
//then
XCTAssertTrue(service1Instance is ServiceImp1)
XCTAssertTrue(service2Instance is ServiceImp2)
}
func testThatNewRegistrationOverridesPreviousRegistration() {
//given
container.register { ServiceImp1() as Service }
let service1 = container.resolve() as Service
//when
container.register { ServiceImp2() as Service }
let service2 = container.resolve() as Service
//then
XCTAssertTrue(service1 is ServiceImp1)
XCTAssertTrue(service2 is ServiceImp2)
}
func testThatItResolvesTypeAsNewInstanceEveryTime() {
//given
container.register { ServiceImp1() as Service }
//when
let service1 = container.resolve() as Service
let service2 = container.resolve() as Service
//then
XCTAssertFalse((service1 as! ServiceImp1) === (service2 as! ServiceImp1))
}
func testThatItReusesInstanceRegisteredAsSingleton() {
//given
container.register(.Singleton) { ServiceImp1() as Service }
//when
let service1 = container.resolve() as Service
let service2 = container.resolve() as Service
//then
XCTAssertTrue((service1 as! ServiceImp1) === (service2 as! ServiceImp1))
}
}
+1 -1
View File
@@ -9,7 +9,7 @@
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<string>6.1</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
-217
View File
@@ -1,217 +0,0 @@
//
// RuntimeArgumentsTests.swift
// DipTests
//
// Created by Ilya Puchka on 04.11.15.
// Copyright © 2015 AliSoftware. All rights reserved.
//
import XCTest
@testable import Dip
class ServiceImp3: Service {
let name: String
init(name: String, baseURL: NSURL, port: Int) {
self.name = name
}
func getServiceName() -> String {
return name
}
}
class RuntimeArgumentsTests: XCTestCase {
let container = DependencyContainer()
override func setUp() {
super.setUp()
container.reset()
}
func testThatItResolvesInstanceWithOneArgument() {
//given
let arg1 = 1
container.register { (a1: Int) -> Service in
XCTAssertEqual(a1, arg1)
return ServiceImp1()
}
//when
let service = container.resolve(withArguments: arg1) as Service
//then
XCTAssertTrue(service is ServiceImp1)
}
func testThatItResolvesInstanceWithTwoArguments() {
//given
let arg1 = 1, arg2 = 2
container.register { (a1: Int, a2: Int) -> Service in
XCTAssertEqual(a1, arg1)
XCTAssertEqual(a2, arg2)
return ServiceImp1()
}
//when
let service = container.resolve(withArguments: arg1, arg2) as Service
//then
XCTAssertTrue(service is ServiceImp1)
}
func testThatItResolvesInstanceWithThreeArguments() {
let arg1 = 1, arg2 = 2, arg3 = 3
container.register { (a1: Int, a2: Int, a3: Int) -> Service in
XCTAssertEqual(a1, arg1)
XCTAssertEqual(a2, arg2)
XCTAssertEqual(a3, arg3)
return ServiceImp1()
}
//when
let service = container.resolve(withArguments: arg1, arg2, arg3) as Service
//then
XCTAssertTrue(service is ServiceImp1)
}
func testThatItResolvesInstanceWithFourArguments() {
let arg1 = 1, arg2 = 2, arg3 = 3, arg4 = 4
container.register { (a1: Int, a2: Int, a3: Int, a4: Int) -> Service in
XCTAssertEqual(a1, arg1)
XCTAssertEqual(a2, arg2)
XCTAssertEqual(a3, arg3)
XCTAssertEqual(a4, arg4)
return ServiceImp1()
}
//when
let service = container.resolve(withArguments: arg1, arg2, arg3, arg4) as Service
//then
XCTAssertTrue(service is ServiceImp1)
}
func testThatItResolvesInstanceWithFiveArguments() {
let arg1 = 1, arg2 = 2, arg3 = 3, arg4 = 4, arg5 = 5
container.register { (a1: Int, a2: Int, a3: Int, a4: Int, a5: Int) -> Service in
XCTAssertEqual(a1, arg1)
XCTAssertEqual(a2, arg2)
XCTAssertEqual(a3, arg3)
XCTAssertEqual(a4, arg4)
XCTAssertEqual(a5, arg5)
return ServiceImp1()
}
//when
let service = container.resolve(withArguments: arg1, arg2, arg3, arg4, arg5) as Service
//then
XCTAssertTrue(service is ServiceImp1)
}
func testThatItResolvesInstanceWithSixArguments() {
let arg1 = 1, arg2 = 2, arg3 = 3, arg4 = 4, arg5 = 5, arg6 = 6
container.register { (a1: Int, a2: Int, a3: Int, a4: Int, a5: Int, a6: Int) -> Service in
XCTAssertEqual(a1, arg1)
XCTAssertEqual(a2, arg2)
XCTAssertEqual(a3, arg3)
XCTAssertEqual(a4, arg4)
XCTAssertEqual(a5, arg5)
XCTAssertEqual(a6, arg6)
return ServiceImp1()
}
//when
let service = container.resolve(withArguments: arg1, arg2, arg3, arg4, arg5, arg6) as Service
//then
XCTAssertTrue(service is ServiceImp1)
}
func testThatItRegistersDifferentFactoriesForDifferentNumberOfArguments() {
//given
let arg1 = 1, arg2 = 2
container.register { (a1: Int) in ServiceImp1() as Service }
container.register { (a1: Int, a2: Int) in ServiceImp2() as Service }
//when
let service1 = container.resolve(withArguments: arg1) as Service
let service2 = container.resolve(withArguments: arg1, arg2) as Service
//then
XCTAssertTrue(service1 is ServiceImp1)
XCTAssertTrue(service2 is ServiceImp2)
}
func testThatItRegistersDifferentFactoriesForDifferentTypesOfArguments() {
//given
let arg1 = 1, arg2 = "string"
container.register { (a1: Int) in ServiceImp1() as Service }
container.register { (a1: String) in ServiceImp2() as Service }
//when
let service1 = container.resolve(withArguments: arg1) as Service
let service2 = container.resolve(withArguments: arg2) as Service
//then
XCTAssertTrue(service1 is ServiceImp1)
XCTAssertTrue(service2 is ServiceImp2)
}
func testThatItRegistersDifferentFactoriesForDifferentOrderOfArguments() {
//given
let arg1 = 1, arg2 = "string"
container.register { (a1: Int, a2: String) in ServiceImp1() as Service }
container.register { (a1: String, a2: Int) in ServiceImp2() as Service }
//when
let service1 = container.resolve(withArguments: arg1, arg2) as Service
let service2 = container.resolve(withArguments: arg2, arg1) as Service
//then
XCTAssertTrue(service1 is ServiceImp1)
XCTAssertTrue(service2 is ServiceImp2)
}
func testThatNewRegistrationWithSameArgumentsOverridesPreviousRegistration() {
//given
let arg1 = 1, arg2 = 2
container.register { (a1: Int, a2: Int) in ServiceImp1() as Service }
let service1 = container.resolve(withArguments: arg1, arg2) as Service
//when
container.register { (a1: Int, a2: Int) in ServiceImp2() as Service }
let service2 = container.resolve(withArguments: arg1, arg2) as Service
//then
XCTAssertTrue(service1 is ServiceImp1)
XCTAssertTrue(service2 is ServiceImp2)
}
func testThatDifferentFactoriesRegisteredIfArgumentIsOptional() {
//given
let name1 = "1", name2 = "2", name3 = "3"
container.register { (port: Int, url: NSURL) in ServiceImp3(name: name1, baseURL: url, port: port) as Service }
container.register { (port: Int, url: NSURL?) in ServiceImp3(name: name2, baseURL: url!, port: port) as Service }
container.register { (port: Int, url: NSURL!) in ServiceImp3(name: name3, baseURL: url, port: port) as Service }
//when
let url: NSURL = NSURL(string: "http://example.com")!
let service1 = container.resolve(withArguments: 80, url) as Service
let service2 = container.resolve(withArguments: 80, NSURL(string: "http://example.com")) as Service
let service3 = container.resolve(withArguments: 80, NSURL(string: "http://example.com")! as NSURL!) as Service
let service4 = container.resolve(withArguments: 80, NSURL(string: "http://example.com")!) as Service
//then
XCTAssertEqual(service1.getServiceName(), name1)
XCTAssertEqual(service2.getServiceName(), name2)
XCTAssertEqual(service3.getServiceName(), name3)
XCTAssertEqual(service4.getServiceName(), name1) //implicitly unwrapped optional parameter is the same as not optional parameter
}
}
Binary file not shown.
Binary file not shown.
+2
View File
@@ -0,0 +1,2 @@
[
]
@@ -0,0 +1,211 @@
//: [Previous: Auto-wiring](@previous)
import UIKit
import Dip
let container = DependencyContainer()
/*:
### Auto-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:
*/
protocol Service: class {
var logger: Logger? { get }
var tracker: Tracker? { get }
}
class ServiceImp: Service {
var logger: Logger?
var tracker: Tracker?
}
/*:
When you register them in a container you will end up with something like this:
*/
container.register() { TrackerImp() as Tracker }
container.register() { LoggerImp() as Logger }
container.register() { ServiceImp() as Service }
.resolvingProperties { container, service in
let service = service as! ServiceImp
service.logger = try container.resolve() as Logger
service.tracker = try container.resolve() as Tracker
}
let service = try! container.resolve() as Service
service.logger
service.tracker
/*:
Notice that the same boilerplate code that we saw in constructor injection now moved to `resolveDepedencies` block.
With auto-injection your code transforms to this:
*/
class AutoInjectedServiceImp: Service {
private let injectedLogger = Injected<Logger>()
var logger: Logger? { return injectedLogger.value }
private let injectedTracker = Injected<Tracker>()
var tracker: Tracker? { return injectedTracker.value }
}
container.register() { AutoInjectedServiceImp() as Service }
let autoInjectedService = try! container.resolve() as Service
autoInjectedService.logger
autoInjectedService.tracker
/*:
As you can see we added two private properties to our implementation of `Service` - `injectedLogger` and `injectedTracker`. Their types are `Injeceted<Logger>` and `Injected<Tracker>` respectively. Note that we've not just defined them as properties of those types, but defined them with some initial value. `Injected<T>` is a simple _wrapper class_ that wraps value of generic type and provides read-write access to it with `value` property. This property is defined as optional, so that when we create instance of `Injected<T>` it will have `nil` in its value. There is also another wrapper - `InjectedWeak<T>` - which in contrast to `Injected<T>` holds a week reference to its wrapped object, thus requiring it to be a _reference type_ (or `AnyObject`), when `Injected<T>` can also wrap value types (or `Any`).
What is happening under the hood is that after concrete instance of resolved type is created (`Service` in that case), container will iterate through its properties using `Mirror`. For each of the properties wrapped with `Injected<T>` or `InjectedWeak<T>` it will search a definition that can be used to create an instance of wrapped type and use it to create and inject a concrete instance in a `value` property of a wrapper. The fact that wrappers are _classes_ or _reference types_ makes it possible at runtime to inject dependency in instance of resolved type.
The requirement for auto-injection is that types injected types should be registered in a container and should use factories with no runtime arguments.
Auto-injected properties can be marked with tag. Then container will search for definition tagged by the same tag to resolve this property.
You can provide closure that will be called when the dependency will be injected in the property. It is similar to `didSet` property observer.
Auto-injected properties are required by default. That means that if container fails to resolve any of auto-injected properties of the instance (or any of its dependencies) it will fail resolution of the object graph in whole.
*/
class ServerWithRequiredClient {
var client = Injected<Client>()
}
container.register { ServerWithRequiredClient() }
do {
let serverWithClient = try container.resolve() as ServerWithRequiredClient
}
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).
*/
class ServerWithOptionalClient {
var optionalClient = Injected<Client>(required: false)
}
container.register { ServerWithOptionalClient() }
let serverWithNoClient = try! container.resolve() as ServerWithOptionalClient
serverWithNoClient.optionalClient.value
/*:
Another example of using auto-injection is circular dependencies. Let's say you have a `Server` and a `ServerClient` both referencing each other.
*/
protocol Server: class {
weak var client: ServerClient? { get }
}
protocol ServerClient: class {
var server: Server? { get }
}
class ServerImp: Server {
weak var client: ServerClient?
}
class ServerClientImp: ServerClient {
var server: Server?
init(server: Server) {
self.server = server
}
}
/*:
The standard way to register such components in `DependencyContainer` will lead to such code:
*/
container.register {
ServerClientImp(server: try container.resolve()) as ServerClient
}
container.register { ServerImp() as Server }
.resolvingProperties { (container: DependencyContainer, server: Server) in
(server as! ServerImp).client = try container.resolve() as ServerClient
}
let client = try! container.resolve() as ServerClient
client.server
/*:
With auto-injection you will have the following code:
*/
class InjectedServerImp: Server {
private var injectedClient = InjectedWeak<ServerClient>()
var client: ServerClient? { return injectedClient.value }
}
class InjectedClientImp: ServerClient {
private var injectedServer = Injected<Server>()
var server: Server? { get { return injectedServer.value } }
}
container.register { InjectedServerImp() as Server }
container.register { InjectedClientImp() as ServerClient }
let injectedClient = try! container.resolve() as ServerClient
injectedClient.server
injectedClient.server?.client === injectedClient //circular dependencies were resolved correctly
/*:
You can see that component registration looks much simpler now. But on the other side it requires some boilerplate code in implementations, and also tightly coupls your code with Dip.
Here is an example with higher number of dependencies.
*/
container.register() { RouterImp() as Router }
container.register() { DataProviderImp() as DataProvider }
class ViewController: UIViewController {
var logger: Logger?
var tracker: Tracker?
var dataProvider: DataProvider?
var router: Router?
}
container.register { ViewController() }
.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
controller.router = try container.resolve() as Router
}
let viewController = try! container.resolve() as ViewController
viewController.router
/*:
With auto-injection you can replace that with something like this:
*/
class AutoInjectedViewController: UIViewController {
let logger = Injected<Logger>()
let tracker = Injected<Tracker>()
let dataProvider = Injected<DataProvider>()
let router = Injected<Router>()
}
container.register { AutoInjectedViewController() }
let autoViewController = try! container.resolve() as AutoInjectedViewController
autoViewController.router.value
/*:
In such scenario when view controller is created by storyboard you will need to use property injection anyway, so the overhead of adding additional properties for auto-injection is smaller. Also all the boilerplate code of unwrapping injected properties (if you need that) can be moved to extension, cleaning implementation a bit.
> **Note**: For such cases concider using [DipUI](https://github.com/AliSoftware/Dip-UI). It is a small extension for Dip that allows you to do exactly what we need in this example - inject dependencies in instances created by storyboards. It does not require to use auto-injection feature but plays nice with it.
So as you can see there are certain advantages and disadvatages of using auto-injection. It makes your definitions simpler, especially if there are circular dependencies involved or the number of dependencies is high, removing boilerplate calls to `resolve` method in `resolveDependencies` block of your definitions. But it requires additional properties and some boilerplate code in your implementations, makes your implementatios tightly coupled with Dip. You can avoid tight coupoling by using your own boxing classes instead of `Injected<T>` and `InjectedWeak<T>` (see `AutoInjectedPropertyBox`).
*/
//: [Next: Type Forwarding](@next)
@@ -0,0 +1,117 @@
//: [Previous: Shared Instances](@previous)
import Dip
import UIKit
/*:
### Auto-wiring
Among three main DI patterns - _constructor_, _property_ and _method_ injection - 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:
*/
protocol Service {}
protocol Interactor {
var service: Service { get }
}
protocol Router {}
protocol ViewOutput {}
protocol Presenter {
var router: Router { get }
var interactor: Interactor { get }
var view: ViewOutput { get }
}
class RouterImp: Router {}
class View: UIView, ViewOutput {}
class ServiceImp: Service {}
/*:
VIPER module by its nature consists of a lot of components, wired up using protocols. Using constructor injection you can end up with following constructors for presenter and interactor:
*/
class InteractorImp: Interactor {
var service: Service
init(service: Service) {
self.service = service
}
}
class PresenterImp: Presenter {
let router: Router
let interactor: Interactor
let view: ViewOutput
init(view: ViewOutput, interactor: Interactor, router: Router) {
self.view = view
self.interactor = interactor
self.router = router
}
}
/*:
If you register these components in a container you will end up with rather boilerplate code:
*/
let container = DependencyContainer()
container.register { ServiceImp() as Service }
container.register { RouterImp() as Router }
container.register { View() as ViewOutput }
container.register { try InteractorImp(service: container.resolve()) as Interactor }
container.register {
try PresenterImp(
view: container.resolve(),
interactor: container.resolve(),
router: container.resolve()) as Presenter
}
var presenter = try! container.resolve() as Presenter
presenter.interactor.service
/*:
While definition for `Interactor` looks fine, `Presenter`'s definition is overloaded with the same `resolve` calls to container.
The other option you have is to register factory with runtime arguments:
*/
container.register { InteractorImp(service: $0) as Interactor }
container.register { PresenterImp(view: $0, interactor: $1, router: $2) as Presenter }
/*:
But then to resolve presenter or interactor you will first need to resolve their dependencies and pass them as arguments to `resolve` method:
*/
let service = try! container.resolve() as Service
let interactor = try! container.resolve(arguments: service) as Interactor
let view = try! container.resolve() as ViewOutput
let router = try! container.resolve() as Router
presenter = try! container.resolve(arguments: view, interactor, router) as Presenter
presenter.interactor.service
/*:
Again to much of boilerplate code. Also it's easy to make a mistake in the order of arguments.
Auto-wiring solves this problem by combining these two approaches - you register factories with runtime arguments, but resolve components with just a call to `resolve()`. Container will resolve all consturctor arguments for you.
*/
container.register { InteractorImp(service: $0) as Interactor }
container.register { PresenterImp(view: $0, interactor: $1, router: $2) as Presenter }
presenter = try! container.resolve() as Presenter
presenter.interactor.service
/*:
You don't need to call `resolve` in a factory and care about order of arguments any more.
The only requirement is that all constructor arguments should be registered in the container and there should be no several factories with the same _number_ of arguments registered for the same components.
In very rare 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.
*/
//: [Next: Auto-injection](@next)
@@ -0,0 +1,107 @@
//: [Previous: Scopes](@previous)
import Dip
import Foundation
let container = DependencyContainer()
/*:
### Circular Dependencies
Very often we encounter situations when we have circular dependencies between components. The most obvious example is delegation pattern. Dip can resolve such dependencies easily.
Let's say you have some network client and it's delegate defined like this:
*/
protocol NetworkClientDelegate: class {
var networkClient: NetworkClient { get }
}
protocol NetworkClient: class {
weak var delegate: NetworkClientDelegate? { get set }
}
class NetworkClientImp: NetworkClient {
weak var delegate: NetworkClientDelegate?
init() {}
}
class Interactor: NSObject, NetworkClientDelegate {
let networkClient: NetworkClient
init(networkClient: NetworkClient) {
self.networkClient = networkClient
}
}
/*:
Note that:
- one of this classes uses _property injection_ (`NetworkClientImp`) you'll give the `delegate` value via its property directly, _after_ initialization
- and another uses _constructor injection_ (`Interactor`) you'll need to give the `networkclient` value via the constructor, _during_ initialization.
It's very important that _at least one_ of them uses property injection, because if you try to use constructor injection for both of them then you will enter infinite loop when you will call `resolve`.
Now you can register those classes in container:
*/
container.register {
Interactor(networkClient: try container.resolve()) as NetworkClientDelegate
}
container.register { NetworkClientImp() as NetworkClient }
.resolvingProperties { (container, client) -> () in
client.delegate = try container.resolve() as NetworkClientDelegate
}
/*:
Here you can spot the difference in the way we register classes.
- `Interactor` class uses constructor injection, so to register it we use the block factory where we call `resolve` to obtain instance of `NetworkClient` and pass it to constructor.
- `NetworkClientImp` uses property injection for it's delegate property. Again we use block factory to create instance, but to inject the delegate property we use the special `resolveDependencies` method. Block passed to this method will be called right _after_ the block factory. So you can use this block to perform additional setup or, like in this example, to resolve circular dependencies.
This way `DependencyContainer` breaks infinite recursion that would happen if we used constructor injection for both of our components.
*Note*: You can use container reference inside instance factory without using capture list, there will be [no retain cycle](https://github.com/AliSoftware/Dip/issues/23)
Now when you resolve `NetworkClientDelegate` you will get an instance of `Interactor` that will have client with delegate referencing the same `Interactor` instance:
*/
let interactor = try! container.resolve() as NetworkClientDelegate
interactor.networkClient.delegate === interactor // true: they are the same instances
/*:
**Warning**: Note that one of the properties (`delegate`) is defined as _weak_. That's crucial to avoid retain cycle. But now if you try to resolve `NetworkClient` first it's delegate will be released before `resolve` returns, bcause no one holds a reference to it except the container.
*/
let networkClient = try! container.resolve() as NetworkClient
networkClient.delegate // delegate was alread released =(
/*:
Note also that we used `.shared` scope to register implementations. This is also very important to preserve consistency of objects relationships.
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 `.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(.unique) {
Interactor(networkClient: try container.resolve()) as NetworkClientDelegate
}
container.register { NetworkClientImp() as NetworkClient }
.resolvingProperties { (container, client) -> () in
client.delegate = try container.resolve() as NetworkClientDelegate
}
let invalidInteractor = try! container.resolve() as NetworkClientDelegate
invalidInteractor.networkClient.delegate // that is not valid
//: [Next: Shared Instances](@next)
@@ -0,0 +1,71 @@
//: [Previous: Type Forwarding](@previous)
import Dip
/*:
### Containers collaboration
Sometimes it makes sence to break your configuration in separate modules. For that you can use containers collaboration. You can link containers with each other and when you try to resolve a type using container where it was not registered, this container will forward request to its collaborating container. This way you can share core configurations or break them in separate modules, for example matching user stories, and still be able to link components from different modules.
*/
protocol DataStore {}
class CoreDataStore: DataStore {}
class AddEventWireframe {
var eventsListWireframe: EventsListWireframe?
}
class EventsListWireframe {
var addEventWireframe: AddEventWireframe?
let dataStore: DataStore
init(dataStore: DataStore) {
self.dataStore = dataStore
}
}
let rootContainer = DependencyContainer()
rootContainer.register(.singleton) { CoreDataStore() as DataStore }
let eventsListModule = DependencyContainer()
eventsListModule.register { EventsListWireframe(dataStore: $0) }
.resolvingProperties { container, wireframe in
wireframe.addEventWireframe = try container.resolve()
}
let addEventModule = DependencyContainer()
addEventModule.register { AddEventWireframe() }
eventsListModule.collaborate(with: addEventModule, rootContainer)
var eventsListWireframe = try eventsListModule.resolve() as EventsListWireframe
eventsListWireframe.dataStore
eventsListWireframe.addEventWireframe
/*:
As you can see dependencies were resolved even though not all components were registered in the same container.
It is even safe to make circular references between containers. This way you can resolve circular dependencies between components registered in different containers.
*/
eventsListModule.reset()
addEventModule.reset()
eventsListModule.register { EventsListWireframe(dataStore: $0) }
.resolvingProperties { container, wireframe in
wireframe.addEventWireframe = try container.resolve()
}
addEventModule.register { AddEventWireframe() }
.resolvingProperties { container, wireframe in
wireframe.eventsListWireframe = try container.resolve()
}
addEventModule.collaborate(with: eventsListModule)
eventsListWireframe = try eventsListModule.resolve() as EventsListWireframe
eventsListWireframe.addEventWireframe
eventsListWireframe.addEventWireframe?.eventsListWireframe === eventsListWireframe
/*:
If you try to link container with itself it will be silently ignored. When forwarding request collaborating containers will be iterated in the same order that they were added.
*/
//: [Next: Testing](@next)
@@ -21,6 +21,9 @@ or using a configuration block:
*/
container = DependencyContainer { container in
//do not forget to use unowned reference if you will need
//to reference container inside definition's factory
unowned let container = container
//register components here
}
@@ -29,7 +32,7 @@ Both syntaxes are equivalent. The one using the configuration block is simply a
### When/where to create container?
While there is an option to use container as a global variable we advise instead to create and configure container in your app delegate and pass it between your objects.
While there is an option to use container as a global variable we advise instead to create and configure container in your app delegate and pass it between your objects (see [Shared Instances](Shared%20Instances)).
*/
//: [Next: Registering Components](@next)
@@ -31,14 +31,28 @@ container.register(factory: factory.someService)
/*:
Optionally you can associate definitions with Integer or String tags. This way you can register different implementations for the same protocol.
You can use String or Integer literals, or the `DependencyContainer.Tag` enum.
You can use `DependencyContainer.Tag` enum, String or Integer literals, or instances of types that conform to `DependencyTagConvertible` protocol.
*/
container.register(tag: "tag") { ServiceImp1() as Service }
container.register(tag: DependencyContainer.Tag.Int(0)) { ServiceImp1() as Service }
container.register(tag: 0) { ServiceImp1() as Service }
enum MyCustomTag: String, DependencyTagConvertible {
case SomeTag
}
container.register(tag: MyCustomTag.SomeTag) { ServiceImp1() as Service }
/*:
We recommand you to use constants for the tags, to make the intent clear and avoid magic numbers and typos.
You can remove all registered definitions or register and remove them one by one:
*/
let serviceDefinition = container.register { ServiceImp1() as Service }
container
container.remove(serviceDefinition)
container.reset()
//: [Next: Resolving Components](@next)
@@ -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 }
/*:
@@ -12,7 +11,7 @@ let container = DependencyContainer { container in
You resolve previously registered definition using `resolve` method:
*/
var service = container.resolve() as Service
var service = try! container.resolve() as Service
/*:
That code says that you want your `container` to give you an instance that was registered as implementation of `Service` protocol.
@@ -20,7 +19,7 @@ That code says that you want your `container` to give you an instance that was r
It's important to specify the same type that you used for registration. You can use either `as` syntax, or specify type of you variable when you define it:
*/
let otherService: Service = container.resolve()
let otherService: Service = try! container.resolve()
/*:
Both ways will let the `container` detect the type that you want to resolve as. We prefer the `as` syntax because it reads more naturally in Swift.
@@ -32,16 +31,16 @@ container.register(tag: "production") { ServiceImp1() as Service }
container.register(tag: "test") { ServiceImp2() as Service }
// Will give you a ServiceImp1 instance
let productionService = container.resolve(tag: "production") as Service
let productionService = try! container.resolve(tag: "production") as Service
// Will give you a ServiceImp2 instance
let testService = container.resolve(tag: "test") as Service
let testService = try! container.resolve(tag: "test") as Service
// Will give you a ServiceImp1 because one was registered without a tag on line 4
let defaultService = container.resolve() as Service
let defaultService = try! container.resolve() as Service
/*:
You can use runtime arguments to resolve components. Dip supports up to six arguments. For more details see ["Runtime arguments"](Runtime%20arguments).
*/
container.register { service in ClientImp1(service: service) as Client }
let client = container.resolve(service) as Client
let client = try! container.resolve(arguments: service) as Client
//: [Next: Runtime Arguments](@next)
@@ -12,21 +12,18 @@ Dip lets you use runtime arguments to register and resolve your components.
Note that __types__, __number__ and __order__ of arguments matters and you can register different factories with different set of runtime arguments for the same protocol. To resolve using one of this factory you will need to pass runtime arguments of the same types, number and in the same order to `resolve` as you used in `register` method.
*/
container.register { (url: NSURL, port: Int) in ServiceImp3(name: "1", baseURL: url, port: port) as Service }
container.register { (port: Int, url: NSURL) in ServiceImp3(name: "2", baseURL: url, port: port) as Service }
container.register { (port: Int, url: NSURL?) in ServiceImp3(name: "3", baseURL: url!, port: port) as Service }
container.register { (port: Int, url: NSURL!) in ServiceImp3(name: "4", baseURL: url, port: port) as Service }
container.register { (url: NSURL, port: Int) in ServiceImp4(name: "1", baseURL: url, port: port) as Service }
container.register { (port: Int, url: NSURL) in ServiceImp4(name: "2", baseURL: url, port: port) as Service }
container.register { (port: Int, url: NSURL?) in ServiceImp4(name: "3", baseURL: url!, port: port) as Service }
let url: NSURL = NSURL(string: "http://example.com")!
let service1 = container.resolve(withArguments: url, 80) as Service
let service2 = container.resolve(withArguments: 80, url) as Service
let service3 = container.resolve(withArguments: 80, NSURL(string: "http://example.com")) as Service
let service4 = container.resolve(withArguments: 80, NSURL(string: "http://example.com")! as NSURL!) as Service
let service1 = try! container.resolve(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! ServiceImp3).name
(service2 as! ServiceImp3).name
(service3 as! ServiceImp3).name
(service4 as! ServiceImp3).name
(service1 as! ServiceImp4).name
(service2 as! ServiceImp4).name
(service3 as! ServiceImp4).name
/*:
Note that all of the services were resolved using different factories.
@@ -35,12 +32,16 @@ _Dip_ supports up to six runtime arguments. If that is not enougth you can exten
*/
extension DependencyContainer {
public func register<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6>(tag tag: Tag? = nil, factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> T) -> DefinitionOf<T> {
return register(tag: tag, factory: factory, scope: .Prototype) as DefinitionOf<T>
@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, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6>(tag tag: Tag? = nil, _ arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6) -> T {
return resolve(tag: tag) { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6) -> T) in factory(arg1, arg2, arg3, arg4, arg5, arg6) }
public func resolve<T, 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) }
}
}
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Timeline
version = "3.0">
<TimelineItems>
</TimelineItems>
</Timeline>
@@ -2,30 +2,66 @@
import Dip
let container = DependencyContainer()
/*:
### Scopes
Dip supports two different scopes of objects: _Prototype_ 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`.
* The `.Singleton` scope will make the `DependencyContainer` retain the instance once resolved the first time, and reuse it in the next calls during the container lifetime.
* The `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 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 register a singleton, use `register(tag:instance:)`
The `Unique` scope is the default. To set a scope you pass it as an argument to `register` method.
*/
let container = DependencyContainer { container in
container.register(tag:"sharedService", instance: ServiceImp1() as Service)
container.register { ServiceImp1() as Service }
}
container.register { ServiceImp1() as Service }
container.register(.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 sharedService = container.resolve(tag: "sharedService") as Service
let sameSharedService = container.resolve(tag: "sharedService") as Service
sharedService as! ServiceImp1 === sameSharedService as! ServiceImp1
let service = try! container.resolve() as Service
let anotherService = try! container.resolve() as Service
// They are different instances as the scope defaults to .unique
service as! ServiceImp1 === anotherService as! ServiceImp1 // false
let service = container.resolve() as Service
let anotherService = container.resolve() as Service
service as! ServiceImp1 === anotherService as! ServiceImp1
let prototypeService = try! container.resolve(tag: "prototype") as Service
let anotherUniqueService = try! container.resolve(tag: "prototype") as Service
// They are different instances:
prototypeService === anotherUniqueService // false
//: [Next: Shared Instances](@next)
let graphService = try! container.resolve(tag: "object graph") as Service
let anotherGraphService = try! container.resolve(tag: "object graph") as Service
// still different instances the 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
let sharedService = try! container.resolve(tag: "shared instance") as Service
let sameSharedService = try! container.resolve(tag: "shared instance") as Service
// same instances, the singleton scope keep and reuse instances during the lifetime of the container
sharedService as! ServiceImp3 === sameSharedService as! ServiceImp3
/*:
### Bootstrapping
You can use `bootstrap()` method to fix your container setup and initialise components registered with `EagerSingleton` scope.
After bootstrapping if you try to add or remove any definition it will cause runtime exception. Call `boostrap` when you registered all the components, for example at the end of initialization block if you use `init(configBlock:)`.
*/
var resolvedEagerSingleton = false
let definition = container.register(.eagerSingleton, tag: "eager shared instance") { ServiceImp1() as Service }
.resolvingProperties { _ in resolvedEagerSingleton = true }
try! container.bootstrap()
resolvedEagerSingleton
let eagerSharedService = try! container.resolve(tag: "eager shared instance") as Service
container.remove(definition)
//: [Next: Circular Dependencies](@next)
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Timeline
version = "3.0">
<TimelineItems>
</TimelineItems>
</Timeline>
@@ -1,4 +1,4 @@
//: [Previous: Scopes](@previous)
//: [Previous: Circular Dependencies](@previous)
import Dip
import UIKit
@@ -20,6 +20,8 @@ Dip supports singletons, but it reduces cost of using them. Their singleton natu
- You can easyly change concrete implementations without the rest of your system even notice that something changed.
- Also it's easy to test - you just register another object in your tests. Even if you still want to use a singleton in your system.
Those features you got when using Dip limits tight coupling in your code and gives you back your code flexibility.
Probably the most common example is using a singleton in the network layer or "API client".
*/
@@ -31,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 */ }
}
@@ -43,7 +45,7 @@ Sure, this is very easy to code indeed. And nothing bad so far.
But probably if you wrote a unit test or integration test for that code first, you would have noticed a problem earilier. How you test that code? And how you ensure that your tests are idenpendent of the API client's state from the previous test?
Of cource you can work around all of the problems and the fact that `ApiClient` is a singleton, reset it's state somehow, or mock a class so that it will not return a singleton instance. But look - a moment before the singleton was your best friend and now you are fighting against it.
Think - why do you want API client to be a singleton in a first place? To queue or throttle requests? Then do your queue or throttler a singleton, not an API client. Or is there any other reason. Most likely API client itself does not have a requirement to have only one system during the whole lifecycle of your application. Imagine that in the future we need two API Clients, because you now have to address two different servers & plaforms? Imposing that singleton restricts now your flexibility a lot.
Think - why do you want API client to be a singleton in a first place? To queue or throttle requests? Then do your queue or throttler a singleton, not an API client. Or is there any other reason. Most likely API client itself does not have a requirement to have one and only one instance during the lifecycle of your application. Imagine that in the future we need two API Clients, because you now have to address two different servers & plaforms? Imposing that singleton restricts now your flexibility a lot.
Instead, inject API client in view controller with property injection or constructor injection.
*/
@@ -69,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() {
@@ -88,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()
@@ -96,64 +98,23 @@ 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 = 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 used as singleton too. Instead, inject it to objects that need to access it. And use a protocol for that. For example if your view controller needs to access API client, it does not need a reference to `DependencyContainer`, it only needs a reference to _something_ that can provide it an API client instance.
*/
Of cource `DependencyContainer` should not be a singleton too. 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.
protocol ApiClientProvider {
func apiClient() -> ApiClientProtocol
}
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.
extension DependencyContainer: ApiClientProvider {
func apiClient() -> ApiClientProtocol {
return self.resolve() as ApiClientProtocol
}
}
extension DipViewController {
convenience init(apiClientProvider: ApiClientProvider) {
self.init()
self.apiClient = apiClientProvider.apiClient()
}
}
dipController = DipViewController(apiClientProvider: container)
/*:
This way you also does not depend directly on Dip. Instead you provide a boundary between Dip that you don't have control of and your source code. So when something chagnes in Dip, you update only the boundary code.
Dependency injection is a pattern as well as singleton. And any pattern can be abused. That's why if you adopt DI in one part of your system it does not mean that you should inject everything and everywhere. The same with using protocols instead of concrete implementations. For every tool there is a right time and the same way as singleton can harm you the same way DI and protocols abuse can make your code unnececerry complex.
If you want to know more about Dependency Injection in general we recomend you to read ["Dependency Injection in .Net" by Mark Seemann](https://www.manning.com/books/dependency-injection-in-dot-net). Dip was inspired by implementations of IoC container for .Net platform and shares core principles described in that book.
*/
//: [Next: Testing](@next)
//: [Next: Auto-wiring](@next)
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Timeline
version = "3.0">
<TimelineItems>
</TimelineItems>
</Timeline>
@@ -1,37 +1,49 @@
//: [Previous: Shared Instances](@previous)
//: [Previous: Containers Collaboration](@previous)
//import XCTest
import Dip
let container = DependencyContainer()
/*:
### Testing
If you use Dependency Injection patterns like contructor and property injection it will be much easier
to unit test your components. When it comes to integration tests you may want to mock some real services.
In these tests you can register mock implementation in the container and it will be injected instead of the real implementation.
Dip is convenient to use for testing. Here is s simple example of how you can write tests with Dip.
__Note__: That's a very simple example just to demostrate use of Dip in tests, not how you should or should not tests your code in general.
> That's a very simple example just to demonstrate use of Dip in tests, not how you should or should not test your code in general.
You can learn more about testing based on state verification vs behavior verification [here](http://martinfowler.com/articles/mocksArentStubs.html).
> XCTest is not supported by playgrounds so to be able to compile this page we commented out XCTest specific code.
*/
protocol Service {
protocol ServiceType {
func doSomething()
}
class RealService: ServiceType {
func doSomething() {
//do something real
}
}
class Client {
var service: Service!
var service: ServiceType!
func callService() {
service.doSomething()
}
}
import XCTest
import Dip
/*:
Instead of the real `Service` implementation, provide a _fake_ implementation with test hooks that you need:
*/
class FakeService: Service {
class FakeService: ServiceType {
var doSomethingCalled = false
func doSomething() {
@@ -41,30 +53,60 @@ class FakeService: Service {
init() {}
}
class MyTests: XCTestCase {
var container: DependencyContainer!
override func setUp() {
super.setUp()
/*:
Register fake implementation as `Service`:
Somewhere in your production code you register real implementations:
*/
func configure(container: DependencyContainer) {
container.register { RealService() as ServiceType }
container.register { Client() }
.resolvingProperties { container, client in
client.service = try container.resolve()
}
}
class MyTests/*: XCTestCase*/ {
/*override*/ func setUp() {
//super.setUp()
/*:
Reset container configuration to normal state:
*/
container = DependencyContainer { container in
container.register { FakeService() as Service }
}
container.reset()
configure(container: container)
}
func testThatDoSomethingIsCalled() {
let sut = Client()
sut.service = container.resolve() as Service
/*:
Register fake implementation as `Service`:
*/
container.register { FakeService() as ServiceType }
let sut = try! container.resolve() as Client
sut.callService()
/*:
And finally you test it was called:
*/
XCTAssertTrue((sut.service as! FakeService).doSomethingCalled)
let service = sut.service as! FakeService
//XCTAssertTrue(service.doSomethingCalled)
}
}
/*:
You can also validate your container configuration. You can do that either in a separate test suit or when runnging application in `DEBUG` mode.
During validation container will try to resolve all the definitions registered in it. If some of definitions requires runtime arguments you can provide them as arguments to `validate` method. They should exactly match types of arguments required by factories. Multiple arguments for the single factory should be grouped in a tuple. If you don't provide arguments validation will fail.
*/
container.register { (url: NSURL, port: Int) in ServiceImp4(name: "1", baseURL: url, port: port) as Service }
try! container.validate((NSURL(string: "https://github.com/AliSoftware/Dip")!, 80))
do {
try container.validate()
}
catch {
print(error)
}
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Timeline
version = "3.0">
<TimelineItems>
</TimelineItems>
</Timeline>
@@ -0,0 +1,110 @@
//: [Previous: Auto-injection](@previous)
import Foundation
import Dip
let container = DependencyContainer()
/*:
### Type Forwarding
Very often we end up with single class that implements several protocols. This is normal even in [VIPER architecture](https://github.com/mutualmobile/VIPER-SWIFT/blob/master/VIPER-SWIFT/Classes/Modules/List/User%20Interface/Presenter/ListPresenter.swift#L12) that constantly strives for Single Responsibility Principle.
Let's look at example of VIPER architecture:
*/
extension ListPresenter: ListInteractorOutput, ListModuleInterface, AddModuleDelegate {}
extension ListInteractor: ListInteractorInput {}
extension AddPresenter: AddModuleInterface {}
/*:
In VIPER we need to create several objects (presenters, wireframes, interactors) which should be accessed thorugh different interfaces. We need to wire them all together so that we have the same instances in place for different types.
- `ListInteractor` referenced by `ListPresenter` in its `listInteractor` property (via `ListInteractorInput` protocol) should hold a backward reference to the same presenter in its `output` property
- `ListWireframe` referenced by `ListPresenter` should also hold a backward reference to the same presenter in its `listPresenter` property
- `AddWireframe` should hold a reference to `AddPresenter` that should hold reference to the same `ListPresenter` in its `addModuleDelegate` property (via `AddModuleDelegate` protocol).
We can achieve this result by explicitly rosolving concrete types:
*/
container.register { ListWireframe(addWireFrame: $0, listPresenter: $1) }
container.register { AddWireframe(addPresenter: $0) }
var listInteractorDefinition = container.register { ListInteractor() }
.resolvingProperties { container, interactor in
interactor.output = try container.resolve() as ListPresenter
}
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 { AddPresenter() }
.resolvingProperties { container, presenter in
presenter.addModuleDelegate = try container.resolve() as ListPresenter
}
var addPresenter = try! container.resolve() as AddPresenter
var listPresenter = addPresenter.addModuleDelegate as! ListPresenter
var listInteractor = listPresenter.listInteractor as! ListInteractor
listInteractor.output === listPresenter
var listWireframe = listPresenter.listWireframe
listWireframe?.listPresenter === listPresenter
/*:
Alternatively we can use type-forwarding. With type-forwarding we register definition for one (source) type and also for another (forwarded) type. When container will try to resolve forwarded type it will use the same definition as for source type, and (if registered in `Shared` scope or as a singleton) will reuse the same instance. With that you don't need to resolve concrete types in definitions:
*/
listInteractorDefinition = container.register { ListInteractor() }
.resolvingProperties { container, interactor in
interactor.output = try container.resolve()
}
listPresenterDefinition = container.register { ListPresenter() }
.resolvingProperties { container, presenter in
presenter.listInteractor = try container.resolve()
presenter.listWireframe = try container.resolve()
}
addPresenterDefinition = container.register { AddPresenter() }
.resolvingProperties { container, presenter in
presenter.addModuleDelegate = try container.resolve()
}
/*:
And now we register definitions for type-forwarding:
*/
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
listInteractor = listPresenter.listInteractor as! ListInteractor
listInteractor.output === listPresenter
listWireframe = listPresenter.listWireframe
listWireframe?.listPresenter === listPresenter
/*:
Type forwarding will work the same way whenever your resolve dependencies with property injection using `resolveDependencies` block, or with auto-injected properties, or with constructor injection and auto-wiring.
Registering definition for type forwarding will effectively register another definition in the container, linked with original one. So the same overriding rool will be applied for such registrations - last wins. If you need to register different definitions for the same type you should register them with different tags.
You can also provide `resolveDependencies` block for forwarded definition. First container will call `resolveDependencies` block of the source definition, and then of forwarded definition:
*/
listInteractorDefinition
.resolvingProperties { container, interactor in
print("resolved ListInteractor")
}
let _ = container.register(listInteractorDefinition, type: ListInteractorInput.self)
.resolvingProperties { container, interactor in
print("resolved ListInteractorInput")
}
addPresenter = try! container.resolve() as AddPresenter
//: [Next: Containers Collaboration](@next)
@@ -16,6 +16,9 @@ If you follow [Protocol-Oriented programming](https://developer.apple.com/videos
But still there should be some point in your program where concrete instances are created. The thing is that it's better to have one well defined point for that than to scatter setup logic all over the place with different factories and lazy properties. IoC containers like _Dip_ play the role of that point.
The following pages in this Playground demonstrates how to use _Dip_ to adopt all those concepts in practice.
If you want to know more about Dependency Injection in general we recomend you to read ["Dependency Injection in .Net" by Mark Seemann](https://www.manning.com/books/dependency-injection-in-dot-net). Dip was inspired by implementations of IoC container for .Net platform and shares core principles described in that book.
*/
//: [Next: Creating a DependencyContainer](@next)
+66 -3
View File
@@ -1,6 +1,6 @@
import Foundation
public protocol Service {}
public protocol Service: class {}
public class ServiceImp1: Service {
public init() {}
@@ -8,8 +8,11 @@ public class ServiceImp1: Service {
public class ServiceImp2: Service {
public init() {}
}
public class ServiceImp3: Service {
public init() {}
}
public class ServiceImp4: Service {
public let name: String
@@ -19,7 +22,7 @@ public class ServiceImp3: Service {
}
public protocol Client {
public protocol Client: class {
var service: Service {get}
init(service: Service)
}
@@ -45,3 +48,63 @@ public class ServiceFactory {
}
}
public class ClientServiceImp: Service {
public weak var client: Client?
public init() {}
}
public protocol Logger {}
public protocol Tracker {}
public protocol DataProvider {}
public protocol Router {}
public class LoggerImp: Logger {
public init() {}
}
public class TrackerImp: Tracker {
public init() {}
}
public class RouterImp: Router {
public init() {}
}
public class DataProviderImp: DataProvider {
public init() {}
}
public protocol ListInteractorOutput: class {}
public protocol ListModuleInterface: class {}
public protocol ListInteractorInput: class {}
public class ListPresenter: NSObject {
public var listInteractor : ListInteractorInput?
public var listWireframe : ListWireframe?
public override init() {}
}
public class ListInteractor: NSObject {
public var output : ListInteractorOutput?
public override init() {}
}
public class ListWireframe : NSObject {
public let addWireframe: AddWireframe
public let listPresenter: ListPresenter
public init(addWireFrame: AddWireframe, listPresenter: ListPresenter) {
self.addWireframe = addWireFrame
self.listPresenter = listPresenter
}
}
public protocol AddModuleDelegate: class {}
public protocol AddModuleInterface: class {}
public class AddWireframe: NSObject {
let addPresenter : AddPresenter
public init(addPresenter: AddPresenter) {
self.addPresenter = addPresenter
}
}
public class AddPresenter: NSObject {
public var addModuleDelegate : AddModuleDelegate?
public override init() {}
}
@@ -7,7 +7,12 @@
<page name='Resolving components'/>
<page name='Runtime arguments'/>
<page name='Scopes'/>
<page name='Circular dependencies'/>
<page name='Shared Instances'/>
<page name='Auto-wiring'/>
<page name='Auto-injection'/>
<page name='Type Forwarding'/>
<page name='Containers Collaboration'/>
<page name='Testing'/>
</pages>
</playground>
+30
View File
@@ -0,0 +1,30 @@
//
// Dip
//
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import PackageDescription
let package = Package(
name: "Dip"
)
+160 -187
View File
@@ -1,9 +1,12 @@
# Dip
[![CI Status](http://img.shields.io/travis/AliSoftware/Dip.svg?style=flat)](https://travis-ci.org/AliSoftware/Dip)
[![CI Status](https://travis-ci.org/AliSoftware/Dip.svg?branch=develop)](https://travis-ci.org/AliSoftware/Dip)
[![Version](https://img.shields.io/cocoapods/v/Dip.svg?style=flat)](http://cocoapods.org/pods/Dip)
[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![License](https://img.shields.io/cocoapods/l/Dip.svg?style=flat)](http://cocoapods.org/pods/Dip)
[![Platform](https://img.shields.io/cocoapods/p/Dip.svg?style=flat)](http://cocoapods.org/pods/Dip)
[![Swift Version](https://img.shields.io/badge/Swift-3.0--3.1-F16D39.svg?style=flat)](https://developer.apple.com/swift)
[![Swift Version](https://img.shields.io/badge/Linux-3.0--3.1-4BC51D.svg?style=flat)](https://developer.apple.com/swift)
![Animated Dipping GIF](cinnamon-pretzels-caramel-dipping.gif)
_Photo courtesy of [www.kevinandamanda.com](http://www.kevinandamanda.com/recipes/appetizer/homemade-soft-cinnamon-sugar-pretzel-bites-with-salted-caramel-dipping-sauce.html)_
@@ -12,219 +15,189 @@ _Photo courtesy of [www.kevinandamanda.com](http://www.kevinandamanda.com/recipe
`Dip` is a simple **Dependency Injection Container**.
It's not true Dependency Injection, but it's damn close, and aimed to be as simple as possible.
It's inspired by `.NET`'s [Unity Container](https://msdn.microsoft.com/library/ff647202.aspx).
It's aimed to be as simple as possible yet provide rich functionality usual for DI containers on other platforms. It's inspired by `.NET`'s [Unity Container](https://msdn.microsoft.com/library/ff647202.aspx) and other DI containers.
* You start by creating `let dc = DependencyContainer()` and **register all your dependencies, by associating a `protocol` to an `instance` or a `factory`**.
* Then anywhere in your application, you can call `dc.resolve()` to **resolve a `protocol` into an instance of a concrete type** using that `DependencyContainer`.
* You start by creating `let 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** - checkout **[Dip-UI](https://github.com/AliSoftware/Dip-UI)** extensions. There is also a **[code generator](https://github.com/ilyapuchka/dipgen)** that can help to simplify registering new components.
This allows you to define the real, concrete types only in one place ([e.g. like this in your app](https://github.com/AliSoftware/Dip/blob/master/Example/DipSampleApp/DependencyContainers.swift#L22-L27), and [resetting it in your `setUp` for each Unit Tests](https://github.com/AliSoftware/Dip/blob/master/Example/Tests/SWAPIPersonProviderTests.swift#L17-L21)) and then [only work with `protocols` in your code](https://github.com/AliSoftware/Dip/blob/master/Example/DipSampleApp/Providers/SWAPIStarshipProvider.swift#L12) (which only define an API contract), without worrying about the real implementation.
<details>
<summary>Basic usage</summary>
## Advantages of DI and loose coupling
```swift
import Dip
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
// Create the container
private let container = DependencyContainer { container in
// Register some factory. ServiceImp here implements protocol Service
container.register { ServiceImp() as Service }
}
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Resolve a concrete instance. Container will instantiate new instance of ServiceImp
let service = try! container.resolve() as Service
...
}
}
```
</details>
<details>
<summary>More sophisticated example</summary>
```swift
import Dip
class AppDelegate: UIResponder, UIApplicationDelegate {
private let container = DependencyContainer.configure()
...
}
//CompositionRoot.swift
import Dip
import DipUI
extension DependencyContainer {
static func configure() -> DependencyContainer {
return DependencyContainer { container in
unowned let container = container
DependencyContainer.uiContainers = [container]
container.register(tag: "ViewController") { ViewController() }
.resolvingProperties { container, controller in
controller.animationsFactory = try container.resolve() as AnimatonsFactory
}
container.register { AuthFormBehaviourImp(apiClient: $0) as AuthFormBehaviour }
container.register { container as AnimationsFactory }
container.register { view in ShakeAnimationImp(view: view) as ShakeAnimation }
container.register { APIClient(baseURL: NSURL(string: "http://localhost:2368")!) as ApiClient }
}
}
}
extension DependencyContainer: AnimationsFactory {
func shakeAnimation(view: UIView) -> ShakeAnimation {
return try! self.resolve(withArguments: view)
}
}
extension ViewController: StoryboardInstantiatable {}
//ViewController.swift
class ViewController {
var animationsFactory: AnimationsFactory?
private let _formBehaviour = Injected<AuthFormBehaviour>()
var formBehaviour: AuthFormBehaviour? {
return _formBehaviour.value
}
...
}
```
</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;
* Define clear API contracts before even thinking about implementation, and make your code loosly coupled with the real implementation.
* Easily switch between implementations — as long as they respect the same API contact (the `protocol`
* Greatly improve testability, as you can register a real instance in your app but a fake instance in your tests dedicated for testing / mocking the fonctionnality
* Get rid of those `sharedInstances` and avoid the singleton pattern at all costs
## Installation
Dip is available through [CocoaPods](http://cocoapods.org). To install
it, simply add the following line to your Podfile:
Since version 5.0.0 Dip is built with Swift 3.0. You can install Dip using your favorite dependency manager:
<details>
<summary>CocoaPods</summary>
`pod "Dip"`
To build for Swift 2.3 add this code to the bottom of your Podfile
```ruby
pod "Dip"
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['SWIFT_VERSION'] = '2.3'
end
end
end
```
If you use _Carthage_ add this line to your Cartfile:
> You need at least 1.1.0.rc.2 version of CocoaPods.
</details>
<details>
<summary>Carthage</summary>
```
github "AliSoftware/Dip"
```
## Playground
To build for Swift 2.3 run Carthage with `--toolchain com.apple.dt.toolchain.Swift_2_3` option.
Dip comes with a **Playground** to introduce you to Inversion of Control, Dependency Injection, and how to use Dip in practice.
</details>
To play with it, [open `Dip.xcworkspace`](Dip/Dip.xcworkspace), then click on the `DipPlayground` entry in Xcode's Project Navigator and let it be your guide.
_Note: Do not open the `DipPlayground.playground` file directly, as it needs to be part of the workspace to access the Dip framework so that the demo code it contains can work._
The next paragraphs give you an overview of the Usage of _Dip_ directly, but if you're new to Dependency Injection, the Playground is probably a better start.
## Usage
*See [CHANGELOG.md](https://github.com/AliSoftware/Dip/blob/master/CHANGELOG.md) for instructions to migrate from 2.0.0 to 3.0.0*
### Register instance factories
First, create a `DependencyContainer` and use it to register instance factories with protocols, using those methods:
* `register(.Singleton) { … }` will register a singleton instance with a given protocol.
* `register(.Prototype) { … }` (or just `register { … }`) will register an instance factory which generates a new instance each time you `resolve()`.
* You need **cast the instance to the protocol type** you want to register it with (e.g. `register { PlistUsersProvider() as UsersListProviderType }`).
Typically, to register your dependencies as early as possible in your app life-cycle, you will declare a `let dip: DependencyContainer = { … }()` somewhere (for example [in a dedicated `.swift` file](https://github.com/AliSoftware/Dip/blob/master/Example/DipSampleApp/DependencyContainers.swift#L22-L27)). In your (non-hosted, standalone) unit tests, you'll probably [reset them in your `func setUp()`](https://github.com/AliSoftware/Dip/blob/master/Example/Tests/SWAPIPersonProviderTests.swift#L17-L21) instead.
### Resolve dependencies
* `resolve()` will return a new instance matching the requested protocol.
* Explicitly specify the return type of `resolve` so that Swift's type inference knows which protocol you're trying to resolve.
<details>
<summary>Swift Package Manager</summary>
```swift
container.register { ServiceImp() as Service }
let service = container.resolve() as Service
.Package(url: "https://github.com/AliSoftware/Dip", majorVersion: 5, minor: 0)
```
### Scopes
</details>
Dip provides two _scopes_ that you can use to register dependencies:
* The `.Prototype` scope will make the `DependencyContainer` resolve your type as __a new instance every time__ you call `resolve`. It's a default scope.
* The `.Singleton` scope will make the `DependencyContainer` retain the instance once resolved the first time, and reuse it in the next calls to `resolve` during the container lifetime.
### Using block-based initialization
When calling the initializer of `DependencyContainer()`, you can pass a block that will be called right after the initialization. This allows you to have a nice syntax to do all your `register(…)` calls in there, instead of having to do them separately.
It may not seem to provide much, but given the fact that `DependencyContainers` are typically declared as global constants using a top-level `let`, it gets very useful, because instead of having to do it like this:
```swift
let dip: DependencyContainer = {
let dip = DependencyContainer()
dip.register { ProductionEnvironment(analytics: true) as EnvironmentType }
dip.register { WebService() as WebServiceAPI }
return dip
}()
```
You can instead write this exact equivalent code, which is more compact, and indent better in Xcode (as the final closing brack is properly aligned):
```swift
let dip = DependencyContainer { dip in
dip.register { ProductionEnvironment(analytics: true) as EnvironmentType }
dip.register { WebService() as WebServiceAPI }
}
```
### Using tags to associate various factories to one protocol
* If you give a `tag` in the parameter to `register()`, it will associate that instance or factory with this tag, which can be used later during `resolve` (see below).
* `resolve(tag: tag)` will try to find an instance (or instance factory) that match both the requested protocol _and_ the tag. If it doesn't find any, it will fallback to an instance (or instance factory) that only match the requested protocol.
* The tags can be StringLiteralType or IntegerLiteralType. That said you can use plain strings or integers as tags.
```swift
enum WebService: String {
case PersonWS
case StarshipWS
var tag: Tag { return Tag.String(self.rawValue) }
}
let wsDependencies = DependencyContainer() { dip in
dip.register(tag: WebService.PersonWS.tag) { URLSessionNetworkLayer(baseURL: "http://prod.myapi.com/api/")! as NetworkLayer }
dip.register(tag: WebService.StashipWS.tag) { URLSessionNetworkLayer(baseURL: "http://dev.myapi.com/api/")! as NetworkLayer }
}
let networkLayer = dip.resolve(tag: WebService.PersonWS.tag) as NetworkLayer
```
### Runtime arguments
You can register factories that accept up to six arguments. When you resolve dependency you can pass those arguments to `resolve()` method and they will be passed to the factory. Note that _number_, _types_ and _order_ of parameters matters. Also use of optional parameter and not optional parameter will result in two factories registered in container.
```swift
let webServices = DependencyContainer() { webServices in
webServices.register { (url: NSURL, port: Int) in WebServiceImp1(url, port: port) as WebServiceAPI }
webServices.register { (port: Int, url: NSURL) in WebServiceImp2(url, port: port) as WebServiceAPI }
webServices.register { (port: Int, url: NSURL?) in WebServiceImp3(url!, port: port) as WebServiceAPI }
}
let service1 = webServices.resolve(withArguments: NSURL(string: "http://example.url")!, 80) as WebServiceAPI // service1 is WebServiceImp1
let service2 = webServices.resolve(withArguments: 80, NSURL(string: "http://example.url")!) as WebServiceAPI // service2 is WebServiceImp2
let service3 = webServices.resolve(withArguments: 80, NSURL(string: "http://example.url")) as WebServiceAPI // service3 is WebServiceImp3
```
Though Dip provides support for up to six runtime arguments out of the box you can extend this number using following code snippet for seven arguments:
```
func register<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7>(tag: Tag? = nil, scope: ComponentScope = .Prototype, factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7) -> T) -> DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7) -> T)> {
return registerFactory(tag, scope: .Prototype, factory: factory) as DefinitionOf<T, (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7) -> T)>
}
func resolve<T, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7>(tag tag: Tag? = nil, withArguments arg1: Arg1, _ arg2: Arg2, _ arg3: Arg3, _ arg4: Arg4, _ arg5: Arg5, _ arg6: Arg6, _ arg7: Arg7) -> T {
return resolve(tag) { (factory: (Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7) -> T) in factory(arg1, arg2, arg3, arg4, arg5, arg6, arg7) }
}
```
### Concrete Example
Somewhere in your App target, register the dependencies:
```swift
let dip: DependencyContainer = {
let dip = DependencyContainer()
let env = ProductionEnvironment(analytics: true)
dip.register(.Singleton) { env as EnvironmentType }
dip.register(.Singleton) { WebService() as WebServiceType }
dip.register() { (name: String) in DummyFriendsProvider(user: name) as FriendsProviderType }
dip.register(tag: "me") { (_: String) in PlistFriendsProvider(plist: "myfriends") as FriendsProviderType }
return dip
}
```
> Do the same in your Unit Tests target & test cases, but obviously with different Dependencies registered, depending on what you want to test and what instances you need to inject to provide dummy implementations for your tests.
Then to use dependencies throughout your app, use `dip.resolve()`, like this:
```swift
struct WebService {
let env: EnvironmentType = dip.resolve()
func sendRequest(path: String, ) {
// ... use stuff like env.baseURL here
}
}
struct SomeViewModel {
let ws: WebServiceType = dip.resolve()
var friendsProvider: FriendsProviderType
init(userName: String) {
friendsProvider = dip.resolve(tag: userName, userName)
}
func foo() {
ws.someMethodDeclaredOnWebServiceType()
let friends = friendsProvider.someFriendsProviderTypeMethod()
print("friends: \(friends)")
}
```
This way, when running your app target:
* `ws` will be resolved as your singleton instance `WebService` registered before.
* `friendsProvider` will be resolved as a new instance each time, which will be an instance created via `PlistFriendsProvider(plist: "myfriends")` if `userName` is `me` and created via `DummyFriendsProvider(userName)` for any other `userName` value (because `resolve(tag: userName, userName)` will fallback to `resolve(tag: nil, userName)` in that case, using the instance factory which was registered without a tag, but will pass `userName` as argument).
But when running your Unit tests target, it will probably resolve to other instances, depending on how you registered your dependencies in your Test Case.
### Complete Example Project
In addition to this Usage overview and to the aforementioned playground, you can also find a complete example in the `SampleApp/DipSampleApp` project provided in this repository.
This sample project is a bit more complex, but closer to real-world applications (even if this sample is all about StarWars!),
by declaring protocols like `NetworkLayer` which can be resolved to a `URLSessionNetworkLayer` in the real app, but to a dummy
network layer returning fixture data during the Unit Tests.
This sample uses the Star Wars API provided by swapi.co to fetch Star Wars characters and starships info and display them in TableViews.
## 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 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.
@@ -10,14 +10,12 @@
090012291BC6FECA0079C600 /* BaseCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090012161BC6FECA0079C600 /* BaseCell.swift */; };
0900122A1BC6FECA0079C600 /* PersonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090012171BC6FECA0079C600 /* PersonCell.swift */; };
0900122C1BC6FECA0079C600 /* PersonProviderAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0900121B1BC6FECA0079C600 /* PersonProviderAPI.swift */; };
0900122D1BC6FECA0079C600 /* DummyPilotProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0900121C1BC6FECA0079C600 /* DummyPilotProvider.swift */; };
0900122E1BC6FECA0079C600 /* HardCodedStarshipProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0900121D1BC6FECA0079C600 /* HardCodedStarshipProvider.swift */; };
0900122F1BC6FECA0079C600 /* PlistPersonProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0900121E1BC6FECA0079C600 /* PlistPersonProvider.swift */; };
0900123B1BC6FF4D0079C600 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0900123A1BC6FF4D0079C600 /* Main.storyboard */; };
0900123D1BC7012A0079C600 /* StarshipProviderAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0900123C1BC7012A0079C600 /* StarshipProviderAPI.swift */; };
090012401BC704C60079C600 /* Person.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0900123F1BC704C60079C600 /* Person.swift */; };
090012421BC7059E0079C600 /* Starship.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090012411BC7059E0079C600 /* Starship.swift */; };
090012441BC708A00079C600 /* DummyStarshipProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090012431BC708A00079C600 /* DummyStarshipProvider.swift */; };
0982AF081C503EEE00B62463 /* FakePersonsProviders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0982AF071C503EEE00B62463 /* FakePersonsProviders.swift */; };
0982AF0A1C50401800B62463 /* FakeStarshipProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0982AF091C50401800B62463 /* FakeStarshipProvider.swift */; };
099022621BC123C000E76F43 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099022611BC123C000E76F43 /* AppDelegate.swift */; };
099022691BC123C000E76F43 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 099022681BC123C000E76F43 /* Assets.xcassets */; };
0990226C1BC123C000E76F43 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0990226A1BC123C000E76F43 /* LaunchScreen.storyboard */; };
@@ -52,14 +50,12 @@
090012161BC6FECA0079C600 /* BaseCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseCell.swift; sourceTree = "<group>"; };
090012171BC6FECA0079C600 /* PersonCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersonCell.swift; sourceTree = "<group>"; };
0900121B1BC6FECA0079C600 /* PersonProviderAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersonProviderAPI.swift; sourceTree = "<group>"; };
0900121C1BC6FECA0079C600 /* DummyPilotProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DummyPilotProvider.swift; sourceTree = "<group>"; };
0900121D1BC6FECA0079C600 /* HardCodedStarshipProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HardCodedStarshipProvider.swift; sourceTree = "<group>"; };
0900121E1BC6FECA0079C600 /* PlistPersonProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlistPersonProvider.swift; sourceTree = "<group>"; };
0900123A1BC6FF4D0079C600 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
0900123C1BC7012A0079C600 /* StarshipProviderAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StarshipProviderAPI.swift; sourceTree = "<group>"; };
0900123F1BC704C60079C600 /* Person.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Person.swift; sourceTree = "<group>"; };
090012411BC7059E0079C600 /* Starship.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Starship.swift; sourceTree = "<group>"; };
090012431BC708A00079C600 /* DummyStarshipProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DummyStarshipProvider.swift; sourceTree = "<group>"; };
0982AF071C503EEE00B62463 /* FakePersonsProviders.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FakePersonsProviders.swift; sourceTree = "<group>"; };
0982AF091C50401800B62463 /* FakeStarshipProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FakeStarshipProvider.swift; sourceTree = "<group>"; };
0990225F1BC123C000E76F43 /* DipSampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DipSampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
099022611BC123C000E76F43 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
099022681BC123C000E76F43 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@@ -150,8 +146,7 @@
090012381BC6FEFD0079C600 /* PersonProviders */ = {
isa = PBXGroup;
children = (
0900121C1BC6FECA0079C600 /* DummyPilotProvider.swift */,
0900121E1BC6FECA0079C600 /* PlistPersonProvider.swift */,
0982AF071C503EEE00B62463 /* FakePersonsProviders.swift */,
09D796121BC9A5BC003C68EB /* SWAPIPersonProvider.swift */,
);
name = PersonProviders;
@@ -160,8 +155,7 @@
090012391BC6FF080079C600 /* StarshipProviders */ = {
isa = PBXGroup;
children = (
090012431BC708A00079C600 /* DummyStarshipProvider.swift */,
0900121D1BC6FECA0079C600 /* HardCodedStarshipProvider.swift */,
0982AF091C50401800B62463 /* FakeStarshipProvider.swift */,
09D7961A1BC9BE65003C68EB /* SWAPIStarshipProvider.swift */,
);
name = StarshipProviders;
@@ -285,7 +279,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0700;
LastUpgradeCheck = 0700;
LastUpgradeCheck = 0930;
ORGANIZATIONNAME = AliSoftware;
TargetAttributes = {
0990225E1BC123C000E76F43 = {
@@ -342,25 +336,23 @@
buildActionMask = 2147483647;
files = (
09D796151BC9A5FC003C68EB /* NetworkLayer.swift in Sources */,
0982AF081C503EEE00B62463 /* FakePersonsProviders.swift in Sources */,
09D795FF1BC71F5A003C68EB /* PersonListViewController.swift in Sources */,
0900122A1BC6FECA0079C600 /* PersonCell.swift in Sources */,
09D796011BC722C0003C68EB /* StarshipListViewController.swift in Sources */,
0900122C1BC6FECA0079C600 /* PersonProviderAPI.swift in Sources */,
09D7961D1BC9C62E003C68EB /* SWAPICommon.swift in Sources */,
090012291BC6FECA0079C600 /* BaseCell.swift in Sources */,
0900122D1BC6FECA0079C600 /* DummyPilotProvider.swift in Sources */,
099022621BC123C000E76F43 /* AppDelegate.swift in Sources */,
09D796131BC9A5BC003C68EB /* SWAPIPersonProvider.swift in Sources */,
090012421BC7059E0079C600 /* Starship.swift in Sources */,
0900123D1BC7012A0079C600 /* StarshipProviderAPI.swift in Sources */,
0982AF0A1C50401800B62463 /* FakeStarshipProvider.swift in Sources */,
09D7961B1BC9BE65003C68EB /* SWAPIStarshipProvider.swift in Sources */,
0900122E1BC6FECA0079C600 /* HardCodedStarshipProvider.swift in Sources */,
09D796071BC73E8B003C68EB /* StoryboardConstants.swift in Sources */,
09D796031BC72691003C68EB /* StarshipCell.swift in Sources */,
090012401BC704C60079C600 /* Person.swift in Sources */,
090012441BC708A00079C600 /* DummyStarshipProvider.swift in Sources */,
09D7960D1BC7431C003C68EB /* FetchableTrait.swift in Sources */,
0900122F1BC6FECA0079C600 /* PlistPersonProvider.swift in Sources */,
09D796191BC9BA49003C68EB /* DependencyContainers.swift in Sources */,
09D796171BC9B53D003C68EB /* URLSessionNetworkLayer.swift in Sources */,
);
@@ -422,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;
@@ -434,13 +427,23 @@
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";
@@ -468,6 +471,7 @@
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.0;
};
name = Debug;
};
@@ -479,13 +483,23 @@
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";
@@ -504,6 +518,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 8.3;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_VERSION = 4.0;
VALIDATE_PRODUCT = YES;
};
name = Release;
@@ -529,6 +544,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 = "0930"
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">
+14 -1
View File
@@ -7,21 +7,34 @@
//
import UIKit
import Dip
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
private let container = DependencyContainer()
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
configure(container: container)
let personProvider = try! container.resolve() as PersonProviderAPI
let starshipProvider = try! container.resolve() as StarshipProviderAPI
if let tabBarVC = self.window?.rootViewController as? UITabBarController,
let vcs = tabBarVC.viewControllers as? [UINavigationController] {
if let personListVC = vcs[0].topViewController as? PersonListViewController {
personListVC.personProvider = personProvider
personListVC.starshipProvider = starshipProvider
personListVC.loadFirstPage()
}
if let starshipListVC = vcs[1].topViewController as? StarshipListViewController {
starshipListVC.starshipProvider = starshipProvider
starshipListVC.personProvider = personProvider
starshipListVC.loadFirstPage()
}
}
@@ -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" : {
+8 -8
View File
@@ -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,20 +22,20 @@ 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
}
}
protocol FillableCell: BaseCell {
typealias ObjectType
associatedtype ObjectType
func fillWithObject(object: ObjectType)
}
@@ -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))
}
}
@@ -17,42 +17,58 @@ private let FAKE_STARSHIPS = false
/* ---- */
// MARK: Dependency Container for WebServices & NetworkLayer
let wsDependencies = DependencyContainer() { dip in
// Register the NetworkLayer, same for everyone here (but we have the ability to register a different one for a specific WebService if we wanted to)
dip.register(.Singleton) { URLSessionNetworkLayer(baseURL: "http://swapi.co/api/")! as NetworkLayer }
enum DependencyTags: Int, DependencyTagConvertible {
case Hardcoded
case Dummy
}
// MARK: Dependency Container for Providers
let providerDependencies = DependencyContainer() { dip in
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 }
if FAKE_PERSONS {
// 1) Register the PersonProviderAPI singleton, one generic and one specific for a specific personID
dip.register(.Singleton) { DummyPilotProvider() as PersonProviderAPI }
dip.register(tag: 0, .Singleton) { PlistPersonProvider(plist: "mainPilot") as PersonProviderAPI }
// 1) Register fake persons provider
//Here we use constructor injection for one of the dependencies property injection for another, and we provide dependencies manually
dip.register() { FakePersonsProvider(dummyProvider: DummyPilotProvider()) as PersonProviderAPI }
.resolvingProperties { (_, resolved: PersonProviderAPI) in
//here we resolve optional dependencies
//see what happens when you comment this out
(resolved as! FakePersonsProvider).plistProvider = PlistPersonProvider(plist: "mainPilot")
}
} else {
// 1) Register the SWAPIPersonProvider (that hits the real swapi.co WebService)
dip.register(.Singleton) { SWAPIPersonProvider() as PersonProviderAPI }
// Here we use constructor injection again, but let the container to resolve dependency for us
dip.register() { SWAPIPersonProvider(webService: try dip.resolve()) as PersonProviderAPI }
}
if FAKE_STARSHIPS {
// 2) Register the StarshipProviderAPI factories, one generic and one specific for a specific starshipID
dip.register() { HardCodedStarshipProvider() as StarshipProviderAPI }
dip.register(tag: 0) { DummyStarshipProvider(pilotName: "Main Pilot") as StarshipProviderAPI }
// 2) Register fake starships provider
//Here we register different implementations for the same protocol using tags
dip.register(tag: DependencyTags.Hardcoded) { HardCodedStarshipProvider() as StarshipProviderAPI }
//Here we register factory that will require a runtime argument
dip.register(tag: DependencyTags.Dummy) { DummyStarshipProvider(pilotName: $0) as StarshipProviderAPI }
//Here we use constructor injection, but instead of providing dependencies manually container resolves them for us
dip.register() {
FakeStarshipProvider(
dummyProvider: try dip.resolve(tag: DependencyTags.Dummy, arguments: "Main Pilot"),
hardCodedProvider: try dip.resolve(tag: DependencyTags.Hardcoded)) as StarshipProviderAPI
}
} else {
// 2) Register the SWAPIStarshipProvider (that hits the real swapi.co WebService)
dip.register(.Singleton) { SWAPIStarshipProvider() as StarshipProviderAPI }
// Here we use constructor injection again, but let the container to resolve dependency for us
dip.register() { SWAPIStarshipProvider(webService: try dip.resolve()) as StarshipProviderAPI }
}
+1 -1
View File
@@ -9,7 +9,7 @@
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<string>6.1</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
+24 -54
View File
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9059" systemVersion="15B42" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="Nnt-Mi-Wf8">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9531" systemVersion="15C50" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="Nnt-Mi-Wf8">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9049"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
</dependencies>
<scenes>
@@ -13,7 +13,6 @@
<tabBar key="tabBar" contentMode="scaleToFill" id="hlX-Lx-tB1">
<rect key="frame" x="0.0" y="0.0" width="320" height="49"/>
<autoresizingMask key="autoresizingMask"/>
<animations/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</tabBar>
<connections>
@@ -32,96 +31,83 @@
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="95" sectionHeaderHeight="28" sectionFooterHeight="28" id="EYu-RQ-JO9">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="PersonCell" rowHeight="95" id="Sba-Wm-z4c" customClass="PersonCell" customModule="DipSampleApp" customModuleProvider="target">
<rect key="frame" x="0.0" y="28" width="600" height="95"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="Sba-Wm-z4c" id="UE9-zV-NsG">
<rect key="frame" x="0.0" y="0.0" width="567" height="94.5"/>
<rect key="frame" x="0.0" y="0.0" width="567" height="94"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" text="Name:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gVy-Vx-i2h">
<rect key="frame" x="8" y="8" width="49" height="19.5"/>
<animations/>
<rect key="frame" x="8" y="8" width="49" height="20"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" text="Height (cm):" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ymL-8F-Mpa">
<rect key="frame" x="8" y="35" width="94" height="19.5"/>
<animations/>
<rect key="frame" x="8" y="36" width="94" height="20"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" text="Mass (kg):" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9sd-2A-Ixx">
<rect key="frame" x="8" y="63" width="79" height="19.5"/>
<animations/>
<rect key="frame" x="8" y="64" width="79" height="20"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" text="Eyes:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="YPu-dh-Fhf">
<rect key="frame" x="378" y="63" width="41" height="19.5"/>
<animations/>
<rect key="frame" x="378" y="64" width="41" height="20"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="1CQ-rV-Y7c">
<rect key="frame" x="539" y="8" width="20" height="20"/>
<animations/>
<constraints>
<constraint firstAttribute="width" constant="20" id="PK0-f1-YTg"/>
<constraint firstAttribute="height" constant="20" id="TOR-23-CAr"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="-name-" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GhU-yA-Nkj">
<rect key="frame" x="110" y="7" width="421" height="20.5"/>
<animations/>
<rect key="frame" x="110" y="7" width="421" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="-height-" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VLd-vh-c8m">
<rect key="frame" x="110" y="34" width="260.5" height="20.5"/>
<animations/>
<rect key="frame" x="110" y="35" width="260" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="-mass-" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="zgG-Ij-dCY">
<rect key="frame" x="110" y="62" width="260" height="20.5"/>
<animations/>
<rect key="frame" x="110" y="63" width="260" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="-hair-" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7TM-Aw-IPT">
<rect key="frame" x="427" y="34" width="120" height="20.5"/>
<animations/>
<rect key="frame" x="427" y="35" width="120" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="-eyes-" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Hv7-qr-iAY">
<rect key="frame" x="427" y="62" width="120" height="20.5"/>
<animations/>
<rect key="frame" x="427" y="63" width="120" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" text="Hair:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rDV-iQ-Ugd">
<rect key="frame" x="378" y="35" width="36.5" height="19.5"/>
<animations/>
<rect key="frame" x="378" y="36" width="37" height="20"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<animations/>
<constraints>
<constraint firstItem="Hv7-qr-iAY" firstAttribute="baseline" secondItem="YPu-dh-Fhf" secondAttribute="baseline" id="590-8v-aT3"/>
<constraint firstItem="zgG-Ij-dCY" firstAttribute="baseline" secondItem="9sd-2A-Ixx" secondAttribute="baseline" id="62H-PJ-mjV"/>
@@ -152,7 +138,6 @@
<constraint firstItem="rDV-iQ-Ugd" firstAttribute="leading" secondItem="VLd-vh-c8m" secondAttribute="trailing" constant="8" symbolic="YES" id="xL3-7N-f8f"/>
</constraints>
</tableViewCellContentView>
<animations/>
<connections>
<outlet property="eyesLabel" destination="Hv7-qr-iAY" id="bhd-4V-chX"/>
<outlet property="genderImageView" destination="1CQ-rV-Y7c" id="8rr-1s-qr4"/>
@@ -182,75 +167,65 @@
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="102" sectionHeaderHeight="28" sectionFooterHeight="28" id="uPj-BZ-JVx">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="StarshipCell" rowHeight="102" id="rfe-RG-ql1" customClass="StarshipCell" customModule="DipSampleApp" customModuleProvider="target">
<rect key="frame" x="0.0" y="28" width="600" height="102"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="rfe-RG-ql1" id="RHz-uO-ANq">
<rect key="frame" x="0.0" y="0.0" width="567" height="101.5"/>
<rect key="frame" x="0.0" y="0.0" width="567" height="101"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" text="Name:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pR4-Aq-S9H">
<rect key="frame" x="8" y="8" width="49" height="19.5"/>
<animations/>
<rect key="frame" x="8" y="8" width="49" height="20"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" text="Model:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="V8k-Fh-teX">
<rect key="frame" x="8" y="33" width="52" height="19.5"/>
<animations/>
<rect key="frame" x="8" y="34" width="52" height="20"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" text="Manufacturer:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="iWU-h6-HNF">
<rect key="frame" x="8" y="61" width="108" height="19.5"/>
<animations/>
<rect key="frame" x="8" y="62" width="108" height="20"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" horizontalCompressionResistancePriority="1000" text="Crew:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JoH-bT-HcN">
<rect key="frame" x="460" y="33" width="44.5" height="19.5"/>
<animations/>
<rect key="frame" x="459" y="34" width="45" height="20"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="750" verticalHuggingPriority="251" horizontalCompressionResistancePriority="1000" text="Pass:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="arT-4h-pLt">
<rect key="frame" x="463" y="61" width="41" height="19.5"/>
<animations/>
<rect key="frame" x="463" y="62" width="41" height="20"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="-name-" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Lwe-Ix-EQE">
<rect key="frame" x="124" y="7" width="423" height="20.5"/>
<animations/>
<rect key="frame" x="124" y="7" width="423" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="500" text="-model-" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kIz-wz-fGn">
<rect key="frame" x="124" y="32" width="328" height="20.5"/>
<animations/>
<rect key="frame" x="124" y="33" width="327" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="500" text="-manufacturer-" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fVY-W3-78a">
<rect key="frame" x="124" y="60" width="331" height="20.5"/>
<animations/>
<rect key="frame" x="124" y="61" width="331" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="-crew-" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WGV-Ar-PMC">
<rect key="frame" x="512" y="32" width="35" height="20.5"/>
<animations/>
<rect key="frame" x="512" y="33" width="35" height="21"/>
<constraints>
<constraint firstAttribute="width" constant="35" id="tsk-nr-zwr"/>
</constraints>
@@ -259,14 +234,12 @@
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="-pass-" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="YnA-qH-8AA">
<rect key="frame" x="512" y="60" width="35" height="20.5"/>
<animations/>
<rect key="frame" x="512" y="61" width="35" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<animations/>
<constraints>
<constraint firstAttribute="trailing" secondItem="YnA-qH-8AA" secondAttribute="trailing" constant="20" symbolic="YES" id="0iR-Ax-Tba"/>
<constraint firstItem="pR4-Aq-S9H" firstAttribute="leading" secondItem="iWU-h6-HNF" secondAttribute="leading" id="1dW-gb-Qyn"/>
@@ -294,7 +267,6 @@
<constraint firstItem="WGV-Ar-PMC" firstAttribute="leading" secondItem="YnA-qH-8AA" secondAttribute="leading" id="ycM-ic-t05"/>
</constraints>
</tableViewCellContentView>
<animations/>
<connections>
<outlet property="crewLabel" destination="WGV-Ar-PMC" id="f6d-9q-59z"/>
<outlet property="manufacturerLabel" destination="fVY-W3-78a" id="ABE-NG-bZY"/>
@@ -324,7 +296,6 @@
<navigationBar key="navigationBar" contentMode="scaleToFill" id="VfI-ho-mqu">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<animations/>
</navigationBar>
<connections>
<segue destination="nuL-fZ-hCQ" kind="relationship" relationship="rootViewController" id="l50-ri-Dfy"/>
@@ -342,7 +313,6 @@
<navigationBar key="navigationBar" contentMode="scaleToFill" id="YPS-Al-CkA">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<animations/>
</navigationBar>
<connections>
<segue destination="I87-Zh-w4A" kind="relationship" relationship="rootViewController" id="07W-Xm-zjJ"/>
@@ -1,34 +0,0 @@
//
// DummyPilotProvider.swift
// Dip
//
// Created by Olivier Halligon on 12/09/2015.
// Copyright © 2015 AliSoftware. All rights reserved.
//
import Foundation
struct DummyPilotProvider : PersonProviderAPI {
func fetchIDs(completion: [Int] -> Void) {
completion(Array(0..<5))
}
func fetch(id: Int, completion: Person? -> Void) {
completion(dummyPerson(id))
}
private func dummyPerson(idx: Int) -> Person {
let colors = ["blue", "brown", "yellow", "orange", "red", "dark"]
let genders: [Gender?] = [Gender.Male, Gender.Female, nil]
return Person(
name: "John Dummy Doe #\(idx)",
height: 150 + (idx*27%40),
mass: 50 + (idx*7%30),
hairColor: colors[idx*3%colors.count],
eyeColor: colors[idx*2%colors.count],
gender: genders[idx%3],
starshipIDs: [idx % 3, 2*idx % 4]
)
}
}
@@ -1,33 +0,0 @@
//
// DummyStarshipProvider.swift
// Dip
//
// Created by Olivier Halligon on 08/10/2015.
// Copyright © 2015 AliSoftware. All rights reserved.
//
import Foundation
struct DummyStarshipProvider : StarshipProviderAPI {
var pilotName: String
func fetchIDs(completion: [Int] -> Void) {
let nbShips = pilotName.characters.count
completion(Array(0..<nbShips))
}
func fetch(id: Int, completion: Starship? -> Void) {
completion(dummyStarship(id))
}
private func dummyStarship(idx: Int) -> Starship {
return Starship(
name: "\(pilotName)'s awesome starship #\(idx)",
model: "\(pilotName)Ship",
manufacturer: "Dummy Industries",
crew: 1 + (idx%3),
passengers: 10 + (idx*7 % 40),
pilotIDs: [idx]
)
}
}
@@ -0,0 +1,114 @@
//
// PlistPersonProvider.swift
// Dip
//
// Created by Ilya Puchka on 12/09/2015.
// Copyright © 2015 AliSoftware. All rights reserved.
//
import Foundation
///Provides some dummy Person entities
struct DummyPilotProvider : PersonProviderAPI {
func fetchIDs(completion: @escaping ([Int]) -> Void) {
completion(Array(0..<5))
}
func fetch(id: Int, completion: @escaping (Person?) -> Void) {
completion(dummyPerson(idx: id))
}
private func dummyPerson(idx: Int) -> Person {
let colors = ["blue", "brown", "yellow", "orange", "red", "dark"]
let genders: [Gender?] = [Gender.Male, Gender.Female, nil]
return Person(
name: "John Dummy Doe #\(idx)",
height: 150 + (idx*27%40),
mass: 50 + (idx*7%30),
hairColor: colors[idx*3%colors.count],
eyeColor: colors[idx*2%colors.count],
gender: genders[idx%3],
starshipIDs: [idx % 3, 2*idx % 4]
)
}
}
///Provides Person entities reading then from plist file
class PlistPersonProvider : PersonProviderAPI {
let people: [Person]
init(plist basename: String) {
guard
let path = Bundle.main.path(forResource: basename, ofType: "plist"),
let list = NSArray(contentsOfFile: path),
let peopleDict = list as? [[String:AnyObject]]
else {
fatalError("PLIST for \(basename) not found")
}
self.people = peopleDict.map(PlistPersonProvider.personFromDict)
}
func fetchIDs(completion: @escaping ([Int]) -> Void) {
completion(Array(0..<people.count))
}
func fetch(id: Int, completion: @escaping (Person?) -> Void) {
guard id < people.count else {
completion(nil)
return
}
completion(people[id])
}
private static func personFromDict(dict: [String:AnyObject]) -> Person {
guard
let name = dict["name"] as? String,
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")
}
return Person(
name: name,
height: height,
mass: mass,
hairColor: hairColor,
eyeColor: eyeColor,
gender: Gender(rawValue: genderStr),
starshipIDs: starshipsIDs
)
}
}
class FakePersonsProvider: PersonProviderAPI {
let dummyProvider: PersonProviderAPI
var plistProvider: PersonProviderAPI!
//In this class we use both constructor injection and property injection,
//nil is a valid local default
init(dummyProvider: PersonProviderAPI) {
self.dummyProvider = dummyProvider
}
func fetchIDs(completion: @escaping ([Int]) -> Void) {
dummyProvider.fetchIDs(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: id, completion: completion)
}
}
}
@@ -0,0 +1,84 @@
//
// FakeStarshipProvider.swift
// DipSampleApp
//
// Created by Ilya Puchka on 20.01.16.
// Copyright © 2016 AliSoftware. All rights reserved.
//
import Foundation
///Provides some dummy Starship entities
struct DummyStarshipProvider : StarshipProviderAPI {
var pilotName: String
func fetchIDs(completion: @escaping ([Int]) -> Void) {
let nbShips = pilotName.count
completion(Array(0..<nbShips))
}
func fetch(id: Int, completion: @escaping (Starship?) -> Void) {
completion(dummyStarship(idx: id))
}
private func dummyStarship(idx: Int) -> Starship {
return Starship(
name: "\(pilotName)'s awesome starship #\(idx)",
model: "\(pilotName)Ship",
manufacturer: "Dummy Industries",
crew: 1 + (idx%3),
passengers: 10 + (idx*7 % 40),
pilotIDs: [idx]
)
}
}
///Provides hardcoded Starship entities stored in memory
class HardCodedStarshipProvider : StarshipProviderAPI {
let starships = [
Starship(name: "First Ship", model: "AwesomeShip", manufacturer: "HardCoded Inc.", crew: 3, passengers: 20, pilotIDs: [1,2]),
Starship(name: "Second Ship", model: "AwesomeShip Express", manufacturer: "HardCoded Inc.", crew: 4, passengers: 10, pilotIDs: [1]),
Starship(name: "Third Ship", model: "AwesomeShip Cargo", manufacturer: "HardCoded Inc.", crew: 12, passengers: 150, pilotIDs: [2]),
] + Array(4..<75).map { Starship(name: "Ship #\($0)", model: "AwesomeShip Fighter", manufacturer: "HardCoded Inc.", crew: 1, passengers: 2, pilotIDs: [1]) }
func fetchIDs(completion: @escaping ([Int]) -> Void) {
completion(Array(0..<starships.count))
}
func fetch(id: Int, completion: @escaping (Starship?) -> Void) {
guard id < starships.count else {
completion(nil)
return
}
completion(starships[id])
}
}
class FakeStarshipProvider: StarshipProviderAPI {
let dummyProvider: StarshipProviderAPI
let hardCodedProvider: StarshipProviderAPI
//Constructor injection again here
init(dummyProvider: StarshipProviderAPI, hardCodedProvider: StarshipProviderAPI) {
self.dummyProvider = dummyProvider
self.hardCodedProvider = hardCodedProvider
}
func fetchIDs(completion: @escaping ([Int]) -> Void) {
hardCodedProvider.fetchIDs(completion: completion)
}
func fetch(id: Int, completion: @escaping (Starship?) -> Void) {
if id == 0 {
dummyProvider.fetch(id: id, completion: completion)
}
else {
hardCodedProvider.fetch(id: id, completion: completion)
}
}
}
@@ -1,30 +0,0 @@
//
// HardCodedStarshipProvider.swift
// Dip
//
// Created by Olivier Halligon on 11/09/2015.
// Copyright © 2015 AliSoftware. All rights reserved.
//
import Foundation
class HardCodedStarshipProvider : StarshipProviderAPI {
let starships = [
Starship(name: "First Ship", model: "AwesomeShip", manufacturer: "HardCoded Inc.", crew: 3, passengers: 20, pilotIDs: [1,2]),
Starship(name: "Second Ship", model: "AwesomeShip Express", manufacturer: "HardCoded Inc.", crew: 4, passengers: 10, pilotIDs: [1]),
Starship(name: "Third Ship", model: "AwesomeShip Cargo", manufacturer: "HardCoded Inc.", crew: 12, passengers: 150, pilotIDs: [2]),
] + Array(4..<75).map { Starship(name: "Ship #\($0)", model: "AwesomeShip Fighter", manufacturer: "HardCoded Inc.", crew: 1, passengers: 2, pilotIDs: [1]) }
func fetchIDs(completion: [Int] -> Void) {
completion(Array(0..<starships.count))
}
func fetch(id: Int, completion: Starship? -> Void) {
guard id < starships.count else {
completion(nil)
return
}
completion(starships[id])
}
}
@@ -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)
}
@@ -1,61 +0,0 @@
//
// PlistPersonProvider.swift
// Dip
//
// Created by Olivier Halligon on 12/09/2015.
// Copyright © 2015 AliSoftware. All rights reserved.
//
import Foundation
class PlistPersonProvider : PersonProviderAPI {
let people: [Person]
init(plist basename: String) {
guard
let path = NSBundle.mainBundle().pathForResource(basename, ofType: "plist"),
let list = NSArray(contentsOfFile: path),
peopleDict = list as? [[String:AnyObject]]
else {
fatalError("PLIST for \(basename) not found")
}
self.people = peopleDict.map(PlistPersonProvider.personFromDict)
}
func fetchIDs(completion: [Int] -> Void) {
completion(Array(0..<people.count))
}
func fetch(id: Int, completion: Person? -> Void) {
guard id < people.count else {
completion(nil)
return
}
completion(people[id])
}
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]
else {
fatalError("Invalid Plist")
}
return Person(
name: name,
height: height,
mass: mass,
hairColor: hairColor,
eyeColor: eyeColor,
gender: Gender(rawValue: genderStr),
starshipIDs: starshipsIDs
)
}
}
@@ -9,16 +9,10 @@
import Foundation
import Dip
enum SWAPIError: ErrorType {
enum SWAPIError: Error {
case InvalidJSON
}
enum WebService: String {
case PersonWS
case StarshipWS
var tag: DependencyContainer.Tag { return DependencyContainer.Tag.String(self.rawValue) }
}
func idFromURLString(urlString: String) -> Int? {
let url = NSURL(string: urlString)
let idString = url.flatMap { $0.lastPathComponent }
@@ -8,18 +8,27 @@
import Foundation
///Provides Person entitis fetching them with web service
struct SWAPIPersonProvider : PersonProviderAPI {
let ws = wsDependencies.resolve(tag: WebService.PersonWS.tag) as NetworkLayer
let ws: NetworkLayer
func fetchIDs(completion: [Int] -> Void) {
ws.request("people") { response in
//Here we inject dependency using _constructor injection_ pattern.
//The alternative way is a _property injection_
//but it should be used only for optional dependencies
//where there is a good local default implementation
init(webService: NetworkLayer) {
self.ws = webService
}
func fetchIDs(completion: @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)
}
@@ -29,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,
@@ -52,7 +61,7 @@ struct SWAPIPersonProvider : PersonProviderAPI {
hairColor: hairColor,
eyeColor: eyeColor,
gender: Gender(rawValue: gender),
starshipIDs: starshipURLStrings.flatMap(idFromURLString)
starshipIDs: starshipURLStrings.compactMap(idFromURLString)
)
completion(person)
}
@@ -8,18 +8,27 @@
import Foundation
///Provides Starship entities fetching them using web service
struct SWAPIStarshipProvider : StarshipProviderAPI {
let ws = wsDependencies.resolve(tag: WebService.StarshipWS.tag) as NetworkLayer
let ws: NetworkLayer
func fetchIDs(completion: [Int] -> Void) {
ws.request("starships") { response in
//Here we inject dependency using _constructor injection_ pattern.
//The alternative way is a _property injection_
//but it should be used only for optional dependencies
//where there is a good local default implementation
init(webService: NetworkLayer) {
self.ws = webService
}
func fetchIDs(completion: @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)
}
@@ -29,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
@@ -50,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)
}
@@ -8,34 +8,35 @@
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()
@@ -9,13 +9,13 @@
import UIKit
protocol FetchableTrait: class {
typealias ObjectType
associatedtype ObjectType
var objects: [ObjectType]? { get set }
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)
}
@@ -11,46 +11,49 @@ import UIKit
class PersonListViewController: UITableViewController, FetchableTrait {
var objects: [Person]?
var batchRequestID = 0
func fetchIDs(completion: [Int] -> Void) {
let provider = providerDependencies.resolve() as PersonProviderAPI
return provider.fetchIDs(completion)
var personProvider: PersonProviderAPI!
var starshipProvider: StarshipProviderAPI!
func fetchIDs(completion: @escaping ([Int]) -> Void) {
return personProvider.fetchIDs(completion: completion)
}
func fetchOne(personID: Int, completion: Person? -> Void) {
let provider = providerDependencies.resolve(tag: .Int(personID)) as PersonProviderAPI
return provider.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.loadObjects(person.starshipIDs)
destVC.starshipProvider = starshipProvider
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
}
}
@@ -13,47 +13,48 @@ class StarshipListViewController : UITableViewController, FetchableTrait {
var objects: [Starship]?
var batchRequestID = 0
private func provider(tag:Int?) -> StarshipProviderAPI {
return providerDependencies.resolve(tag: tag.flatMap { .Int($0) })
}
var starshipProvider: StarshipProviderAPI!
var personProvider: PersonProviderAPI!
func fetchIDs(completion: [Int] -> Void) {
provider(nil).fetchIDs(completion)
func fetchIDs(completion: @escaping ([Int]) -> Void) {
starshipProvider.fetchIDs(completion: completion)
}
func fetchOne(shipID:Int, completion: Starship? -> Void) {
provider(shipID).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.loadObjects(starship.pilotIDs)
destVC.personProvider = personProvider
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
}
}
+1 -1
View File
@@ -9,7 +9,7 @@
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<string>6.1</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
+8 -8
View File
@@ -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))
}
}
}
+12 -13
View File
@@ -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,9 +23,9 @@ class SWAPIPersonProviderTests: XCTestCase {
func testFetchPersonIDs() {
let mock = NetworkMock(json: ["results": [fakePerson1, fakePerson2]])
wsDependencies.register(tag: WebService.PersonWS.tag, .Singleton) { mock as NetworkLayer }
wsDependencies.register(.singleton) { mock as NetworkLayer }
let provider = SWAPIPersonProvider()
let provider = SWAPIPersonProvider(webService: try! wsDependencies.resolve())
provider.fetchIDs { personIDs in
XCTAssertNotNil(personIDs)
XCTAssertEqual(personIDs.count, 2)
@@ -36,12 +36,11 @@ class SWAPIPersonProviderTests: XCTestCase {
}
func testFetchOnePerson() {
let mock = NetworkMock(json: fakePerson1)
wsDependencies.register(tag: WebService.PersonWS.tag, .Singleton) { mock as NetworkLayer }
wsDependencies.register(.singleton) { mock as NetworkLayer }
let provider = SWAPIPersonProvider()
provider.fetch(1) { person in
let provider = SWAPIPersonProvider(webService: try! wsDependencies.resolve())
provider.fetch(id: 1) { person in
XCTAssertNotNil(person)
XCTAssertEqual(person?.name, "John Doe")
XCTAssertEqual(person?.mass, 72)
@@ -58,10 +57,10 @@ class SWAPIPersonProviderTests: XCTestCase {
func testFetchInvalidPerson() {
let json = ["error":"whoops"]
let mock = NetworkMock(json: json)
wsDependencies.register(tag: WebService.PersonWS.tag, .Singleton) { mock as NetworkLayer }
wsDependencies.register(.singleton) { mock as NetworkLayer }
let provider = SWAPIPersonProvider()
provider.fetch(12) { person in
let provider = SWAPIPersonProvider(webService: try! wsDependencies.resolve())
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,9 +23,9 @@ class SWAPIStarshipProviderTests: XCTestCase {
func testFetchStarshipIDs() {
let mock = NetworkMock(json: ["results": [fakeShip1, fakeShip2]])
wsDependencies.register(tag: WebService.StarshipWS.tag, .Singleton) { mock as NetworkLayer }
wsDependencies.register(.singleton) { mock as NetworkLayer }
let provider = SWAPIStarshipProvider()
let provider = SWAPIStarshipProvider(webService: try! wsDependencies.resolve())
provider.fetchIDs { shipIDs in
XCTAssertNotNil(shipIDs)
XCTAssertEqual(shipIDs.count, 2)
@@ -38,10 +38,10 @@ class SWAPIStarshipProviderTests: XCTestCase {
func testFetchOneStarship() {
let mock = NetworkMock(json: fakeShip1)
wsDependencies.register(tag: WebService.StarshipWS.tag, .Singleton) { mock as NetworkLayer }
wsDependencies.register(.singleton) { mock as NetworkLayer }
let provider = SWAPIStarshipProvider()
provider.fetch(1) { starship in
let provider = SWAPIStarshipProvider(webService: try! wsDependencies.resolve())
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(tag: WebService.StarshipWS.tag, .Singleton) { mock as NetworkLayer }
wsDependencies.register(.singleton) { mock as NetworkLayer }
let provider = SWAPIStarshipProvider()
provider.fetch(12) { starship in
let provider = SWAPIStarshipProvider(webService: try! wsDependencies.resolve())
provider.fetch(id: 12) { starship in
XCTAssertNil(starship)
}
}
+300
View File
@@ -0,0 +1,300 @@
//
// Dip
//
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
extension DependencyContainer {
/**
Resolves properties of passed object wrapped with `Injected<T>` or `InjectedWeak<T>`
*/
func autoInjectProperties(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 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)
}
}
}
/**
Implement this protocol if you want to use your own type to wrap auto-injected properties
instead of using `Injected<T>` or `InjectedWeak<T>` types.
**Example**:
```swift
class MyCustomBox<T> {
private(set) var value: T?
init() {}
}
extension MyCustomBox: AutoInjectedPropertyBox {
static var wrappedType: Any.Type { return T.self }
func resolve(container: DependencyContainer) throws {
value = try container.resolve() as T
}
}
```
*/
public protocol AutoInjectedPropertyBox: class {
///The type of wrapped property.
static var wrappedType: Any.Type { get }
/**
This method will be called by `DependencyContainer` during processing resolved instance properties.
In this method you should resolve an instance for wrapped property and store a reference to it.
- parameter container: A container to be used to resolve an instance
- note: This method is not intended to be called manually, `DependencyContainer` will call it by itself.
*/
func resolve(_ container: DependencyContainer) throws
}
/**
Use this wrapper to identify _strong_ properties of the instance that should be
auto-injected by `DependencyContainer`. Type T can be any type.
- warning: Do not define this property as optional or container will not be able to inject it.
Instead define it with initial value of `Injected<T>()`.
**Example**:
```swift
class ClientImp: Client {
var service = Injected<Service>()
}
```
- seealso: `InjectedWeak`
*/
public final class Injected<T>: _InjectedPropertyBox<T>, AutoInjectedPropertyBox {
///The type of wrapped property.
public static var wrappedType: Any.Type {
return T.self
}
///Wrapped value.
public internal(set) var value: T? {
didSet {
if let value = value { didInject(value) }
}
}
init(value: T?, required: Bool = true, tag: DependencyTagConvertible?, overrideTag: Bool, didInject: @escaping (T) -> ()) {
self.value = value
super.init(required: required, tag: tag, overrideTag: overrideTag, 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.
*/
public convenience 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: @escaping (T) -> () = { _ in }) {
self.init(value: nil, required: required, tag: tag, overrideTag: true, didInject: didInject)
}
public func resolve(_ container: DependencyContainer) throws {
let resolved: T? = try super.resolve(with: container)
value = resolved
}
/// Returns a new wrapper with provided value.
public func setValue(_ value: T?) -> Injected {
guard (required && value != nil) || !required else {
fatalError("Can not set required property to nil.")
}
return Injected(value: value, required: required, tag: tag, overrideTag: overrideTag, didInject: didInject)
}
}
/**
Use this wrapper to identify _weak_ properties of the instance that should be
auto-injected by `DependencyContainer`. Type T should be a **class** type.
Otherwise it will cause runtime exception when container will try to resolve the property.
Use this wrapper to define one of two circular dependencies to avoid retain cycle.
- note: The only difference between `InjectedWeak` and `Injected` is that `InjectedWeak` uses
_weak_ reference to store underlying value, when `Injected` uses _strong_ reference.
For that reason if you resolve instance that has a _weak_ auto-injected property this property
will be released when `resolve` will complete.
Use `InjectedWeak<T>` to define one of two circular dependecies if another dependency is defined as `Injected<U>`.
This will prevent a retain cycle between resolved instances.
- warning: Do not define this property as optional or container will not be able to inject it.
Instead define it with initial value of `InjectedWeak<T>()`.
**Example**:
```swift
class ServiceImp: Service {
var client = InjectedWeak<Client>()
}
```
- seealso: `Injected`
*/
public final class InjectedWeak<T>: _InjectedPropertyBox<T>, AutoInjectedPropertyBox {
//Only classes (means AnyObject) can be used as `weak` properties
//but we can not make <T: AnyObject> because that will prevent using protocol as generic type
//so we just rely on user reading documentation and passing AnyObject in runtime
//also we will throw fatal error if type can not be casted to AnyObject during resolution.
///The type of wrapped property.
public static var wrappedType: Any.Type {
return T.self
}
var valueBox: WeakBox<T>? = nil {
didSet {
if let value = value { didInject(value) }
}
}
///Wrapped value.
public var value: T? {
return valueBox?.value
}
init(value: T?, required: Bool = true, tag: DependencyTagConvertible?, overrideTag: Bool, didInject: @escaping (T) -> ()) {
self.valueBox = value.map(WeakBox.init)
super.init(required: required, tag: tag, overrideTag: overrideTag, didInject: didInject)
}
/**
Creates a new wrapper for weak auto-injected property.
- parameters:
- required: Defines if the property is required or not.
If container fails to inject required property it will als fail to resolve
the instance that defines that property. Default is `true`.
- tag: An optional tag to use to lookup definitions when injecting this property. Default is `nil`.
- didInject: Block that will be called when concrete instance is injected in this property.
Similar to `didSet` property observer. Default value does nothing.
*/
public convenience 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: @escaping (T) -> () = { _ in }) {
self.init(value: nil, required: required, tag: tag, overrideTag: true, didInject: didInject)
}
public func resolve(_ container: DependencyContainer) throws {
let resolved: T? = try super.resolve(with: container)
valueBox = resolved.map(WeakBox.init)
}
/// Returns a new wrapper with provided value.
public 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)
}
}
public class _InjectedPropertyBox<T> {
let required: Bool
let didInject: (T) -> ()
let tag: DependencyContainer.Tag?
let overrideTag: Bool
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
}
func resolve(with container: DependencyContainer) throws -> T? {
let tag = overrideTag ? self.tag : container.context.tag
do {
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)
if required {
throw error
}
else {
log(level: .Errors, error)
return nil
}
}
}
private 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)
})
}
}
+86
View File
@@ -0,0 +1,86 @@
//
// Dip
//
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
protocol AutoWiringDefinition: DefinitionType {
var numberOfArguments: Int { get }
var autoWiringFactory: ((DependencyContainer, DependencyContainer.Tag?) throws -> Any)? { get }
}
extension DependencyContainer {
/// 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 autoWiringKey = try autoWiringDefinition(byKey: key).key
do {
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: key.type, underlyingError: error)
}
}
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) })
definitions = filter(definitions: definitions, byKey: key, strictByTag: strictByTag)
definitions = definitions.sorted(by: { $0.definition.numberOfArguments > $1.definition.numberOfArguments })
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]
}
}
}
+17
View File
@@ -0,0 +1,17 @@
#if _runtime(_ObjC)
extension String {
func has(prefix aPrefix: String) -> Bool {
return hasPrefix(aPrefix)
}
}
#else
extension String {
func has(prefix aPrefix: String) -> Bool {
return aPrefix ==
String(self.characters.prefix(aPrefix.characters.count))
}
}
#endif
+120
View File
@@ -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
}
+291
View File
@@ -0,0 +1,291 @@
//
// Dip
//
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
///A key used to store definitons in a container.
public struct DefinitionKey: Hashable, CustomStringConvertible {
public let 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 "\(type)-\(typeOfArguments)-\(tag.desc)".hashValue
}
public var description: String {
return "type: \(type), arguments: \(typeOfArguments), tag: \(tag.desc)"
}
func tagged(with tag: DependencyContainer.Tag?) -> DefinitionKey {
var tagged = self
tagged.tag = tag
return tagged
}
/// 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 == rhs.tag
}
}
///Dummy protocol to store definitions for different types in collection
public protocol DefinitionType: class { }
/**
`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
- `U` is the type of runtime arguments accepted by factory that will create an instance of T.
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 Definition<T, U>: DefinitionType {
public typealias F = (U) throws -> T
//MARK: - _Definition
weak var container: DependencyContainer?
let factory: F
let scope: ComponentScope
var weakFactory: ((Any) throws -> Any)!
var resolveProperties: ((DependencyContainer, Any) throws -> ())?
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 resolve property dependencies of the instance.
- returns: modified definition
- note: To resolve circular dependencies at least one of them should use this block
to resolve its dependencies. Otherwise the application will enter an infinite loop and crash.
- note: You can call this method several times on the same definition.
Container will call all provided blocks in the same order.
**Example**
```swift
container.register { ClientImp(service: try container.resolve() as Service) as Client }
container.register { ServiceImp() as Service }
.resolvingProperties { container, service in
service.client = try container.resolve() as Client
}
```
*/
@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
}
/// Calls `resolveDependencies` block if it was set.
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
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]
/// Return `true` if type can be resolved using this definition
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
func _implements(type aType: Any.Type) {
_implements(types: [aType])
}
/// Adds types as being able to be resolved using this definition
func _implements(types aTypes: [Any.Type]) {
implementingTypes.append(contentsOf: aTypes.filter({ !doesImplements(type: $0) }))
}
/// Definition to which resolution will be forwarded to
weak var forwardsTo: _TypeForwardingDefinition? {
didSet {
//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)
//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)
}
//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
var forwardsFrom: [_TypeForwardingDefinition] = []
}
//MARK: - _Definition
protocol _Definition: AutoWiringDefinition, TypeForwardingDefinition {
var type: Any.Type { get }
var scope: ComponentScope { get }
var weakFactory: ((Any) throws -> Any)! { get }
func resolveProperties(of instance: Any, container: DependencyContainer) throws
var container: DependencyContainer? { get set }
}
//MARK: - Type Forwarding
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 Definition: _TypeForwardingDefinition {
var type: Any.Type {
return T.self
}
}
extension Definition: CustomStringConvertible {
public var description: String {
return "type: \(T.self), factory: \(F.self), scope: \(scope)"
}
}
//MARK: - Definition Builder
/// Internal class used to build definition
class DefinitionBuilder<T, U> {
typealias F = (U) throws -> T
var scope: ComponentScope!
var factory: F!
var numberOfArguments: Int = 0
var autoWiringFactory: ((DependencyContainer, DependencyContainer.Tag?) throws -> T)?
var forwardsTo: _Definition?
init(configure: (DefinitionBuilder) -> ()) {
configure(self)
}
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.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 })
}
+517
View File
@@ -0,0 +1,517 @@
//
// 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.
//
/**
`DependencyContainer` allows you to do _Dependency Injection_
by associating abstractions to concrete implementations.
*/
public final class DependencyContainer {
/**
Use a tag in case you need to register multiple factories fo the same type,
to differentiate them. Tags can be either String or Int, to your convenience.
- seealso: `DependencyTagConvertible`
*/
public enum Tag {
case String(StringLiteralType)
case Int(IntegerLiteralType)
}
internal(set) public var context: Context!
var definitions = [DefinitionKey: _Definition]()
var resolvedInstances = ResolvedInstances()
private let lock = RecursiveLock()
var bootstrapped = false
var bootstrapQueue: [() throws -> ()] = []
private var _weakCollaborators: [WeakBox<DependencyContainer>] = []
var _collaborators: [DependencyContainer] {
get {
return _weakCollaborators.compactMap({ $0.value })
}
set {
_weakCollaborators = newValue.filter({ $0 !== self }).map(WeakBox.init)
}
}
/**
Designated initializer for a DependencyContainer
- parameter configBlock: A configuration block in which you typically put all you `register` calls.
- note: The `configBlock` is simply called at the end of the `init` to let you configure everything.
It is only present for convenience to have a cleaner syntax when declaring and initializing
your `DependencyContainer` instances.
- 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(configBlock: (DependencyContainer)->() = { _ in }) {
configBlock(self)
}
/**
Call this method to complete container setup. After container is bootstrapped
you can not add or remove definitions. Trying to do so will cause runtime exception.
You can completely reset container, after reset you can bootstrap it again.
During bootsrap container will instantiate components registered with `EagerSingleton` scope.
- throws: `DipError` if failed to instantiate any component
*/
public func bootstrap() throws {
try threadSafe {
bootstrapped = true
try bootstrapQueue.forEach({ try $0() })
bootstrapQueue.removeAll()
}
}
func threadSafe<T>(_ closure: () throws -> T) rethrows -> T {
lock.lock()
defer { lock.unlock() }
return try closure()
}
}
extension DependencyContainer {
/**
Context provides contextual information about resolution process.
You can use the context for debugging or to pass through tag when you explicitly resolve dependencies.
When auto-wiring or auto-injecting tag will be implicitly passed through by the container.
For auto-injected properties you can disable that by providing tag (some value or `nil`) when defining property.
**Example**:
```swift
class SomeServiceImp: SomeService {
//container will pass through the tag ("tag") used to resolve containing instance to resolve this property
let injected = Injected<SomeDependency>()
//container will use "someTag" tag to resolve this property
let injectedTagged = Injected<SomeDependency>(tag: "someTag")
//container will use `nil` tag to resolve this property
let injectedNilTag = Injected<SomeDependency>(tag: nil)
}
container.register {
//container will pass through the tag ("tag") used to resolve SomeService to resolve $0
SomeServiceImp(dependency: $0) as SomeService
}.resolvingProperties { container, service in
//container will use `nil` tag to resolve this dependency
self.dependency = try container.resolve() as SomeDependency
//container will use current context tag ("tag") to resolve this dependency
self.taggedDependency = try container.resolve(tag: container.context.tag) as SomeDependency
}
//container will use "tag" to resolve this instance
let service = try! container.resolve(tag: "tag") as SomeService
```
*/
public struct Context: CustomStringConvertible, CustomDebugStringConvertible {
internal(set) public var key: DefinitionKey
/// Currently resolving type.
public var resolvingType: Any.Type {
return key.type
}
/// The tag used to resolve currently resolving type.
public var tag: Tag? {
return key.tag
}
/// The type that caused currently resolving type to be resolved.
/// `nil` for root object in a dependencies graph.
private(set) public var injectedInType: Any.Type?
/// The label of the property where resolved instance will be auto-injected.
private(set) public var injectedInProperty: String?
let inCollaboration: Bool
var logErrors: Bool = true
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 {
return "Context(key: \(key), injectedInType: \(injectedInType.desc), injectedInProperty: \(injectedInProperty.desc) logErrors: \(logErrors))"
}
public var description: String {
let resolvingDescription = "Resolving type \(key.type) with arguments \(key.typeOfArguments) \(key.tag != nil ? "tagged with \(key.tag!)" : "")"
if injectedInProperty != nil {
return "\(resolvingDescription) while auto-injecting property \(injectedInProperty.desc) of \(injectedInType.desc)"
}
else if injectedInType != nil {
return "\(resolvingDescription) while injecting in type \(injectedInType.desc)"
}
else {
return resolvingDescription
}
}
}
/// Pushes new context created with provided values and calls block. When block returns previous context is restored.
/// 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
defer {
context = currentContext
//clean instances pool if it is owned not by other container
if context == nil {
resolvedInstances.resolvedInstances.removeAll()
for (key, instance) in resolvedInstances.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(instance)
}
for resolvedInstance in resolvedInstances.resolvableInstances.reversed() {
resolvedInstance.didResolveDependencies()
}
resolvedInstances.resolvableInstances.removeAll()
}
}
context = Context(
key: key,
injectedInType: injectedInType,
injectedInProperty: injectedInProperty,
inCollaboration: inCollaboration
)
context.logErrors = logErrors ?? currentContext?.logErrors ?? true
do {
return try block()
}
catch {
if context.logErrors { log(level: .Errors, error) }
throw error
}
}
}
}
//MARK: - Collaborating containers
extension DependencyContainer {
/**
Adds collaborating containers as weak references. Circular references are allowed.
References to the container itself are ignored.
*/
public func collaborate(with containers: DependencyContainer...) {
collaborate(with: containers)
}
/**
Adds collaborating containers as weak references. Circular references are allowed.
References to the container itself are ignored.
*/
public func collaborate(with containers: [DependencyContainer]) {
_collaborators += containers
for container in containers {
container._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
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 {
//Pass current container's instances pool to collect instances resolved by collaborator
let resolvedInstances = collaborator.resolvedInstances
collaborator.resolvedInstances = self.resolvedInstances
//Set collaborator context to preserve current container context
let context = collaborator.context
collaborator.context = self.context
defer {
collaborator.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:key, injectedInType: self.context.injectedInType, injectedInProperty: self.context.injectedInProperty, inCollaboration: true, logErrors: false) {
try collaborator._resolve(key: key, builder: builder)
}
return resolved
}
catch { }
}
return nil
}
}
// MARK: - Removing definitions
extension DependencyContainer {
/**
Removes definition registered in the container.
- parameters:
- tag: The tag used to register definition.
- definition: The definition to remove
*/
public func remove<T, U>(_ definition: Definition<T, U>, tag: DependencyTagConvertible? = nil) {
_remove(definition: definition, tag: tag)
}
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
}
}
/**
Removes all definitions registered in the container.
*/
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 {
/**
Validates container configuration trying to resolve each registered definition one by one.
If definition fails to be resolved without arguments will search provided arguments array
for arguments matched by type and try to resolve this definition using these arguments.
If there are no matching arguments will rethrow original error.
- parameter arguments: set of arguments to use to resolve registered definitions.
Use a tuple for registered factories that accept several runtime arguments.
*/
public func validate(_ arguments: Any...) throws {
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 {
guard type(of: argumentsSet) == key.typeOfArguments else { continue }
do {
let _ = try inContext(key:key, injectedInType: nil) {
try self._resolve(key: key, builder: { definition throws -> Any in
try definition.weakFactory(argumentsSet)
})
}
continue validateNextDefinition
}
catch let error as DipError {
throw error
}
//ignore other errors
catch { log(level: .Errors, error) }
}
//try to resolve key using auto-wiring
do {
let _ = try self.resolve(key.type, tag: key.tag)
}
catch let error as DipError {
throw error
}
//ignore other errors
catch { log(level: .Errors, error) }
}
}
}
}
extension DependencyContainer: CustomStringConvertible {
public var description: String {
return "Definitions: \(definitions.count)\n" + definitions.map({ "\($0.0)" }).joined(separator: "\n")
}
}
//MARK: - DependencyTagConvertible
/// 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 }
}
extension DependencyContainer.Tag: DependencyTagConvertible {
public var dependencyTag: DependencyContainer.Tag {
return self
}
}
extension String: DependencyTagConvertible {
public var dependencyTag: DependencyContainer.Tag {
return .String(self)
}
}
extension Int: DependencyTagConvertible {
public var dependencyTag: DependencyContainer.Tag {
return .Int(self)
}
}
extension DependencyTagConvertible where Self: RawRepresentable, Self.RawValue == Int {
public var dependencyTag: DependencyContainer.Tag {
return .Int(rawValue)
}
}
extension DependencyTagConvertible where Self: RawRepresentable, Self.RawValue == String {
public var dependencyTag: DependencyContainer.Tag {
return .String(rawValue)
}
}
extension DependencyContainer.Tag: ExpressibleByStringLiteral {
public init(stringLiteral value: StringLiteralType) {
self = .String(value)
}
public init(unicodeScalarLiteral value: StringLiteralType) {
self.init(stringLiteral: value)
}
public init(extendedGraphemeClusterLiteral value: StringLiteralType) {
self.init(stringLiteral: value)
}
}
extension DependencyContainer.Tag: ExpressibleByIntegerLiteral {
public init(integerLiteral value: IntegerLiteralType) {
self = .Int(value)
}
}
extension DependencyContainer.Tag: Equatable {
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
}
}
}
+94
View File
@@ -0,0 +1,94 @@
//
// Dip
//
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
/**
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 \(key.type)."
case let .autoInjectionFailed(label, type, error):
return "Failed to auto-inject property \"\(label.desc)\" of type \(type). \(error)"
case let .autoWiringFailed(type, error):
return "Failed to auto-wire type \"\(type)\". \(error)"
case let .ambiguousDefinitions(type, definitions):
return "Ambiguous definitions for \(type):\n" +
definitions.map({ "\($0)" }).joined(separator: ";\n")
case let .invalidType(resolved, key):
return "Resolved instance \(resolved ?? "nil") does not implement expected type \(key.type)."
}
}
}
+84
View File
@@ -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 })
}
}
}
}
+330
View File
@@ -0,0 +1,330 @@
//
// 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
}
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 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(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)
}
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 = resolvedInstances[key: key, inScope: definition.scope, context: context] as? T {
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 = resolvedInstances[key: key, inScope: definition.scope, context: context] as? T {
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(key key: DefinitionKey, inScope scope: ComponentScope, context context: DependencyContainer.Context) -> Any? {
get {
switch scope {
case .singleton, .eagerSingleton:
return context.inCollaboration ? sharedSingletons[key] : singletons[key]
case .weakSingleton:
let singletons = context.inCollaboration ? sharedWeakSingletons : weakSingletons
if let boxed = singletons[key] as? WeakBoxType { return boxed.unboxed }
else { return singletons[key] }
case .shared:
return resolvedInstances[key]
case .unique:
return nil
}
}
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() {}
}
+253
View File
@@ -0,0 +1,253 @@
//
// 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 {
/**
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
/**
Register factory that accepts one runtime argument of type `A`. You can use up to six runtime arguments.
- note: You can have several factories with different number or types of arguments registered for same type,
optionally associated with some tags. When container resolves that type it matches the type,
__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 `Shared`.
- factory: The factory to register.
- seealso: `register(_:type:tag:factory:)`
*/
@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.
- note: When resolving a type container will first try to use definition
that exactly matches types of arguments that you pass to resolve method.
If it fails or no such definition is found container will try to _auto-wire_ component.
For that it will iterate through all the definitions registered for that type
which factories accept any number of runtime arguments and are tagged with the same tag,
passed to `resolve` method, or with no tag. Container will try to use these definitions
to resolve a component one by one until one of them succeeds, starting with tagged definitions
in order of decreasing their's factories number of arguments. If none of them succeds it will
throw an error. If it finds two definitions with the same number of arguments - it will throw
an error.
- parameters:
- tag: The arbitrary tag to lookup registered definition.
- arg1: The first argument to pass to the definition's factory.
- throws: `DipError.DefinitionNotFound`, `DipError.AutoInjectionFailed`, `DipError.AmbiguousDefinitions`
- returns: An instance of type `T`.
- seealso: `register(_:type:tag:factory:)`, `resolve(tag:builder:)`
*/
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: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(_: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:)`, `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(_: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: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(_: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: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(_: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: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)) }
}
}
+143
View File
@@ -0,0 +1,143 @@
//
// Dip
//
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
protocol TypeForwardingDefinition: DefinitionType {
var implementingTypes: [Any.Type] { get }
func doesImplements(type aType: Any.Type) -> Bool
}
extension Definition {
/**
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`.
- returns: definition on which `implements` was called
*/
@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
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))
}
}
$0.numberOfArguments = definition.numberOfArguments
$0.autoWiringFactory = definition.autoWiringFactory.map({ factory in
{ [unowned self] in
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))
}
}
})
$0.forwardsTo = definition
}.build()
register(forwardDefinition, tag: tag)
return forwardDefinition
}
/// Searches for definition that forwards requested type
func typeForwardingDefinition(forKey key: DefinitionKey) -> KeyDefinitionPair? {
var forwardingDefinitions = self.definitions.map({ (key: $0.0, definition: $0.1) })
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(with: key.tag), $0.definition) })
}
}
+146
View File
@@ -0,0 +1,146 @@
//
// 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.
//
public enum LogLevel: Int {
case None
case Errors
case Verbose
}
public var logLevel: LogLevel = .Errors
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.
protocol BoxType {
var unboxed: Any? { get }
}
extension Optional: BoxType {
var unboxed: Any? {
return self ?? nil
}
}
extension ImplicitlyUnwrappedOptional: BoxType {
var unboxed: Any? {
return self ?? nil
}
}
class Box<T> {
var unboxed: T
init(_ value: T) {
self.unboxed = value
}
}
protocol WeakBoxType {
var unboxed: AnyObject? { get }
}
class WeakBox<T>: WeakBoxType {
weak var unboxed: AnyObject?
var value: T? {
return unboxed as? T
}
init(_ value: T) {
#if _runtime(_ObjC)
weak var value: AnyObject? = value as AnyObject
#else
weak var value: AnyObject? = value as? AnyObject
#endif
guard value != nil else {
fatalError("Can not store weak reference to not a class instance (\(T.self))")
}
self.unboxed = value
}
}
extension Dictionary {
subscript(key: Key?) -> Value? {
get {
guard let key = key else { return nil }
return self[key]
}
set {
guard let key = key else { return }
self[key] = newValue
}
}
}
extension Optional {
var desc: String {
return self.map { "\($0)" } ?? "nil"
}
}
#if !_runtime(_ObjC)
import Glibc
class RecursiveLock {
private var _lock = _initializeRecursiveMutex()
func lock() {
_lock.lock()
}
func unlock() {
_lock.unlock()
}
deinit {
pthread_mutex_destroy(&_lock)
}
}
private func _initializeRecursiveMutex() -> pthread_mutex_t {
var mutex: pthread_mutex_t = pthread_mutex_t()
var mta: pthread_mutexattr_t = pthread_mutexattr_t()
pthread_mutexattr_init(&mta)
pthread_mutexattr_settype(&mta, Int32(PTHREAD_MUTEX_RECURSIVE))
pthread_mutex_init(&mutex, &mta)
return mutex
}
extension pthread_mutex_t {
mutating func lock() {
pthread_mutex_lock(&self)
}
mutating func unlock() {
pthread_mutex_unlock(&self)
}
}
#else
import Foundation
typealias RecursiveLock = NSRecursiveLock
#endif
+377
View File
@@ -0,0 +1,377 @@
//
// Dip
//
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import XCTest
@testable import Dip
private protocol Server: class {
weak var client: Client! {get}
var anotherClient: Client! {get set}
}
private protocol Client: class {
var server: Server? {get}
var anotherServer: Server! {get set}
}
private class ServerImp: Server {
var _client = InjectedWeak<Client>() { _ in
AutoInjectionTests.clientDidInjectCalled = true
}
var client: Client! {
return _client.value
}
weak var anotherClient: Client!
weak var _optionalProperty = InjectedWeak<AnyObject>(required: false)
}
private class ClientImp: Client {
var _server = Injected<Server>() { _ in
AutoInjectionTests.serverDidInjectCalled = true
}
var server: Server? {
return _server.value
}
var anotherServer: Server!
var _optionalProperty = Injected<AnyObject>(required: false)
var taggedServer = Injected<Server>(tag: "tagged")
var nilTaggedServer = Injected<Server>(tag: nil)
}
private class Obj1 {
let obj2 = InjectedWeak<Obj2>()
let obj3 = Injected<Obj3>()
}
private class Obj2 {
let obj1 = Injected<Obj1>()
}
private class Obj3 {
weak var obj1: Obj1?
init(obj: Obj1) {
self.obj1 = obj
}
}
class AutoInjectionTests: XCTestCase {
static var serverDidInjectCalled: Bool = false
static var clientDidInjectCalled: Bool = false
let container = DependencyContainer()
static var allTests = {
return [
("testThatItResolvesAutoInjectedDependencies", testThatItResolvesAutoInjectedDependencies),
("testThatItResolvesInheritedDependencies", testThatItResolvesInheritedDependencies),
("testThatItCanSetInjectedProperty", testThatItCanSetInjectedProperty),
("testThatItThrowsErrorIfFailsToAutoInjectDependency", testThatItThrowsErrorIfFailsToAutoInjectDependency),
("testThatItResolvesAutoInjectedSingletons", testThatItResolvesAutoInjectedSingletons),
("testThatItCallsResolveDependencyBlockWhenAutoInjecting", testThatItCallsResolveDependencyBlockWhenAutoInjecting),
("testThatItReusesResolvedAutoInjectedInstances", testThatItReusesResolvedAutoInjectedInstances),
("testThatItReusesAutoInjectedInstancesOnNextResolveOrAutoInjection", testThatItReusesAutoInjectedInstancesOnNextResolveOrAutoInjection),
("testThatThereIsNoRetainCycleBetweenAutoInjectedCircularDependencies", testThatThereIsNoRetainCycleBetweenAutoInjectedCircularDependencies),
("testThatItCallsDidInjectOnAutoInjectedProperty", testThatItCallsDidInjectOnAutoInjectedProperty),
("testThatNoErrorThrownWhenOptionalPropertiesAreNotAutoInjected", testThatNoErrorThrownWhenOptionalPropertiesAreNotAutoInjected),
("testThatItResolvesTaggedAutoInjectedProperties", testThatItResolvesTaggedAutoInjectedProperties),
("testThatItPassesTagToAutoInjectedProperty", testThatItPassesTagToAutoInjectedProperty),
("testThatItDoesNotPassTagToAutoInjectedPropertyWithExplicitTag", testThatItDoesNotPassTagToAutoInjectedPropertyWithExplicitTag),
("testThatItAutoInjectsPropertyWithCollaboratingContainer", testThatItAutoInjectsPropertyWithCollaboratingContainer)
]
}()
override func setUp() {
container.reset()
}
func testThatItResolvesAutoInjectedDependencies() {
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 {
var _client2 = InjectedWeak<Client>() { _ in
XCTAssertTrue(AutoInjectionTests.serverDidInjectCalled, "Inherited properties should be resolved first")
}
var client2: Client? { return _client2.value }
}
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 { 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()
client._server = client._server.setValue(newServer)
server._client = server._client.setValue(newClient)
XCTAssertTrue(client.server === newServer)
XCTAssertTrue(server.client === newClient)
}
func testThatItThrowsErrorIfFailsToAutoInjectDependency() {
container.register { ClientImp() as Client }
AssertThrows(expression: try container.resolve() as Client)
}
func testThatItResolvesAutoInjectedSingletons() {
//given
container.register(.singleton) { ServerImp() as Server }
container.register(.singleton) { ClientImp() as Client }
//when
let sharedClient = try! container.resolve() as Client
let sharedServer = try! container.resolve() as Server
let client = try! container.resolve() as Client
let server = client.server
//then
XCTAssertTrue(client === sharedClient)
XCTAssertTrue(client === server?.client)
XCTAssertTrue(server === sharedServer)
}
func testThatItCallsResolveDependencyBlockWhenAutoInjecting() {
var serverBlockWasCalled = false
//given
container.register { ServerImp() as Server }
.resolvingProperties { (container, server) -> () in
serverBlockWasCalled = true
}
var clientBlockWasCalled = false
container.register { ClientImp() as Client }
.resolvingProperties { (container, client) -> () in
clientBlockWasCalled = true
}
//when
let _ = try! container.resolve() as Client
XCTAssertTrue(serverBlockWasCalled)
let _ = try! container.resolve() as Server
XCTAssertTrue(clientBlockWasCalled)
}
func testThatItReusesResolvedAutoInjectedInstances() {
//given
container.register { ServerImp() as Server }
.resolvingProperties { (container, server) -> () in
server.anotherClient = try! container.resolve() as Client
}
container.register { ClientImp() as Client }
.resolvingProperties { (container, client) -> () in
client.anotherServer = try! container.resolve() as Server
}
//when
let client = try! container.resolve() as Client
//then
let server = client.server
let anotherServer = client.anotherServer
XCTAssertTrue(server === anotherServer)
let oneClient = server!.client
let anotherClient = server!.anotherClient
XCTAssertTrue(oneClient === anotherClient)
XCTAssertTrue(client === anotherClient)
}
func testThatItReusesAutoInjectedInstancesOnNextResolveOrAutoInjection() {
//given
container.register { 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!,
"Auto-injected instance should be reused on next auto-injection")
XCTAssertTrue(obj2.obj1.value! === obj2.obj1.value!.obj3.value!.obj1,
"Auto-injected instance should be reused on next resolve")
}
func testThatThereIsNoRetainCycleBetweenAutoInjectedCircularDependencies() {
//given
container.register { ServerImp() as Server }
container.register { ClientImp() as Client }
//when
var client: Client? = try! container.resolve() as Client
//then
weak var weakServer: Server? = client?.server
weak var weakClient = client
XCTAssertNotNil(weakClient)
XCTAssertNotNil(weakServer)
client = nil
XCTAssertNil(weakClient)
XCTAssertNil(weakServer)
}
func testThatItCallsDidInjectOnAutoInjectedProperty() {
AutoInjectionTests.clientDidInjectCalled = false
AutoInjectionTests.serverDidInjectCalled = false
//given
container.register { ServerImp() as Server }
container.register { ClientImp() as Client }
//when
let _ = try! container.resolve() as Client
//then
XCTAssertTrue(AutoInjectionTests.clientDidInjectCalled)
XCTAssertTrue(AutoInjectionTests.serverDidInjectCalled)
}
func testThatNoErrorThrownWhenOptionalPropertiesAreNotAutoInjected() {
//given
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.")
}
func testThatItResolvesTaggedAutoInjectedProperties() {
//given
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
let taggedServer = (client as! ClientImp).taggedServer.value!
let server = client.server!
//server and tagged server should be resolved as different instances
XCTAssertTrue(server !== taggedServer)
XCTAssertNotNil(server)
XCTAssertNotNil(taggedServer)
}
func testThatItPassesTagToAutoInjectedProperty() {
//given
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
let taggedServer = (client as! ClientImp).taggedServer.value!
let server = client.server!
//server and tagged server should be resolved as the same instance
XCTAssertTrue(server === taggedServer)
}
func testThatItDoesNotPassTagToAutoInjectedPropertyWithExplicitTag() {
//given
container.register { ServerImp() as Server }
container.register(tag: "tagged") { ServerImp() as Server }
container.register { ClientImp() as Client }
.resolvingProperties { (container, client) -> () in
client.anotherServer = try! container.resolve() as Server
}
//when
let client = try! container.resolve(tag: "otherTag") as Client
//then
let taggedServer = (client as! ClientImp).taggedServer.value!
let nilTaggedServer = (client as! ClientImp).nilTaggedServer.value!
let server = client.server!
//server and tagged server should be resolved as different instances
XCTAssertTrue(server !== taggedServer)
XCTAssertTrue((client.anotherServer as! ServerImp) === nilTaggedServer)
XCTAssertNotNil(server)
XCTAssertNotNil(taggedServer)
XCTAssertNotNil(nilTaggedServer)
}
func testThatItAutoInjectsPropertyWithCollaboratingContainer() {
let collaborator = DependencyContainer()
collaborator.register { 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)
}
}
+484
View File
@@ -0,0 +1,484 @@
//
// 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()
static var allTests = {
return [
("testThatItCanResolveWithAutoWiring", testThatItCanResolveWithAutoWiring),
("testThatItUsesAutoWireFactoryWithMostNumberOfArguments", testThatItUsesAutoWireFactoryWithMostNumberOfArguments),
("testThatItThrowsAmbiguityErrorWhenUsingAutoWire", testThatItThrowsAmbiguityErrorWhenUsingAutoWire),
("testThatItUsesAutoWireFactoryWithMostNumberOfArguments", testThatItUsesAutoWireFactoryWithMostNumberOfArguments),
("testThatItPrefersTaggedFactoryWithDifferentNumberOfArgumentsWhenUsingAutoWire", testThatItPrefersTaggedFactoryWithDifferentNumberOfArgumentsWhenUsingAutoWire),
("testThatItPrefersTaggedFactoryWithDifferentTypesOfArgumentsWhenUsingAutoWire", testThatItPrefersTaggedFactoryWithDifferentTypesOfArgumentsWhenUsingAutoWire),
("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)
]
}()
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
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 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
AssertThrows(expression: try container.resolve(arguments: service) as AutoWiredClient,
"Container should not use auto-wiring when resolving with runtime arguments")
}
func testThatItDoesNotUseAutoWiringWhenFailedToResolveLowLevelDependency() {
//given
container.register { 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
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 { 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
//when doing another auto-wiring during resolve we should reuse instance
XCTAssertTrue((resolved as! AutoWiredClientImp) === (anotherInstance as! AutoWiredClientImp))
}
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
//when doing another auto-wiring during resolve we should reuse instance
XCTAssertTrue((resolved as! AutoWiredClientImp) === (anotherInstance as! AutoWiredClientImp))
}
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
//when doing another auto-wiring during resolve we should reuse instance
XCTAssertTrue((resolved as! AutoWiredClientImp) === (anotherInstance as! AutoWiredClientImp))
}
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
//when doing another auto-wiring during resolve we should reuse instance
XCTAssertTrue((resolved as! AutoWiredClientImp) !== (anotherInstance as! AutoWiredClientImp))
}
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
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)
}
}
+377
View File
@@ -0,0 +1,377 @@
//
// Dip
//
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import XCTest
@testable import Dip
private protocol Service: class {}
private class ServiceImp1: Service {}
private class ServiceImp2: Service {}
private class Server {
weak var client: Client?
init() {}
}
private class Client {
var server: Server
init(server: Server) {
self.server = server
}
}
class ComponentScopeTests: XCTestCase {
let container = DependencyContainer()
static var allTests = {
return [
("testThatSharedIsDefaultScope", testThatSharedIsDefaultScope),
("testThatScopeCanBeChanged", testThatScopeCanBeChanged),
("testThatItResolvesTypeAsNewInstanceForUniqueScope", testThatItResolvesTypeAsNewInstanceForUniqueScope),
("testThatItReusesInstanceForSingletonScope", testThatItReusesInstanceForSingletonScope),
("testThatSingletonIsNotReusedAcrossContainers", testThatSingletonIsNotReusedAcrossContainers),
("testThatSingletonIsReleasedWhenDefinitionIsRemoved", testThatSingletonIsReleasedWhenDefinitionIsRemoved),
("testThatSingletonIsReleasedWhenDefinitionIsOverridden", testThatSingletonIsReleasedWhenDefinitionIsOverridden),
("testThatSingletonIsReleasedWhenContainerIsReset", testThatSingletonIsReleasedWhenContainerIsReset),
("testThatItReusesInstanceInSharedScopeDuringResolve", testThatItReusesInstanceInSharedScopeDuringResolve),
("testThatItDoesNotReuseInstanceInSharedScopeInNextResolve", testThatItDoesNotReuseInstanceInSharedScopeInNextResolve),
("testThatItDoesNotReuseInstanceInSharedScopeResolvedForNilTag", testThatItDoesNotReuseInstanceInSharedScopeResolvedForNilTagWhenResolvingForAnotherTag),
("testThatItReusesInstanceInSharedScopeResolvedForNilTag", testThatItReusesInstanceInSharedScopeResolvedForNilTag),
("testThatItReusesResolvedInstanceWhenResolvingOptional", testThatItReusesResolvedInstanceWhenResolvingOptional),
("testThatItHoldsWeakReferenceToWeakSingletonInstance", testThatItHoldsWeakReferenceToWeakSingletonInstance),
("testThatItResolvesWeakSingletonAgainAfterItWasReleased", testThatItResolvesWeakSingletonAgainAfterItWasReleased),
("testThatCollaboratingContainersReuseSingletonsResolvedByAnotherContainer", testThatCollaboratingContainersReuseSingletonsResolvedByAnotherContainer)
]
}()
override func setUp() {
container.reset()
}
func testThatSharedIsDefaultScope() {
let def = container.register { ServiceImp1() as Service }
XCTAssertEqual(def.scope, ComponentScope.shared)
}
func testThatScopeCanBeChanged() {
let def = container.register(.singleton) { ServiceImp1() as Service }
XCTAssertEqual(def.scope, ComponentScope.singleton)
}
func testThatItResolvesTypeAsNewInstanceForUniqueScope() {
//given
container.register { ServiceImp1() as Service }
//when
let service1 = try! container.resolve() as Service
let service2 = try! container.resolve() as Service
//then
XCTAssertFalse(service1 === service2)
}
func testThatItReusesInstanceForSingletonScope() {
func test(_ scope: ComponentScope, line: UInt = #line) {
//given
container.register(scope) { ServiceImp1() as Service }
//when
let service1 = try! container.resolve() as Service
let service2 = try! container.resolve() as Service
//then
XCTAssertTrue(service1 === service2, line: line)
}
test(.singleton)
test(.weakSingleton)
test(.eagerSingleton)
}
func testThatSingletonIsNotReusedAcrossContainers() {
func test(_ scope: ComponentScope, line: UInt = #line) {
//given
let def = container.register(scope) { ServiceImp1() as Service }
let secondContainer = DependencyContainer()
secondContainer.register(def, 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", line: line)
}
test(.singleton)
test(.weakSingleton)
test(.eagerSingleton)
}
func testThatSingletonIsReleasedWhenDefinitionIsRemoved() {
func test(_ scope: ComponentScope, line: UInt = #line) {
//given
let def = container.register(scope) { ServiceImp1() as Service }
let service1 = try! container.resolve() as Service
//when
container.remove(def, 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", line: line)
}
test(.singleton)
test(.weakSingleton)
test(.eagerSingleton)
}
func testThatSingletonIsReleasedWhenDefinitionIsOverridden() {
func test(_ scope: ComponentScope, line: UInt = #line) {
//given
let def = container.register(scope) { ServiceImp1() as Service }
let service1 = try! container.resolve() as Service
//when
container.register(def, tag: nil)
//then
let service2 = try! container.resolve() as Service
XCTAssertTrue(service1 !== service2, "Singleton instances should be released when definition is overridden", line: line)
}
test(.singleton)
test(.weakSingleton)
test(.eagerSingleton)
}
func testThatSingletonIsReleasedWhenContainerIsReset() {
func test(_ scope: ComponentScope, line: UInt = #line) {
//given
let def = container.register(scope) { ServiceImp1() as Service }
let service1 = try! container.resolve() as Service
//when
container.reset()
container.register(def, tag: nil)
//then
let service2 = try! container.resolve() as Service
XCTAssertTrue(service1 !== service2, "Singleton instances should be released when container is reset", line: line)
}
test(.singleton)
test(.weakSingleton)
test(.eagerSingleton)
}
func testThatItReusesInstanceInSharedScopeDuringResolve() {
//given
container.register { Client(server: try self.container.resolve()) as Client }
container.register { Server() as Server }
.resolvingProperties { container, server in
server.client = try container.resolve() as Client
}
//when
let client = try! container.resolve() as Client
//then
let server = client.server
XCTAssertTrue(server.client === client)
}
func testThatItDoesNotReuseInstanceInSharedScopeInNextResolve() {
//given
container.register { Client(server: try self.container.resolve()) as Client }
container.register { Server() as Server }
.resolvingProperties { container, server in
server.client = try container.resolve() as Client
}
//when
let client = try! container.resolve() as Client
let server = client.server
let anotherClient = try! container.resolve() as Client
let anotherServer = anotherClient.server
//then
XCTAssertFalse(server === anotherServer)
XCTAssertFalse(client === anotherClient)
}
func testThatItDoesNotReuseInstanceInSharedScopeResolvedForNilTagWhenResolvingForAnotherTag() {
//given
var service2: Service?
container.register { 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") { ServiceImp2() as Service}
//when
let service1 = try! container.resolve(tag: "tag") as Service
//then
XCTAssertTrue(service1 is ServiceImp1)
}
func testThatItReusesInstanceInSharedScopeResolvedForNilTag() {
//given
var service2: Service?
container.register { ServiceImp1() as Service }
.resolvingProperties { (c, service1) in
guard service2 == nil else { return }
//when service1 is resolved using this definition due to fallback to nil tag
//and service is resolved again with another (existing) tag
service2 = try c.resolve(tag: "tag") as Service
//than we don't want every next resolve of service to reuse it
XCTAssertTrue(service1 as! ServiceImp1 === service1)
}
//when
let service1 = try! container.resolve(tag: "tag") as Service
//then
XCTAssertTrue(service1 is ServiceImp1)
}
func testThatOnlyEagerSingletonIsCreatedWhenContainerIsBootsrapped() {
//given
var eagerSingletonResolved = false
container.register(.eagerSingleton, tag: "eager") { ServiceImp1() as Service }
.resolvingProperties { container, service in eagerSingletonResolved = true }
container.register(.singleton, tag: "singleton") { ServiceImp1() as Service }
.resolvingProperties { container, service in XCTFail() }
container.register(.unique, tag: "prototype") { ServiceImp1() as Service }
.resolvingProperties { container, service in XCTFail() }
container.register(.shared, tag: "graph") { ServiceImp1() as Service }
.resolvingProperties { container, service in XCTFail() }
//when
try! container.bootstrap()
XCTAssertTrue(eagerSingletonResolved)
}
func testThatContainerCanBeBootstrappedAgainAfterReset() {
try! container.bootstrap()
XCTAssertTrue(container.bootstrapped)
container.reset()
XCTAssertFalse(container.bootstrapped)
}
func testThatItReusesResolvedInstanceWhenResolvingOptional() {
var otherService: Service!
var impOtherService: Service!
var anyOtherService: Any!
var anyImpOtherService: Any!
container.register { ServiceImp1() as Service }
.resolvingProperties { container, service in
otherService = try! container.resolve() as Service?
impOtherService = try! container.resolve() as Service!
anyOtherService = try! container.resolve((Service?).self)
anyImpOtherService = try! container.resolve((Service!).self)
}
let service = try! container.resolve() as Service
XCTAssertTrue(otherService as! ServiceImp1 === service as! ServiceImp1)
XCTAssertTrue(impOtherService as! ServiceImp1 === service as! ServiceImp1)
XCTAssertTrue(anyOtherService as! ServiceImp1 === service as! ServiceImp1)
XCTAssertTrue(anyImpOtherService as! ServiceImp1 === service as! ServiceImp1)
}
func testThatItHoldsWeakReferenceToWeakSingletonInstance() {
//given
container.register(.weakSingleton) { ServiceImp1() as Service }
var strongSingleton: Service? = try! container.resolve() as Service
weak var weakSingleton = try! container.resolve() as Service
//then
XCTAssertTrue(weakSingleton === strongSingleton)
//when
strongSingleton = nil
//then
XCTAssertNil(weakSingleton)
}
func 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
AssertNoThrow(expression: 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)
}
}
+261
View File
@@ -0,0 +1,261 @@
//
// Dip
//
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import XCTest
@testable import Dip
private protocol Service {}
private class ServiceImp1: Service {
let injected = Injected<ServiceImp2>()
let injectedWeak = InjectedWeak<ServiceImp2>()
let taggedInjected = Injected<ServiceImp2>(tag: "injectedTag")
let taggedInjectedWeak = InjectedWeak<ServiceImp2>(tag: "injectedTag")
let injectedNilTag = Injected<ServiceImp2>(tag: nil)
}
private class ServiceImp2: Service {}
class ContextTests: XCTestCase {
let container = DependencyContainer()
static var allTests = {
return [
("testThatContextStoresCurrentlyResolvedType", testThatContextStoresCurrentlyResolvedType),
("testThatContextStoresInjectedInType", testThatContextStoresInjectedInType),
("testThatContextStoresTheTagPassedToResolve", testThatContextStoresTheTagPassedToResolve),
("testThatContextStoresTheTagPassedToResolveWhenAutoInjecting", testThatContextStoresTheTagPassedToResolveWhenAutoInjecting),
("testThatContextStoresTheTagPassedToResolveWhenAutoWiring", testThatContextStoresTheTagPassedToResolveWhenAutoWiring),
("testThatContextDoesNotOverrideNilTagPassedToResolve", testThatContextDoesNotOverrideNilTagPassedToResolve),
("testThatContextStoresNameOfAutoInjectedProperty", testThatContextStoresNameOfAutoInjectedProperty),
("testThatItDoesNotSetInjectedInTypeWhenResolvingWithCollaboration", testThatItDoesNotSetInjectedInTypeWhenResolvingWithCollaboration),
("testThatContextIsPreservedWhenResolvingWithCollaboration", testThatContextIsPreservedWhenResolvingWithCollaboration)
]
}()
override func setUp() {
container.reset()
container.register { ServiceImp2() }
}
func testThatContextStoresCurrentlyResolvedType() {
container.register { () -> Service in
XCTAssertTrue(self.container.context.resolvingType == Service.self)
let _ = try self.container.resolve() as ServiceImp1
return ServiceImp1() as Service
}.resolvingProperties { _,_ in
XCTAssertTrue(self.container.context.resolvingType == Service.self)
let _ = try self.container.resolve() as ServiceImp1
}
container.register { () -> ServiceImp1 in
XCTAssertTrue(self.container.context.resolvingType == ServiceImp1.self)
return ServiceImp1()
}.resolvingProperties { _,_ in
XCTAssertTrue(self.container.context.resolvingType == ServiceImp1.self)
}
let _ = try! container.resolve() as Service
}
func testThatContextStoresInjectedInType() {
container.register { () -> Service in
XCTAssertNil(self.container.context.injectedInType)
let _ = try self.container.resolve() as ServiceImp1
return ServiceImp1() as Service
}.resolvingProperties { _,_ in
XCTAssertNil(self.container.context.injectedInType)
let _ = try self.container.resolve() as ServiceImp1
}
container.register { () -> ServiceImp1 in
XCTAssertTrue(self.container.context.injectedInType == Service.self)
return ServiceImp1()
}.resolvingProperties { _,_ in
XCTAssertTrue(self.container.context.injectedInType == Service.self)
}
let _ = try! container.resolve() as Service
}
func testThatContextStoresTheTagPassedToResolve() {
container.register { () -> Service in
XCTAssertNotNil(self.container.context.tag)
XCTAssertTrue(DependencyContainer.Tag.String("tag") ~= self.container.context.tag!)
let _ = try self.container.resolve(tag: "otherTag") as ServiceImp1
return ServiceImp1() as Service
}.resolvingProperties { _,_ in
XCTAssertNotNil(self.container.context.tag)
XCTAssertTrue(DependencyContainer.Tag.String("tag") ~= self.container.context.tag!)
let _ = try self.container.resolve(tag: "otherTag") as ServiceImp1
}
container.register { () -> ServiceImp1 in
XCTAssertNotNil(self.container.context.tag)
XCTAssertTrue(DependencyContainer.Tag.String("otherTag") ~= self.container.context.tag!)
return ServiceImp1()
}.resolvingProperties { _,_ in
XCTAssertNotNil(self.container.context.tag)
XCTAssertTrue(DependencyContainer.Tag.String("otherTag") ~= self.container.context.tag!)
}
let _ = try! container.resolve(tag: "tag") as Service
}
func testThatContextStoresTheTagPassedToResolveWhenAutoInjecting() {
container.register { ServiceImp1() as Service }
container.register { ServiceImp1() }
container.register { () -> ServiceImp2 in
if self.container.context.injectedInProperty == "injectedNilTag" {
XCTAssertNil(self.container.context.tag)
}
else {
XCTAssertNotNil(self.container.context.tag)
XCTAssertTrue(DependencyContainer.Tag.String("injectedTag") ~= self.container.context.tag!)
}
return ServiceImp2()
}.resolvingProperties { _,_ in
if self.container.context.injectedInProperty == "injectedNilTag" {
XCTAssertNil(self.container.context.tag)
}
else {
XCTAssertNotNil(self.container.context.tag)
XCTAssertTrue(DependencyContainer.Tag.String("injectedTag") ~= self.container.context.tag!)
}
}
container.register(tag: "tag") { () -> ServiceImp2 in
XCTAssertNotNil(self.container.context.tag)
XCTAssertTrue(DependencyContainer.Tag.String("tag") ~= self.container.context.tag!)
return ServiceImp2()
}.resolvingProperties { _,_ in
XCTAssertNotNil(self.container.context.tag)
XCTAssertTrue(DependencyContainer.Tag.String("tag") ~= self.container.context.tag!)
}
let _ = try! container.resolve(tag: "tag") as Service
}
func testThatContextStoresTheTagPassedToResolveWhenAutoWiring() {
container.register { (_: ServiceImp1) -> Service in
return ServiceImp1() as Service
}.resolvingProperties { _,_ in
}
container.register { () -> ServiceImp1 in
XCTAssertNotNil(self.container.context.tag)
XCTAssertTrue(DependencyContainer.Tag.String("tag") ~= self.container.context.tag!)
return ServiceImp1()
}.resolvingProperties { _,_ in
XCTAssertNotNil(self.container.context.tag)
XCTAssertTrue(DependencyContainer.Tag.String("tag") ~= self.container.context.tag!)
}
let _ = try! container.resolve(tag: "tag") as Service
}
func testThatContextDoesNotOverrideNilTagPassedToResolve() {
container.register { () -> Service in
XCTAssertNotNil(self.container.context.tag)
XCTAssertTrue(DependencyContainer.Tag.String("tag") ~= self.container.context.tag!)
let _ = try self.container.resolve() as ServiceImp1
return ServiceImp1() as Service
}.resolvingProperties { _,_ in
XCTAssertNotNil(self.container.context.tag)
XCTAssertTrue(DependencyContainer.Tag.String("tag") ~= self.container.context.tag!)
let _ = try self.container.resolve() as ServiceImp1
}
container.register { () -> ServiceImp1 in
XCTAssertNil(self.container.context.tag)
return ServiceImp1()
}.resolvingProperties { _,_ in
XCTAssertNil(self.container.context.tag)
}
let _ = try! container.resolve(tag: "tag") as Service
}
func testThatContextStoresNameOfAutoInjectedProperty() {
container.register { ServiceImp1() as Service }
container.register { ServiceImp1() }
let names = ["injected", "injectedWeak", "taggedInjected", "taggedInjectedWeak", "injectedNilTag"]
container.register { () -> ServiceImp2 in
XCTAssertNotNil(self.container.context.injectedInProperty)
XCTAssertTrue(names.contains(self.container.context.injectedInProperty!))
return ServiceImp2()
}.resolvingProperties { _,_ in
XCTAssertNotNil(self.container.context.injectedInProperty)
XCTAssertTrue(names.contains(self.container.context.injectedInProperty!))
}
let _ = try! container.resolve() as Service
}
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()
}
}
+123
View File
@@ -0,0 +1,123 @@
//
// Dip
//
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import XCTest
@testable import Dip
private protocol Service {}
private class ServiceImp: Service {}
class DefinitionTests: XCTestCase {
private typealias F1 = () -> Service
private typealias F2 = (String) -> Service
let tag1 = DependencyContainer.Tag.String("tag1")
let tag2 = DependencyContainer.Tag.String("tag2")
static var allTests = {
return [
("testThatDefinitionKeyIsEqualBy_Type_Factory_Tag", testThatDefinitionKeyIsEqualBy_Type_Factory_Tag),
("testThatDefinitionKeysWithDifferentTypesAreNotEqual", testThatDefinitionKeysWithDifferentTypesAreNotEqual),
("testThatDefinitionKeysWithDifferentFactoriesAreNotEqual", testThatDefinitionKeysWithDifferentFactoriesAreNotEqual),
("testThatDefinitionKeysWithDifferentTagsAreNotEqual", testThatDefinitionKeysWithDifferentTagsAreNotEqual),
("testThatResolveDependenciesCallsResolveDependenciesBlock", testThatResolveDependenciesCallsResolveDependenciesBlock),
("testThatResolveDependenciesBlockIsNotCalledWhenPassedWrongInstance", testThatResolveDependenciesBlockIsNotCalledWhenPassedWrongInstance),
("testThatItRegisteresOptionalTypesAsForwardedTypes", testThatItRegisteresOptionalTypesAsForwardedTypes)
]
}()
func testThatDefinitionKeyIsEqualBy_Type_Factory_Tag() {
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(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(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(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)
}
func testThatResolveDependenciesCallsResolveDependenciesBlock() {
var blockCalled = false
//given
let def = Definition<Service, ()>(scope: .unique) { ServiceImp() as Service }
.resolvingProperties { container, service in
blockCalled = true
}
//when
try! def.resolveProperties(of: ServiceImp(), container: DependencyContainer())
//then
XCTAssertTrue(blockCalled)
}
func testThatResolveDependenciesBlockIsNotCalledWhenPassedWrongInstance() {
var blockCalled = false
//given
let def = Definition<Service, ()>(scope: .unique) { ServiceImp() as Service }
.resolvingProperties { container, service in
blockCalled = true
}
//when
try! def.resolveProperties(of: String(), container: DependencyContainer())
//then
XCTAssertFalse(blockCalled)
}
func testThatItRegisteresOptionalTypesAsForwardedTypes() {
let def = Definition<Service, ()>(scope: .unique) { ServiceImp() as Service }
XCTAssertTrue(def.implementingTypes.contains(where: { $0 == Service?.self }))
XCTAssertTrue(def.implementingTypes.contains(where: { $0 == Service!.self }))
}
}
+884
View File
@@ -0,0 +1,884 @@
//
// Dip
//
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import XCTest
@testable import Dip
private protocol Service: class { }
private class ServiceImp1: Service { }
private class ServiceImp2: Service { }
private protocol Server: class {
weak var client: Client! { get }
}
private protocol Client: class {
var server: Server! { get }
}
class ResolvableService: Service, Resolvable {
var didResolveDependenciesCalled = false
func didResolveDependencies() {
XCTAssertFalse(didResolveDependenciesCalled, "didResolveDependencies should be called only once per instance")
didResolveDependenciesCalled = true
}
}
class DipTests: XCTestCase {
let container = DependencyContainer()
static var allTests = {
return [
("testThatCreatingContainerWithConfigBlockDoesNotCreateRetainCycle", testThatCreatingContainerWithConfigBlockDoesNotCreateRetainCycle),
("testThatItResolvesInstanceRegisteredWithoutTag", testThatItResolvesInstanceRegisteredWithoutTag),
("testThatItResolvesInstanceRegisteredWithTag", testThatItResolvesInstanceRegisteredWithTag),
("testThatItResolvesDifferentInstancesRegisteredForDifferentTags", testThatItResolvesDifferentInstancesRegisteredForDifferentTags),
("testThatNewRegistrationOverridesPreviousRegistration", testThatNewRegistrationOverridesPreviousRegistration),
("testThatItCallsResolveDependenciesOnDefinition", testThatItCallsResolveDependenciesOnDefinition),
("testThatItThrowsErrorIfCanNotFindDefinitionForType", testThatItThrowsErrorIfCanNotFindDefinitionForType),
("testThatItThrowsErrorIfCanNotFindDefinitionForTag", testThatItThrowsErrorIfCanNotFindDefinitionForTag),
("testThatItThrowsErrorIfCanNotFindDefinitionForFactoryWithArguments", testThatItThrowsErrorIfCanNotFindDefinitionForFactoryWithArguments),
("testThatItThrowsErrorIfConstructorThrows", testThatItThrowsErrorIfConstructorThrows),
("testThatItThrowsErrorIfFailsToResolveDependency", testThatItThrowsErrorIfFailsToResolveDependency),
("testThatItCallsDidResolveDependenciesOnResolvableIntance", testThatItCallsDidResolveDependenciesOnResolvableIntance),
("testThatItCallsDidResolveDependenciesInReverseOrder", testThatItCallsDidResolveDependenciesInReverseOrder),
("testItCallsResolveDependenciesOnResolableInstance", testItCallsResolveDependenciesOnResolableInstance),
("testThatItResolvesCircularDependencies", testThatItResolvesCircularDependencies),
("testThatItCanResolveUsingContainersCollaboration", testThatItCanResolveUsingContainersCollaboration),
("testThatCollaboratingWithSelfIsIgnored", testThatCollaboratingWithSelfIsIgnored),
("testThatCollaboratingContainersAreWeakReferences", testThatCollaboratingContainersAreWeakReferences),
("testThatCollaboratingContainersReuseInstancesResolvedByAnotherContainer", testThatCollaboratingContainersReuseInstancesResolvedByAnotherContainer),
("testThatItCanHandleSeparateContainersAndTheirCollaboration", testThatItCanHandleSeparateContainersAndTheirCollaboration)
]
}()
override func setUp() {
container.reset()
}
func testThatCreatingContainerWithConfigBlockDoesNotCreateRetainCycle() {
var container: DependencyContainer! = DependencyContainer() { container in
//compiler crashes if you try to capture container in capture list
//so instead we capture it in a variable
unowned let container = container
container.register { ServiceImp1() }
container.register { (_: ServiceImp1)->Service in
//referencing container in factory
let _ = container
return ServiceImp1() as Service
}.resolvingProperties { container, _ in
//when container is passed as argument there will be no retain cycle
let _ = container
}
}
let _ = try! container.resolve() as Service
weak var weakContainer = container
container = nil
XCTAssertNil(weakContainer)
}
func testThatItResolvesInstanceRegisteredWithoutTag() {
//given
container.register { ServiceImp1() as Service }
//when
let serviceInstance = try! container.resolve() as Service
//then
XCTAssertTrue(serviceInstance is ServiceImp1)
//and when
let anyService = try! container.resolve(Service.self)
//then
XCTAssertTrue(anyService is ServiceImp1)
//and when
let optService = try! container.resolve((Service?).self)
//then
XCTAssertTrue(optService is ServiceImp1)
//and when
let impService = try! container.resolve((Service!).self)
//then
XCTAssertTrue(impService is ServiceImp1)
}
func testThatItResolvesInstanceRegisteredWithTag() {
//given
container.register(tag: "service") { ServiceImp1() as Service }
//when
let serviceInstance = try! container.resolve(tag: "service") as Service
//then
XCTAssertTrue(serviceInstance is ServiceImp1)
//and when
let anyService = try! container.resolve(Service.self, tag: "service")
//then
XCTAssertTrue(anyService is ServiceImp1)
//and when
let optService = try! container.resolve((Service?).self, tag: "service")
//then
XCTAssertTrue(optService is ServiceImp1)
//and when
let impService = try! container.resolve((Service!).self, tag: "service")
//then
XCTAssertTrue(impService is ServiceImp1)
}
func testThatItResolvesDifferentInstancesRegisteredForDifferentTags() {
//given
container.register(tag: "service1") { ServiceImp1() as Service }
container.register(tag: "service2") { ServiceImp2() as Service }
//when
let service1Instance = try! container.resolve(tag: "service1") as Service
let service2Instance = try! container.resolve(tag: "service2") as Service
//then
XCTAssertTrue(service1Instance is ServiceImp1)
XCTAssertTrue(service2Instance is ServiceImp2)
//and when
let anyService1 = try! container.resolve(Service.self, tag: "service1")
let anyService2 = try! container.resolve(Service.self, tag: "service2")
//then
XCTAssertTrue(anyService1 is ServiceImp1)
XCTAssertTrue(anyService2 is ServiceImp2)
//and when
let optService1 = try! container.resolve((Service?).self, tag: "service1")
let optService2 = try! container.resolve((Service?).self, tag: "service2")
//then
XCTAssertTrue(optService1 is ServiceImp1)
XCTAssertTrue(optService2 is ServiceImp2)
//and when
let impService1 = try! container.resolve((Service!).self, tag: "service1")
let impService2 = try! container.resolve((Service!).self, tag: "service2")
//then
XCTAssertTrue(impService1 is ServiceImp1)
XCTAssertTrue(impService2 is ServiceImp2)
}
func testThatNewRegistrationOverridesPreviousRegistration() {
//given
container.register { ServiceImp1() as Service }
let service1 = try! container.resolve() as Service
//when
container.register { ServiceImp2() as Service }
let service2 = try! container.resolve() as Service
//then
XCTAssertTrue(service1 is ServiceImp1)
XCTAssertTrue(service2 is ServiceImp2)
}
func testThatItCallsResolveDependenciesOnDefinition() {
//given
var resolveDependenciesCalled = false
container.register { ServiceImp1() as Service }.resolvingProperties { (c, s) in
resolveDependenciesCalled = true
}
//when
let _ = try! container.resolve() as Service
//then
XCTAssertTrue(resolveDependenciesCalled)
resolveDependenciesCalled = false
//and when
let _ = try! container.resolve(Service.self)
//then
XCTAssertTrue(resolveDependenciesCalled)
resolveDependenciesCalled = false
//and when
let _ = try! container.resolve((Service?).self)
//then
XCTAssertTrue(resolveDependenciesCalled)
resolveDependenciesCalled = false
//and when
let _ = try! container.resolve((Service!).self)
//then
XCTAssertTrue(resolveDependenciesCalled)
}
func testThatItThrowsErrorIfCanNotFindDefinitionForType() {
//given
container.register { ServiceImp1() as ServiceImp1 }
//when
AssertThrows(expression: try container.resolve() as Service) { error in
guard case let DipError.definitionNotFound(key) = error else { return false }
//then
let expectedKey = DefinitionKey(type: Service.self, typeOfArguments: Void.self, tag: nil)
XCTAssertEqual(key, expectedKey)
return true
}
//and when
AssertThrows(expression: try container.resolve(Service.self)) { error in
guard case let DipError.definitionNotFound(key) = error else { return false }
//then
let expectedKey = DefinitionKey(type: Service.self, typeOfArguments: Void.self, tag: nil)
XCTAssertEqual(key, expectedKey)
return true
}
}
func testThatItThrowsErrorIfCanNotFindDefinitionForTag() {
//given
container.register(tag: "some tag") { ServiceImp1() as Service }
//when
AssertThrows(expression: try container.resolve(tag: "other tag") as Service) { error in
guard case let DipError.definitionNotFound(key) = error else { return false }
//then
let expectedKey = DefinitionKey(type: Service.self, typeOfArguments: Void.self, tag: "other tag")
XCTAssertEqual(key, expectedKey)
return true
}
//and when
AssertThrows(expression: try container.resolve(Service.self, tag: "other tag")) { error in
guard case let DipError.definitionNotFound(key) = error else { return false }
//then
let expectedKey = DefinitionKey(type: Service.self, typeOfArguments: Void.self, tag: "other tag")
XCTAssertEqual(key, expectedKey)
return true
}
}
func testThatItThrowsErrorIfCanNotFindDefinitionForFactoryWithArguments() {
//given
container.register { ServiceImp1() as Service }
//when
AssertThrows(expression: try container.resolve(arguments: "some string") as Service) { error in
guard case let DipError.definitionNotFound(key) = error else { return false }
//then
let expectedKey = DefinitionKey(type: Service.self, typeOfArguments: String.self, tag: nil)
XCTAssertEqual(key, expectedKey)
return true
}
//and when
AssertThrows(expression: try container.resolve(Service.self, arguments: "some string")) { error in
guard case let DipError.definitionNotFound(key) = error else { return false }
//then
let expectedKey = DefinitionKey(type: Service.self, typeOfArguments: String.self, tag: nil)
XCTAssertEqual(key, expectedKey)
return true
}
}
func testThatItThrowsErrorIfConstructorThrows() {
//given
let failedKey = DefinitionKey(type: Any.self, typeOfArguments: Any.self)
let expectedError = DipError.definitionNotFound(key: failedKey)
container.register { () throws -> Service in throw expectedError }
//when
AssertThrows(expression: try container.resolve() as Service) { error in
switch error {
case let DipError.definitionNotFound(key) where key == failedKey: return true
default: return false
}
}
//and when
AssertThrows(expression: try container.resolve(Service.self)) { error in
switch error {
case let DipError.definitionNotFound(key) where key == failedKey: return true
default: return false
}
}
}
func testThatItThrowsErrorIfFailsToResolveDependency() {
//given
let failedKey = DefinitionKey(type: Any.self, typeOfArguments: Any.self)
let expectedError = DipError.definitionNotFound(key: failedKey)
container.register { ServiceImp1() as Service }
.resolvingProperties { container, service in
//simulate throwing error when resolving dependency
throw expectedError
}
//when
AssertThrows(expression: try container.resolve() as Service) { error in
switch error {
case let DipError.definitionNotFound(key) where key == failedKey: return true
default: return false
}
}
//and when
AssertThrows(expression: try container.resolve(Service.self)) { error in
switch error {
case let DipError.definitionNotFound(key) where key == failedKey: return true
default: return false
}
}
}
func testThatItCallsDidResolveDependenciesOnResolvableIntance() {
//given
container.register { ResolvableService() as Service }
.resolvingProperties { _, service in
XCTAssertFalse((service as! ResolvableService).didResolveDependenciesCalled, "didResolveDependencies should not be called yet")
return
}
container.register(tag: "graph") { ResolvableService() as Service }
.resolvingProperties { _, service in
XCTAssertFalse((service as! ResolvableService).didResolveDependenciesCalled, "didResolveDependencies should not be called yet")
return
}
container.register(.singleton, tag: "singleton") { ResolvableService() as Service }
.resolvingProperties { _, service in
XCTAssertFalse((service as! ResolvableService).didResolveDependenciesCalled, "didResolveDependencies should not be called yet")
return
}
//when
let service = try! container.resolve() as Service
//then
XCTAssertTrue((service as! ResolvableService).didResolveDependenciesCalled)
//and when
let graphService = try! container.resolve(tag: "graph") as Service
//then
XCTAssertTrue((graphService as! ResolvableService).didResolveDependenciesCalled)
//and when
let singletonService = try! container.resolve(tag: "singleton") as Service
let _ = try! container.resolve(tag: "singleton") as Service
//then
XCTAssertTrue((singletonService as! ResolvableService).didResolveDependenciesCalled)
}
func testThatItCallsDidResolveDependenciesInReverseOrder() {
class ResolvableService: Service, Resolvable {
static var resolved: [Service] = []
func didResolveDependencies() {
ResolvableService.resolved.append(self)
}
}
//given
var resolveDependenciesCalled = false
var service2: Service!
container.register { ResolvableService() as Service }
.resolvingProperties { _, service in
if !resolveDependenciesCalled {
resolveDependenciesCalled = true
service2 = try self.container.resolve() as Service
}
return
}
//when
let service1 = try! container.resolve() as Service
//then
XCTAssertTrue(ResolvableService.resolved.first === service2)
XCTAssertTrue(ResolvableService.resolved.last === service1)
}
func 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!
init(client: Client) {
self.client = client
}
var didResolveDependenciesCalled = false
func didResolveDependencies() {
XCTAssertFalse(didResolveDependenciesCalled, "didResolveDependencies should be called only once per instance")
didResolveDependenciesCalled = true
XCTAssertNotNil(self.client)
XCTAssertNotNil(self.secondClient)
XCTAssertNotNil(self.client?.server)
XCTAssertNotNil(self.secondClient)
XCTAssertNotNil(self.secondClient?.server)
}
}
class ResolvableClient: Client, Resolvable {
var server: Server!
var secondServer: Server!
var didResolveDependenciesCalled = false
func didResolveDependencies() {
XCTAssertFalse(didResolveDependenciesCalled, "didResolveDependencies should be called only once per instance")
didResolveDependenciesCalled = true
XCTAssertNotNil(self.server)
XCTAssertNotNil(self.secondServer)
XCTAssertNotNil(self.server?.client)
XCTAssertNotNil(self.secondServer?.client)
}
}
//given
container.register { try ResolvableServer(client: self.container.resolve()) as Server }
.resolvingProperties { (container: DependencyContainer, server: Server) in
let server = server as! ResolvableServer
server.secondClient = try container.resolve() as Client
}
container.register { ResolvableClient() as Client }
.resolvingProperties { (container: DependencyContainer, client: Client) in
let client = client as! ResolvableClient
client.server = try container.resolve() as Server
client.secondServer = try container.resolve() as Server
}
//when
let client = (try! container.resolve() as Client) as! ResolvableClient
let server = client.server as! ResolvableServer
let secondServer = client.secondServer as! ResolvableServer
let secondClient = server.secondClient as! ResolvableClient
//then
XCTAssertTrue(client === server.client)
XCTAssertTrue(client === server.secondClient)
XCTAssertTrue(client === secondServer.client)
XCTAssertTrue(client === secondServer.secondClient)
XCTAssertTrue(client === secondClient)
XCTAssertTrue(server === secondServer)
XCTAssertTrue(client.didResolveDependenciesCalled)
XCTAssertTrue(server.didResolveDependenciesCalled)
}
func testThatItValidatesConfiguration() {
//given
var createdService1 = false
var createdService2 = false
var createdService3 = false
var createdService = false
container.register { ServiceImp1() }
.resolvingProperties { container, _ in
if container.context.resolvingType == ServiceImp1.self {
createdService1 = true
}
if container.context.resolvingType == Service.self {
createdService = true
}
}.implements(Service.self)
container.register(tag: "tag") { ServiceImp2() as Service }
.resolvingProperties { _,_ in
createdService2 = true
}
container.register { (arg: String) in ServiceImp1() }
.resolvingProperties { _,_ in
createdService3 = true
}
//then
AssertNoThrow(expression: try container.validate("arg"))
XCTAssertTrue(createdService1)
XCTAssertTrue(createdService2)
XCTAssertTrue(createdService3)
XCTAssertTrue(createdService)
}
func testThatItPicksRuntimeArgumentsWhenValidatingConfiguration() {
//given
let expectedIntArgument = 1
let expectedStringArgument = "a"
container.register { (a: Int) -> Service in
XCTAssertEqual(a, expectedIntArgument)
return ServiceImp1() as Service
}
container.register { (a: Int, b: String) -> Service in
XCTAssertEqual(a, expectedIntArgument)
XCTAssertEqual(b, expectedStringArgument)
return ServiceImp1() as Service
}
//then
AssertNoThrow(expression:
try container.validate(
"1",
expectedIntArgument,
"x",
(expectedStringArgument, expectedIntArgument),
(expectedIntArgument, expectedStringArgument)
)
)
}
func testThatItFailsValidationIfNoMatchingArgumentsFound() {
//given
container.register { (a: Int) -> Service in ServiceImp1() as Service }
//then
AssertThrows(expression: try container.validate()) { error in error is DipError }
AssertThrows(expression: try container.validate("1")) { error in error is DipError }
}
func testThatItFailsValidationOnlyForDipErrors() {
//given
enum SomeError: Error { case error }
container.register { () -> Service in
throw SomeError.error
}
//then
AssertNoThrow(expression: try container.validate())
//given
let key = DefinitionKey(type: Service.self, typeOfArguments: Void.self, tag: nil)
container.register { () -> Service in
throw DipError.definitionNotFound(key: key)
}
//then
AssertThrows(expression: try container.validate()) { error in
if case let DipError.definitionNotFound(_key) = error, _key == key { return true }
else { return false }
}
}
}
extension DipTests {
func testThatItCanResolveUsingContainersCollaboration() {
//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))
AssertNoThrow(expression: try collaborator.resolve() as String)
AssertNoThrow(expression: 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() {
//given
var collaborator: DependencyContainer? = DependencyContainer()
weak var weakCollaborator = collaborator
//when
container.collaborate(with: collaborator!)
collaborator = nil
//then
XCTAssertNil(weakCollaborator)
}
func testThatCollaboratingContainersReuseInstancesResolvedByAnotherContainer() {
//given
class ServerImp: Server {
weak var client: Client!
init(client: Client) { self.client = client }
}
class ClientImp: Client {
var server: Server!
var anotherServer: Server!
}
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
serverContainer.collaborate(with: clientContainer)
clientContainer.collaborate(with: serverContainer)
var client = try? clientContainer.resolve() as Client
//then
XCTAssertNotNil(client)
XCTAssertTrue(client === client?.server?.client)
XCTAssertTrue(client === (client as? ClientImp)?.anotherServer?.client)
XCTAssertTrue(client?.server === (client as? ClientImp)?.anotherServer)
client = try? serverContainer.resolve() as Client
//then
XCTAssertNotNil(client)
XCTAssertTrue(client === client?.server?.client)
XCTAssertTrue(client === (client as? ClientImp)?.anotherServer?.client)
XCTAssertTrue(client?.server === (client as? ClientImp)?.anotherServer)
}
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
)
}
}
+298
View File
@@ -0,0 +1,298 @@
//
// Dip
//
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import XCTest
@testable import Dip
private protocol Service {
var name: String { get }
}
private class ServiceImp: Service {
let name: String
init(name: String, baseURL: String, port: Int) {
self.name = name
}
}
private class ServiceImp1: Service {
let name: String = "ServiceImp1"
}
private class ServiceImp2: Service {
let name: String = "ServiceImp2"
}
class RuntimeArgumentsTests: XCTestCase {
let container = DependencyContainer()
static var allTests = {
return [
("testThatItResolvesInstanceWithOneArgument", testThatItResolvesInstanceWithOneArgument),
("testThatItResolvesInstanceWithTwoArguments", testThatItResolvesInstanceWithTwoArguments),
("testThatItResolvesInstanceWithThreeArguments", testThatItResolvesInstanceWithThreeArguments),
("testThatItResolvesInstanceWithFourArguments", testThatItResolvesInstanceWithFourArguments),
("testThatItResolvesInstanceWithFiveArguments", testThatItResolvesInstanceWithFiveArguments),
("testThatItResolvesInstanceWithSixArguments", testThatItResolvesInstanceWithSixArguments),
("testThatItRegistersDifferentFactoriesForDifferentNumberOfArguments", testThatItRegistersDifferentFactoriesForDifferentNumberOfArguments),
("testThatItRegistersDifferentFactoriesForDifferentTypesOfArguments", testThatItRegistersDifferentFactoriesForDifferentTypesOfArguments),
("testThatItRegistersDifferentFactoriesForDifferentOrderOfArguments", testThatItRegistersDifferentFactoriesForDifferentOrderOfArguments),
("testThatNewRegistrationWithSameArgumentsOverridesPreviousRegistration", testThatNewRegistrationWithSameArgumentsOverridesPreviousRegistration),
("testThatDifferentFactoriesRegisteredIfArgumentIsOptional", testThatDifferentFactoriesRegisteredIfArgumentIsOptional)
]
}()
override func setUp() {
container.reset()
}
func testThatItResolvesInstanceWithOneArgument() {
//given
let arg1 = 1
container.register(factory: { (a1: Int) -> Service in
XCTAssertEqual(a1, arg1)
return ServiceImp1()
})
//when
let service = try! container.resolve(arguments: arg1) as Service
//then
XCTAssertTrue(service is ServiceImp1)
//when
let anyService = try! container.resolve(Service.self, arguments: arg1)
//then
XCTAssertTrue(anyService is ServiceImp1)
}
func testThatItResolvesInstanceWithTwoArguments() {
//given
let arg1 = 1, arg2 = 2
container.register { (a1: Int, a2: Int) -> Service in
XCTAssertEqual(a1, arg1)
XCTAssertEqual(a2, arg2)
return ServiceImp1()
}
//when
let service = try! container.resolve(arguments: arg1, arg2) as Service
//then
XCTAssertTrue(service is ServiceImp1)
//when
let anyService = try! container.resolve(Service.self, arguments: arg1, arg2)
//then
XCTAssertTrue(anyService is ServiceImp1)
}
func testThatItResolvesInstanceWithThreeArguments() {
let arg1 = 1, arg2 = 2, arg3 = 3
container.register { (a1: Int, a2: Int, a3: Int) -> Service in
XCTAssertEqual(a1, arg1)
XCTAssertEqual(a2, arg2)
XCTAssertEqual(a3, arg3)
return ServiceImp1()
}
//when
let service = try! container.resolve(arguments: arg1, arg2, arg3) as Service
//then
XCTAssertTrue(service is ServiceImp1)
//when
let anyService = try! container.resolve(Service.self, arguments: arg1, arg2, arg3)
//then
XCTAssertTrue(anyService is ServiceImp1)
}
func testThatItResolvesInstanceWithFourArguments() {
let arg1 = 1, arg2 = 2, arg3 = 3, arg4 = 4
container.register { (a1: Int, a2: Int, a3: Int, a4: Int) -> Service in
XCTAssertEqual(a1, arg1)
XCTAssertEqual(a2, arg2)
XCTAssertEqual(a3, arg3)
XCTAssertEqual(a4, arg4)
return ServiceImp1()
}
//when
let service = try! container.resolve(arguments: arg1, arg2, arg3, arg4) as Service
//then
XCTAssertTrue(service is ServiceImp1)
//when
let anyService = try! container.resolve(Service.self, arguments: arg1, arg2, arg3, arg4)
//then
XCTAssertTrue(anyService is ServiceImp1)
}
func testThatItResolvesInstanceWithFiveArguments() {
let arg1 = 1, arg2 = 2, arg3 = 3, arg4 = 4, arg5 = 5
container.register { (a1: Int, a2: Int, a3: Int, a4: Int, a5: Int) -> Service in
XCTAssertEqual(a1, arg1)
XCTAssertEqual(a2, arg2)
XCTAssertEqual(a3, arg3)
XCTAssertEqual(a4, arg4)
XCTAssertEqual(a5, arg5)
return ServiceImp1()
}
//when
let service = try! container.resolve(arguments: arg1, arg2, arg3, arg4, arg5) as Service
//then
XCTAssertTrue(service is ServiceImp1)
//when
let anyService = try! container.resolve(Service.self, arguments: arg1, arg2, arg3, arg4, arg5)
//then
XCTAssertTrue(anyService is ServiceImp1)
}
func testThatItResolvesInstanceWithSixArguments() {
let arg1 = 1, arg2 = 2, arg3 = 3, arg4 = 4, arg5 = 5, arg6 = 6
container.register { (a1: Int, a2: Int, a3: Int, a4: Int, a5: Int, a6: Int) -> Service in
XCTAssertEqual(a1, arg1)
XCTAssertEqual(a2, arg2)
XCTAssertEqual(a3, arg3)
XCTAssertEqual(a4, arg4)
XCTAssertEqual(a5, arg5)
XCTAssertEqual(a6, arg6)
return ServiceImp1()
}
//when
let service = try! container.resolve(arguments: arg1, arg2, arg3, arg4, arg5, arg6) as Service
//then
XCTAssertTrue(service is ServiceImp1)
//when
let anyService = try! container.resolve(Service.self, arguments: arg1, arg2, arg3, arg4, arg5, arg6)
//then
XCTAssertTrue(anyService is ServiceImp1)
}
func testThatItRegistersDifferentFactoriesForDifferentNumberOfArguments() {
//given
let arg1 = 1, arg2 = 2
container.register { (a1: Int) in ServiceImp1() as Service }
container.register { (a1: Int, a2: Int) in ServiceImp2() as Service }
//when
let service1 = try! container.resolve(arguments: arg1) as Service
let service2 = try! container.resolve(arguments: arg1, arg2) as Service
//then
XCTAssertTrue(service1 is ServiceImp1)
XCTAssertTrue(service2 is ServiceImp2)
}
func testThatItRegistersDifferentFactoriesForDifferentTypesOfArguments() {
//given
let arg1 = 1, arg2 = "string"
container.register(factory: { (a1: Int) in ServiceImp1() as Service })
container.register(factory: { (a1: String) in ServiceImp2() as Service })
//when
let service1 = try! container.resolve(arguments: arg1) as Service
let service2 = try! container.resolve(arguments: arg2) as Service
//then
XCTAssertTrue(service1 is ServiceImp1)
XCTAssertTrue(service2 is ServiceImp2)
}
func testThatItRegistersDifferentFactoriesForDifferentOrderOfArguments() {
//given
let arg1 = 1, arg2 = "string"
container.register { (a1: Int, a2: String) in ServiceImp1() as Service }
container.register { (a1: String, a2: Int) in ServiceImp2() as Service }
//when
let service1 = try! container.resolve(arguments: arg1, arg2) as Service
let service2 = try! container.resolve(arguments: arg2, arg1) as Service
//then
XCTAssertTrue(service1 is ServiceImp1)
XCTAssertTrue(service2 is ServiceImp2)
}
func testThatNewRegistrationWithSameArgumentsOverridesPreviousRegistration() {
//given
let arg1 = 1, arg2 = 2
container.register { (a1: Int, a2: Int) in ServiceImp1() as Service }
let service1 = try! container.resolve(arguments: arg1, arg2) as Service
//when
container.register { (a1: Int, a2: Int) in ServiceImp2() as Service }
let service2 = try! container.resolve(arguments: arg1, arg2) as Service
//then
XCTAssertTrue(service1 is ServiceImp1)
XCTAssertTrue(service2 is ServiceImp2)
}
func testThatDifferentFactoriesRegisteredIfArgumentIsOptional() {
//given
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 }
//when
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)
//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)
}
}
+242
View File
@@ -0,0 +1,242 @@
//
// Dip
//
// Copyright (c) 2015 Olivier Halligon <olivier@halligon.net>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import XCTest
@testable import Dip
private protocol Server: class {
var client: Client! { get set }
}
private protocol Client: class {
var server: Server { get }
}
private class ClientImp: Client, Equatable {
var server: Server
init(server: Server) {
self.server = server
}
}
private func ==<T: ClientImp>(lhs: T, rhs: T) -> Bool {
return lhs === rhs
}
private class ServerImp: Server, Hashable {
weak var client: Client!
init() {}
var hashValue: Int {
return Unmanaged.passUnretained(self).toOpaque().hashValue
}
}
private func ==<T: ServerImp>(lhs: T, rhs: T) -> Bool {
return lhs === rhs
}
private var resolvedServers = Set<ServerImp>()
private var resolvedClients = Array<ClientImp>()
private var container: DependencyContainer!
#if os(Linux)
import Glibc
private var runningThreads: Int = 0
private var lock: pthread_spinlock_t = 0
private let resolveClientSync: () -> Client? = {
let pointer = dispatch_sync { _ in
let resolved = try! container.resolve() as Client
return UnsafeMutableRawPointer(Unmanaged.passRetained(resolved as! ClientImp).toOpaque())
}
guard let clientPointer = pointer else { return nil }
return Unmanaged<ClientImp>.fromOpaque(clientPointer).takeRetainedValue()
}
#else
let queue = OperationQueue()
let lock = RecursiveLock()
private let resolveClientSync: () -> Client? = {
var client: Client?
DispatchQueue.global(qos: .default).sync() {
client = try! container.resolve() as Client
}
return client
}
#endif
let resolveServerAsync = {
let server = try! container.resolve() as Server
lock.lock()
resolvedServers.insert(server as! ServerImp)
#if os(Linux)
runningThreads -= 1
#endif
lock.unlock()
}
let resolveClientAsync = {
let client = try! container.resolve() as Client
lock.lock()
resolvedClients.append(client as! ClientImp)
#if os(Linux)
runningThreads -= 1
#endif
lock.unlock()
}
class ThreadSafetyTests: XCTestCase {
#if os(Linux)
required init(name: String, testClosure: @escaping (XCTestCase) throws -> Void) {
pthread_spin_init(&lock, 0)
super.init(name: name, testClosure: testClosure)
}
#endif
static var allTests = {
return [
("testSingletonThreadSafety", testSingletonThreadSafety),
("testFactoryThreadSafety", testFactoryThreadSafety),
("testCircularReferenceThreadSafety", testCircularReferenceThreadSafety)
]
}()
override func setUp() {
Dip.logLevel = .Verbose
container = DependencyContainer()
}
override func tearDown() {
resolvedServers.removeAll()
resolvedClients.removeAll()
}
func testSingletonThreadSafety() {
container.register(.singleton) { ServerImp() as Server }
for _ in 0..<100 {
#if os(Linux)
lock.lock()
runningThreads += 1
lock.unlock()
dispatch_async { _ in
resolveServerAsync()
return nil
}
#else
queue.addOperation(resolveServerAsync)
#endif
}
#if os(Linux)
while runningThreads > 0 { sleep(1) }
#else
queue.waitUntilAllOperationsAreFinished()
#endif
XCTAssertEqual(resolvedServers.count, 1, "Should create only one instance")
}
func testFactoryThreadSafety() {
container.register { ServerImp() as Server }
for _ in 0..<100 {
#if os(Linux)
lock.lock()
runningThreads += 1
lock.unlock()
dispatch_async { _ in
resolveServerAsync()
return nil
}
#else
queue.addOperation(resolveServerAsync)
#endif
}
#if os(Linux)
while runningThreads > 0 { sleep(1) }
#else
queue.waitUntilAllOperationsAreFinished()
#endif
XCTAssertEqual(resolvedServers.count, 100, "All instances should be different")
}
func testCircularReferenceThreadSafety() {
container.register {
ClientImp(server: try container.resolve()) as Client
}
container.register { ServerImp() as Server }
.resolvingProperties { container, server in
server.client = resolveClientSync()
}
for _ in 0..<100 {
#if os(Linux)
lock.lock()
runningThreads += 1
lock.unlock()
dispatch_async { _ in
resolveClientAsync()
return nil
}
#else
queue.addOperation(resolveClientAsync)
#endif
}
#if os(Linux)
while runningThreads > 0 { sleep(1) }
#else
queue.waitUntilAllOperationsAreFinished()
#endif
XCTAssertEqual(resolvedClients.count, 100, "Instances should be not reused in different object graphs")
for client in resolvedClients {
let service = client.server as! ServerImp
let serviceClient = service.client as! ClientImp
XCTAssertEqual(serviceClient, client, "Instances should be reused when resolving single object graph")
}
}
}
+345
View File
@@ -0,0 +1,345 @@
//
// 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()
static var allTests = {
return [
("testThatItResolvesInstanceByTypeForwarding", testThatItResolvesInstanceByTypeForwarding),
("testThatItReusesInstanceResolvedByTypeForwarding", testThatItReusesInstanceResolvedByTypeForwarding),
("testThatItDoesNotResolveByTypeForwardingIfRegisteredForAnotherTag", testThatItDoesNotResolveByTypeForwardingIfRegisteredForAnotherTag),
("testThatItDoesNotReuseInstanceResolvedByTypeForwardingRegisteredForAnotherTag", testThatItDoesNotReuseInstanceResolvedByTypeForwardingRegisteredForAnotherTag),
("testThatItCallsResolvedDependenciesBlockWhenResolvingByTypeForwarding", testThatItCallsResolvedDependenciesBlockWhenResolvingByTypeForwarding),
("testThatItCallsResolvedDependenciesBlockProvidedAfterRegistrationWhenResolvingByTypeForwarding",testThatItCallsResolvedDependenciesBlockProvidedAfterRegistrationWhenResolvingByTypeForwarding),
("testThatItFallbackToDefinitionWithNoTagWhenResolvingInstanceByTypeForwarding", testThatItFallbackToDefinitionWithNoTagWhenResolvingInstanceByTypeForwarding),
("testThatItCanResolveOptional", testThatItCanResolveOptional),
("testThatItFirstUsesTaggedDefinitionWhenResolvingOptional", testThatItFirstUsesTaggedDefinitionWhenResolvingOptional),
("testThatItThrowsErrorWhenResolvingNotImplementedTypeWithTypeForwarding", testThatItThrowsErrorWhenResolvingNotImplementedTypeWithTypeForwarding),
("testThatItOverridesIfSeveralDefinitionsWithTheSameTagForwardTheSameType", testThatItOverridesIfSeveralDefinitionsWithTheSameTagForwardTheSameType),
("testThatItDoesNotOverrideIfDefinitionForwardsTheSameTypeWithDifferentTag", testThatItDoesNotOverrideIfDefinitionForwardsTheSameTypeWithDifferentTag)
]
}()
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
AssertThrows(expression: try container.resolve(tag: "tag") as ForwardedType)
AssertThrows(expression: try container.resolve(ForwardedType.self, tag: "tag"))
//and given
def.implements(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
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 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
AssertThrows(expression: try container.resolve() as ServiceImp2) { error in
guard case let DipError.invalidType(_, key) = error else { return false }
let expectedKey = DefinitionKey(type: ServiceImp2.self, typeOfArguments: Void.self, tag: nil)
XCTAssertEqual(key, expectedKey)
return true
}
AssertThrows(expression: try container.resolve(ServiceImp2.self)) { error in
guard case let DipError.invalidType(_, key) = error else { return false }
let expectedKey = DefinitionKey(type: ServiceImp2.self, typeOfArguments: Void.self, tag: nil)
XCTAssertEqual(key, expectedKey)
return true
}
}
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)
}
}
+95
View File
@@ -0,0 +1,95 @@
//
// 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
func AssertThrows<T>(_ file: StaticString = #file, line: UInt = #line, expression: @autoclosure () throws -> T) {
AssertThrows(file, line: line, expression: expression, "")
}
func AssertThrows<T>(_ file: StaticString = #file, line: UInt = #line, expression: @autoclosure () throws -> T, _ message: String) {
AssertThrows(expression: expression, checkError: { _ in true }, message)
}
func AssertThrows<T>(_ file: StaticString = #file, line: UInt = #line, expression: @autoclosure () throws -> T, checkError: (Error) -> Bool) {
AssertThrows(file, line: line, expression: expression, checkError: checkError, "")
}
func AssertThrows<T>(_ file: StaticString = #file, line: UInt = #line, expression: @autoclosure () throws -> T, checkError: (Error) -> Bool, _ message: String) {
do {
let _ = 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, expression: @autoclosure () throws -> T) {
AssertNoThrow(file, line: line, expression: expression, "")
}
func AssertNoThrow<T>(_ file: StaticString = #file, line: UInt = #line, expression: @autoclosure () throws -> T, _ message: String) {
do {
let _ = try expression()
}
catch {
XCTFail(message, file: file, line: line)
}
}
#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(bytes: 1, alignedTo: 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
+14
View File
@@ -0,0 +1,14 @@
import XCTest
@testable import DipTests
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),
testCase(TypeForwardingTests.allTests)
])