You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

252 lines
8.4 KiB

2 years ago
  1. SwinjectAutoregistration
  2. ========
  3. [![Build Status](https://travis-ci.org/Swinject/SwinjectAutoregistration.svg?branch=master)](https://travis-ci.org/Swinject/SwinjectAutoregistration)
  4. [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
  5. [![CocoaPods Version](https://img.shields.io/cocoapods/v/SwinjectAutoregistration.svg?style=flat)](http://cocoapods.org/pods/SwinjectAutoregistration)
  6. [![License](https://img.shields.io/cocoapods/l/SwinjectAutoregistration.svg?style=flat)](http://cocoapods.org/pods/SwinjectAutoregistration)
  7. [![Platform](https://img.shields.io/cocoapods/p/SwinjectAutoregistration.svg?style=flat)](http://cocoapods.org/pods/SwinjectAutoregistration)
  8. [![Swift Version](https://img.shields.io/badge/Swift-5-F16D39.svg?style=flat)](https://developer.apple.com/swift)
  9. SwinjectAutoregistration is an extension of Swinject that allows to automatically register your services and greatly reduce the amount of boilerplate code.
  10. ## Requirements
  11. - iOS 8.0+ / Mac OS X 10.10+ / tvOS 9.0+
  12. - Xcode 8+
  13. ## Installation
  14. Swinject is available through [Carthage](https://github.com/Carthage/Carthage), [CocoaPods](https://cocoapods.org) or [Swift Package Manager](https://swift.org/package-manager/).
  15. ### Carthage
  16. To install Swinject with Carthage, add the following line to your `Cartfile`.
  17. ```
  18. github "Swinject/Swinject" "2.7.0"
  19. github "Swinject/SwinjectAutoregistration" "2.7.0"
  20. ```
  21. 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).
  22. ### CocoaPods
  23. To install Swinject with CocoaPods, add the following lines to your `Podfile`.
  24. ```ruby
  25. source 'https://github.com/CocoaPods/Specs.git'
  26. platform :ios, '8.0' # or platform :osx, '10.10' if your target is OS X.
  27. use_frameworks!
  28. pod 'Swinject', '2.7.0'
  29. pod 'SwinjectAutoregistration', '2.7.0'
  30. ```
  31. Then run `pod install` command. For details of the installation and usage of CocoaPods, visit [its official website](https://cocoapods.org).
  32. ### Swift Package Manager
  33. in `Package.swift` add the following:
  34. ```swift
  35. dependencies: [
  36. .package(url: "https://github.com/Swinject/SwinjectAutoregistration.git", from: "2.7.0")
  37. ],
  38. targets: [
  39. .target(
  40. name: "MyProject",
  41. dependencies: [..., "SwinjectAutoregistration"]
  42. )
  43. ...
  44. ]
  45. ```
  46. ### Registration
  47. Here is a simple example to auto-register a pet owner
  48. ```swift
  49. let container = Container()
  50. container.register(Animal.self) { _ in Cat(name: "Mimi") } // Regular register method
  51. container.autoregister(Person.self, initializer: PetOwner.init) // Autoregistration
  52. ```
  53. where PetOwner looks like this:
  54. ```swift
  55. class PetOwner: Person {
  56. let pet: Animal
  57. init(pet: Animal) {
  58. self.pet = pet
  59. }
  60. }
  61. ```
  62. 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.
  63. Autoregistration becomes especially useful when used to register services with many dependencies. Compare autoregistration code:
  64. ```swift
  65. container.autoregister(MyService.self, initializer: MyService.init)
  66. ```
  67. with equivalent code in pure Swinject:
  68. ```swift
  69. container.register(MyService.self) { r in
  70. MyService(dependencyA: r.resolve(DependencyA.self)!, dependencyB: r.resolve(DependencyB.self)!, dependencyC: r.resolve(DependencyC.self)!, dependencyD: r.resolve(DependencyD.self)!)
  71. }
  72. ```
  73. Another advantage is that if you add more dependencies during the development the registration code doesn't have to be rewritten.
  74. #### Registration with name
  75. Service can be also given name - same as with the regular register method.
  76. ```swift
  77. container.autoregister(Person.self, name: "johnny", initializer: PetOwner.init)
  78. ```
  79. #### Arguments
  80. 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:
  81. ```swift
  82. class PetOwner: Person {
  83. let name: String
  84. let pet: Animal
  85. init(name: String, pet: Animal) {
  86. self.name = name
  87. self.pet = pet
  88. }
  89. }
  90. ```
  91. And registered like this
  92. ```swift
  93. container.autoregister(Person.self, argument: String.self, initializer: PetOwner.init)
  94. ```
  95. 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.
  96. To also pass pet as argument you can call
  97. ```swift
  98. container.autoregister(Person.self, arguments: String.self, Animal.self, initializer: PetOwner.init)
  99. //or
  100. container.autoregister(Person.self, arguments: Animal.self, String.self, initializer: PetOwner.init)
  101. ```
  102. > 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.
  103. ### What kind of sorcery is this?
  104. 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:
  105. ```swift
  106. public func autoregister<Service, A, B>(_ service: Service.Type, initializer: (A, B) -> Service) -> ServiceEntry<Service> {
  107. return self.register(service.self, factory: { r in
  108. return initializer(r.resolve(A.self)!, r.resolve(B.self)!)
  109. } as (ResolverType) -> Service)
  110. }
  111. ```
  112. 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.
  113. ### Operators ###
  114. This extension also aims to reduce the amount of boilerplate while improving readability of the registration code. For that reason the operator `~>` is introduced.
  115. ```swift
  116. Petowner(pet: r~>)
  117. // equivalent to
  118. Petowner(pet: r.resolve(Animal.self)!)
  119. ```
  120. The dependency is again inferred from the type in the initializer. To specify a concrete class you can use:
  121. ```swift
  122. Petowner(pet: r ~> Cat.self)
  123. ```
  124. To use a named service:
  125. ```swift
  126. Petowner(pet: r ~> (Cat.self, name: "mimi"))
  127. ```
  128. or to pass argument/s:
  129. ```swift
  130. Petowner(pet: r ~> (Cat.self, argument: "Mimi"))
  131. Petowner(pet: r ~> (Cat.self, arguments: ("Mimi", UIColor.black)))
  132. ```
  133. ### Limitations ###
  134. 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.
  135. The solution is to specify the initializer like this:
  136. ```
  137. container.autoregister(Person.self, initializer: PetOwner.init(name:pet:))
  138. ```
  139. 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:
  140. ```swift
  141. container.register(Animal.self, name: "mimi") { _ in Cat(name: "Mimi") }
  142. container.register(Animal.self, name: "charles") { _ in Cat(name: "Charles") }
  143. container.register(Person.self) {
  144. PetOwner(pet: r.resolve(Animal.self, name: "mimi")
  145. }
  146. ```
  147. ### Swift 5.3
  148. Since Swift 5.3 the compiler behaves differently when infering initializers in structs that have variables with a default value:
  149. ```swift
  150. struct Cat {
  151. let height: Int = 50
  152. }
  153. ```
  154. Compiler will generate two init functions:
  155. `Cat.init` and `Cat.init(height:)`
  156. Since the Swift 5.3 the following registration
  157. ```swift
  158. container.autoregister(Animal.self, initializer: Cat.init)
  159. ```
  160. will try to use the `Cat.init(height:)` which will then fail with `Unresolved service: Int Initializer: (Int) -> Animal`
  161. Solution is to make the compiler use the init without a parameter
  162. ```swift
  163. container.autoregister(Animal.self, initializer: Cat.init as () -> Cat)
  164. ```
  165. ## For Maintainers
  166. ### Making a new release version
  167. Our release procedure is described as [Makefile](https://github.com/Swinject/SwinjectAutoregistration/blob/master/Makefile). Run `make help` coomand for more info.
  168. ## Credits
  169. SwinjectAutoregistration generics is inspired by:
  170. - [Curry](https://github.com/thoughtbot/Curry) - [Thoughtbot](https://thoughtbot.com/)
  171. ## License
  172. MIT license. See the [LICENSE file](LICENSE) for details.