SwinjectAutoregistration ======== [![Build Status](https://travis-ci.org/Swinject/SwinjectAutoregistration.svg?branch=master)](https://travis-ci.org/Swinject/SwinjectAutoregistration) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![CocoaPods Version](https://img.shields.io/cocoapods/v/SwinjectAutoregistration.svg?style=flat)](http://cocoapods.org/pods/SwinjectAutoregistration) [![License](https://img.shields.io/cocoapods/l/SwinjectAutoregistration.svg?style=flat)](http://cocoapods.org/pods/SwinjectAutoregistration) [![Platform](https://img.shields.io/cocoapods/p/SwinjectAutoregistration.svg?style=flat)](http://cocoapods.org/pods/SwinjectAutoregistration) [![Swift Version](https://img.shields.io/badge/Swift-5-F16D39.svg?style=flat)](https://developer.apple.com/swift) SwinjectAutoregistration is an extension of Swinject that allows to automatically register your services and greatly reduce the amount of boilerplate code. ## Requirements - iOS 8.0+ / Mac OS X 10.10+ / tvOS 9.0+ - Xcode 8+ ## Installation Swinject is available through [Carthage](https://github.com/Carthage/Carthage), [CocoaPods](https://cocoapods.org) or [Swift Package Manager](https://swift.org/package-manager/). ### Carthage To install Swinject with Carthage, add the following line to your `Cartfile`. ``` github "Swinject/Swinject" "2.7.0" github "Swinject/SwinjectAutoregistration" "2.7.0" ``` Then run `carthage update --use-xcframeworks --no-use-binaries` command or just `carthage update --use-xcframeworks`. For details of the installation and usage of Carthage, visit [its project page](https://github.com/Carthage/Carthage). ### CocoaPods To install Swinject with CocoaPods, add the following lines to your `Podfile`. ```ruby source 'https://github.com/CocoaPods/Specs.git' platform :ios, '8.0' # or platform :osx, '10.10' if your target is OS X. use_frameworks! pod 'Swinject', '2.7.0' pod 'SwinjectAutoregistration', '2.7.0' ``` Then run `pod install` command. For details of the installation and usage of CocoaPods, visit [its official website](https://cocoapods.org). ### Swift Package Manager in `Package.swift` add the following: ```swift dependencies: [ .package(url: "https://github.com/Swinject/SwinjectAutoregistration.git", from: "2.7.0") ], targets: [ .target( name: "MyProject", dependencies: [..., "SwinjectAutoregistration"] ) ... ] ``` ### Registration Here is a simple example to auto-register a pet owner ```swift let container = Container() container.register(Animal.self) { _ in Cat(name: "Mimi") } // Regular register method container.autoregister(Person.self, initializer: PetOwner.init) // Autoregistration ``` where PetOwner looks like this: ```swift class PetOwner: Person { let pet: Animal init(pet: Animal) { self.pet = pet } } ``` The `autoregister` function is given the `PetOwner` initializer `init(pet:Animal)`. From its signature Swinject knows that it needs a dependency `Animal` and resolves it from the container. Nothing else is needed. Autoregistration becomes especially useful when used to register services with many dependencies. Compare autoregistration code: ```swift container.autoregister(MyService.self, initializer: MyService.init) ``` with equivalent code in pure Swinject: ```swift container.register(MyService.self) { r in MyService(dependencyA: r.resolve(DependencyA.self)!, dependencyB: r.resolve(DependencyB.self)!, dependencyC: r.resolve(DependencyC.self)!, dependencyD: r.resolve(DependencyD.self)!) } ``` Another advantage is that if you add more dependencies during the development the registration code doesn't have to be rewritten. #### Registration with name Service can be also given name - same as with the regular register method. ```swift container.autoregister(Person.self, name: "johnny", initializer: PetOwner.init) ``` #### Arguments You can also use auto-registration for services with dynamic arguments. Pet owner whose name needs to be passed as argument is defined like this: ```swift class PetOwner: Person { let name: String let pet: Animal init(name: String, pet: Animal) { self.name = name self.pet = pet } } ``` And registered like this ```swift container.autoregister(Person.self, argument: String.self, initializer: PetOwner.init) ``` Swinject will register `Person` with the argument of type `String`. When `container.resolve(Person.self, argument: "Michael")` is called Swinject won't try to resolve `String` as dependency but instead pass "Michael" as the name. To also pass pet as argument you can call ```swift container.autoregister(Person.self, arguments: String.self, Animal.self, initializer: PetOwner.init) //or container.autoregister(Person.self, arguments: Animal.self, String.self, initializer: PetOwner.init) ``` > The order of the arguments listed is interchangeable. The auto-registration can't be used with more arguments and/or dependencies of the same type. ### What kind of sorcery is this? Wondering how does that work? Generics are heavily leveraged for the auto-registration. For registering service with two dependencies something similar to a following function is used: ```swift public func autoregister(_ service: Service.Type, initializer: (A, B) -> Service) -> ServiceEntry { return self.register(service.self, factory: { r in return initializer(r.resolve(A.self)!, r.resolve(B.self)!) } as (ResolverType) -> Service) } ``` The initializer is a function like any other. By passing it as a parameter its dependencies can be inferred as `(A, B)` and automatically resolved. These functions are generated for up to 20 dependencies. Checkout the [code](https://github.com/Swinject/SwinjectAutoregistration/blob/master/Sources/AutoRegistration.swift) for more info. ### Operators ### This extension also aims to reduce the amount of boilerplate while improving readability of the registration code. For that reason the operator `~>` is introduced. ```swift Petowner(pet: r~>) // equivalent to Petowner(pet: r.resolve(Animal.self)!) ``` The dependency is again inferred from the type in the initializer. To specify a concrete class you can use: ```swift Petowner(pet: r ~> Cat.self) ``` To use a named service: ```swift Petowner(pet: r ~> (Cat.self, name: "mimi")) ``` or to pass argument/s: ```swift Petowner(pet: r ~> (Cat.self, argument: "Mimi")) Petowner(pet: r ~> (Cat.self, arguments: ("Mimi", UIColor.black))) ``` ### Limitations ### When a service has multiple initializers, swift compiler can't be sure which should be used and you will get a `ambigious use of init(x: y: z:)`. This can also happen if the service is extending another class that have initializer with the same number of arguments. The solution is to specify the initializer like this: ``` container.autoregister(Person.self, initializer: PetOwner.init(name:pet:)) ``` Auto-registration **can't** be used with **named dependencies** in their initializers. There is no way to get a name of dependency from the initializer. For example, following code can't be auto-registered: ```swift container.register(Animal.self, name: "mimi") { _ in Cat(name: "Mimi") } container.register(Animal.self, name: "charles") { _ in Cat(name: "Charles") } container.register(Person.self) { PetOwner(pet: r.resolve(Animal.self, name: "mimi") } ``` ### Swift 5.3 Since Swift 5.3 the compiler behaves differently when infering initializers in structs that have variables with a default value: ```swift struct Cat { let height: Int = 50 } ``` Compiler will generate two init functions: `Cat.init` and `Cat.init(height:)` Since the Swift 5.3 the following registration ```swift container.autoregister(Animal.self, initializer: Cat.init) ``` will try to use the `Cat.init(height:)` which will then fail with `Unresolved service: Int Initializer: (Int) -> Animal` Solution is to make the compiler use the init without a parameter ```swift container.autoregister(Animal.self, initializer: Cat.init as () -> Cat) ``` ## For Maintainers ### Making a new release version Our release procedure is described as [Makefile](https://github.com/Swinject/SwinjectAutoregistration/blob/master/Makefile). Run `make help` coomand for more info. ## Credits SwinjectAutoregistration generics is inspired by: - [Curry](https://github.com/thoughtbot/Curry) - [Thoughtbot](https://thoughtbot.com/) ## License MIT license. See the [LICENSE file](LICENSE) for details.