├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Dip.podspec ├── Dip.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Dip ├── Dip.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Dip.xcscheme ├── Dip │ ├── Dip.h │ └── Info.plist └── DipTests │ ├── Info.plist │ ├── NSStoryboard.storyboard │ ├── TVStoryboard.storyboard │ └── UIStoryboard.storyboard ├── DipPlayground.playground ├── Contents.o ├── Models.o ├── Models.remap ├── Pages │ ├── Auto-injection.xcplaygroundpage │ │ └── Contents.swift │ ├── Auto-wiring.xcplaygroundpage │ │ ├── Contents.swift │ │ └── timeline.xctimeline │ ├── Circular dependencies.xcplaygroundpage │ │ └── Contents.swift │ ├── Containers Collaboration.xcplaygroundpage │ │ └── Contents.swift │ ├── Creating container.xcplaygroundpage │ │ ├── Contents.swift │ │ └── timeline.xctimeline │ ├── Registering components.xcplaygroundpage │ │ └── Contents.swift │ ├── Resolving components.xcplaygroundpage │ │ ├── Contents.swift │ │ └── timeline.xctimeline │ ├── Runtime arguments.xcplaygroundpage │ │ └── Contents.swift │ ├── Scopes.xcplaygroundpage │ │ └── Contents.swift │ ├── Shared Instances.xcplaygroundpage │ │ └── Contents.swift │ ├── Testing.xcplaygroundpage │ │ └── Contents.swift │ ├── Type Forwarding.xcplaygroundpage │ │ └── Contents.swift │ └── What is Dip?.xcplaygroundpage │ │ ├── Contents.swift │ │ ├── Models.remap │ │ └── timeline.xctimeline ├── Sources │ └── Models.swift ├── contents.xcplayground └── playground.xcworkspace │ └── contents.xcworkspacedata ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── LinuxMain.swift ├── Package.swift ├── README.md ├── SampleApp ├── DipSampleApp.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── DipSampleApp.xcscheme ├── DipSampleApp │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-120.png │ │ │ ├── Icon-152.png │ │ │ └── Icon-76.png │ │ ├── Churros.imageset │ │ │ ├── Churros.png │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── female.imageset │ │ │ ├── Contents.json │ │ │ ├── female.png │ │ │ └── female@2x.png │ │ ├── male.imageset │ │ │ ├── Contents.json │ │ │ ├── male.png │ │ │ └── male@2x.png │ │ ├── person.imageset │ │ │ ├── Contents.json │ │ │ └── person@2x.png │ │ └── spaceship.imageset │ │ │ ├── Contents.json │ │ │ └── spaceship@2x.png │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── Cells │ │ ├── BaseCell.swift │ │ ├── PersonCell.swift │ │ └── StarshipCell.swift │ ├── DependencyContainers.swift │ ├── Info.plist │ ├── Main.storyboard │ ├── Model │ │ ├── Person.swift │ │ └── Starship.swift │ ├── Providers │ │ ├── FakePersonsProviders.swift │ │ ├── FakeStarshipProvider.swift │ │ ├── NetworkLayer.swift │ │ ├── PersonProviderAPI.swift │ │ ├── SWAPICommon.swift │ │ ├── SWAPIPersonProvider.swift │ │ ├── SWAPIStarshipProvider.swift │ │ ├── StarshipProviderAPI.swift │ │ └── URLSessionNetworkLayer.swift │ ├── StoryboardConstants.swift │ ├── ViewControllers │ │ ├── FetchableTrait.swift │ │ ├── PersonListViewController.swift │ │ └── StarshipListViewController.swift │ └── mainPilot.plist └── Tests │ ├── Info.plist │ ├── NetworkMock.swift │ ├── SWAPIPersonProviderTests.swift │ └── SWAPIStarshipProviderTests.swift ├── Sources ├── AutoInjection.swift ├── AutoWiring.swift ├── Compatibility.swift ├── ComponentScope.swift ├── Definition.swift ├── Dip.swift ├── DipError.swift ├── Register.swift ├── Resolve.swift ├── RuntimeArguments.swift ├── StoryboardInstantiatable.swift ├── TypeForwarding.swift └── Utils.swift ├── Tests ├── DipTests │ ├── AutoInjectionTests.swift │ ├── AutoWiringTests.swift │ ├── ComponentScopeTests.swift │ ├── ContextTests.swift │ ├── DefinitionTests.swift │ ├── DipTests.swift │ ├── DipUITests.swift │ ├── RuntimeArgumentsTests.swift │ ├── ThreadSafetyTests.swift │ ├── TypeForwardingTests.swift │ └── Utils.swift └── XCTestManifests.swift ├── chocolate-churros.gif └── cinnamon-pretzels-caramel-dipping.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | *.xcscmblueprint 17 | profile 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | 23 | # Bundler 24 | .bundle 25 | 26 | Carthage 27 | # We recommend against adding the Pods directory to your .gitignore. However 28 | # you should judge for yourself, the pros and cons are mentioned at: 29 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 30 | # 31 | # Note: if you ignore the Pods directory, make sure to uncomment 32 | # `pod install` in .travis.yml 33 | # 34 | # Pods/ 35 | 36 | # SPM 37 | .build/ 38 | Packages 39 | .swiftpm 40 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | matrix: 2 | allow_failures: 3 | - os: linux 4 | include: 5 | - script: 6 | - 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 7 | - set -o pipefail && xcodebuild test -workspace Dip.xcworkspace -scheme Dip -sdk macosx -destination 'platform=macOS,arch=x86_64' ONLY_ACTIVE_ARCH=NO | xcpretty -c 8 | # - pod spec lint --allow-warnings 9 | - carthage build --no-skip-current 10 | - swift package clean && swift build && swift test 11 | os: osx 12 | osx_image: xcode10.2 13 | language: objective-c 14 | before_install: 15 | - gem install cocoapods --version 1.8.4 --no-document 16 | - script: 17 | - set -o pipefail && xcodebuild test -workspace Dip.xcworkspace -scheme Dip -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 11,OS=latest' ONLY_ACTIVE_ARCH=NO | xcpretty -c 18 | - set -o pipefail && xcodebuild test -workspace Dip.xcworkspace -scheme Dip -sdk macosx -destination 'platform=macOS,arch=x86_64' ONLY_ACTIVE_ARCH=NO | xcpretty -c 19 | # - pod spec lint --allow-warnings 20 | - carthage build --no-skip-current 21 | - swift package clean && swift build && swift test 22 | os: osx 23 | osx_image: xcode11.2 24 | language: objective-c 25 | before_install: 26 | - gem install cocoapods --version 1.8.4 --no-document 27 | - script: 28 | - swift package clean && swift build && swift test 29 | os: linux 30 | dist: trusty 31 | sudo: required 32 | language: generic 33 | before_install: 34 | - wget -q -O - https://swift.org/keys/all-keys.asc | gpg --import - 35 | - cd .. 36 | - export SWIFT_VERSION=swift-5.1-RELEASE 37 | - wget https://swift.org/builds/swift-5.1-release/ubuntu1404/$SWIFT_VERSION/$SWIFT_VERSION-ubuntu14.04.tar.gz 38 | - tar xzf $SWIFT_VERSION-ubuntu14.04.tar.gz 39 | - export PATH="${PWD}/${SWIFT_VERSION}-ubuntu14.04/usr/bin:${PATH}" 40 | - cd Dip 41 | - script: 42 | - swift package clean && swift build && swift test 43 | os: linux 44 | dist: trusty 45 | sudo: required 46 | language: generic 47 | before_install: 48 | - wget -q -O - https://swift.org/keys/all-keys.asc | gpg --import - 49 | - cd .. 50 | - export SWIFT_VERSION=swift-5.0-RELEASE 51 | - wget https://swift.org/builds/swift-5.0-release/ubuntu1404/$SWIFT_VERSION/$SWIFT_VERSION-ubuntu14.04.tar.gz 52 | - tar xzf $SWIFT_VERSION-ubuntu14.04.tar.gz 53 | - export PATH="${PWD}/${SWIFT_VERSION}-ubuntu14.04/usr/bin:${PATH}" 54 | - cd Dip 55 | 56 | notifications: 57 | email: false 58 | -------------------------------------------------------------------------------- /Dip.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "Dip" 3 | s.version = "7.1.1" 4 | s.summary = "Dependency Injection for Swift made easy." 5 | 6 | s.description = <<-DESC 7 | Dip is a Swift Dependency Injection Container. 8 | It provides reusable functionality for managing dependencies of your types 9 | and will help you to wire up different parts of your app. 10 | DESC 11 | 12 | s.homepage = "https://github.com/AliSoftware/Dip" 13 | s.license = 'MIT' 14 | s.authors = { "Olivier Halligon" => "olivier@halligon.net", "Ilya Puchka" => "ilyapuchka@gmail.com" } 15 | s.source = { :git => "https://github.com/AliSoftware/Dip.git", :tag => s.version.to_s } 16 | s.social_media_url = 'https://twitter.com/aligatr' 17 | 18 | s.ios.deployment_target = '8.0' 19 | s.osx.deployment_target = '10.9' 20 | s.tvos.deployment_target = '9.0' 21 | s.watchos.deployment_target = '2.0' 22 | 23 | s.requires_arc = true 24 | 25 | s.source_files = 'Sources/**/*.swift' 26 | 27 | s.swift_version = "5.0", "5.1" 28 | end 29 | -------------------------------------------------------------------------------- /Dip.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 16 | 18 | 19 | 21 | 22 | 24 | 25 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Dip.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Dip/Dip.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Dip/Dip.xcodeproj/xcshareddata/xcschemes/Dip.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 79 | 85 | 86 | 87 | 88 | 89 | 90 | 96 | 97 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /Dip/Dip/Dip.h: -------------------------------------------------------------------------------- 1 | // 2 | // Dip 3 | // 4 | // Copyright (c) 2015 Olivier Halligon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | #import 26 | 27 | //! Project version number for Dip. 28 | FOUNDATION_EXPORT double DipVersionNumber; 29 | 30 | //! Project version string for Dip. 31 | FOUNDATION_EXPORT const unsigned char DipVersionString[]; 32 | 33 | // In this header, you should import all the public headers of your framework using statements like #import 34 | 35 | 36 | -------------------------------------------------------------------------------- /Dip/Dip/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 7.1.1 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Dip/DipTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 7.1.1 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Dip/DipTests/NSStoryboard.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /Dip/DipTests/TVStoryboard.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /Dip/DipTests/UIStoryboard.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /DipPlayground.playground/Contents.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliSoftware/Dip/7ec008a5c09520b67f07669ad5a753d757f05bbe/DipPlayground.playground/Contents.o -------------------------------------------------------------------------------- /DipPlayground.playground/Models.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliSoftware/Dip/7ec008a5c09520b67f07669ad5a753d757f05bbe/DipPlayground.playground/Models.o -------------------------------------------------------------------------------- /DipPlayground.playground/Models.remap: -------------------------------------------------------------------------------- 1 | [ 2 | ] -------------------------------------------------------------------------------- /DipPlayground.playground/Pages/Auto-injection.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous: Auto-wiring](@previous) 2 | 3 | import UIKit 4 | import Dip 5 | 6 | let container = DependencyContainer() 7 | /*: 8 | 9 | ### Auto-Injection 10 | 11 | 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. 12 | 13 | Let's say you have following related components: 14 | */ 15 | 16 | protocol Service: AnyObject { 17 | var logger: Logger? { get } 18 | var tracker: Tracker? { get } 19 | } 20 | 21 | class ServiceImp: Service { 22 | var logger: Logger? 23 | var tracker: Tracker? 24 | } 25 | 26 | /*: 27 | When you register them in a container you will end up with something like this: 28 | */ 29 | 30 | container.register() { TrackerImp() as Tracker } 31 | container.register() { LoggerImp() as Logger } 32 | 33 | container.register() { ServiceImp() as Service } 34 | .resolvingProperties { container, service in 35 | let service = service as! ServiceImp 36 | service.logger = try container.resolve() as Logger 37 | service.tracker = try container.resolve() as Tracker 38 | } 39 | 40 | let service = try! container.resolve() as Service 41 | service.logger 42 | service.tracker 43 | 44 | /*: 45 | Notice that the same boilerplate code that we saw in constructor injection now moved to `resolveDepedencies` block. 46 | With auto-injection your code transforms to this: 47 | */ 48 | 49 | class AutoInjectedServiceImp: Service { 50 | private let injectedLogger = Injected() 51 | var logger: Logger? { return injectedLogger.value } 52 | 53 | private let injectedTracker = Injected() 54 | var tracker: Tracker? { return injectedTracker.value } 55 | } 56 | 57 | container.register() { AutoInjectedServiceImp() as Service } 58 | 59 | let autoInjectedService = try! container.resolve() as Service 60 | autoInjectedService.logger 61 | autoInjectedService.tracker 62 | 63 | /*: 64 | As you can see we added two private properties to our implementation of `Service` - `injectedLogger` and `injectedTracker`. Their types are `Injeceted` and `Injected` respectively. Note that we've not just defined them as properties of those types, but defined them with some initial value. `Injected` 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` it will have `nil` in its value. There is also another wrapper - `InjectedWeak` - which in contrast to `Injected` holds a week reference to its wrapped object, thus requiring it to be a _reference type_ (or `AnyObject`), when `Injected` can also wrap value types (or `Any`). 65 | 66 | 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` or `InjectedWeak` 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. 67 | 68 | The requirement for auto-injection is that types injected types should be registered in a container and should use factories with no runtime arguments. 69 | 70 | Auto-injected properties can be marked with tag. Then container will search for definition tagged by the same tag to resolve this property. 71 | 72 | You can provide closure that will be called when the dependency will be injected in the property. It is similar to `didSet` property observer. 73 | 74 | 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. 75 | */ 76 | 77 | class ServerWithRequiredClient { 78 | var client = Injected() 79 | } 80 | 81 | container.register { ServerWithRequiredClient() } 82 | 83 | do { 84 | let serverWithClient = try container.resolve() as ServerWithRequiredClient 85 | } 86 | catch {} 87 | 88 | /*: 89 | You can make auto-injected property optional by passing `false` to `required` parameter of `Injected`/`InjectedWeak` constructor. For such properties container will ignore any errors when it resolves this property (or any of its dependencies). 90 | */ 91 | 92 | class ServerWithOptionalClient { 93 | var optionalClient = Injected(required: false) 94 | } 95 | 96 | container.register { ServerWithOptionalClient() } 97 | let serverWithNoClient = try! container.resolve() as ServerWithOptionalClient 98 | serverWithNoClient.optionalClient.value 99 | 100 | /*: 101 | Another example of using auto-injection is circular dependencies. Let's say you have a `Server` and a `ServerClient` both referencing each other. 102 | */ 103 | 104 | protocol Server: AnyObject { 105 | weak var client: ServerClient? { get } 106 | } 107 | 108 | protocol ServerClient: AnyObject { 109 | var server: Server? { get } 110 | } 111 | 112 | class ServerImp: Server { 113 | weak var client: ServerClient? 114 | } 115 | 116 | class ServerClientImp: ServerClient { 117 | var server: Server? 118 | 119 | init(server: Server) { 120 | self.server = server 121 | } 122 | } 123 | 124 | /*: 125 | The standard way to register such components in `DependencyContainer` will lead to such code: 126 | */ 127 | 128 | container.register { 129 | ServerClientImp(server: try container.resolve()) as ServerClient 130 | } 131 | 132 | container.register { ServerImp() as Server } 133 | .resolvingProperties { (container: DependencyContainer, server: Server) in 134 | (server as! ServerImp).client = try container.resolve() as ServerClient 135 | } 136 | 137 | let client = try! container.resolve() as ServerClient 138 | client.server 139 | 140 | /*: 141 | With auto-injection you will have the following code: 142 | */ 143 | 144 | class InjectedServerImp: Server { 145 | private var injectedClient = InjectedWeak() 146 | var client: ServerClient? { return injectedClient.value } 147 | } 148 | 149 | class InjectedClientImp: ServerClient { 150 | private var injectedServer = Injected() 151 | var server: Server? { get { return injectedServer.value } } 152 | } 153 | 154 | container.register { InjectedServerImp() as Server } 155 | container.register { InjectedClientImp() as ServerClient } 156 | 157 | let injectedClient = try! container.resolve() as ServerClient 158 | injectedClient.server 159 | injectedClient.server?.client === injectedClient //circular dependencies were resolved correctly 160 | 161 | /*: 162 | 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. 163 | 164 | Here is an example with higher number of dependencies. 165 | */ 166 | container.register() { RouterImp() as Router } 167 | container.register() { DataProviderImp() as DataProvider } 168 | 169 | class ViewController: UIViewController { 170 | var logger: Logger? 171 | var tracker: Tracker? 172 | var dataProvider: DataProvider? 173 | var router: Router? 174 | } 175 | 176 | container.register { ViewController() } 177 | .resolvingProperties { container, controller in 178 | controller.logger = try container.resolve() as Logger 179 | controller.tracker = try container.resolve() as Tracker 180 | controller.dataProvider = try container.resolve() as DataProvider 181 | controller.router = try container.resolve() as Router 182 | } 183 | 184 | let viewController = try! container.resolve() as ViewController 185 | viewController.router 186 | 187 | /*: 188 | With auto-injection you can replace that with something like this: 189 | */ 190 | 191 | class AutoInjectedViewController: UIViewController { 192 | let logger = Injected() 193 | let tracker = Injected() 194 | let dataProvider = Injected() 195 | let router = Injected() 196 | } 197 | 198 | container.register { AutoInjectedViewController() } 199 | 200 | let autoViewController = try! container.resolve() as AutoInjectedViewController 201 | autoViewController.router.value 202 | 203 | /*: 204 | 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. 205 | 206 | > **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. 207 | 208 | 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` and `InjectedWeak` (see `AutoInjectedPropertyBox`). 209 | */ 210 | 211 | //: [Next: Type Forwarding](@next) 212 | -------------------------------------------------------------------------------- /DipPlayground.playground/Pages/Auto-wiring.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous: Shared Instances](@previous) 2 | 3 | import Dip 4 | import UIKit 5 | 6 | /*: 7 | 8 | ### Auto-wiring 9 | 10 | 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. 11 | 12 | Let's say you have some VIPER module with following components: 13 | */ 14 | protocol Service {} 15 | protocol Interactor { 16 | var service: Service { get } 17 | } 18 | protocol Router {} 19 | protocol ViewOutput {} 20 | protocol Presenter { 21 | var router: Router { get } 22 | var interactor: Interactor { get } 23 | var view: ViewOutput { get } 24 | } 25 | 26 | class RouterImp: Router {} 27 | class View: UIView, ViewOutput {} 28 | class ServiceImp: Service {} 29 | 30 | /*: 31 | 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: 32 | */ 33 | 34 | class InteractorImp: Interactor { 35 | var service: Service 36 | 37 | init(service: Service) { 38 | self.service = service 39 | } 40 | } 41 | 42 | class PresenterImp: Presenter { 43 | let router: Router 44 | let interactor: Interactor 45 | let view: ViewOutput 46 | 47 | init(view: ViewOutput, interactor: Interactor, router: Router) { 48 | self.view = view 49 | self.interactor = interactor 50 | self.router = router 51 | } 52 | } 53 | 54 | /*: 55 | If you register these components in a container you will end up with rather boilerplate code: 56 | */ 57 | 58 | let container = DependencyContainer() 59 | container.register { ServiceImp() as Service } 60 | container.register { RouterImp() as Router } 61 | container.register { View() as ViewOutput } 62 | 63 | container.register { try InteractorImp(service: container.resolve()) as Interactor } 64 | container.register { 65 | try PresenterImp( 66 | view: container.resolve(), 67 | interactor: container.resolve(), 68 | router: container.resolve()) as Presenter 69 | } 70 | 71 | 72 | var presenter = try! container.resolve() as Presenter 73 | presenter.interactor.service 74 | 75 | /*: 76 | While definition for `Interactor` looks fine, `Presenter`'s definition is overloaded with the same `resolve` calls to container. 77 | 78 | The other option you have is to register factory with runtime arguments: 79 | */ 80 | 81 | container.register { InteractorImp(service: $0) as Interactor } 82 | container.register { PresenterImp(view: $0, interactor: $1, router: $2) as Presenter } 83 | 84 | /*: 85 | But then to resolve presenter or interactor you will first need to resolve their dependencies and pass them as arguments to `resolve` method: 86 | */ 87 | 88 | let service = try! container.resolve() as Service 89 | let interactor = try! container.resolve(arguments: service) as Interactor 90 | let view = try! container.resolve() as ViewOutput 91 | let router = try! container.resolve() as Router 92 | presenter = try! container.resolve(arguments: view, interactor, router) as Presenter 93 | presenter.interactor.service 94 | 95 | /*: 96 | Again to much of boilerplate code. Also it's easy to make a mistake in the order of arguments. 97 | 98 | 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. 99 | */ 100 | 101 | container.register { InteractorImp(service: $0) as Interactor } 102 | container.register { PresenterImp(view: $0, interactor: $1, router: $2) as Presenter } 103 | 104 | presenter = try! container.resolve() as Presenter 105 | presenter.interactor.service 106 | 107 | /*: 108 | You don't need to call `resolve` in a factory and care about order of arguments any more. 109 | 110 | 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. 111 | 112 | 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. 113 | 114 | 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. 115 | */ 116 | 117 | //: [Next: Auto-injection](@next) 118 | -------------------------------------------------------------------------------- /DipPlayground.playground/Pages/Auto-wiring.xcplaygroundpage/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /DipPlayground.playground/Pages/Circular dependencies.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous: Scopes](@previous) 2 | 3 | import Dip 4 | import Foundation 5 | 6 | let container = DependencyContainer() 7 | 8 | /*: 9 | ### Circular Dependencies 10 | 11 | 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. 12 | 13 | Let's say you have some network client and it's delegate defined like this: 14 | */ 15 | 16 | protocol NetworkClientDelegate: AnyObject { 17 | var networkClient: NetworkClient { get } 18 | } 19 | 20 | protocol NetworkClient: AnyObject { 21 | weak var delegate: NetworkClientDelegate? { get set } 22 | } 23 | 24 | class NetworkClientImp: NetworkClient { 25 | weak var delegate: NetworkClientDelegate? 26 | init() {} 27 | } 28 | 29 | class Interactor: NSObject, NetworkClientDelegate { 30 | let networkClient: NetworkClient 31 | init(networkClient: NetworkClient) { 32 | self.networkClient = networkClient 33 | } 34 | } 35 | 36 | /*: 37 | Note that: 38 | 39 | - one of this classes uses _property injection_ (`NetworkClientImp`) — you'll give the `delegate` value via its property directly, _after_ initialization 40 | - and another uses _constructor injection_ (`Interactor`) — you'll need to give the `networkclient` value via the constructor, _during_ initialization. 41 | 42 | 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`. 43 | 44 | Now you can register those classes in container: 45 | */ 46 | 47 | container.register { 48 | Interactor(networkClient: try container.resolve()) as NetworkClientDelegate 49 | } 50 | 51 | container.register { NetworkClientImp() as NetworkClient } 52 | .resolvingProperties { (container, client) -> () in 53 | client.delegate = try container.resolve() as NetworkClientDelegate 54 | } 55 | 56 | /*: 57 | Here you can spot the difference in the way we register classes. 58 | 59 | - `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. 60 | - `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. 61 | 62 | This way `DependencyContainer` breaks infinite recursion that would happen if we used constructor injection for both of our components. 63 | 64 | *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) 65 | 66 | 67 | Now when you resolve `NetworkClientDelegate` you will get an instance of `Interactor` that will have client with delegate referencing the same `Interactor` instance: 68 | */ 69 | 70 | let interactor = try! container.resolve() as NetworkClientDelegate 71 | interactor.networkClient.delegate === interactor // true: they are the same instances 72 | 73 | /*: 74 | **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. 75 | */ 76 | 77 | let networkClient = try! container.resolve() as NetworkClient 78 | networkClient.delegate // delegate was alread released =( 79 | 80 | /*: 81 | Note also that we used `.shared` scope to register implementations. This is also very important to preserve consistency of objects relationships. 82 | 83 | If we would have used `.unique` scope for both components then container would not reuse instances and we would have an infinite loop: 84 | 85 | - Each attempt to resolve `NetworkClientDelegate` will create new instance of `Interactor`. 86 | - It will resolve `NetworkClient` which will create new instance of `NetworkClientImp`. 87 | - It will try to resolve it's delegate property and that will create new instance of `Interactor` 88 | - … And so on and so on. 89 | 90 | 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: 91 | */ 92 | 93 | container.reset() 94 | 95 | container.register(.unique) { 96 | Interactor(networkClient: try container.resolve()) as NetworkClientDelegate 97 | } 98 | 99 | container.register { NetworkClientImp() as NetworkClient } 100 | .resolvingProperties { (container, client) -> () in 101 | client.delegate = try container.resolve() as NetworkClientDelegate 102 | } 103 | 104 | let invalidInteractor = try! container.resolve() as NetworkClientDelegate 105 | invalidInteractor.networkClient.delegate // that is not valid 106 | 107 | //: [Next: Shared Instances](@next) 108 | -------------------------------------------------------------------------------- /DipPlayground.playground/Pages/Containers Collaboration.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous: Type Forwarding](@previous) 2 | 3 | import Dip 4 | 5 | /*: 6 | ### Containers collaboration 7 | 8 | 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. 9 | */ 10 | 11 | protocol DataStore {} 12 | class CoreDataStore: DataStore {} 13 | class AddEventWireframe { 14 | var eventsListWireframe: EventsListWireframe? 15 | } 16 | class EventsListWireframe { 17 | var addEventWireframe: AddEventWireframe? 18 | let dataStore: DataStore 19 | init(dataStore: DataStore) { 20 | self.dataStore = dataStore 21 | } 22 | } 23 | 24 | 25 | let rootContainer = DependencyContainer() 26 | rootContainer.register(.singleton) { CoreDataStore() as DataStore } 27 | 28 | let eventsListModule = DependencyContainer() 29 | eventsListModule.register { EventsListWireframe(dataStore: $0) } 30 | .resolvingProperties { container, wireframe in 31 | wireframe.addEventWireframe = try container.resolve() 32 | } 33 | 34 | let addEventModule = DependencyContainer() 35 | addEventModule.register { AddEventWireframe() } 36 | 37 | eventsListModule.collaborate(with: addEventModule, rootContainer) 38 | 39 | var eventsListWireframe = try eventsListModule.resolve() as EventsListWireframe 40 | eventsListWireframe.dataStore 41 | eventsListWireframe.addEventWireframe 42 | 43 | /*: 44 | As you can see dependencies were resolved even though not all components were registered in the same container. 45 | It is even safe to make circular references between containers. This way you can resolve circular dependencies between components registered in different containers. 46 | */ 47 | 48 | eventsListModule.reset() 49 | addEventModule.reset() 50 | 51 | eventsListModule.register { EventsListWireframe(dataStore: $0) } 52 | .resolvingProperties { container, wireframe in 53 | wireframe.addEventWireframe = try container.resolve() 54 | } 55 | 56 | addEventModule.register { AddEventWireframe() } 57 | .resolvingProperties { container, wireframe in 58 | wireframe.eventsListWireframe = try container.resolve() 59 | } 60 | 61 | addEventModule.collaborate(with: eventsListModule) 62 | 63 | eventsListWireframe = try eventsListModule.resolve() as EventsListWireframe 64 | eventsListWireframe.addEventWireframe 65 | eventsListWireframe.addEventWireframe?.eventsListWireframe === eventsListWireframe 66 | 67 | /*: 68 | 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. 69 | */ 70 | 71 | //: [Next: Testing](@next) 72 | -------------------------------------------------------------------------------- /DipPlayground.playground/Pages/Creating container.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous: What is Dip?](@previous) 2 | 3 | import Dip 4 | 5 | /*: 6 | Dip has two base components: a _DependencyContainer_ and its _Definitions_. 7 | 8 | - _DependencyContainer_ is used to register _Definitions_ and to resolve them. 9 | - _Definitions_ describe how component should be created by the _DependencyContainer_. 10 | 11 | ### Creating the container 12 | 13 | You can create a container using a simple `init()`: 14 | */ 15 | 16 | var container = DependencyContainer() 17 | //register components here 18 | 19 | /*: 20 | or using a configuration block: 21 | */ 22 | 23 | container = DependencyContainer { container in 24 | //do not forget to use unowned reference if you will need 25 | //to reference container inside definition's factory 26 | unowned let container = container 27 | //register components here 28 | } 29 | 30 | /*: 31 | Both syntaxes are equivalent. The one using the configuration block is simply a convenience way to scope your components registrations in a nice looking way. 32 | 33 | ### When/where to create container? 34 | 35 | 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)). 36 | 37 | */ 38 | //: [Next: Registering Components](@next) 39 | -------------------------------------------------------------------------------- /DipPlayground.playground/Pages/Creating container.xcplaygroundpage/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /DipPlayground.playground/Pages/Registering components.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous: Creating a DependencyContainer](@previous) 2 | 3 | import Dip 4 | 5 | /*: 6 | 7 | ### Registering components 8 | 9 | You register a definition in a container using the `register` method: 10 | */ 11 | let container = DependencyContainer() 12 | container.register { ServiceImp1() as Service } 13 | 14 | /*: 15 | That code means that when you need a `Service`, you want to use instances of `ServiceImp1` class created with it's `init()` initializer. 16 | 17 | You can also register factories that accept runtime arguments: 18 | */ 19 | 20 | container.register { service in ClientImp1(service: service) as Client } 21 | 22 | /*: 23 | Dip supports up to six runtime arguments, but you can use as many as you want. For more details see ["Runtime arguments"](Runtime%20arguments). 24 | 25 | You can also use factory methods in definitions. This can be useful if you already have some factories but want to migrate to Dip. 26 | */ 27 | 28 | let factory = ServiceFactory() 29 | // factory.someService is a method with signature `() -> Service`, Cmd-Click to see definition 30 | container.register(factory: factory.someService) 31 | 32 | /*: 33 | Optionally you can associate definitions with Integer or String tags. This way you can register different implementations for the same protocol. 34 | You can use `DependencyContainer.Tag` enum, String or Integer literals, or instances of types that conform to `DependencyTagConvertible` protocol. 35 | */ 36 | 37 | container.register(tag: "tag") { ServiceImp1() as Service } 38 | container.register(tag: 0) { ServiceImp1() as Service } 39 | 40 | enum MyCustomTag: String, DependencyTagConvertible { 41 | case SomeTag 42 | } 43 | 44 | container.register(tag: MyCustomTag.SomeTag) { ServiceImp1() as Service } 45 | 46 | /*: 47 | We recommand you to use constants for the tags, to make the intent clear and avoid magic numbers and typos. 48 | 49 | You can remove all registered definitions or register and remove them one by one: 50 | */ 51 | 52 | let serviceDefinition = container.register { ServiceImp1() as Service } 53 | container 54 | container.remove(serviceDefinition) 55 | 56 | container.reset() 57 | 58 | //: [Next: Resolving Components](@next) 59 | -------------------------------------------------------------------------------- /DipPlayground.playground/Pages/Resolving components.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous: Registering Components](@previous) 2 | 3 | import Dip 4 | let container = DependencyContainer() 5 | container.register { ServiceImp1() as Service } 6 | 7 | /*: 8 | 9 | ### Resolving components 10 | 11 | You resolve previously registered definition using `resolve` method: 12 | */ 13 | 14 | var service = try! container.resolve() as Service 15 | 16 | /*: 17 | That code says that you want your `container` to give you an instance that was registered as implementation of `Service` protocol. 18 | 19 | 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: 20 | */ 21 | 22 | let otherService: Service = try! container.resolve() 23 | 24 | /*: 25 | 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. 26 | 27 | If you used a tag to register your component, you can use the same tag to resolve it. If there is no definition with such tag, the `container` will try to find a definition for the same type with no tag (`nil` tag), and resolve it instead, allowing you to provide default components in such cases. 28 | */ 29 | 30 | container.register(tag: "production") { ServiceImp1() as Service } 31 | container.register(tag: "test") { ServiceImp2() as Service } 32 | 33 | // Will give you a ServiceImp1 instance 34 | let productionService = try! container.resolve(tag: "production") as Service 35 | // Will give you a ServiceImp2 instance 36 | let testService = try! container.resolve(tag: "test") as Service 37 | // Will give you a ServiceImp1 because one was registered without a tag on line 4 38 | let defaultService = try! container.resolve() as Service 39 | 40 | /*: 41 | You can use runtime arguments to resolve components. Dip supports up to six arguments. For more details see ["Runtime arguments"](Runtime%20arguments). 42 | */ 43 | container.register { service in ClientImp1(service: service) as Client } 44 | let client = try! container.resolve(arguments: service) as Client 45 | 46 | //: [Next: Runtime Arguments](@next) 47 | -------------------------------------------------------------------------------- /DipPlayground.playground/Pages/Resolving components.xcplaygroundpage/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /DipPlayground.playground/Pages/Runtime arguments.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous: Resolving Components](@previous) 2 | 3 | import Dip 4 | 5 | let container = DependencyContainer() 6 | 7 | /*: 8 | 9 | ### Runtime arguments 10 | 11 | Dip lets you use runtime arguments to register and resolve your components. 12 | 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. 13 | */ 14 | 15 | container.register { (url: NSURL, port: Int) in ServiceImp4(name: "1", baseURL: url, port: port) as Service } 16 | container.register { (port: Int, url: NSURL) in ServiceImp4(name: "2", baseURL: url, port: port) as Service } 17 | container.register { (port: Int, url: NSURL?) in ServiceImp4(name: "3", baseURL: url!, port: port) as Service } 18 | 19 | let url: NSURL = NSURL(string: "http://example.com")! 20 | let service1 = try! container.resolve(arguments: url, 80) as Service 21 | let service2 = try! container.resolve(arguments: 80, url) as Service 22 | let service3 = try! container.resolve(arguments: 80, NSURL(string: "http://example.com")) as Service 23 | 24 | (service1 as! ServiceImp4).name 25 | (service2 as! ServiceImp4).name 26 | (service3 as! ServiceImp4).name 27 | 28 | /*: 29 | Note that all of the services were resolved using different factories. 30 | 31 | _Dip_ supports up to six runtime arguments. If that is not enougth you can extend `DependencyContainer` to accept more arguments. For example, here is how you can extend it to serve seven arguments. 32 | */ 33 | 34 | extension DependencyContainer { 35 | 36 | @discardableResult 37 | public func register(_ scope: ComponentScope = .shared, type: T.Type = T.self, tag: DependencyTagConvertible? = nil, factory: @escaping (A, B, C, D, E, F, G) throws -> T) -> Definition { 38 | return register(scope: scope, type: type, tag: tag, factory: factory, numberOfArguments: 7) { container, tag in 39 | 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)) 40 | } 41 | } 42 | 43 | public func resolve(tag: DependencyTagConvertible? = nil, _ arg1: A, _ arg2: B, _ arg3: C, _ arg4: D, _ arg5: E, _ arg6: F, _ arg7: G) throws -> T { 44 | return try resolve(tag: tag) { factory in try factory(arg1, arg2, arg3, arg4, arg5, arg6, arg7) } 45 | } 46 | } 47 | 48 | /*: 49 | However, if you find yourself thinking about adding more runtime arguments, stop and think about your design instead. Having too many dependencies could be a sign of some problem in your architecture, so we strongly suggest that you refrain from doing so; six runtime arguments is already a lot. 50 | */ 51 | 52 | //: [Next: Scopes](@next) 53 | -------------------------------------------------------------------------------- /DipPlayground.playground/Pages/Scopes.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous: Runtime Arguments](@previous) 2 | 3 | import Dip 4 | 5 | let container = DependencyContainer() 6 | 7 | /*: 8 | 9 | ### Scopes 10 | 11 | Dip supports three different scopes of objects: _Unique_, _Shared_ and _Singleton_. 12 | 13 | * The `Unique` scope will make the `DependencyContainer` resolve your type as __a new instance every time__ you call `resolve`. This is the default scope. 14 | * 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). 15 | * 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. 16 | * 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. 17 | * 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. 18 | 19 | The `Unique` scope is the default. To set a scope you pass it as an argument to `register` method. 20 | */ 21 | 22 | container.register { ServiceImp1() as Service } 23 | container.register(.unique, tag: "prototype") { ServiceImp1() as Service } 24 | container.register(.shared, tag: "object graph") { ServiceImp2() as Service } 25 | container.register(.singleton, tag: "shared instance") { ServiceImp3() as Service } 26 | 27 | let service = try! container.resolve() as Service 28 | let anotherService = try! container.resolve() as Service 29 | // They are different instances as the scope defaults to .unique 30 | service as! ServiceImp1 === anotherService as! ServiceImp1 // false 31 | 32 | let prototypeService = try! container.resolve(tag: "prototype") as Service 33 | let anotherUniqueService = try! container.resolve(tag: "prototype") as Service 34 | // They are different instances: 35 | prototypeService === anotherUniqueService // false 36 | 37 | let graphService = try! container.resolve(tag: "object graph") as Service 38 | let anotherGraphService = try! container.resolve(tag: "object graph") as Service 39 | // still different instances — the Shared scope only keep instances during one (recursive) resolution call, 40 | // so the two calls on the two lines above are different calls and use different instances 41 | graphService === anotherGraphService // false 42 | 43 | let sharedService = try! container.resolve(tag: "shared instance") as Service 44 | let sameSharedService = try! container.resolve(tag: "shared instance") as Service 45 | // same instances, the singleton scope keep and reuse instances during the lifetime of the container 46 | sharedService as! ServiceImp3 === sameSharedService as! ServiceImp3 47 | 48 | /*: 49 | ### Bootstrapping 50 | 51 | You can use `bootstrap()` method to fix your container setup and initialise components registered with `EagerSingleton` scope. 52 | 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:)`. 53 | */ 54 | 55 | var resolvedEagerSingleton = false 56 | let definition = container.register(.eagerSingleton, tag: "eager shared instance") { ServiceImp1() as Service } 57 | .resolvingProperties { _ in resolvedEagerSingleton = true } 58 | 59 | try! container.bootstrap() 60 | resolvedEagerSingleton 61 | 62 | let eagerSharedService = try! container.resolve(tag: "eager shared instance") as Service 63 | 64 | container.remove(definition) 65 | 66 | //: [Next: Circular Dependencies](@next) 67 | 68 | -------------------------------------------------------------------------------- /DipPlayground.playground/Pages/Shared Instances.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous: Circular Dependencies](@previous) 2 | 3 | import Dip 4 | import UIKit 5 | 6 | /*: 7 | ### Shared Instances 8 | 9 | The Singleton pattern is probably the most debatable and abused pattern in Cocoa development (and probably in programming in general). It's probably the first thing that you will hear from a candidate developer on interview when you ask about Cocoa patterns (the other one will be a delegate). 10 | 11 | The problem with singleton is not that it's a worst pattern. The problem is that developers use it to solve problems that do not require it at all. Another problem is that it's very easy to be tempted by this pattern cause it's very easy to implement and use - import file and call `sharedInstance`. But that leads to all kinds of problems: 12 | 13 | - First - singleton is a shared mutable state. And the worst thing is that it's a _mutable_ state. 14 | - Second - singleton tigthly couple components of your system. 15 | - Third - it limits your code flexibility. 16 | 17 | Dip supports singletons, but it reduces cost of using them. Their singleton nature is managed by the _Container_ and defined only by the _Definitions_ that you register, not by concrete implementations of your classes. 18 | 19 | - No need for calls to `sharedInstance` in your code anymore. Instead you get the instance from the _Container_ by resolving a protocol. 20 | - You can easyly change concrete implementations without the rest of your system even notice that something changed. 21 | - 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. 22 | 23 | Those features you got when using Dip limits tight coupling in your code and gives you back your code flexibility. 24 | 25 | Probably the most common example is using a singleton in the network layer or "API client". 26 | */ 27 | 28 | class ApiClientSingleton { 29 | static let sharedInstance = ApiClientSingleton() 30 | private init() {} 31 | // Typically a method that makes a GET request on your API 32 | func get(path: String, completion:()->()) {} 33 | } 34 | 35 | class MyViewControllerWithSingleton: UIViewController { 36 | override func viewDidAppear(_ amimated: Bool) { 37 | super.viewDidAppear(amimated) 38 | ApiClientSingleton.sharedInstance.get("/users") { /* refresh your UI */ } 39 | } 40 | } 41 | 42 | /*: 43 | Sure, this is very easy to code indeed. And nothing bad so far. 44 | 45 | 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? 46 | 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. 47 | 48 | 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. 49 | 50 | Instead, inject API client in view controller with property injection or constructor injection. 51 | */ 52 | 53 | protocol ApiClientProtocol { 54 | func get(path: String, completion:()->()) 55 | } 56 | 57 | class ApiClient: ApiClientProtocol { 58 | 59 | private struct ApiScheduler { 60 | /* … */ 61 | } 62 | 63 | private let scheduler = ApiScheduler() 64 | 65 | init(){} 66 | 67 | func get(path: String, completion:()->()) {} 68 | } 69 | 70 | class MyViewController: UIViewController { 71 | var apiClient: ApiClientProtocol! 72 | 73 | override func viewDidAppear(amimated: Bool) { 74 | super.viewDidAppear(_ amimated) 75 | apiClient.get("path") {} 76 | } 77 | 78 | convenience init(apiClient: ApiClientProtocol) { 79 | self.apiClient = apiClient 80 | self.init() 81 | } 82 | 83 | init() { 84 | super.init(nibName: nil, bundle: nil) 85 | } 86 | 87 | required init?(coder aDecoder: NSCoder) { 88 | fatalError("init(coder:) has not been implemented") 89 | } 90 | } 91 | 92 | //inject with constructor 93 | var viewController = MyViewController(apiClient: ApiClient()) 94 | //or with property 95 | viewController.apiClient = ApiClient() 96 | 97 | /*: 98 | With Dip this code can look like this: 99 | */ 100 | 101 | let container = DependencyContainer() 102 | container.register { ApiClient() as ApiClientProtocol } 103 | 104 | //inject with constructor 105 | viewController = try MyViewController(apiClient: container.resolve()) 106 | //or with property 107 | viewController.apiClient = try container.resolve() 108 | 109 | /*: 110 | 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. 111 | 112 | 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. 113 | 114 | 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. 115 | 116 | */ 117 | 118 | //: [Next: Auto-wiring](@next) 119 | 120 | 121 | -------------------------------------------------------------------------------- /DipPlayground.playground/Pages/Testing.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous: Containers Collaboration](@previous) 2 | 3 | //import XCTest 4 | import Dip 5 | 6 | let container = DependencyContainer() 7 | 8 | /*: 9 | 10 | ### Testing 11 | 12 | If you use Dependency Injection patterns like contructor and property injection it will be much easier 13 | to unit test your components. When it comes to integration tests you may want to mock some real services. 14 | In these tests you can register mock implementation in the container and it will be injected instead of the real implementation. 15 | 16 | Dip is convenient to use for testing. Here is s simple example of how you can write tests with Dip. 17 | 18 | > 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. 19 | You can learn more about testing based on state verification vs behavior verification [here](http://martinfowler.com/articles/mocksArentStubs.html). 20 | 21 | > XCTest is not supported by playgrounds so to be able to compile this page we commented out XCTest specific code. 22 | */ 23 | 24 | protocol ServiceType { 25 | func doSomething() 26 | } 27 | 28 | class RealService: ServiceType { 29 | func doSomething() { 30 | //do something real 31 | } 32 | } 33 | 34 | class Client { 35 | var service: ServiceType! 36 | 37 | func callService() { 38 | service.doSomething() 39 | } 40 | } 41 | 42 | /*: 43 | Instead of the real `Service` implementation, provide a _fake_ implementation with test hooks that you need: 44 | */ 45 | 46 | class FakeService: ServiceType { 47 | var doSomethingCalled = false 48 | 49 | func doSomething() { 50 | doSomethingCalled = true 51 | } 52 | 53 | init() {} 54 | } 55 | 56 | /*: 57 | Somewhere in your production code you register real implementations: 58 | */ 59 | func configure(container: DependencyContainer) { 60 | container.register { RealService() as ServiceType } 61 | container.register { Client() } 62 | .resolvingProperties { container, client in 63 | client.service = try container.resolve() 64 | } 65 | } 66 | 67 | class MyTests/*: XCTestCase*/ { 68 | 69 | /*override*/ func setUp() { 70 | //super.setUp() 71 | 72 | /*: 73 | Reset container configuration to normal state: 74 | */ 75 | container.reset() 76 | configure(container: container) 77 | } 78 | 79 | func testThatDoSomethingIsCalled() { 80 | /*: 81 | Register fake implementation as `Service`: 82 | */ 83 | container.register { FakeService() as ServiceType } 84 | 85 | let sut = try! container.resolve() as Client 86 | sut.callService() 87 | 88 | /*: 89 | And finally you test it was called: 90 | */ 91 | let service = sut.service as! FakeService 92 | //XCTAssertTrue(service.doSomethingCalled) 93 | } 94 | } 95 | 96 | /*: 97 | You can also validate your container configuration. You can do that either in a separate test suit or when runnging application in `DEBUG` mode. 98 | 99 | 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. 100 | */ 101 | container.register { (url: NSURL, port: Int) in ServiceImp4(name: "1", baseURL: url, port: port) as Service } 102 | try! container.validate((NSURL(string: "https://github.com/AliSoftware/Dip")!, 80)) 103 | 104 | do { 105 | try container.validate() 106 | } 107 | catch { 108 | print(error) 109 | } 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /DipPlayground.playground/Pages/Type Forwarding.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous: Auto-injection](@previous) 2 | 3 | import Foundation 4 | import Dip 5 | 6 | let container = DependencyContainer() 7 | 8 | /*: 9 | ### Type Forwarding 10 | 11 | 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. 12 | 13 | Let's look at example of VIPER architecture: 14 | */ 15 | 16 | extension ListPresenter: ListInteractorOutput, ListModuleInterface, AddModuleDelegate {} 17 | extension ListInteractor: ListInteractorInput {} 18 | extension AddPresenter: AddModuleInterface {} 19 | 20 | /*: 21 | 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. 22 | 23 | - `ListInteractor` referenced by `ListPresenter` in its `listInteractor` property (via `ListInteractorInput` protocol) should hold a backward reference to the same presenter in its `output` property 24 | - `ListWireframe` referenced by `ListPresenter` should also hold a backward reference to the same presenter in its `listPresenter` property 25 | - `AddWireframe` should hold a reference to `AddPresenter` that should hold reference to the same `ListPresenter` in its `addModuleDelegate` property (via `AddModuleDelegate` protocol). 26 | 27 | We can achieve this result by explicitly rosolving concrete types: 28 | */ 29 | 30 | container.register { ListWireframe(addWireFrame: $0, listPresenter: $1) } 31 | container.register { AddWireframe(addPresenter: $0) } 32 | 33 | var listInteractorDefinition = container.register { ListInteractor() } 34 | .resolvingProperties { container, interactor in 35 | interactor.output = try container.resolve() as ListPresenter 36 | } 37 | 38 | var listPresenterDefinition = container.register { ListPresenter() } 39 | .resolvingProperties { container, presenter in 40 | presenter.listInteractor = try container.resolve() as ListInteractor 41 | presenter.listWireframe = try container.resolve() 42 | } 43 | 44 | var addPresenterDefinition = container.register { AddPresenter() } 45 | .resolvingProperties { container, presenter in 46 | presenter.addModuleDelegate = try container.resolve() as ListPresenter 47 | } 48 | 49 | var addPresenter = try! container.resolve() as AddPresenter 50 | var listPresenter = addPresenter.addModuleDelegate as! ListPresenter 51 | var listInteractor = listPresenter.listInteractor as! ListInteractor 52 | listInteractor.output === listPresenter 53 | var listWireframe = listPresenter.listWireframe 54 | listWireframe?.listPresenter === listPresenter 55 | 56 | /*: 57 | 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: 58 | */ 59 | 60 | listInteractorDefinition = container.register { ListInteractor() } 61 | .resolvingProperties { container, interactor in 62 | interactor.output = try container.resolve() 63 | } 64 | 65 | listPresenterDefinition = container.register { ListPresenter() } 66 | .resolvingProperties { container, presenter in 67 | presenter.listInteractor = try container.resolve() 68 | presenter.listWireframe = try container.resolve() 69 | } 70 | 71 | addPresenterDefinition = container.register { AddPresenter() } 72 | .resolvingProperties { container, presenter in 73 | presenter.addModuleDelegate = try container.resolve() 74 | } 75 | 76 | /*: 77 | And now we register definitions for type-forwarding: 78 | */ 79 | listInteractorDefinition 80 | .implements(ListInteractorInput.self) 81 | listPresenterDefinition 82 | .implements(ListInteractorOutput.self) 83 | .implements(ListModuleInterface.self) 84 | .implements(AddModuleDelegate.self) 85 | 86 | addPresenter = try! container.resolve() as AddPresenter 87 | listPresenter = addPresenter.addModuleDelegate as! ListPresenter 88 | listInteractor = listPresenter.listInteractor as! ListInteractor 89 | listInteractor.output === listPresenter 90 | listWireframe = listPresenter.listWireframe 91 | listWireframe?.listPresenter === listPresenter 92 | 93 | /*: 94 | 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. 95 | 96 | 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. 97 | 98 | You can also provide `resolveDependencies` block for forwarded definition. First container will call `resolveDependencies` block of the source definition, and then of forwarded definition: 99 | */ 100 | listInteractorDefinition 101 | .resolvingProperties { container, interactor in 102 | print("resolved ListInteractor") 103 | } 104 | let _ = container.register(listInteractorDefinition, type: ListInteractorInput.self) 105 | .resolvingProperties { container, interactor in 106 | print("resolved ListInteractorInput") 107 | } 108 | addPresenter = try! container.resolve() as AddPresenter 109 | 110 | //: [Next: Containers Collaboration](@next) 111 | -------------------------------------------------------------------------------- /DipPlayground.playground/Pages/What is Dip?.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | __Note__: _This playground needs to be open as part of the `Dip.xcworkspace` so it can import the Dip framework / module and demonstrate its usage. (The playground won't work properly if you open it on its own)._ 3 | 4 | _You might also need to ask Xcode to build the Dip framework first (Command-B) before it can find and import it in this playground._ 5 | */ 6 | 7 | /*: 8 | ## What is Dip? 9 | 10 | _Dip_ is a lightweight Swift implementation of [IoC container](https://en.wikipedia.org/wiki/Inversion_of_control). 11 | 12 | If you follow [Protocol-Oriented programming](https://developer.apple.com/videos/play/wwdc2015-408/) or [SOLID principles](http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod) then instead of concrete classes you should use protocols to define dependencies between components of your system. I.e. if you need to access some network API, you should use instances of an `APIClient` protocol instead of instances of a concrete class `APIClientImp`. 13 | 14 | [Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection) is a good tool to leverage Protocol-Oriented or SOLID design. Using this principle, you move the point where you create concrete instances _from inside objects_ that use them, _to higher levels_ of your system. **Now your objects do not depend on concrete implementations of their dependencies**, they depend **only on their public interfaces**, defined by protocols that they implement. That gives you all sorts of advantages from **easier testability** to **greater flexibility** of your system. 15 | 16 | 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. 17 | 18 | The following pages in this Playground demonstrates how to use _Dip_ to adopt all those concepts in practice. 19 | 20 | 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. 21 | 22 | */ 23 | //: [Next: Creating a DependencyContainer](@next) 24 | 25 | -------------------------------------------------------------------------------- /DipPlayground.playground/Pages/What is Dip?.xcplaygroundpage/Models.remap: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /DipPlayground.playground/Pages/What is Dip?.xcplaygroundpage/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /DipPlayground.playground/Sources/Models.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol Service: AnyObject {} 4 | 5 | public class ServiceImp1: Service { 6 | public init() {} 7 | } 8 | public class ServiceImp2: Service { 9 | public init() {} 10 | } 11 | public class ServiceImp3: Service { 12 | public init() {} 13 | } 14 | 15 | public class ServiceImp4: Service { 16 | 17 | public let name: String 18 | 19 | public init(name: String, baseURL: NSURL, port: Int) { 20 | self.name = name 21 | } 22 | 23 | } 24 | 25 | public protocol Client: AnyObject { 26 | var service: Service {get} 27 | init(service: Service) 28 | } 29 | public class ClientImp1: Client { 30 | public var service: Service 31 | public required init(service: Service) { 32 | self.service = service 33 | } 34 | } 35 | 36 | public class ClientImp2: Client { 37 | public var service: Service 38 | public required init(service: Service) { 39 | self.service = service 40 | } 41 | } 42 | 43 | public class ServiceFactory { 44 | public init() {} 45 | 46 | public func someService() -> Service { 47 | return ServiceImp1() 48 | } 49 | } 50 | 51 | public class ClientServiceImp: Service { 52 | public weak var client: Client? 53 | public init() {} 54 | } 55 | 56 | public protocol Logger {} 57 | public protocol Tracker {} 58 | public protocol DataProvider {} 59 | public protocol Router {} 60 | 61 | public class LoggerImp: Logger { 62 | public init() {} 63 | } 64 | 65 | public class TrackerImp: Tracker { 66 | public init() {} 67 | } 68 | 69 | public class RouterImp: Router { 70 | public init() {} 71 | } 72 | 73 | public class DataProviderImp: DataProvider { 74 | public init() {} 75 | } 76 | 77 | public protocol ListInteractorOutput: AnyObject {} 78 | public protocol ListModuleInterface: AnyObject {} 79 | public protocol ListInteractorInput: AnyObject {} 80 | public class ListPresenter: NSObject { 81 | public var listInteractor : ListInteractorInput? 82 | public var listWireframe : ListWireframe? 83 | public override init() {} 84 | } 85 | public class ListInteractor: NSObject { 86 | public var output : ListInteractorOutput? 87 | public override init() {} 88 | } 89 | 90 | public class ListWireframe : NSObject { 91 | public let addWireframe: AddWireframe 92 | public let listPresenter: ListPresenter 93 | public init(addWireFrame: AddWireframe, listPresenter: ListPresenter) { 94 | self.addWireframe = addWireFrame 95 | self.listPresenter = listPresenter 96 | } 97 | } 98 | 99 | public protocol AddModuleDelegate: AnyObject {} 100 | public protocol AddModuleInterface: AnyObject {} 101 | public class AddWireframe: NSObject { 102 | let addPresenter : AddPresenter 103 | public init(addPresenter: AddPresenter) { 104 | self.addPresenter = addPresenter 105 | } 106 | } 107 | public class AddPresenter: NSObject { 108 | public var addModuleDelegate : AddModuleDelegate? 109 | public override init() {} 110 | } 111 | -------------------------------------------------------------------------------- /DipPlayground.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /DipPlayground.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "cocoapods", '=1.4.0' 4 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.0) 5 | activesupport (4.2.11) 6 | i18n (~> 0.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.3, >= 0.3.4) 9 | tzinfo (~> 1.1) 10 | atomos (0.1.3) 11 | claide (1.0.2) 12 | cocoapods (1.4.0) 13 | activesupport (>= 4.0.2, < 5) 14 | claide (>= 1.0.2, < 2.0) 15 | cocoapods-core (= 1.4.0) 16 | cocoapods-deintegrate (>= 1.0.2, < 2.0) 17 | cocoapods-downloader (>= 1.1.3, < 2.0) 18 | cocoapods-plugins (>= 1.0.0, < 2.0) 19 | cocoapods-search (>= 1.0.0, < 2.0) 20 | cocoapods-stats (>= 1.0.0, < 2.0) 21 | cocoapods-trunk (>= 1.3.0, < 2.0) 22 | cocoapods-try (>= 1.1.0, < 2.0) 23 | colored2 (~> 3.1) 24 | escape (~> 0.0.4) 25 | fourflusher (~> 2.0.1) 26 | gh_inspector (~> 1.0) 27 | molinillo (~> 0.6.4) 28 | nap (~> 1.0) 29 | ruby-macho (~> 1.1) 30 | xcodeproj (>= 1.5.4, < 2.0) 31 | cocoapods-core (1.4.0) 32 | activesupport (>= 4.0.2, < 6) 33 | fuzzy_match (~> 2.0.4) 34 | nap (~> 1.0) 35 | cocoapods-deintegrate (1.0.2) 36 | cocoapods-downloader (1.2.2) 37 | cocoapods-plugins (1.0.0) 38 | nap 39 | cocoapods-search (1.0.0) 40 | cocoapods-stats (1.0.0) 41 | cocoapods-trunk (1.3.1) 42 | nap (>= 0.8, < 2.0) 43 | netrc (~> 0.11) 44 | cocoapods-try (1.1.0) 45 | colored2 (3.1.2) 46 | concurrent-ruby (1.1.4) 47 | escape (0.0.4) 48 | fourflusher (2.0.1) 49 | fuzzy_match (2.0.4) 50 | gh_inspector (1.1.3) 51 | i18n (0.9.5) 52 | concurrent-ruby (~> 1.0) 53 | minitest (5.11.3) 54 | molinillo (0.6.6) 55 | nanaimo (0.2.6) 56 | nap (1.1.0) 57 | netrc (0.11.0) 58 | ruby-macho (1.3.1) 59 | thread_safe (0.3.6) 60 | tzinfo (1.2.5) 61 | thread_safe (~> 0.1) 62 | xcodeproj (1.7.0) 63 | CFPropertyList (>= 2.3.3, < 4.0) 64 | atomos (~> 0.1.3) 65 | claide (>= 1.0.2, < 2.0) 66 | colored2 (~> 3.1) 67 | nanaimo (~> 0.2.6) 68 | 69 | PLATFORMS 70 | ruby 71 | 72 | DEPENDENCIES 73 | cocoapods (= 1.4.0) 74 | 75 | BUNDLED WITH 76 | 1.16.5 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Olivier Halligon 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import DipTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += DipTests.__allTests() 7 | 8 | XCTMain(tests) 9 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "Dip", 7 | products: [ 8 | .library(name: "Dip", targets: ["Dip"]), 9 | ], 10 | targets: [ 11 | .target(name: "Dip", dependencies: [], path: "Sources"), 12 | .testTarget(name: "DipTests", dependencies: ["Dip"], path: "Tests"), 13 | ] 14 | ) 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dip 2 | 3 | [![CI Status](https://travis-ci.org/AliSoftware/Dip.svg?branch=develop)](https://travis-ci.org/AliSoftware/Dip) 4 | [![Version](https://img.shields.io/cocoapods/v/Dip.svg?style=flat)](http://cocoapods.org/pods/Dip) 5 | [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 6 | [![License](https://img.shields.io/cocoapods/l/Dip.svg?style=flat)](http://cocoapods.org/pods/Dip) 7 | [![Platform](https://img.shields.io/cocoapods/p/Dip.svg?style=flat)](http://cocoapods.org/pods/Dip) 8 | [![Swift Version](https://img.shields.io/badge/Swift-4.0--4.2-F16D39.svg?style=flat)](https://developer.apple.com/swift) 9 | [![Swift Version](https://img.shields.io/badge/Linux-4.0--4.2-4BC51D.svg?style=flat)](https://developer.apple.com/swift) 10 | 11 | ![Animated Dipping GIF](cinnamon-pretzels-caramel-dipping.gif) 12 | _Photo courtesy of [www.kevinandamanda.com](http://www.kevinandamanda.com/recipes/appetizer/homemade-soft-cinnamon-sugar-pretzel-bites-with-salted-caramel-dipping-sauce.html)_ 13 | 14 | ## Introduction 15 | 16 | `Dip` is a simple **Dependency Injection Container**. 17 | 18 | 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. 19 | 20 | * 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 }`. 21 | * Then you can call `container.resolve() as Service` to **resolve an instance of _protocol_ or _type_** using that `DependencyContainer`. 22 | * You can easily use Dip along with **Storyboards and Nibs** . There is also a **[code generator](https://github.com/ilyapuchka/dipgen)** that can help to simplify registering new components. 23 | 24 |
25 | Basic usage 26 | 27 | ```swift 28 | import Dip 29 | 30 | @UIApplicationMain 31 | class AppDelegate: UIResponder, UIApplicationDelegate { 32 | 33 | // Create the container 34 | private let container = DependencyContainer { container in 35 | 36 | // Register some factory. ServiceImp here implements protocol Service 37 | container.register { ServiceImp() as Service } 38 | } 39 | 40 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 41 | 42 | // Resolve a concrete instance. Container will instantiate new instance of ServiceImp 43 | let service = try! container.resolve() as Service 44 | 45 | ... 46 | } 47 | } 48 | 49 | ``` 50 | 51 |
52 | 53 |
54 | More sophisticated example 55 | 56 | ```swift 57 | import Dip 58 | 59 | class AppDelegate: UIResponder, UIApplicationDelegate { 60 | private let container = DependencyContainer.configure() 61 | ... 62 | } 63 | 64 | //CompositionRoot.swift 65 | import Dip 66 | import DipUI 67 | 68 | extension DependencyContainer { 69 | 70 | static func configure() -> DependencyContainer { 71 | return DependencyContainer { container in 72 | unowned let container = container 73 | DependencyContainer.uiContainers = [container] 74 | 75 | container.register(tag: "ViewController") { ViewController() } 76 | .resolvingProperties { container, controller in 77 | controller.animationsFactory = try container.resolve() as AnimatonsFactory 78 | } 79 | 80 | container.register { AuthFormBehaviourImp(apiClient: $0) as AuthFormBehaviour } 81 | container.register { container as AnimationsFactory } 82 | container.register { view in ShakeAnimationImp(view: view) as ShakeAnimation } 83 | container.register { APIClient(baseURL: NSURL(string: "http://localhost:2368")!) as ApiClient } 84 | } 85 | } 86 | 87 | } 88 | 89 | extension DependencyContainer: AnimationsFactory { 90 | func shakeAnimation(view: UIView) -> ShakeAnimation { 91 | return try! self.resolve(withArguments: view) 92 | } 93 | } 94 | 95 | extension ViewController: StoryboardInstantiatable {} 96 | 97 | //ViewController.swift 98 | 99 | class ViewController { 100 | var animationsFactory: AnimationsFactory? 101 | 102 | private let _formBehaviour = Injected() 103 | 104 | var formBehaviour: AuthFormBehaviour? { 105 | return _formBehaviour.value 106 | } 107 | ... 108 | } 109 | 110 | ``` 111 | 112 |
113 | 114 | ## Documentation & Usage Examples 115 | 116 | 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`. 117 | 118 | > 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. 119 | 120 | You can find bunch of usage examples and usfull tips in a [wiki](../../wiki). 121 | 122 | 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. 123 | 124 | There are also several blog posts that describe how to use Dip and some of its implementation details: 125 | 126 | - [IoC container in Swift](http://ilya.puchka.me/ioc-container-in-swift/) 127 | - [IoC container in Swift. Circular dependencies and auto-injection](http://ilya.puchka.me/ioc-container-in-swift-circular-dependencies-and-auto-injection/) 128 | - [Dependency injection with Dip](http://ilya.puchka.me/dependency-injecinjection-with-dip/) 129 | 130 | File an issue if you have any question. Pull requests are warmly welcome too. 131 | 132 | 133 | ## Features 134 | 135 | - **[Scopes](../../wiki/scopes)**. Dip supports 5 different scopes (or life cycle strategies): _Unique_, _Shared_, _Singleton_, _EagerSingleton_, _WeakSingleton_; 136 | - **[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. 137 | - **[Resolving optionals](../../wiki/resolving-optionals)**. Dip is able to resolve constructor or property dependencies defined as optionals. 138 | - **[Type forwarding](../../wiki/type-forwarding)**. You can register the same factory to resolve different types implemeted by a single class. 139 | - **[Circular dependencies](../../wiki/circular-dependencies)**. Dip will be able to resolve circular dependencies if you will follow some simple rules; 140 | - **[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; 141 | - **[Named definitions](../../wiki/named-definitions)**. You can register different factories for the same protocol or type by registering them with [tags](); 142 | - **[Runtime arguments](../../wiki/runtime-arguments)**. You can register factories that accept up to 6 runtime arguments (and extend it if you need); 143 | - **[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. 144 | - **Weakly typed components**. Dip can resolve "weak" types when they are unknown at compile time. 145 | - **Thread safety**. Registering and resolving components is thread safe; 146 | - **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; 147 | 148 | 149 | ## Installation 150 | 151 | You can install Dip using your favorite dependency manager: 152 | 153 |
154 | CocoaPods 155 | 156 | `pod "Dip"` 157 | 158 |
159 | 160 |
161 | Carthage 162 | 163 | ``` 164 | github "AliSoftware/Dip" 165 | ``` 166 | 167 | To build for Swift 2.3 run Carthage with `--toolchain com.apple.dt.toolchain.Swift_2_3` option. 168 | 169 |
170 | 171 |
172 | Swift Package Manager 173 | 174 | ```swift 175 | .Package(url: "https://github.com/AliSoftware/Dip", majorVersion: 5, minor: 0) 176 | ``` 177 | 178 |
179 | 180 | ## Running tests 181 | 182 | 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` 183 | 184 | ## Credits 185 | 186 | This library has been created by [**Olivier Halligon**](olivier@halligon.net) and is maintained by [**Ilya Puchka**](https://twitter.com/ilyapuchka). 187 | 188 | **Dip** is available under the **MIT license**. See the `LICENSE` file for more info. 189 | 190 | The animated GIF at the top of this `README.md` is from [this recipe](http://www.kevinandamanda.com/recipes/appetizer/homemade-soft-cinnamon-sugar-pretzel-bites-with-salted-caramel-dipping-sauce.html) on the yummy blog of [Kevin & Amanda](http://www.kevinandamanda.com/recipes/). Go try the recipe! 191 | 192 | The image used as the SampleApp LaunchScreen and Icon is from [Matthew Hine](https://commons.wikimedia.org/wiki/File:Chocolate_con_churros_-_San_Ginés,_Madrid.jpg) and is under _CC-by-2.0_. 193 | -------------------------------------------------------------------------------- /SampleApp/DipSampleApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SampleApp/DipSampleApp.xcodeproj/xcshareddata/xcschemes/DipSampleApp.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 48 | 54 | 55 | 56 | 57 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 79 | 81 | 87 | 88 | 89 | 90 | 91 | 92 | 98 | 100 | 106 | 107 | 108 | 109 | 111 | 112 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Dip 4 | // 5 | // Created by Olivier Halligon on 04/10/2015. 6 | // Copyright © 2015 AliSoftware. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Dip 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | private let container = DependencyContainer() 18 | 19 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool { 20 | // Override point for customization after application launch. 21 | 22 | //This is a composition root where container is configured and all dependencies are resolved 23 | configure(container: container) 24 | 25 | let personProvider = try! container.resolve() as PersonProviderAPI 26 | let starshipProvider = try! container.resolve() as StarshipProviderAPI 27 | 28 | if let tabBarVC = self.window?.rootViewController as? UITabBarController, 29 | let vcs = tabBarVC.viewControllers as? [UINavigationController] { 30 | if let personListVC = vcs[0].topViewController as? PersonListViewController { 31 | personListVC.personProvider = personProvider 32 | personListVC.starshipProvider = starshipProvider 33 | personListVC.loadFirstPage() 34 | } 35 | if let starshipListVC = vcs[1].topViewController as? StarshipListViewController { 36 | starshipListVC.starshipProvider = starshipProvider 37 | starshipListVC.personProvider = personProvider 38 | starshipListVC.loadFirstPage() 39 | } 40 | } 41 | 42 | return true 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "60x60", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-120.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "idiom" : "iphone", 41 | "size" : "60x60", 42 | "scale" : "3x" 43 | }, 44 | { 45 | "idiom" : "ipad", 46 | "size" : "20x20", 47 | "scale" : "1x" 48 | }, 49 | { 50 | "idiom" : "ipad", 51 | "size" : "20x20", 52 | "scale" : "2x" 53 | }, 54 | { 55 | "idiom" : "ipad", 56 | "size" : "29x29", 57 | "scale" : "1x" 58 | }, 59 | { 60 | "idiom" : "ipad", 61 | "size" : "29x29", 62 | "scale" : "2x" 63 | }, 64 | { 65 | "idiom" : "ipad", 66 | "size" : "40x40", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "idiom" : "ipad", 71 | "size" : "40x40", 72 | "scale" : "2x" 73 | }, 74 | { 75 | "size" : "76x76", 76 | "idiom" : "ipad", 77 | "filename" : "Icon-76.png", 78 | "scale" : "1x" 79 | }, 80 | { 81 | "size" : "76x76", 82 | "idiom" : "ipad", 83 | "filename" : "Icon-152.png", 84 | "scale" : "2x" 85 | }, 86 | { 87 | "idiom" : "ipad", 88 | "size" : "83.5x83.5", 89 | "scale" : "2x" 90 | }, 91 | { 92 | "idiom" : "ios-marketing", 93 | "size" : "1024x1024", 94 | "scale" : "1x" 95 | } 96 | ], 97 | "info" : { 98 | "version" : 1, 99 | "author" : "xcode" 100 | } 101 | } -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliSoftware/Dip/7ec008a5c09520b67f07669ad5a753d757f05bbe/SampleApp/DipSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-120.png -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliSoftware/Dip/7ec008a5c09520b67f07669ad5a753d757f05bbe/SampleApp/DipSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-152.png -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliSoftware/Dip/7ec008a5c09520b67f07669ad5a753d757f05bbe/SampleApp/DipSampleApp/Assets.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Assets.xcassets/Churros.imageset/Churros.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliSoftware/Dip/7ec008a5c09520b67f07669ad5a753d757f05bbe/SampleApp/DipSampleApp/Assets.xcassets/Churros.imageset/Churros.png -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Assets.xcassets/Churros.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Churros.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Assets.xcassets/female.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "female.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "female@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Assets.xcassets/female.imageset/female.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliSoftware/Dip/7ec008a5c09520b67f07669ad5a753d757f05bbe/SampleApp/DipSampleApp/Assets.xcassets/female.imageset/female.png -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Assets.xcassets/female.imageset/female@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliSoftware/Dip/7ec008a5c09520b67f07669ad5a753d757f05bbe/SampleApp/DipSampleApp/Assets.xcassets/female.imageset/female@2x.png -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Assets.xcassets/male.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "male.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "male@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Assets.xcassets/male.imageset/male.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliSoftware/Dip/7ec008a5c09520b67f07669ad5a753d757f05bbe/SampleApp/DipSampleApp/Assets.xcassets/male.imageset/male.png -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Assets.xcassets/male.imageset/male@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliSoftware/Dip/7ec008a5c09520b67f07669ad5a753d757f05bbe/SampleApp/DipSampleApp/Assets.xcassets/male.imageset/male@2x.png -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Assets.xcassets/person.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "person@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Assets.xcassets/person.imageset/person@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliSoftware/Dip/7ec008a5c09520b67f07669ad5a753d757f05bbe/SampleApp/DipSampleApp/Assets.xcassets/person.imageset/person@2x.png -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Assets.xcassets/spaceship.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "spaceship@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Assets.xcassets/spaceship.imageset/spaceship@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliSoftware/Dip/7ec008a5c09520b67f07669ad5a753d757f05bbe/SampleApp/DipSampleApp/Assets.xcassets/spaceship.imageset/spaceship@2x.png -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Cells/BaseCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseCell.swift 3 | // Dip 4 | // 5 | // Created by Olivier Halligon on 10/09/2015. 6 | // Copyright © 2015 AliSoftware. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol BaseCell { 12 | static var identifier: String { get } 13 | static var nib: UINib? { get } 14 | 15 | static func register(_ tableView: UITableView) 16 | static func dequeueFromTableView(_ tableView: UITableView, forIndexPath indexPath: IndexPath) -> Self 17 | } 18 | 19 | extension BaseCell where Self : UITableViewCell { 20 | static var identifier: String { 21 | return "\(Self.self)" 22 | } 23 | static var nib: UINib? { return nil } 24 | 25 | static func register(_ tableView: UITableView) { 26 | if let cellNib = self.nib { 27 | tableView.register(cellNib, forCellReuseIdentifier: identifier) 28 | } else { 29 | tableView.register(Self.self as AnyClass, forCellReuseIdentifier: identifier) 30 | } 31 | } 32 | 33 | static func dequeueFromTableView(_ tableView: UITableView, forIndexPath indexPath: IndexPath) -> Self { 34 | return tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) as! Self 35 | } 36 | } 37 | 38 | protocol FillableCell: BaseCell { 39 | associatedtype ObjectType 40 | func fillWithObject(object: ObjectType) 41 | } 42 | -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Cells/PersonCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserCell.swift 3 | // Dip 4 | // 5 | // Created by Olivier Halligon on 10/09/2015. 6 | // Copyright © 2015 AliSoftware. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class PersonCell : UITableViewCell, FillableCell { 12 | @IBOutlet weak var nameLabel: UILabel! 13 | @IBOutlet weak var genderImageView: UIImageView! 14 | @IBOutlet weak var heightLabel: UILabel! 15 | @IBOutlet weak var massLabel: UILabel! 16 | @IBOutlet weak var hairLabel: UILabel! 17 | @IBOutlet weak var eyesLabel: UILabel! 18 | 19 | let heightFormatter: LengthFormatter = { 20 | let f = LengthFormatter() 21 | f.isForPersonHeightUse = true 22 | return f 23 | }() 24 | let massFormatter: MassFormatter = { 25 | let f = MassFormatter() 26 | f.isForPersonMassUse = true 27 | return f 28 | }() 29 | 30 | func fillWithObject(object person: Person) { 31 | nameLabel.text = person.name 32 | genderImageView.image = person.gender.flatMap { UIImage(named: $0.rawValue) } 33 | heightLabel.text = heightFormatter.string(fromValue: Double(person.height), unit: .centimeter) 34 | massLabel.text = massFormatter.string(fromValue: Double(person.mass), unit: .kilogram) 35 | hairLabel.text = person.hairColor 36 | eyesLabel.text = person.eyeColor 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Cells/StarshipCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StarshipCell.swift 3 | // Dip 4 | // 5 | // Created by Olivier Halligon on 09/10/2015. 6 | // Copyright © 2015 AliSoftware. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class StarshipCell : UITableViewCell, FillableCell { 12 | @IBOutlet weak var nameLabel: UILabel! 13 | @IBOutlet weak var modelLabel: UILabel! 14 | @IBOutlet weak var manufacturerLabel: UILabel! 15 | @IBOutlet weak var crewLabel: UILabel! 16 | @IBOutlet weak var passengersLabel: UILabel! 17 | 18 | let numberFormatter = NumberFormatter() 19 | 20 | func fillWithObject(object starship: Starship) { 21 | nameLabel.text = starship.name 22 | modelLabel.text = starship.model 23 | manufacturerLabel.text = starship.manufacturer 24 | crewLabel.text = numberFormatter.string(from: NSNumber(integerLiteral: starship.crew)) 25 | passengersLabel.text = numberFormatter.string(from: NSNumber(integerLiteral: starship.passengers)) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/DependencyContainers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DependencyContainers.swift 3 | // Dip 4 | // 5 | // Created by Olivier Halligon on 10/10/2015. 6 | // Copyright © 2015 AliSoftware. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Dip 11 | 12 | // MARK: Configuration 13 | 14 | /* Change this to toggle between real and fake data */ 15 | private let FAKE_PERSONS = false 16 | private let FAKE_STARSHIPS = false 17 | /* ---- */ 18 | 19 | 20 | enum DependencyTags: Int, DependencyTagConvertible { 21 | case Hardcoded 22 | case Dummy 23 | } 24 | 25 | // MARK: Dependency Container for Providers 26 | func configure(container dip: DependencyContainer) { 27 | 28 | // 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) 29 | dip.register(.singleton) { URLSessionNetworkLayer(baseURL: "http://swapi.co/api/")! as NetworkLayer } 30 | 31 | if FAKE_PERSONS { 32 | 33 | // 1) Register fake persons provider 34 | //Here we use constructor injection for one of the dependencies property injection for another, and we provide dependencies manually 35 | dip.register() { FakePersonsProvider(dummyProvider: DummyPilotProvider()) as PersonProviderAPI } 36 | .resolvingProperties { (_, resolved: PersonProviderAPI) in 37 | //here we resolve optional dependencies 38 | //see what happens when you comment this out 39 | (resolved as! FakePersonsProvider).plistProvider = PlistPersonProvider(plist: "mainPilot") 40 | } 41 | 42 | } else { 43 | 44 | // 1) Register the SWAPIPersonProvider (that hits the real swapi.co WebService) 45 | // Here we use constructor injection again, but let the container to resolve dependency for us 46 | dip.register() { SWAPIPersonProvider(webService: try dip.resolve()) as PersonProviderAPI } 47 | 48 | } 49 | 50 | if FAKE_STARSHIPS { 51 | 52 | // 2) Register fake starships provider 53 | 54 | //Here we register different implementations for the same protocol using tags 55 | dip.register(tag: DependencyTags.Hardcoded) { HardCodedStarshipProvider() as StarshipProviderAPI } 56 | 57 | //Here we register factory that will require a runtime argument 58 | dip.register(tag: DependencyTags.Dummy) { DummyStarshipProvider(pilotName: $0) as StarshipProviderAPI } 59 | 60 | //Here we use constructor injection, but instead of providing dependencies manually container resolves them for us 61 | dip.register() { 62 | FakeStarshipProvider( 63 | dummyProvider: try dip.resolve(tag: DependencyTags.Dummy, arguments: "Main Pilot"), 64 | hardCodedProvider: try dip.resolve(tag: DependencyTags.Hardcoded)) as StarshipProviderAPI 65 | } 66 | 67 | } else { 68 | 69 | // 2) Register the SWAPIStarshipProvider (that hits the real swapi.co WebService) 70 | // Here we use constructor injection again, but let the container to resolve dependency for us 71 | dip.register() { SWAPIStarshipProvider(webService: try dip.resolve()) as StarshipProviderAPI } 72 | 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 7.1.1 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | NSAppTransportSecurity 30 | 31 | NSAllowsArbitraryLoads 32 | 33 | 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UISupportedInterfaceOrientations~ipad 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationPortraitUpsideDown 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Model/Person.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Person.swift 3 | // Dip 4 | // 5 | // Created by Olivier Halligon on 08/10/2015. 6 | // Copyright © 2015 AliSoftware. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum Gender: String { 12 | case Male = "male" 13 | case Female = "female" 14 | } 15 | 16 | struct Person { 17 | var name: String 18 | var height: Int 19 | var mass: Int 20 | var hairColor: String 21 | var eyeColor: String 22 | var gender: Gender? 23 | var starshipIDs: [Int] 24 | } 25 | -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Model/Starship.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Starship.swift 3 | // Dip 4 | // 5 | // Created by Olivier Halligon on 08/10/2015. 6 | // Copyright © 2015 AliSoftware. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Starship { 12 | var name: String 13 | var model: String 14 | var manufacturer: String 15 | var crew: Int 16 | var passengers: Int 17 | var pilotIDs: [Int] 18 | } 19 | -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Providers/FakePersonsProviders.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlistPersonProvider.swift 3 | // Dip 4 | // 5 | // Created by Ilya Puchka on 12/09/2015. 6 | // Copyright © 2015 AliSoftware. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | ///Provides some dummy Person entities 12 | struct DummyPilotProvider : PersonProviderAPI { 13 | 14 | func fetchIDs(completion: @escaping ([Int]) -> Void) { 15 | completion(Array(0..<5)) 16 | } 17 | 18 | func fetch(id: Int, completion: @escaping (Person?) -> Void) { 19 | completion(dummyPerson(idx: id)) 20 | } 21 | 22 | private func dummyPerson(idx: Int) -> Person { 23 | let colors = ["blue", "brown", "yellow", "orange", "red", "dark"] 24 | let genders: [Gender?] = [Gender.Male, Gender.Female, nil] 25 | return Person( 26 | name: "John Dummy Doe #\(idx)", 27 | height: 150 + (idx*27%40), 28 | mass: 50 + (idx*7%30), 29 | hairColor: colors[idx*3%colors.count], 30 | eyeColor: colors[idx*2%colors.count], 31 | gender: genders[idx%3], 32 | starshipIDs: [idx % 3, 2*idx % 4] 33 | ) 34 | } 35 | } 36 | 37 | ///Provides Person entities reading then from plist file 38 | class PlistPersonProvider : PersonProviderAPI { 39 | let people: [Person] 40 | 41 | init(plist basename: String) { 42 | guard 43 | let path = Bundle.main.path(forResource: basename, ofType: "plist"), 44 | let list = NSArray(contentsOfFile: path), 45 | let peopleDict = list as? [[String:AnyObject]] 46 | else { 47 | fatalError("PLIST for \(basename) not found") 48 | } 49 | 50 | self.people = peopleDict.map(PlistPersonProvider.personFromDict) 51 | } 52 | 53 | func fetchIDs(completion: @escaping ([Int]) -> Void) { 54 | completion(Array(0.. Void) { 58 | guard id < people.count else { 59 | completion(nil) 60 | return 61 | } 62 | completion(people[id]) 63 | } 64 | 65 | private static func personFromDict(dict: [String:AnyObject]) -> Person { 66 | guard 67 | let name = dict["name"] as? String, 68 | let height = dict["height"] as? Int, 69 | let mass = dict["mass"] as? Int, 70 | let hairColor = dict["hairColor"] as? String, 71 | let eyeColor = dict["eyeColor"] as? String, 72 | let genderStr = dict["gender"] as? String, 73 | let starshipsIDs = dict["starships"] as? [Int] 74 | else { 75 | fatalError("Invalid Plist") 76 | } 77 | 78 | return Person( 79 | name: name, 80 | height: height, 81 | mass: mass, 82 | hairColor: hairColor, 83 | eyeColor: eyeColor, 84 | gender: Gender(rawValue: genderStr), 85 | starshipIDs: starshipsIDs 86 | ) 87 | } 88 | } 89 | 90 | class FakePersonsProvider: PersonProviderAPI { 91 | 92 | let dummyProvider: PersonProviderAPI 93 | var plistProvider: PersonProviderAPI! 94 | 95 | //In this class we use both constructor injection and property injection, 96 | //nil is a valid local default 97 | init(dummyProvider: PersonProviderAPI) { 98 | self.dummyProvider = dummyProvider 99 | } 100 | 101 | func fetchIDs(completion: @escaping ([Int]) -> Void) { 102 | dummyProvider.fetchIDs(completion: completion) 103 | } 104 | 105 | func fetch(id: Int, completion: @escaping (Person?) -> Void) { 106 | if let plistProvider = plistProvider, id == 0 { 107 | plistProvider.fetch(id: id, completion: completion) 108 | } 109 | else { 110 | dummyProvider.fetch(id: id, completion: completion) 111 | } 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Providers/FakeStarshipProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FakeStarshipProvider.swift 3 | // DipSampleApp 4 | // 5 | // Created by Ilya Puchka on 20.01.16. 6 | // Copyright © 2016 AliSoftware. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | ///Provides some dummy Starship entities 12 | struct DummyStarshipProvider : StarshipProviderAPI { 13 | var pilotName: String 14 | 15 | func fetchIDs(completion: @escaping ([Int]) -> Void) { 16 | let nbShips = pilotName.count 17 | completion(Array(0.. Void) { 21 | completion(dummyStarship(idx: id)) 22 | } 23 | 24 | private func dummyStarship(idx: Int) -> Starship { 25 | return Starship( 26 | name: "\(pilotName)'s awesome starship #\(idx)", 27 | model: "\(pilotName)Ship", 28 | manufacturer: "Dummy Industries", 29 | crew: 1 + (idx%3), 30 | passengers: 10 + (idx*7 % 40), 31 | pilotIDs: [idx] 32 | ) 33 | } 34 | } 35 | 36 | ///Provides hardcoded Starship entities stored in memory 37 | class HardCodedStarshipProvider : StarshipProviderAPI { 38 | 39 | let starships = [ 40 | Starship(name: "First Ship", model: "AwesomeShip", manufacturer: "HardCoded Inc.", crew: 3, passengers: 20, pilotIDs: [1,2]), 41 | Starship(name: "Second Ship", model: "AwesomeShip Express", manufacturer: "HardCoded Inc.", crew: 4, passengers: 10, pilotIDs: [1]), 42 | Starship(name: "Third Ship", model: "AwesomeShip Cargo", manufacturer: "HardCoded Inc.", crew: 12, passengers: 150, pilotIDs: [2]), 43 | ] + Array(4..<75).map { Starship(name: "Ship #\($0)", model: "AwesomeShip Fighter", manufacturer: "HardCoded Inc.", crew: 1, passengers: 2, pilotIDs: [1]) } 44 | 45 | func fetchIDs(completion: @escaping ([Int]) -> Void) { 46 | completion(Array(0.. Void) { 50 | guard id < starships.count else { 51 | completion(nil) 52 | return 53 | } 54 | completion(starships[id]) 55 | } 56 | } 57 | 58 | class FakeStarshipProvider: StarshipProviderAPI { 59 | 60 | let dummyProvider: StarshipProviderAPI 61 | let hardCodedProvider: StarshipProviderAPI 62 | 63 | //Constructor injection again here 64 | init(dummyProvider: StarshipProviderAPI, hardCodedProvider: StarshipProviderAPI) { 65 | self.dummyProvider = dummyProvider 66 | self.hardCodedProvider = hardCodedProvider 67 | } 68 | 69 | func fetchIDs(completion: @escaping ([Int]) -> Void) { 70 | hardCodedProvider.fetchIDs(completion: completion) 71 | } 72 | 73 | func fetch(id: Int, completion: @escaping (Starship?) -> Void) { 74 | if id == 0 { 75 | dummyProvider.fetch(id: id, completion: completion) 76 | } 77 | else { 78 | hardCodedProvider.fetch(id: id, completion: completion) 79 | } 80 | } 81 | 82 | } 83 | 84 | 85 | -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Providers/NetworkLayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkLayer.swift 3 | // Dip 4 | // 5 | // Created by Olivier Halligon on 10/10/2015. 6 | // Copyright © 2015 AliSoftware. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum NetworkResponse { 12 | case Success(Data, HTTPURLResponse) 13 | case Error(NSError) 14 | 15 | func unwrap() throws -> (Data, HTTPURLResponse) { 16 | switch self { 17 | case .Success(let data, let response): 18 | return (data, response) 19 | case .Error(let error): 20 | throw error 21 | } 22 | } 23 | 24 | func json() throws -> T { 25 | let (data, _) = try self.unwrap() 26 | let obj = try JSONSerialization.jsonObject(with: data, options: []) 27 | guard let json = obj as? T else { 28 | throw SWAPIError.InvalidJSON 29 | } 30 | return json 31 | } 32 | } 33 | 34 | protocol NetworkLayer { 35 | func request(path: String, completion: @escaping (NetworkResponse) -> Void) 36 | } 37 | -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Providers/PersonProviderAPI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PersonProviderAPI.swift 3 | // Dip 4 | // 5 | // Created by Olivier Halligon on 10/09/2015. 6 | // Copyright © 2015 AliSoftware. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol PersonProviderAPI { 12 | func fetchIDs(completion: @escaping ([Int]) -> Void) 13 | func fetch(id: Int, completion: @escaping (Person?) -> Void) 14 | } 15 | -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Providers/SWAPICommon.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SWAPICommon.swift 3 | // Dip 4 | // 5 | // Created by Olivier Halligon on 11/10/2015. 6 | // Copyright © 2015 AliSoftware. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Dip 11 | 12 | enum SWAPIError: Error { 13 | case InvalidJSON 14 | } 15 | 16 | func idFromURLString(urlString: String) -> Int? { 17 | let url = NSURL(string: urlString) 18 | let idString = url.flatMap { $0.lastPathComponent } 19 | return idString.flatMap { Int($0) } 20 | } 21 | -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Providers/SWAPIPersonProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SWAPIPersonProvider.swift 3 | // Dip 4 | // 5 | // Created by Olivier Halligon on 10/10/2015. 6 | // Copyright © 2015 AliSoftware. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | ///Provides Person entities fetching them with web service 12 | struct SWAPIPersonProvider : PersonProviderAPI { 13 | let ws: NetworkLayer 14 | 15 | //Here we inject dependency using _constructor injection_ pattern. 16 | //The alternative way is a _property injection_ 17 | //but it should be used only for optional dependencies 18 | //where there is a good local default implementation 19 | init(webService: NetworkLayer) { 20 | self.ws = webService 21 | } 22 | 23 | func fetchIDs(completion: @escaping ([Int]) -> Void) { 24 | ws.request(path: "people") { response in 25 | do { 26 | let dict = try response.json() as NSDictionary 27 | guard let results = dict["results"] as? [NSDictionary] else { throw SWAPIError.InvalidJSON } 28 | 29 | // Extract URLs (flatten to ignore invalid ones) 30 | let urlStrings = results.compactMap({ $0["url"] as? String }) 31 | let ids = urlStrings.compactMap(idFromURLString) 32 | 33 | completion(ids) 34 | } 35 | catch { 36 | completion([]) 37 | } 38 | } 39 | } 40 | 41 | func fetch(id: Int, completion: @escaping (Person?) -> Void) { 42 | ws.request(path: "people/\(id)") { response in 43 | do { 44 | let json = try response.json() as NSDictionary 45 | guard 46 | let name = json["name"] as? String, 47 | let heightStr = json["height"] as? String, let height = Int(heightStr), 48 | let massStr = json["mass"] as? String, let mass = Int(massStr), 49 | let hairColor = json["hair_color"] as? String, 50 | let eyeColor = json["eye_color"] as? String, 51 | let gender = json["gender"] as? String, 52 | let starshipURLStrings = json["starships"] as? [String] 53 | else { 54 | throw SWAPIError.InvalidJSON 55 | } 56 | 57 | let person = Person( 58 | name: name, 59 | height: height, 60 | mass: mass, 61 | hairColor: hairColor, 62 | eyeColor: eyeColor, 63 | gender: Gender(rawValue: gender), 64 | starshipIDs: starshipURLStrings.compactMap(idFromURLString) 65 | ) 66 | completion(person) 67 | } 68 | catch { 69 | completion(nil) 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Providers/SWAPIStarshipProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SWAPIStarshipProvider.swift 3 | // Dip 4 | // 5 | // Created by Olivier Halligon on 10/10/2015. 6 | // Copyright © 2015 AliSoftware. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | ///Provides Starship entities fetching them using web service 12 | struct SWAPIStarshipProvider : StarshipProviderAPI { 13 | let ws: NetworkLayer 14 | 15 | //Here we inject dependency using _constructor injection_ pattern. 16 | //The alternative way is a _property injection_ 17 | //but it should be used only for optional dependencies 18 | //where there is a good local default implementation 19 | init(webService: NetworkLayer) { 20 | self.ws = webService 21 | } 22 | 23 | func fetchIDs(completion: @escaping ([Int]) -> Void) { 24 | ws.request(path: "starships") { response in 25 | do { 26 | let dict = try response.json() as NSDictionary 27 | guard let results = dict["results"] as? [NSDictionary] else { throw SWAPIError.InvalidJSON } 28 | 29 | // Extract URLs (flatten to ignore invalid ones) 30 | let urlStrings = results.compactMap({ $0["url"] as? String }) 31 | let ids = urlStrings.compactMap(idFromURLString) 32 | 33 | completion(ids) 34 | } 35 | catch { 36 | completion([]) 37 | } 38 | } 39 | } 40 | 41 | func fetch(id: Int, completion: @escaping (Starship?) -> Void) { 42 | ws.request(path: "starships/\(id)") { response in 43 | do { 44 | let json = try response.json() as NSDictionary 45 | guard 46 | let name = json["name"] as? String, 47 | let model = json["model"] as? String, 48 | let manufacturer = json["manufacturer"] as? String, 49 | let crewStr = json["crew"] as? String, let crew = Int(crewStr), 50 | let passengersStr = json["passengers"] as? String, let passengers = Int(passengersStr), 51 | let pilotIDStrings = json["pilots"] as? [String] 52 | else { 53 | throw SWAPIError.InvalidJSON 54 | } 55 | 56 | let ship = Starship( 57 | name: name, 58 | model: model, 59 | manufacturer: manufacturer, 60 | crew: crew, 61 | passengers: passengers, 62 | pilotIDs: pilotIDStrings.compactMap(idFromURLString) 63 | ) 64 | completion(ship) 65 | } 66 | catch { 67 | completion(nil) 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Providers/StarshipProviderAPI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StarshipProviderAPI.swift 3 | // Dip 4 | // 5 | // Created by Olivier Halligon on 08/10/2015. 6 | // Copyright © 2015 AliSoftware. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol StarshipProviderAPI { 12 | func fetchIDs(completion: @escaping ([Int]) -> Void) 13 | func fetch(id: Int, completion: @escaping (Starship?) -> Void) 14 | } 15 | -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/Providers/URLSessionNetworkLayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLSessionNetworkLayer.swift 3 | // Dip 4 | // 5 | // Created by Olivier Halligon on 10/10/2015. 6 | // Copyright © 2015 AliSoftware. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | ///NetworkLayer implementation on top of NSURLSession 12 | struct URLSessionNetworkLayer : NetworkLayer { 13 | let baseURL: URL 14 | let session: URLSession 15 | let responseQueue: DispatchQueue 16 | 17 | init?(baseURL: String, session: URLSession = URLSession.shared, responseQueue: DispatchQueue = DispatchQueue.main) { 18 | guard let url = URL(string: baseURL) else { return nil } 19 | self.init(baseURL: url, session: session) 20 | } 21 | 22 | init(baseURL: URL, session: URLSession = URLSession.shared, responseQueue: DispatchQueue = DispatchQueue.main) { 23 | self.baseURL = baseURL 24 | self.session = session 25 | self.responseQueue = responseQueue 26 | } 27 | 28 | func request(path: String, completion: @escaping (NetworkResponse) -> Void) { 29 | let url = self.baseURL.appendingPathComponent(path) 30 | let task = session.dataTask(with: url) { data, response, error in 31 | if let data = data, let response = response as? HTTPURLResponse { 32 | self.responseQueue.async() { 33 | completion(NetworkResponse.Success(data, response)) 34 | } 35 | } 36 | else { 37 | let err = error ?? NSError(domain: NSURLErrorDomain, code: URLError.unknown.rawValue, userInfo: nil) 38 | self.responseQueue.async() { 39 | completion(NetworkResponse.Error(err as NSError)) 40 | } 41 | } 42 | } 43 | task.resume() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/StoryboardConstants.swift: -------------------------------------------------------------------------------- 1 | // Generated using SwiftGen, by O.Halligon — https://github.com/AliSoftware/SwiftGen 2 | 3 | import Foundation 4 | import UIKit 5 | 6 | protocol StoryboardScene : RawRepresentable { 7 | static var storyboardName : String { get } 8 | static func storyboard() -> UIStoryboard 9 | static func initialViewController() -> UIViewController 10 | func viewController() -> UIViewController 11 | static func viewController(identifier: Self) -> UIViewController 12 | } 13 | 14 | extension StoryboardScene where Self.RawValue == String { 15 | static func storyboard() -> UIStoryboard { 16 | return UIStoryboard(name: self.storyboardName, bundle: nil) 17 | } 18 | 19 | static func initialViewController() -> UIViewController { 20 | return storyboard().instantiateInitialViewController()! 21 | } 22 | 23 | func viewController() -> UIViewController { 24 | return Self.storyboard().instantiateViewController(withIdentifier: self.rawValue) 25 | } 26 | static func viewController(identifier: Self) -> UIViewController { 27 | return identifier.viewController() 28 | } 29 | } 30 | 31 | extension UIStoryboard { 32 | struct Scene { 33 | enum Main { 34 | static let storyboardName = "Main" 35 | } 36 | enum LaunchScreen { 37 | static let storyboardName = "LaunchScreen" 38 | } 39 | } 40 | 41 | struct Segue { 42 | enum Main : String { 43 | case StarshipsSegue = "StarshipsSegue" 44 | case PilotsSegue = "PilotsSegue" 45 | } 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/ViewControllers/FetchableTrait.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FetchableTrait.swift 3 | // Dip 4 | // 5 | // Created by Olivier Halligon on 09/10/2015. 6 | // Copyright © 2015 AliSoftware. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol FetchableTrait: AnyObject { 12 | associatedtype ObjectType 13 | var objects: [ObjectType]? { get set } 14 | var batchRequestID: Int { get set } 15 | var tableView: UITableView! { get } 16 | 17 | func fetchIDs(completion: @escaping ([Int]) -> Void) 18 | func fetchOne(id: Int, completion: @escaping (ObjectType?) -> Void) 19 | var fetchProgress: (current: Int, total: Int?) { get set } 20 | } 21 | 22 | extension FetchableTrait { 23 | func loadObjects(objectIDs: [Int]) { 24 | self.batchRequestID += 1 25 | let batch = self.batchRequestID 26 | 27 | objects?.removeAll() 28 | fetchProgress = (0,objectIDs.count) 29 | for objectID in objectIDs { 30 | fetchOne(id: objectID) { (object: ObjectType?) in 31 | // Exit if we failed to retrive an object for this ID, or if the request 32 | // should be ignored because a new batch request has been started since 33 | guard let object = object, batch == self.batchRequestID else { return } 34 | 35 | if self.objects == nil { self.objects = [] } 36 | self.objects?.append(object) 37 | self.fetchProgress.current = self.objects?.count ?? 0 38 | self.tableView?.reloadData() 39 | } 40 | } 41 | } 42 | 43 | func loadFirstPage() { 44 | self.batchRequestID += 1 45 | let batch = self.batchRequestID 46 | fetchProgress = (0, nil) 47 | fetchIDs() { objectIDs in 48 | guard batch == self.batchRequestID else { return } 49 | self.loadObjects(objectIDs: objectIDs) 50 | } 51 | } 52 | 53 | func displayProgressInNavBar(navigationItem: UINavigationItem) { 54 | let text: String 55 | if let total = fetchProgress.total { 56 | if fetchProgress.current == fetchProgress.total { 57 | text = "Done." 58 | } else { 59 | text = "Loading \(fetchProgress.current) / \(total)…" 60 | } 61 | } else { 62 | text = "Loading IDs…" 63 | } 64 | let label = UILabel(frame: CGRect(x: 0, y: 0, width: 0, height: 0)) 65 | label.text = text 66 | label.textColor = .gray 67 | label.font = UIFont.systemFont(ofSize: 12) 68 | label.sizeToFit() 69 | navigationItem.rightBarButtonItem = UIBarButtonItem(customView: label) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/ViewControllers/PersonListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PersonListViewController.swift 3 | // Dip 4 | // 5 | // Created by Olivier Halligon on 09/10/2015. 6 | // Copyright © 2015 AliSoftware. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PersonListViewController: UITableViewController, FetchableTrait { 12 | var objects: [Person]? 13 | var batchRequestID = 0 14 | 15 | var personProvider: PersonProviderAPI! 16 | var starshipProvider: StarshipProviderAPI! 17 | 18 | func fetchIDs(completion: @escaping ([Int]) -> Void) { 19 | return personProvider.fetchIDs(completion: completion) 20 | } 21 | 22 | func fetchOne(id personID: Int, completion: @escaping (Person?) -> Void) { 23 | return personProvider.fetch(id: personID, completion: completion) 24 | } 25 | 26 | var fetchProgress: (current: Int, total: Int?) = (0, nil) { 27 | didSet { 28 | displayProgressInNavBar(navigationItem: self.navigationItem) 29 | } 30 | } 31 | 32 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 33 | guard 34 | let id = segue.identifier, 35 | let segueID = UIStoryboard.Segue.Main(rawValue: id), 36 | segueID == .StarshipsSegue, 37 | let indexPath = self.tableView.indexPathForSelectedRow, 38 | let destVC = segue.destination as? StarshipListViewController, 39 | let person = self.objects?[indexPath.row] 40 | else { 41 | fatalError() 42 | } 43 | destVC.starshipProvider = starshipProvider 44 | destVC.loadObjects(objectIDs: person.starshipIDs) 45 | } 46 | } 47 | 48 | extension PersonListViewController { 49 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 50 | return objects?.count ?? 0 51 | } 52 | 53 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 54 | guard let object = self.objects?[indexPath.row] else { fatalError() } 55 | let cell = PersonCell.dequeueFromTableView(tableView, forIndexPath: indexPath) 56 | cell.fillWithObject(object: object) 57 | return cell 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/ViewControllers/StarshipListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StarshipListViewController.swift 3 | // Dip 4 | // 5 | // Created by Olivier Halligon on 09/10/2015. 6 | // Copyright © 2015 AliSoftware. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Dip 11 | 12 | class StarshipListViewController : UITableViewController, FetchableTrait { 13 | var objects: [Starship]? 14 | var batchRequestID = 0 15 | 16 | var starshipProvider: StarshipProviderAPI! 17 | var personProvider: PersonProviderAPI! 18 | 19 | func fetchIDs(completion: @escaping ([Int]) -> Void) { 20 | starshipProvider.fetchIDs(completion: completion) 21 | } 22 | func fetchOne(id shipID:Int, completion: @escaping (Starship?) -> Void) { 23 | starshipProvider.fetch(id: shipID, completion: completion) 24 | } 25 | 26 | var fetchProgress: (current: Int, total: Int?) = (0, nil) { 27 | didSet { 28 | displayProgressInNavBar(navigationItem: self.navigationItem) 29 | } 30 | } 31 | 32 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 33 | guard 34 | let id = segue.identifier, 35 | let segueID = UIStoryboard.Segue.Main(rawValue: id), 36 | segueID == .PilotsSegue, 37 | let indexPath = self.tableView.indexPathForSelectedRow, 38 | let destVC = segue.destination as? PersonListViewController, 39 | let starship = self.objects?[indexPath.row] 40 | else { 41 | fatalError() 42 | } 43 | 44 | destVC.personProvider = personProvider 45 | destVC.loadObjects(objectIDs: starship.pilotIDs) 46 | } 47 | } 48 | 49 | extension StarshipListViewController { 50 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 51 | return objects?.count ?? 0 52 | } 53 | 54 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 55 | guard let object = self.objects?[indexPath.row] else { fatalError() } 56 | let cell = StarshipCell.dequeueFromTableView(tableView, forIndexPath: indexPath) 57 | cell.fillWithObject(object: object) 58 | return cell 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /SampleApp/DipSampleApp/mainPilot.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | name 7 | Main Pilot 8 | height 9 | 175 10 | mass 11 | 67 12 | hairColor 13 | Dark 14 | eyeColor 15 | Brown 16 | gender 17 | male 18 | starships 19 | 20 | 0 21 | 1 22 | 3 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /SampleApp/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 7.1.1 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /SampleApp/Tests/NetworkMock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkMock.swift 3 | // Dip 4 | // 5 | // Created by Olivier Halligon on 11/10/2015. 6 | // Copyright © 2015 AliSoftware. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Dip 11 | 12 | var wsDependencies = DependencyContainer() 13 | 14 | // MARK: - Mock object used for tests 15 | 16 | struct NetworkMock : NetworkLayer { 17 | let fakeData: Data? 18 | 19 | init(json: Any) { 20 | do { 21 | fakeData = try JSONSerialization.data(withJSONObject: json, options: []) 22 | } catch { 23 | fakeData = nil 24 | } 25 | } 26 | 27 | func request(path: String, completion: @escaping (NetworkResponse) -> Void) { 28 | let fakeURL = NSURL(string: "http://stub")?.appendingPathComponent(path) 29 | if let data = fakeData { 30 | let response = HTTPURLResponse(url: fakeURL!, statusCode: 200, httpVersion: "1.1", headerFields:nil)! 31 | completion(.Success(data, response)) 32 | } else { 33 | let response = HTTPURLResponse(url: fakeURL!, statusCode: 204, httpVersion: "1.1", headerFields:nil)! 34 | completion(.Success(Data(), response)) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /SampleApp/Tests/SWAPIPersonProviderTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SWAPIPersonProviderTests.swift 3 | // Dip 4 | // 5 | // Created by Olivier Halligon on 11/10/2015. 6 | // Copyright © 2015 AliSoftware. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Dip 11 | 12 | class SWAPIPersonProviderTests: XCTestCase { 13 | let fakePerson1: [String : Any] = ["name": "John Doe", "mass": "72", "height": "172", "eye_color": "brown", "hair_color": "black", "gender": "male", 14 | "starships": ["http://starship/7/", "http://starship/15"], "url": "http://people/1"] 15 | let fakePerson2: [String: Any] = ["name": "Jane Doe", "mass": "63", "height": "167", "eye_color": "blue", "hair_color": "red", "gender": "female", 16 | "starships": ["http://starship/11/"], "url": "http://people/12"] 17 | 18 | override func setUp() { 19 | super.setUp() 20 | 21 | wsDependencies.reset() 22 | } 23 | 24 | func testFetchPersonIDs() { 25 | let mock = NetworkMock(json: ["results": [fakePerson1, fakePerson2]]) 26 | wsDependencies.register(.singleton) { mock as NetworkLayer } 27 | 28 | let provider = SWAPIPersonProvider(webService: try! wsDependencies.resolve()) 29 | provider.fetchIDs { personIDs in 30 | XCTAssertNotNil(personIDs) 31 | XCTAssertEqual(personIDs.count, 2) 32 | 33 | XCTAssertEqual(personIDs[0], 1) 34 | XCTAssertEqual(personIDs[1], 12) 35 | } 36 | } 37 | 38 | func testFetchOnePerson() { 39 | let mock = NetworkMock(json: fakePerson1) 40 | wsDependencies.register(.singleton) { mock as NetworkLayer } 41 | 42 | let provider = SWAPIPersonProvider(webService: try! wsDependencies.resolve()) 43 | provider.fetch(id: 1) { person in 44 | XCTAssertNotNil(person) 45 | XCTAssertEqual(person?.name, "John Doe") 46 | XCTAssertEqual(person?.mass, 72) 47 | XCTAssertEqual(person?.height, 172) 48 | XCTAssertEqual(person?.eyeColor, "brown") 49 | XCTAssertEqual(person?.hairColor, "black") 50 | XCTAssertEqual(person?.gender, .Male) 51 | XCTAssertEqual(person?.starshipIDs.count, 2) 52 | XCTAssertEqual(person?.starshipIDs[0], 7) 53 | XCTAssertEqual(person?.starshipIDs[1], 15) 54 | } 55 | } 56 | 57 | func testFetchInvalidPerson() { 58 | let json = ["error":"whoops"] 59 | let mock = NetworkMock(json: json) 60 | wsDependencies.register(.singleton) { mock as NetworkLayer } 61 | 62 | let provider = SWAPIPersonProvider(webService: try! wsDependencies.resolve()) 63 | provider.fetch(id: 12) { person in 64 | XCTAssertNil(person) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /SampleApp/Tests/SWAPIStarshipProviderTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SWAPIStarshipProviderTests.swift 3 | // Dip 4 | // 5 | // Created by Olivier Halligon on 11/10/2015. 6 | // Copyright © 2015 AliSoftware. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import Dip 11 | 12 | class SWAPIStarshipProviderTests: XCTestCase { 13 | let fakeShip1: [String: Any] = ["name": "Falcon", "model": "Fighter", "manufacturer": "Fake Industries", "crew": "7", "passengers": "15", 14 | "pilots": ["http://people/1/", "http://people/9"], "url": "http://starship/4"] 15 | let fakeShip2: [String: Any] = ["name": "Voyager", "model": "Cargo", "manufacturer": "Fake Industries", "crew": "18", "passengers": "150", 16 | "pilots": ["http://people/2/", "http://people/3"], "url": "http://starship/31"] 17 | 18 | override func setUp() { 19 | super.setUp() 20 | 21 | wsDependencies.reset() 22 | } 23 | 24 | func testFetchStarshipIDs() { 25 | let mock = NetworkMock(json: ["results": [fakeShip1, fakeShip2]]) 26 | wsDependencies.register(.singleton) { mock as NetworkLayer } 27 | 28 | let provider = SWAPIStarshipProvider(webService: try! wsDependencies.resolve()) 29 | provider.fetchIDs { shipIDs in 30 | XCTAssertNotNil(shipIDs) 31 | XCTAssertEqual(shipIDs.count, 2) 32 | 33 | XCTAssertEqual(shipIDs[0], 4) 34 | XCTAssertEqual(shipIDs[1], 31) 35 | } 36 | } 37 | 38 | func testFetchOneStarship() { 39 | 40 | let mock = NetworkMock(json: fakeShip1) 41 | wsDependencies.register(.singleton) { mock as NetworkLayer } 42 | 43 | let provider = SWAPIStarshipProvider(webService: try! wsDependencies.resolve()) 44 | provider.fetch(id: 1) { starship in 45 | XCTAssertNotNil(starship) 46 | XCTAssertEqual(starship?.name, "Falcon") 47 | XCTAssertEqual(starship?.model, "Fighter") 48 | XCTAssertEqual(starship?.manufacturer, "Fake Industries") 49 | XCTAssertEqual(starship?.crew, 7) 50 | XCTAssertEqual(starship?.passengers, 15) 51 | XCTAssertNotNil(starship?.pilotIDs) 52 | XCTAssertEqual(starship?.pilotIDs[0], 1) 53 | XCTAssertEqual(starship?.pilotIDs[1], 9) 54 | } 55 | } 56 | 57 | func testFetchInvalidStarship() { 58 | let json = ["error":"whoops"] 59 | let mock = NetworkMock(json: json) 60 | wsDependencies.register(.singleton) { mock as NetworkLayer } 61 | 62 | let provider = SWAPIStarshipProvider(webService: try! wsDependencies.resolve()) 63 | provider.fetch(id: 12) { starship in 64 | XCTAssertNil(starship) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Sources/AutoWiring.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dip 3 | // 4 | // Copyright (c) 2015 Olivier Halligon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | protocol AutoWiringDefinition: DefinitionType { 26 | var numberOfArguments: Int { get } 27 | var autoWiringFactory: ((DependencyContainer, DependencyContainer.Tag?) throws -> Any)? { get } 28 | } 29 | 30 | extension DependencyContainer { 31 | 32 | /// Tries to resolve instance using auto-wiring 33 | func autowire(key aKey: DefinitionKey) throws -> T { 34 | let key = aKey 35 | guard key.typeOfArguments == Void.self else { 36 | throw DipError.definitionNotFound(key: key) 37 | } 38 | 39 | let autoWiringKey = try autoWiringDefinition(byKey: key).key 40 | 41 | do { 42 | let key = autoWiringKey.tagged(with: key.tag ?? context.tag) 43 | return try _resolve(key: key) { definition in 44 | try definition.autoWiringFactory!(self, key.tag) as! T 45 | } 46 | } 47 | catch { 48 | throw DipError.autoWiringFailed(type: key.type, underlyingError: error) 49 | } 50 | } 51 | 52 | private func autoWiringDefinition(byKey key: DefinitionKey) throws -> KeyDefinitionPair { 53 | do { 54 | return try autoWiringDefinition(byKey: key, strictByTag: true) 55 | } catch { 56 | if key.tag != nil { 57 | return try autoWiringDefinition(byKey: key, strictByTag: false) 58 | } else { 59 | throw error 60 | } 61 | } 62 | } 63 | 64 | private func autoWiringDefinition(byKey key: DefinitionKey, strictByTag: Bool) throws -> KeyDefinitionPair { 65 | var definitions = self.definitions.map({ (key: $0.0, definition: $0.1) }) 66 | 67 | definitions = filter(definitions: definitions, byKey: key, strictByTag: strictByTag) 68 | definitions = definitions.sorted(by: { $0.definition.numberOfArguments > $1.definition.numberOfArguments }) 69 | 70 | guard definitions.count > 0 && definitions[0].definition.numberOfArguments > 0 else { 71 | throw DipError.definitionNotFound(key: key) 72 | } 73 | 74 | let maximumNumberOfArguments = definitions.first?.definition.numberOfArguments 75 | definitions = definitions.filter({ $0.definition.numberOfArguments == maximumNumberOfArguments }) 76 | 77 | //when there are several definitions with the same number of arguments but different arguments types 78 | if definitions.count > 1 && definitions[0].key.typeOfArguments != definitions[1].key.typeOfArguments { 79 | let error = DipError.ambiguousDefinitions(type: key.type, definitions: definitions.map({ $0.definition })) 80 | throw DipError.autoWiringFailed(type: key.type, underlyingError: error) 81 | } else { 82 | return definitions[0] 83 | } 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /Sources/Compatibility.swift: -------------------------------------------------------------------------------- 1 | extension String { 2 | func has(prefix aPrefix: String) -> Bool { 3 | return hasPrefix(aPrefix) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /Sources/ComponentScope.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dip 3 | // 4 | // Copyright (c) 2015 Olivier Halligon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | ///Component scope defines a strategy used by the `DependencyContainer` to manage resolved instances life cycle. 26 | public enum ComponentScope { 27 | 28 | /** 29 | A new instance will be created every time it's resolved. 30 | This is a default strategy. Use this strategy when you don't want instances to be shared 31 | between different consumers (i.e. if it is not thread safe). 32 | 33 | **Example**: 34 | 35 | ``` 36 | container.register { ServiceImp() as Service } 37 | container.register { 38 | ServiceConsumerImp( 39 | service1: try container.resolve() as Service 40 | service2: try container.resolve() as Service 41 | ) as ServiceConsumer 42 | } 43 | let consumer = container.resolve() as ServiceConsumer 44 | consumer.service1 !== consumer.service2 //true 45 | 46 | ``` 47 | */ 48 | case unique 49 | 50 | /** 51 | Instance resolved with the same definition will be reused until topmost `resolve(tag:)` method returns. 52 | When you resolve the same object graph again the container will create new instances. 53 | Use this strategy if you want different object in objects graph to share the same instance. 54 | 55 | - warning: Make sure this component is thread safe or accessed always from the same thread. 56 | 57 | **Example**: 58 | 59 | ``` 60 | container.register { ServiceImp() as Service } 61 | container.register { 62 | ServiceConsumerImp( 63 | service1: try container.resolve() as Service 64 | service2: try container.resolve() as Service 65 | ) as ServiceConsumer 66 | } 67 | let consumer1 = container.resolve() as ServiceConsumer 68 | let consumer2 = container.resolve() as ServiceConsumer 69 | consumer1.service1 === consumer1.service2 //true 70 | consumer2.service1 === consumer2.service2 //true 71 | consumer1.service1 !== consumer2.service1 //true 72 | ``` 73 | */ 74 | case shared 75 | 76 | /** 77 | Resolved instance will be retained by the container and always reused. 78 | Do not mix this life cycle with _singleton pattern_. 79 | Instance will be not shared between different containers unless they collaborate. 80 | 81 | - warning: Make sure this component is thread safe or accessed always from the same thread. 82 | 83 | - note: When you override or remove definition from the container an instance 84 | that was resolved with this definition will be released. When you reset 85 | the container it will release all singleton instances. 86 | 87 | **Example**: 88 | 89 | ``` 90 | container.register(.singleton) { ServiceImp() as Service } 91 | container.register { 92 | ServiceConsumerImp( 93 | service1: try container.resolve() as Service 94 | service2: try container.resolve() as Service 95 | ) as ServiceConsumer 96 | } 97 | let consumer1 = container.resolve() as ServiceConsumer 98 | let consumer2 = container.resolve() as ServiceConsumer 99 | consumer1.service1 === consumer1.service2 //true 100 | consumer2.service1 === consumer2.service2 //true 101 | consumer1.service1 === consumer2.service1 //true 102 | ``` 103 | */ 104 | case singleton 105 | 106 | /** 107 | The same scope as a `Singleton`, but instance will be created when container is bootstrapped. 108 | 109 | - seealso: `bootstrap()` 110 | */ 111 | case eagerSingleton 112 | 113 | /** 114 | The same scope as a `Singleton`, but container stores week reference to the resolved instance. 115 | While a strong reference to the resolved instance exists resolve will return the same instance. 116 | After the resolved instance is deallocated next resolve will produce a new instance. 117 | */ 118 | case weakSingleton 119 | 120 | } 121 | -------------------------------------------------------------------------------- /Sources/DipError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dip 3 | // 4 | // Copyright (c) 2015 Olivier Halligon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | /** 26 | Errors thrown by `DependencyContainer`'s methods. 27 | 28 | - seealso: `resolve(tag:)` 29 | */ 30 | public enum DipError: Error, CustomStringConvertible { 31 | 32 | /** 33 | Thrown by `resolve(tag:)` if no matching definition was registered in container. 34 | 35 | - parameter key: definition key used to lookup matching definition 36 | */ 37 | case definitionNotFound(key: DefinitionKey) 38 | 39 | /** 40 | Thrown by `resolve(tag:)` if failed to auto-inject required property. 41 | 42 | - parameters: 43 | - label: The name of the property 44 | - type: The type of the property 45 | - underlyingError: The error that caused auto-injection to fail 46 | */ 47 | case autoInjectionFailed(label: String?, type: Any.Type, underlyingError: Error) 48 | 49 | /** 50 | Thrown by `resolve(tag:)` if failed to auto-wire a type. 51 | 52 | - parameters: 53 | - type: The type that failed to be resolved by auto-wiring 54 | - underlyingError: The error that cause auto-wiring to fail 55 | */ 56 | case autoWiringFailed(type: Any.Type, underlyingError: Error) 57 | 58 | /** 59 | Thrown when auto-wiring type if several definitions with the same number of runtime arguments 60 | are registered for that type. 61 | 62 | - parameters: 63 | - type: The type that failed to be resolved by auto-wiring 64 | - definitions: Ambiguous definitions 65 | */ 66 | case ambiguousDefinitions(type: Any.Type, definitions: [DefinitionType]) 67 | 68 | /** 69 | Thrown by `resolve(tag:)` if resolved instance does not implement resolved type (i.e. when type-forwarding). 70 | 71 | - parameters: 72 | - resolved: Resolved instance 73 | - key: Definition key used to resolve instance 74 | */ 75 | case invalidType(resolved: Any?, key: DefinitionKey) 76 | 77 | public var description: String { 78 | switch self { 79 | case let .definitionNotFound(key): 80 | return "No definition registered for \(key).\nCheck the tag, type you try to resolve, number, order and types of runtime arguments passed to `resolve()` and match them with registered factories for type \(String(reflecting: key.type))." 81 | case let .autoInjectionFailed(label, type, error): 82 | return "Failed to auto-inject property \"\(label.desc)\" of type \(String(reflecting: type)). \(error)" 83 | case let .autoWiringFailed(type, error): 84 | return "Failed to auto-wire type \"\(String(reflecting: type))\". \(error)" 85 | case let .ambiguousDefinitions(type, definitions): 86 | return "Ambiguous definitions for \(String(reflecting: type)):\n" + 87 | definitions.map({ "\($0)" }).joined(separator: ";\n") 88 | case let .invalidType(resolved, key): 89 | return "Resolved instance \(resolved ?? "nil") does not implement expected type \(String(reflecting: key.type))." 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Sources/Register.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dip 3 | // 4 | // Copyright (c) 2015 Olivier Halligon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | extension DependencyContainer { 26 | /** 27 | Registers definition for passed type. 28 | 29 | If instance created by factory of definition, passed as a first parameter, 30 | does not implement type passed in a `type` parameter, 31 | container will throw `DipError.DefinitionNotFound` error when trying to resolve that type. 32 | 33 | - parameters: 34 | - definition: Definition to register 35 | - type: Type to register definition for 36 | - tag: Optional tag to associate definition with. Default is `nil`. 37 | 38 | - returns: New definition registered for passed type. 39 | */ 40 | @discardableResult public func register(_ definition: Definition, type: F.Type, tag: DependencyTagConvertible? = nil) -> Definition { 41 | return _register(definition: definition, type: type, tag: tag) 42 | } 43 | 44 | /** 45 | Register definiton in the container and associate it with an optional tag. 46 | Will override already registered definition for the same type and factory, associated with the same tag. 47 | 48 | - parameters: 49 | - tag: The arbitrary tag to associate this definition with. Pass `nil` to associate with any tag. Default value is `nil`. 50 | - definition: The definition to register in the container. 51 | 52 | */ 53 | public func register(_ definition: Definition, tag: DependencyTagConvertible? = nil) { 54 | _register(definition: definition, tag: tag) 55 | } 56 | 57 | } 58 | 59 | extension DependencyContainer { 60 | 61 | func _register(definition aDefinition: Definition, tag: DependencyTagConvertible? = nil) { 62 | precondition(!bootstrapped, "You can not modify container's definitions after it was bootstrapped.") 63 | let definition = aDefinition 64 | threadSafe { 65 | let key = DefinitionKey(type: T.self, typeOfArguments: U.self, tag: tag?.dependencyTag) 66 | if let _ = definitions[key] { 67 | _remove(definitionForKey: key) 68 | } 69 | 70 | definition.container = self 71 | definitions[key] = definition 72 | resolvedInstances.singletons[key] = nil 73 | resolvedInstances.weakSingletons[key] = nil 74 | resolvedInstances.sharedSingletons[key] = nil 75 | resolvedInstances.sharedWeakSingletons[key] = nil 76 | 77 | if .eagerSingleton == definition.scope { 78 | bootstrapQueue.append({ _ = try self.resolve(tag: tag) as T }) 79 | } 80 | } 81 | } 82 | 83 | } 84 | 85 | -------------------------------------------------------------------------------- /Sources/TypeForwarding.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dip 3 | // 4 | // Copyright (c) 2015 Olivier Halligon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | protocol TypeForwardingDefinition: DefinitionType { 26 | var implementingTypes: [Any.Type] { get } 27 | func doesImplements(type aType: Any.Type) -> Bool 28 | } 29 | 30 | extension Definition { 31 | 32 | /** 33 | Registers definition for passed type. 34 | 35 | If instance created by factory of definition on which method is called 36 | does not implement type passed in a `type` parameter, 37 | container will throw `DipError.DefinitionNotFound` error when trying to resolve that type. 38 | 39 | - parameters: 40 | - type: Type to register definition for 41 | - tag: Optional tag to associate definition with. Default is `nil`. 42 | 43 | - returns: definition on which `implements` was called 44 | */ 45 | @discardableResult public func implements(_ type: F.Type, tag: DependencyTagConvertible? = nil) -> Definition { 46 | precondition(container != nil, "Definition should be registered in the container.") 47 | 48 | container!.register(self, type: type, tag: tag) 49 | return self 50 | } 51 | 52 | /** 53 | Registers definition for passed type. 54 | 55 | If instance created by factory of definition on which method is called 56 | does not implement type passed in a `type` parameter, 57 | container will throw `DipError.DefinitionNotFound` error when trying to resolve that type. 58 | 59 | - parameters: 60 | - type: Type to register definition for 61 | - tag: Optional tag to associate definition with. Default is `nil`. 62 | - resolvingProperties: Optional block to be called to resolve instance property dependencies 63 | 64 | - returns: definition on which `implements` was called 65 | */ 66 | @discardableResult public func implements(_ type: F.Type, tag: DependencyTagConvertible? = nil, resolvingProperties: @escaping (DependencyContainer, F) throws -> ()) -> Definition { 67 | precondition(container != nil, "Definition should be registered in the container.") 68 | 69 | let forwardDefinition = container!.register(self, type: type, tag: tag) 70 | forwardDefinition.resolvingProperties(resolvingProperties) 71 | return self 72 | } 73 | 74 | ///Registers definition for types passed as parameters 75 | @discardableResult public func implements(_ a: A.Type, _ b: B.Type) -> Definition { 76 | return implements(a).implements(b) 77 | } 78 | 79 | ///Registers definition for types passed as parameters 80 | @discardableResult public func implements(_ a: A.Type, _ b: B.Type, _ c: C.Type) -> Definition { 81 | return implements(a).implements(b).implements(c) 82 | } 83 | 84 | ///Registers definition for types passed as parameters 85 | @discardableResult public func implements(_ a: A.Type, _ b: B.Type, _ c: C.Type, _ d: D.Type) -> Definition { 86 | return implements(a).implements(b).implements(c).implements(d) 87 | } 88 | 89 | } 90 | 91 | extension DependencyContainer { 92 | 93 | func _register(definition aDefinition: Definition, type: F.Type, tag: DependencyTagConvertible? = nil) -> Definition { 94 | let definition = aDefinition 95 | precondition(definition.container === self, "Definition should be registered in the container.") 96 | 97 | let key = DefinitionKey(type: F.self, typeOfArguments: U.self) 98 | 99 | let forwardDefinition = DefinitionBuilder { 100 | $0.scope = definition.scope 101 | 102 | let factory = definition.factory 103 | $0.factory = { [unowned self] in 104 | let resolved = try factory($0) 105 | if let resolved = resolved as? F { 106 | return resolved 107 | } 108 | else { 109 | throw DipError.invalidType(resolved: resolved, key: key.tagged(with: self.context.tag)) 110 | } 111 | } 112 | 113 | $0.numberOfArguments = definition.numberOfArguments 114 | $0.autoWiringFactory = definition.autoWiringFactory.map({ factory in 115 | { [unowned self] in 116 | let resolved = try factory($0, $1) 117 | if let resolved = resolved as? F { 118 | return resolved 119 | } 120 | else { 121 | throw DipError.invalidType(resolved: resolved, key: key.tagged(with: self.context.tag)) 122 | } 123 | } 124 | }) 125 | $0.forwardsTo = definition 126 | }.build() 127 | 128 | register(forwardDefinition, tag: tag) 129 | return forwardDefinition 130 | } 131 | 132 | /// Searches for definition that forwards requested type 133 | func typeForwardingDefinition(forKey key: DefinitionKey) -> KeyDefinitionPair? { 134 | var forwardingDefinitions = self.definitions.map({ (key: $0.0, definition: $0.1) }) 135 | 136 | forwardingDefinitions = filter(definitions: forwardingDefinitions, byKey: key, byTypeOfArguments: true) 137 | forwardingDefinitions = order(definitions: forwardingDefinitions, byTag: key.tag) 138 | 139 | //we need to carry on original tag 140 | return forwardingDefinitions.first.map({ ($0.key.tagged(with: key.tag), $0.definition) }) 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /Sources/Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dip 3 | // 4 | // Copyright (c) 2015 Olivier Halligon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | public enum LogLevel: Int { 26 | case None 27 | case Errors 28 | case Verbose 29 | } 30 | 31 | public var logLevel: LogLevel = .Errors 32 | 33 | public var logger: (LogLevel, Any) -> Void = { print($1) } 34 | 35 | func log(level logLevel: LogLevel, _ message: Any) { 36 | guard logLevel.rawValue <= Dip.logLevel.rawValue else { return } 37 | logger(logLevel, message) 38 | } 39 | 40 | ///Internal protocol used to unwrap optional values. 41 | protocol BoxType { 42 | var unboxed: Any? { get } 43 | } 44 | 45 | extension Optional: BoxType { 46 | var unboxed: Any? { 47 | return self ?? nil 48 | } 49 | } 50 | 51 | 52 | class Box { 53 | var unboxed: T 54 | init(_ value: T) { 55 | self.unboxed = value 56 | } 57 | } 58 | 59 | class NullableBox { 60 | var unboxed: T? 61 | init(_ value: T?) { 62 | self.unboxed = value 63 | } 64 | } 65 | 66 | protocol WeakBoxType { 67 | var unboxed: AnyObject? { get } 68 | } 69 | 70 | class WeakBox: WeakBoxType { 71 | weak var unboxed: AnyObject? 72 | var value: T? { 73 | return unboxed as? T 74 | } 75 | 76 | init(_ value: T?) { 77 | weak var value: AnyObject? = value as AnyObject 78 | self.unboxed = value 79 | } 80 | } 81 | 82 | extension Dictionary { 83 | subscript(key: Key?) -> Value? { 84 | get { 85 | guard let key = key else { return nil } 86 | return self[key] 87 | } 88 | set { 89 | guard let key = key else { return } 90 | self[key] = newValue 91 | } 92 | } 93 | } 94 | 95 | extension Optional { 96 | var desc: String { 97 | return self.map { "\($0)" } ?? "nil" 98 | } 99 | } 100 | 101 | #if !_runtime(_ObjC) 102 | import Glibc 103 | class RecursiveLock { 104 | private var _lock = _initializeRecursiveMutex() 105 | 106 | func lock() { 107 | _lock.lock() 108 | } 109 | 110 | func unlock() { 111 | _lock.unlock() 112 | } 113 | 114 | deinit { 115 | pthread_mutex_destroy(&_lock) 116 | } 117 | 118 | } 119 | 120 | private func _initializeRecursiveMutex() -> pthread_mutex_t { 121 | var mutex: pthread_mutex_t = pthread_mutex_t() 122 | var mta: pthread_mutexattr_t = pthread_mutexattr_t() 123 | pthread_mutexattr_init(&mta) 124 | pthread_mutexattr_settype(&mta, Int32(PTHREAD_MUTEX_RECURSIVE)) 125 | pthread_mutex_init(&mutex, &mta) 126 | return mutex 127 | } 128 | 129 | extension pthread_mutex_t { 130 | mutating func lock() { 131 | pthread_mutex_lock(&self) 132 | } 133 | mutating func unlock() { 134 | pthread_mutex_unlock(&self) 135 | } 136 | } 137 | 138 | #else 139 | import Foundation 140 | typealias RecursiveLock = NSRecursiveLock 141 | #endif 142 | -------------------------------------------------------------------------------- /Tests/DipTests/DefinitionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dip 3 | // 4 | // Copyright (c) 2015 Olivier Halligon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import XCTest 26 | @testable import Dip 27 | 28 | private protocol Service {} 29 | private class ServiceImp: Service {} 30 | 31 | class DefinitionTests: XCTestCase { 32 | 33 | private typealias F1 = () -> Service 34 | private typealias F2 = (String) -> Service 35 | 36 | let tag1 = DependencyContainer.Tag.String("tag1") 37 | let tag2 = DependencyContainer.Tag.String("tag2") 38 | 39 | func testThatDefinitionKeyIsEqualBy_Type_Factory_Tag() { 40 | let equalKey1 = DefinitionKey(type: Service.self, typeOfArguments: F1.self, tag: tag1) 41 | let equalKey2 = DefinitionKey(type: Service.self, typeOfArguments: F1.self, tag: tag1) 42 | 43 | XCTAssertEqual(equalKey1, equalKey2) 44 | XCTAssertEqual(equalKey1.hashValue, equalKey2.hashValue) 45 | } 46 | 47 | func testThatDefinitionKeysWithDifferentTypesAreNotEqual() { 48 | let keyWithDifferentType1 = DefinitionKey(type: Service.self, typeOfArguments: F1.self, tag: nil) 49 | let keyWithDifferentType2 = DefinitionKey(type: AnyObject.self, typeOfArguments: F1.self, tag: nil) 50 | 51 | XCTAssertNotEqual(keyWithDifferentType1, keyWithDifferentType2) 52 | XCTAssertNotEqual(keyWithDifferentType1.hashValue, keyWithDifferentType2.hashValue) 53 | } 54 | 55 | func testThatDefinitionKeysWithDifferentFactoriesAreNotEqual() { 56 | let keyWithDifferentFactory1 = DefinitionKey(type: Service.self, typeOfArguments: F1.self, tag: nil) 57 | let keyWithDifferentFactory2 = DefinitionKey(type: Service.self, typeOfArguments: F2.self, tag: nil) 58 | 59 | XCTAssertNotEqual(keyWithDifferentFactory1, keyWithDifferentFactory2) 60 | XCTAssertNotEqual(keyWithDifferentFactory1.hashValue, keyWithDifferentFactory2.hashValue) 61 | } 62 | 63 | func testThatDefinitionKeysWithDifferentTagsAreNotEqual() { 64 | let keyWithDifferentTag1 = DefinitionKey(type: Service.self, typeOfArguments: F1.self, tag: tag1) 65 | let keyWithDifferentTag2 = DefinitionKey(type: Service.self, typeOfArguments: F1.self, tag: tag2) 66 | 67 | XCTAssertNotEqual(keyWithDifferentTag1, keyWithDifferentTag2) 68 | XCTAssertNotEqual(keyWithDifferentTag1.hashValue, keyWithDifferentTag2.hashValue) 69 | } 70 | 71 | func testThatResolveDependenciesCallsResolveDependenciesBlock() { 72 | var blockCalled = false 73 | 74 | //given 75 | let def = Definition(scope: .unique) { ServiceImp() as Service } 76 | .resolvingProperties { container, service in 77 | blockCalled = true 78 | } 79 | 80 | //when 81 | try! def.resolveProperties(of: ServiceImp(), container: DependencyContainer()) 82 | 83 | //then 84 | XCTAssertTrue(blockCalled) 85 | } 86 | 87 | func testThatResolveDependenciesBlockIsNotCalledWhenPassedWrongInstance() { 88 | var blockCalled = false 89 | 90 | //given 91 | let def = Definition(scope: .unique) { ServiceImp() as Service } 92 | .resolvingProperties { container, service in 93 | blockCalled = true 94 | } 95 | 96 | //when 97 | try! def.resolveProperties(of: String(), container: DependencyContainer()) 98 | 99 | //then 100 | XCTAssertFalse(blockCalled) 101 | } 102 | 103 | func testThatItRegistersOptionalTypesAsForwardedTypes() { 104 | let def = Definition(scope: .unique) { ServiceImp() as Service } 105 | 106 | XCTAssertTrue(def.implementingTypes.contains(where: { $0 == Service?.self })) 107 | } 108 | 109 | } 110 | 111 | -------------------------------------------------------------------------------- /Tests/DipTests/DipUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DipUI 3 | // 4 | // Copyright (c) 2016 Ilya Puchka 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | #if (canImport(UIKit) || canImport(AppKit)) && !SWIFT_PACKAGE 26 | 27 | import XCTest 28 | @testable import Dip 29 | 30 | #if canImport(UIKit) 31 | import UIKit 32 | typealias Storyboard = UIStoryboard 33 | typealias ViewController = UIViewController 34 | typealias StoryboardName = String 35 | 36 | extension UIStoryboard { 37 | @nonobjc 38 | @discardableResult func instantiateViewControllerWithIdentifier(_ identifier: String) -> UIViewController { 39 | return instantiateViewController(withIdentifier: identifier) 40 | } 41 | } 42 | 43 | #else 44 | import AppKit 45 | typealias Storyboard = NSStoryboard 46 | typealias ViewController = NSViewController 47 | typealias StoryboardName = NSStoryboard.Name 48 | 49 | extension NSStoryboard { 50 | @discardableResult func instantiateViewControllerWithIdentifier(_ identifier: String) -> NSViewController { 51 | return instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(identifier)) as! NSViewController 52 | } 53 | } 54 | 55 | #endif 56 | 57 | #if os(iOS) 58 | let storyboardName: StoryboardName = "UIStoryboard" 59 | #elseif os(tvOS) 60 | let storyboardName: StoryboardName = "TVStoryboard" 61 | #else 62 | let storyboardName: StoryboardName = StoryboardName("NSStoryboard") 63 | #endif 64 | 65 | class DipViewController: ViewController, StoryboardInstantiatable {} 66 | class NilTagViewController: ViewController, StoryboardInstantiatable {} 67 | 68 | class DipUITests: XCTestCase { 69 | 70 | let storyboard: Storyboard = { 71 | let bundle = Bundle(for: DipUITests.self) 72 | return Storyboard(name: storyboardName, bundle: bundle) 73 | }() 74 | 75 | func testThatViewControllerHasDipTagProperty() { 76 | let viewController = storyboard.instantiateViewControllerWithIdentifier("DipViewController") 77 | XCTAssertEqual(viewController.dipTag, "vc") 78 | } 79 | 80 | func testThatItDoesNotResolveIfContainerIsNotSet() { 81 | let container = DependencyContainer() 82 | container.register(tag: "vc") { ViewController() } 83 | .resolvingProperties { _, _ in 84 | XCTFail("Should not resolve when container is not set.") 85 | } 86 | 87 | storyboard.instantiateViewControllerWithIdentifier("DipViewController") 88 | } 89 | 90 | func testThatItDoesNotResolveIfTagIsNotSet() { 91 | let container = DependencyContainer() 92 | container.register(tag: "vc") { ViewController() } 93 | .resolvingProperties { _, _ in 94 | XCTFail("Should not resolve when container is not set.") 95 | } 96 | 97 | DependencyContainer.uiContainers = [container] 98 | storyboard.instantiateViewControllerWithIdentifier("ViewController") 99 | } 100 | 101 | func testThatItResolvesIfContainerAndStringTagAreSet() { 102 | var resolved = false 103 | let container = DependencyContainer() 104 | container.register(storyboardType: DipViewController.self, tag: "vc") 105 | .resolvingProperties { _, _ in 106 | resolved = true 107 | } 108 | 109 | DependencyContainer.uiContainers = [container] 110 | storyboard.instantiateViewControllerWithIdentifier("DipViewController") 111 | XCTAssertTrue(resolved, "Should resolve when container and tag are set.") 112 | } 113 | 114 | func testThatItResolvesIfContainerAndNilTagAreSet() { 115 | var resolved = false 116 | let container = DependencyContainer() 117 | container.register(storyboardType: NilTagViewController.self) 118 | .resolvingProperties { _, _ in 119 | resolved = true 120 | } 121 | 122 | DependencyContainer.uiContainers = [container] 123 | storyboard.instantiateViewControllerWithIdentifier("NilTagViewController") 124 | XCTAssertTrue(resolved, "Should resolve when container and nil tag are set.") 125 | } 126 | 127 | func testThatItDoesNotResolveIfTagDoesNotMatch() { 128 | let container = DependencyContainer() 129 | container.register(storyboardType: DipViewController.self, tag: "wrong tag") 130 | .resolvingProperties { _, _ in 131 | XCTFail("Should not resolve when container is not set.") 132 | } 133 | 134 | DependencyContainer.uiContainers = [container] 135 | storyboard.instantiateViewControllerWithIdentifier("DipViewController") 136 | } 137 | 138 | func testThatItResolvesWithDefinitionWithNoTag() { 139 | var resolved = false 140 | let container = DependencyContainer() 141 | container.register(storyboardType: DipViewController.self) 142 | .resolvingProperties { _, _ in 143 | resolved = true 144 | } 145 | 146 | DependencyContainer.uiContainers = [container] 147 | storyboard.instantiateViewControllerWithIdentifier("DipViewController") 148 | XCTAssertTrue(resolved, "Should fallback to definition with no tag.") 149 | } 150 | 151 | func testThatItIteratesUIContainers() { 152 | var resolved = false 153 | let container1 = DependencyContainer() 154 | let container2 = DependencyContainer() 155 | container2.register(storyboardType: DipViewController.self, tag: "vc") 156 | .resolvingProperties { container, _ in 157 | XCTAssertTrue(container === container2) 158 | resolved = true 159 | } 160 | 161 | DependencyContainer.uiContainers = [container1, container2] 162 | storyboard.instantiateViewControllerWithIdentifier("DipViewController") 163 | XCTAssertTrue(resolved, "Should resolve using second container") 164 | } 165 | } 166 | 167 | protocol SomeService: AnyObject { 168 | var delegate: SomeServiceDelegate? { get set } 169 | } 170 | protocol SomeServiceDelegate: AnyObject { } 171 | class SomeServiceImp: SomeService { 172 | weak var delegate: SomeServiceDelegate? 173 | init(delegate: SomeServiceDelegate) { 174 | self.delegate = delegate 175 | } 176 | init(){} 177 | } 178 | 179 | protocol OtherService: AnyObject { 180 | var delegate: OtherServiceDelegate? { get set } 181 | } 182 | protocol OtherServiceDelegate: AnyObject {} 183 | class OtherServiceImp: OtherService { 184 | weak var delegate: OtherServiceDelegate? 185 | init(delegate: OtherServiceDelegate){ 186 | self.delegate = delegate 187 | } 188 | init(){} 189 | } 190 | 191 | 192 | protocol SomeScreen: AnyObject { 193 | var someService: SomeService? { get set } 194 | var otherService: OtherService? { get set } 195 | } 196 | 197 | class ViewControllerImp: SomeScreen, SomeServiceDelegate, OtherServiceDelegate { 198 | var someService: SomeService? 199 | var otherService: OtherService? 200 | init(){} 201 | } 202 | 203 | extension DipUITests { 204 | 205 | func testThatItDoesNotCreateNewInstanceWhenResolvingDependenciesOfExternalInstance() { 206 | let container = DependencyContainer() 207 | 208 | //given 209 | var factoryCalled = false 210 | container.register(.shared) { () -> SomeScreen in 211 | factoryCalled = true 212 | return ViewControllerImp() as SomeScreen 213 | } 214 | 215 | //when 216 | let screen = ViewControllerImp() 217 | try! container.resolveDependencies(of: screen as SomeScreen) 218 | 219 | //then 220 | XCTAssertFalse(factoryCalled, "Container should not create new instance when resolving dependencies of external instance.") 221 | } 222 | 223 | func testThatItResolvesInstanceThatImplementsSeveralProtocols() { 224 | let container = DependencyContainer() 225 | 226 | //given 227 | container.register(.shared) { ViewControllerImp() as SomeScreen } 228 | .resolvingProperties { container, resolved in 229 | 230 | //manually provide resolved instance for the delegate properties 231 | resolved.someService = try container.resolve() as SomeService 232 | resolved.someService?.delegate = resolved as? SomeServiceDelegate 233 | resolved.otherService = try container.resolve(arguments: resolved as! OtherServiceDelegate) as OtherService 234 | } 235 | 236 | container.register(.shared) { SomeServiceImp() as SomeService } 237 | container.register(.shared) { OtherServiceImp(delegate: $0) as OtherService } 238 | 239 | //when 240 | let screen = try! container.resolve() as SomeScreen 241 | 242 | //then 243 | XCTAssertNotNil(screen.someService) 244 | XCTAssertNotNil(screen.otherService) 245 | 246 | XCTAssertTrue(screen.someService?.delegate === screen) 247 | XCTAssertTrue(screen.otherService?.delegate === screen) 248 | } 249 | 250 | } 251 | 252 | #endif 253 | -------------------------------------------------------------------------------- /Tests/DipTests/RuntimeArgumentsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dip 3 | // 4 | // Copyright (c) 2015 Olivier Halligon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import XCTest 26 | @testable import Dip 27 | 28 | private protocol Service { 29 | var name: String { get } 30 | } 31 | 32 | private class ServiceImp: Service { 33 | 34 | let name: String 35 | 36 | init(name: String, baseURL: String, port: Int) { 37 | self.name = name 38 | } 39 | 40 | } 41 | 42 | private class ServiceImp1: Service { 43 | let name: String = "ServiceImp1" 44 | } 45 | 46 | private class ServiceImp2: Service { 47 | let name: String = "ServiceImp2" 48 | } 49 | 50 | class RuntimeArgumentsTests: XCTestCase { 51 | 52 | let container = DependencyContainer() 53 | 54 | override func setUp() { 55 | container.reset() 56 | } 57 | 58 | func testThatItResolvesInstanceWithOneArgument() { 59 | //given 60 | let arg1 = 1 61 | container.register(factory: { (a1: Int) -> Service in 62 | XCTAssertEqual(a1, arg1) 63 | return ServiceImp1() 64 | }) 65 | 66 | //when 67 | let service = try! container.resolve(arguments: arg1) as Service 68 | 69 | //then 70 | XCTAssertTrue(service is ServiceImp1) 71 | 72 | //when 73 | let anyService = try! container.resolve(Service.self, arguments: arg1) 74 | 75 | //then 76 | XCTAssertTrue(anyService is ServiceImp1) 77 | } 78 | 79 | func testThatItResolvesInstanceWithTwoArguments() { 80 | //given 81 | let arg1 = 1, arg2 = 2 82 | container.register { (a1: Int, a2: Int) -> Service in 83 | XCTAssertEqual(a1, arg1) 84 | XCTAssertEqual(a2, arg2) 85 | return ServiceImp1() 86 | } 87 | 88 | //when 89 | let service = try! container.resolve(arguments: arg1, arg2) as Service 90 | 91 | //then 92 | XCTAssertTrue(service is ServiceImp1) 93 | 94 | //when 95 | let anyService = try! container.resolve(Service.self, arguments: arg1, arg2) 96 | 97 | //then 98 | XCTAssertTrue(anyService is ServiceImp1) 99 | } 100 | 101 | func testThatItResolvesInstanceWithThreeArguments() { 102 | let arg1 = 1, arg2 = 2, arg3 = 3 103 | container.register { (a1: Int, a2: Int, a3: Int) -> Service in 104 | XCTAssertEqual(a1, arg1) 105 | XCTAssertEqual(a2, arg2) 106 | XCTAssertEqual(a3, arg3) 107 | return ServiceImp1() 108 | } 109 | 110 | //when 111 | let service = try! container.resolve(arguments: arg1, arg2, arg3) as Service 112 | 113 | //then 114 | XCTAssertTrue(service is ServiceImp1) 115 | 116 | //when 117 | let anyService = try! container.resolve(Service.self, arguments: arg1, arg2, arg3) 118 | 119 | //then 120 | XCTAssertTrue(anyService is ServiceImp1) 121 | } 122 | 123 | func testThatItResolvesInstanceWithFourArguments() { 124 | let arg1 = 1, arg2 = 2, arg3 = 3, arg4 = 4 125 | container.register { (a1: Int, a2: Int, a3: Int, a4: Int) -> Service in 126 | XCTAssertEqual(a1, arg1) 127 | XCTAssertEqual(a2, arg2) 128 | XCTAssertEqual(a3, arg3) 129 | XCTAssertEqual(a4, arg4) 130 | return ServiceImp1() 131 | } 132 | 133 | //when 134 | let service = try! container.resolve(arguments: arg1, arg2, arg3, arg4) as Service 135 | 136 | //then 137 | XCTAssertTrue(service is ServiceImp1) 138 | 139 | //when 140 | let anyService = try! container.resolve(Service.self, arguments: arg1, arg2, arg3, arg4) 141 | 142 | //then 143 | XCTAssertTrue(anyService is ServiceImp1) 144 | } 145 | 146 | func testThatItResolvesInstanceWithFiveArguments() { 147 | let arg1 = 1, arg2 = 2, arg3 = 3, arg4 = 4, arg5 = 5 148 | container.register { (a1: Int, a2: Int, a3: Int, a4: Int, a5: Int) -> Service in 149 | XCTAssertEqual(a1, arg1) 150 | XCTAssertEqual(a2, arg2) 151 | XCTAssertEqual(a3, arg3) 152 | XCTAssertEqual(a4, arg4) 153 | XCTAssertEqual(a5, arg5) 154 | return ServiceImp1() 155 | } 156 | 157 | //when 158 | let service = try! container.resolve(arguments: arg1, arg2, arg3, arg4, arg5) as Service 159 | 160 | //then 161 | XCTAssertTrue(service is ServiceImp1) 162 | 163 | //when 164 | let anyService = try! container.resolve(Service.self, arguments: arg1, arg2, arg3, arg4, arg5) 165 | 166 | //then 167 | XCTAssertTrue(anyService is ServiceImp1) 168 | } 169 | 170 | func testThatItResolvesInstanceWithSixArguments() { 171 | let arg1 = 1, arg2 = 2, arg3 = 3, arg4 = 4, arg5 = 5, arg6 = 6 172 | container.register { (a1: Int, a2: Int, a3: Int, a4: Int, a5: Int, a6: Int) -> Service in 173 | XCTAssertEqual(a1, arg1) 174 | XCTAssertEqual(a2, arg2) 175 | XCTAssertEqual(a3, arg3) 176 | XCTAssertEqual(a4, arg4) 177 | XCTAssertEqual(a5, arg5) 178 | XCTAssertEqual(a6, arg6) 179 | return ServiceImp1() 180 | } 181 | 182 | //when 183 | let service = try! container.resolve(arguments: arg1, arg2, arg3, arg4, arg5, arg6) as Service 184 | 185 | //then 186 | XCTAssertTrue(service is ServiceImp1) 187 | 188 | //when 189 | let anyService = try! container.resolve(Service.self, arguments: arg1, arg2, arg3, arg4, arg5, arg6) 190 | 191 | //then 192 | XCTAssertTrue(anyService is ServiceImp1) 193 | } 194 | 195 | func testThatItRegistersDifferentFactoriesForDifferentNumberOfArguments() { 196 | //given 197 | let arg1 = 1, arg2 = 2 198 | container.register { (a1: Int) in ServiceImp1() as Service } 199 | container.register { (a1: Int, a2: Int) in ServiceImp2() as Service } 200 | 201 | //when 202 | let service1 = try! container.resolve(arguments: arg1) as Service 203 | let service2 = try! container.resolve(arguments: arg1, arg2) as Service 204 | 205 | //then 206 | XCTAssertTrue(service1 is ServiceImp1) 207 | XCTAssertTrue(service2 is ServiceImp2) 208 | } 209 | 210 | func testThatItRegistersDifferentFactoriesForDifferentTypesOfArguments() { 211 | //given 212 | let arg1 = 1, arg2 = "string" 213 | container.register(factory: { (a1: Int) in ServiceImp1() as Service }) 214 | container.register(factory: { (a1: String) in ServiceImp2() as Service }) 215 | 216 | //when 217 | let service1 = try! container.resolve(arguments: arg1) as Service 218 | let service2 = try! container.resolve(arguments: arg2) as Service 219 | 220 | //then 221 | XCTAssertTrue(service1 is ServiceImp1) 222 | XCTAssertTrue(service2 is ServiceImp2) 223 | } 224 | 225 | func testThatItRegistersDifferentFactoriesForDifferentOrderOfArguments() { 226 | //given 227 | let arg1 = 1, arg2 = "string" 228 | container.register { (a1: Int, a2: String) in ServiceImp1() as Service } 229 | container.register { (a1: String, a2: Int) in ServiceImp2() as Service } 230 | 231 | //when 232 | let service1 = try! container.resolve(arguments: arg1, arg2) as Service 233 | let service2 = try! container.resolve(arguments: arg2, arg1) as Service 234 | 235 | //then 236 | XCTAssertTrue(service1 is ServiceImp1) 237 | XCTAssertTrue(service2 is ServiceImp2) 238 | } 239 | 240 | func testThatNewRegistrationWithSameArgumentsOverridesPreviousRegistration() { 241 | //given 242 | let arg1 = 1, arg2 = 2 243 | container.register { (a1: Int, a2: Int) in ServiceImp1() as Service } 244 | let service1 = try! container.resolve(arguments: arg1, arg2) as Service 245 | 246 | //when 247 | container.register { (a1: Int, a2: Int) in ServiceImp2() as Service } 248 | let service2 = try! container.resolve(arguments: arg1, arg2) as Service 249 | 250 | //then 251 | XCTAssertTrue(service1 is ServiceImp1) 252 | XCTAssertTrue(service2 is ServiceImp2) 253 | } 254 | 255 | func testThatDifferentFactoriesRegisteredIfArgumentIsOptional() { 256 | //given 257 | let name1 = "1", name2 = "2" 258 | container.register { (port: Int, url: String) in ServiceImp(name: name1, baseURL: url, port: port) as Service } 259 | container.register { (port: Int, url: String?) in ServiceImp(name: name2, baseURL: url!, port: port) as Service } 260 | 261 | //when 262 | let service1 = try! container.resolve(arguments: 80, "http://example.com") as Service 263 | let service2 = try! container.resolve(arguments: 80, "http://example.com" as String?) as Service 264 | 265 | //then 266 | XCTAssertEqual(service1.name, name1) 267 | XCTAssertEqual(service2.name, name2) 268 | 269 | //Due to incomplete implementation of SE-0054 (bug: https://bugs.swift.org/browse/SR-2143) 270 | //registering definition with T? and T! arguments types will produce two different definitions 271 | //but when argument of T! will be passed to `resolve` method it will be transformed to T? 272 | //and wrong definition will be used 273 | //When fixed using T? and T! should not register two different definitions 274 | 275 | // let name3 = "3" 276 | // container.register { (port: Int, url: String!) in ServiceImp(name: name3, baseURL: url, port: port) as Service } 277 | // let service3 = try! container.resolve(arguments: 80, "http://example.com" as String!) as Service 278 | // XCTAssertEqual(service3.name, name3) 279 | } 280 | 281 | } 282 | 283 | -------------------------------------------------------------------------------- /Tests/DipTests/ThreadSafetyTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dip 3 | // 4 | // Copyright (c) 2015 Olivier Halligon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | #if canImport(ObjectiveC) 26 | import XCTest 27 | @testable import Dip 28 | 29 | private protocol Server: AnyObject { 30 | var client: Client! { get set } 31 | } 32 | 33 | private protocol Client: AnyObject { 34 | var server: Server { get } 35 | } 36 | 37 | private class ClientImp: Client, Equatable { 38 | var server: Server 39 | init(server: Server) { 40 | self.server = server 41 | } 42 | } 43 | 44 | private func ==(lhs: T, rhs: T) -> Bool { 45 | return lhs === rhs 46 | } 47 | 48 | private class ServerImp: Server, Hashable { 49 | weak var client: Client! 50 | init() {} 51 | 52 | func hash(into hasher: inout Hasher) { 53 | hasher.combine(ObjectIdentifier(self)) 54 | } 55 | } 56 | 57 | private func ==(lhs: T, rhs: T) -> Bool { 58 | return lhs === rhs 59 | } 60 | 61 | private var resolvedServers = Set() 62 | private var resolvedClients = Array() 63 | 64 | private var container: DependencyContainer! 65 | 66 | let queue = OperationQueue() 67 | let lock = RecursiveLock() 68 | 69 | private let resolveClientSync: () -> Client? = { 70 | var client: Client? 71 | DispatchQueue.global(qos: .default).sync() { 72 | client = try! container.resolve() as Client 73 | } 74 | return client 75 | } 76 | 77 | let resolveServerAsync = { 78 | let server = try! container.resolve() as Server 79 | lock.lock() 80 | resolvedServers.insert(server as! ServerImp) 81 | lock.unlock() 82 | } 83 | 84 | let resolveClientAsync = { 85 | let client = try! container.resolve() as Client 86 | lock.lock() 87 | resolvedClients.append(client as! ClientImp) 88 | lock.unlock() 89 | } 90 | 91 | class ThreadSafetyTests: XCTestCase { 92 | 93 | override func setUp() { 94 | Dip.logLevel = .Verbose 95 | container = DependencyContainer() 96 | } 97 | 98 | override func tearDown() { 99 | resolvedServers.removeAll() 100 | resolvedClients.removeAll() 101 | } 102 | 103 | func testSingletonThreadSafety() { 104 | container.register(.singleton) { ServerImp() as Server } 105 | 106 | for _ in 0..<100 { 107 | queue.addOperation(resolveServerAsync) 108 | } 109 | 110 | queue.waitUntilAllOperationsAreFinished() 111 | 112 | XCTAssertEqual(resolvedServers.count, 1, "Should create only one instance") 113 | } 114 | 115 | 116 | func testFactoryThreadSafety() { 117 | container.register { ServerImp() as Server } 118 | 119 | for _ in 0..<100 { 120 | queue.addOperation(resolveServerAsync) 121 | } 122 | 123 | queue.waitUntilAllOperationsAreFinished() 124 | 125 | XCTAssertEqual(resolvedServers.count, 100, "All instances should be different") 126 | } 127 | 128 | 129 | func testCircularReferenceThreadSafety() { 130 | container.register { 131 | ClientImp(server: try container.resolve()) as Client 132 | } 133 | 134 | container.register { ServerImp() as Server } 135 | .resolvingProperties { container, server in 136 | server.client = resolveClientSync() 137 | } 138 | 139 | for _ in 0..<100 { 140 | queue.addOperation(resolveClientAsync) 141 | } 142 | 143 | queue.waitUntilAllOperationsAreFinished() 144 | 145 | XCTAssertEqual(resolvedClients.count, 100, "Instances should be not reused in different object graphs") 146 | for client in resolvedClients { 147 | let service = client.server as! ServerImp 148 | let serviceClient = service.client as! ClientImp 149 | XCTAssertEqual(serviceClient, client, "Instances should be reused when resolving single object graph") 150 | } 151 | } 152 | 153 | } 154 | #endif 155 | -------------------------------------------------------------------------------- /Tests/DipTests/Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dip 3 | // 4 | // Copyright (c) 2015 Olivier Halligon 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import XCTest 26 | 27 | #if os(Linux) 28 | typealias NSObject = AnyObject 29 | #endif 30 | 31 | #if os(Linux) 32 | import Glibc 33 | typealias TMain = @convention(c) (UnsafeMutableRawPointer?) -> UnsafeMutableRawPointer? 34 | 35 | private func startThread(_ block: @escaping TMain) -> pthread_t { 36 | var pid: pthread_t = 0 37 | pthread_create(&pid, nil, block, nil) 38 | return pid 39 | } 40 | 41 | func dispatch_async(block: @escaping TMain) -> pthread_t { 42 | return startThread(block) 43 | } 44 | 45 | func dispatch_sync(block: @escaping TMain) -> UnsafeMutableRawPointer? { 46 | var result: UnsafeMutableRawPointer? = UnsafeMutableRawPointer.allocate(byteCount: 1, alignment: 0) 47 | let pid = startThread(block) 48 | pthread_join(pid, &result) 49 | return result 50 | } 51 | 52 | extension pthread_spinlock_t { 53 | mutating func lock() { 54 | pthread_spin_lock(&self) 55 | } 56 | mutating func unlock() { 57 | pthread_spin_unlock(&self) 58 | } 59 | } 60 | #endif 61 | -------------------------------------------------------------------------------- /chocolate-churros.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliSoftware/Dip/7ec008a5c09520b67f07669ad5a753d757f05bbe/chocolate-churros.gif -------------------------------------------------------------------------------- /cinnamon-pretzels-caramel-dipping.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AliSoftware/Dip/7ec008a5c09520b67f07669ad5a753d757f05bbe/cinnamon-pretzels-caramel-dipping.gif --------------------------------------------------------------------------------