Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to pass arguments #62

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cartfile
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
github "Swinject/Swinject" ~> 2.4
github "Quick/Nimble"
github "Quick/Quick"
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -221,6 +221,30 @@ container.register(Animal.self) { _ in Cat(name: "Mimi") }

If you implicitly instantiate `UIWindow` and its root view controller, the registrations setup for "Main" storyboard can be shared with the referenced storyboard since `defaultContainer` is configured in `setup` method.

### Arguments
if you want to pass some arguments, you can do it in such way:

1) Register VC
```swift
let container = SwinjectStoryboard.defaultContainer
container.storyboardInitCompletedArgs(AnimalViewController.self) { (r, c, arg1: Int, arg2: SomeValue) in
c.animal = r.resolve(Animal.self)
c.countAnimals = arg1
c.someValue = arg2
}
container.register(Animal.self) { _ in Cat(name: "Mimi") }
```

2) Resolve VC
```swift
let sb = SwinjectStoryboard.create(
name: "Animals", bundle: nil, container: container)
let firstArg: Int = 5
let secondArg: SomeValue = SomeValue()
let catController = sb.instantiateViewController(withIdentifier: "SomeIdentifier", arg1: firstArg, arg2: secondArg) as! AnimalViewController
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be more consistent to have API similar to Swinject, i.e.
instantiateViewController(withIdentifier: "SomeIdentifier", arguments: firstArg, secondArg)

```


## Credits

SwinjectStoryboard is inspired by:
100 changes: 99 additions & 1 deletion Sources/SwinjectStoryboard.swift
Original file line number Diff line number Diff line change
@@ -93,7 +93,7 @@ public class SwinjectStoryboard: _SwinjectStoryboardBase, SwinjectStoryboardProt
return viewController
}

private func injectDependency(to viewController: UIViewController) {
fileprivate func injectDependency(to viewController: UIViewController) {
guard !viewController.wasInjected else { return }
defer { viewController.wasInjected = true }

@@ -105,6 +105,7 @@ public class SwinjectStoryboard: _SwinjectStoryboardBase, SwinjectStoryboardProt
if let container = container.value as? _Resolver {
let option = SwinjectStoryboardOption(controllerType: type(of: viewController))
typealias FactoryType = ((Resolver, Container.Controller)) -> Any

let _ = container._resolve(name: registrationName, option: option) { (factory: FactoryType) in factory((self.container.value, viewController)) as Any } as Container.Controller?
} else {
fatalError("A type conforming Resolver protocol must conform _Resolver protocol too.")
@@ -210,3 +211,100 @@ public class SwinjectStoryboard: _SwinjectStoryboardBase, SwinjectStoryboardProt
}

#endif


extension SwinjectStoryboard {

#if os(iOS) || os(tvOS)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably want to extend this for OSX as well


private func injectDependency<Arg>(to viewController: UIViewController, arg: Arg) {
guard !viewController.wasInjected else { return }
defer { viewController.wasInjected = true }

let registrationName = viewController.swinjectRegistrationName

if let container = container.value as? _Resolver {
let option = SwinjectStoryboardOption(controllerType: type(of: viewController))
typealias FactoryType = ((Resolver, Container.Controller, Arg)) -> Any
let _ = container._resolve(name: registrationName, option: option) { (factory: FactoryType) in factory((self.container.value, viewController, arg)) as Any } as Container.Controller?
} else {
fatalError("A type conforming Resolver protocol must conform _Resolver protocol too.")
}

for child in viewController.children {
injectDependency(to: child)
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a lot of repeated code between methods with different arguments. Can we reuse this code somehow? Alternatively we can generate this similarly to how it is done in Swinject (see. Container.Arguments.erb)


private func injectDependency<Arg1, Arg2>(to viewController: UIViewController, arg1: Arg1, arg2: Arg2) {
guard !viewController.wasInjected else { return }
defer { viewController.wasInjected = true }

let registrationName = viewController.swinjectRegistrationName

if let container = container.value as? _Resolver {
let option = SwinjectStoryboardOption(controllerType: type(of: viewController))
typealias FactoryType = ((Resolver, Container.Controller, Arg1, Arg2)) -> Any
let _ = container._resolve(name: registrationName, option: option) { (factory: FactoryType) in factory((self.container.value, viewController, arg1, arg2)) as Any } as Container.Controller?
} else {
fatalError("A type conforming Resolver protocol must conform _Resolver protocol too.")
}

for child in viewController.children {
injectDependency(to: child)
}
}

private func injectDependency<Arg1, Arg2, Arg3>(to viewController: UIViewController,
arg1: Arg1, arg2: Arg2, arg3: Arg3) {
guard !viewController.wasInjected else { return }
defer { viewController.wasInjected = true }

let registrationName = viewController.swinjectRegistrationName

if let container = container.value as? _Resolver {
let option = SwinjectStoryboardOption(controllerType: type(of: viewController))
typealias FactoryType = ((Resolver, Container.Controller, Arg1, Arg2, Arg3)) -> Any
let _ = container._resolve(name: registrationName, option: option) { (factory: FactoryType) in factory((self.container.value, viewController, arg1, arg2, arg3)) as Any } as Container.Controller?
} else {
fatalError("A type conforming Resolver protocol must conform _Resolver protocol too.")
}

for child in viewController.children {
injectDependency(to: child)
}
}

//MARK: - instantiateViewController with Args

public func instantiateViewController<Arg>(withIdentifier identifier: String,
arg: Arg) -> UIViewController {
let viewController = loadViewController(with: identifier)
injectDependency(to: viewController, arg: arg)
return viewController
}

public func instantiateViewController<Arg1, Arg2>(withIdentifier identifier: String,
arg1: Arg1, arg2: Arg2) -> UIViewController {
let viewController = loadViewController(with: identifier)
injectDependency(to: viewController, arg1: arg1, arg2: arg2)
return viewController
}

public func instantiateViewController<Arg1, Arg2, Arg3>(withIdentifier identifier: String,
arg1: Arg1, arg2: Arg2, arg3: Arg3) -> UIViewController {
let viewController = loadViewController(with: identifier)
injectDependency(to: viewController, arg1: arg1, arg2: arg2, arg3: arg3)
return viewController
}

private func loadViewController(with identifier: String) -> UIViewController {
SwinjectStoryboard.pushInstantiatingStoryboard(self)
let viewController = super.instantiateViewController(withIdentifier: identifier)
SwinjectStoryboard.popInstantiatingStoryboard()
return viewController
}

#endif

}
61 changes: 61 additions & 0 deletions Sources/SwinjectStoryboardExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// SwinjectStoryboardExtensions.swift
// SwinjectStoryboard
//
// Created by Malkevych Bohdan on 10.08.17.
// Copyright © 2017 Swinject Contributors. All rights reserved.
//

import Swinject

#if os(iOS) || os(OSX) || os(tvOS)
extension Container {
/// Adds a registration of the specified view or window controller that is configured in a storyboard.
///
/// - Note: Do NOT explicitly resolve the controller registered by this method.
/// The controller is intended to be resolved by `SwinjectStoryboard` implicitly.
///
/// - Parameters:
/// - controllerType: The controller type to register as a service type.
/// The type is `UIViewController` in iOS, `NSViewController` or `NSWindowController` in OS X.
/// - name: A registration name, which is used to differenciate from other registrations
/// that have the same view or window controller type.
/// - initCompleted: A closure to specifiy how the dependencies of the view or window controller are injected.
/// It is invoked by the `Container` when the view or window controller is instantiated by `SwinjectStoryboard`.

public func storyboardInitCompletedArg<C: Controller, Arg>(_ controllerType: C.Type,
name: String? = nil, initCompleted: @escaping (Resolver, C, Arg) -> ()) {
let factory = { (r: Resolver, c: Controller, arg: Arg) -> Container.Controller in
initCompleted(r, c as! C, arg)
return c
}

let option = SwinjectStoryboardOption(controllerType: controllerType)
_register(Controller.self, factory: factory, name: name, option: option)
}

public func storyboardInitCompletedArgs<C: Controller, Arg1, Arg2>(_ controllerType: C.Type,
name: String? = nil,
initCompleted: @escaping (Resolver, C, Arg1, Arg2) -> ()) {
let factory = { (r: Resolver, c: Controller, arg1: Arg1, arg2: Arg2) -> Container.Controller in
initCompleted(r, c as! C, arg1, arg2)
return c
}

let option = SwinjectStoryboardOption(controllerType: controllerType)
_register(Controller.self, factory: factory, name: name, option: option)
}

public func storyboardInitCompletedArgs<C: Controller, Arg1, Arg2, Arg3>(_ controllerType: C.Type,
name: String? = nil,
initCompleted: @escaping (Resolver, C, Arg1, Arg2, Arg3) -> ()) {
let factory = { (r: Resolver, c: Controller, arg1: Arg1, arg2: Arg2, arg3: Arg3) -> Container.Controller in
initCompleted(r, c as! C, arg1, arg2, arg3)
return c
}

let option = SwinjectStoryboardOption(controllerType: controllerType)
_register(Controller.self, factory: factory, name: name, option: option)
}
}
#endif
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, this file should probably be generated (see. Resolver.erb)

2 changes: 1 addition & 1 deletion SwinjectStoryboard.podspec
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ Pod::Spec.new do |s|
s.homepage = "https://github.com/Swinject/SwinjectStoryboard"
s.license = 'MIT'
s.author = 'Swinject Contributors'
s.source = { :git => "https://github.com/Swinject/SwinjectStoryboard.git", :tag => s.version.to_s }
s.source = { :git => "https://github.com/SeductiveMobile/SwinjectStoryboard.git", :tag => s.version.to_s }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ 😉


core_files = 'Sources/*.{swift,m,h}'
s.ios.source_files = core_files, 'Sources/iOS-tvOS/*.{swift,h,m}'
129 changes: 75 additions & 54 deletions SwinjectStoryboard.xcodeproj/project.pbxproj

Large diffs are not rendered by default.