iosswiftdependency-injectionswinject

Using Swinject to inject a Model class instance into a View class instance in Swift


In a Swift 2.0, Single Page Application project, in XCode 7.0.1, with the Swift dependency injection framework Swinject, I am doing the following:

DuplicateProtocol.swift:

protocol DuplicateProtocol { var id: String { get } }

SingletonProtocol.swift:

protocol SingletonProtocol { var id: String { get } }

DuplicateProxy.swift

class DuplicateProxy: DuplicateProtocol {
    let id: String

    init ( id: String ) {
        self.id = "DuplicateProxy." + id
    }
}

SingletonProxy.swift:

class SingletonProxy: SingletonProtocol {
    let id: String

    init ( id: String ) {
        self.id = "SingletonProxy." + id
    }
}

ViewController.swift:

import UIKit

class ViewController: UIViewController
{
    var duplicate: DuplicateProtocol?
    var singleton: SingletonProtocol?

    required init? ( coder aDecoder: NSCoder ) {
        print( "ViewController.init?", duplicate, singleton )
        super.init( coder: aDecoder )
    }

    override func viewDidLoad () {
        print( "ViewController.viewDidLoad", duplicate, singleton )
        super.viewDidLoad()
    }
}

AppDelegate.swift:

import Swinject
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate
{
    var window: UIWindow?

    func application ( application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]? ) -> Bool {

        let container = Container()
        let singleton = SingletonProxy( id: "first" )

        container.register( SingletonProtocol.self ) { _ in singleton }
        container.register( DuplicateProtocol.self ) { _ in DuplicateProxy( id: "second" ) }

        container.registerForStoryboard( ViewController.self ) {
            r, c in c.duplicate = r.resolve( DuplicateProtocol.self )
        }

        print( "AppDelegate.application" )

        return true
    }
}

And this is the console log I am unfortunately getting:

ViewController.init? nil nil
AppDelegate.application
ViewController.viewDidLoad nil nil

Put as simply as I can: what changes do I need to make? Thank you, JBM.


Solution

  • Initializer injection is not available if you want to instantiate the view controller from a storyboard because UIKit framework calls init?(coder:).

    Instead, property injection should be used to instantiate it from a storyboard:

    class SomeViewController: UIViewController {
        var duplicate: DuplicateProtocol?
        var singleton: SingletonProtocol?
    
        required init?(coder aDecoder: NSCoder)
        {
            super.init(coder: aDecoder)
        }
    }
    

    UPDATE

    Still the implicit instantiation of UIWindow and the initial view controller is not supported by Swinject. You need to instantiate them explicitly as the following example does.

    import Swinject
    import UIKit
    
    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
        var window: UIWindow?
        var container: Container {
            let container = Container()
            let singleton = SingletonProxy( id: "first" )
    
            container.register( SingletonProtocol.self ) { _ in singleton }
            container.register( DuplicateProtocol.self ) { _ in DuplicateProxy( id: "second" ) }
    
            container.registerForStoryboard( ViewController.self ) {
                r, c in c.duplicate = r.resolve( DuplicateProtocol.self )
            }
    
            return container
        }
    
        func application ( application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]? ) -> Bool {
            let window = UIWindow(frame: UIScreen.mainScreen().bounds)
            window.makeKeyAndVisible()
            self.window = window
    
            let storyboard = SwinjectStoryboard.create(name: "Main", bundle: nil, container: container)
            window.rootViewController = storyboard.instantiateInitialViewController()
    
            return true
        }
    }
    

    By the way, you don't have to handle the singleton instance by yourself. Swinject has Singleton (aka Container) Object Scope, which can be set with .inObjectScope(.Container) as documented here.

    UPDATE 2

    Swinject version 0.3 supports the implicit instantiation of UIWindow and its root view controller from "Main" storyboard. Here is its documentation.