Swift Generics

After installing SwiftLint I was going through resolving the issues. Fortunately, I hadn’t too many problems, mostly because it’s still a small project.

Some longish lines did catch my attention though.

enum StoryboardIdentifier: String {
    case splashViewController
    case mainMenuViewController
    case levelSelectViewController
    case optionsViewController
    case gameViewController
}

fileprivate extension UIStoryboard {
    func instantiateAppViewController(withIdentifier identifier: StoryboardIdentifier) -> UIViewController {
        return instantiateViewController(withIdentifier: identifier.rawValue)
    }
}

fileprivate class NavigationAssistant {
    private static let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)

    private static var splashViewController: SplashViewController {
        return (mainStoryboard.instantiateAppViewController(withIdentifier: .splashViewController) as? SplashViewController)!
    }

    private static var mainMenuViewController: MainMenuViewController {
        return (mainStoryboard.instantiateAppViewController(withIdentifier: .mainMenuViewController) as? MainMenuViewController)!
    }

    private static var levelSelectViewController: LevelSelectViewController {
        return (mainStoryboard.instantiateAppViewController(withIdentifier: .levelSelectViewController) as? LevelSelectViewController)!
    }

    private static var optionsViewController: OptionsViewController {
        return (mainStoryboard.instantiateAppViewController(withIdentifier: .optionsViewController) as? OptionsViewController)!
    }

    private static var gameViewController: GameViewController {
        return (mainStoryboard.instantiateAppViewController(withIdentifier: .gameViewController) as? GameViewController)!
    }

As often seems to happen, on revisiting I can see a way to improve it. First off, it's a little bit verbose in the naming. Changing instantiateAppViewController to just instantiate shortens everything up to get it well under the limit. AppViewController is superfluous, named only to stop a naming collision between the extension and the actual class, and ViewController for the thing it is returning. instantiate could possibly be improved upon but it's clear enough in context for the moment.

That’s minor stuff though. What really caught my eye is that I’m repeating the same code over and over. That cast is kind of messy since I’m kind of cheating SwiftLint there. Without that I had return mainStoryboard.instantiateAppViewController(withIdentifier: .splashViewController) as! SplashViewController which is marginally shorter but it’s not really the problem. What I don’t like is that it’s the same call each time with the only change being the Type I’m casting to. If, instead of casting back as a UIViewController, I was to cast back as the type I was expecting then that would save a bunch of repeated code.

I’ve used generics a bunch of times but I keep forgetting about them - at least when I’m first writing out the code. That’s not necessarily a bad thing since the current way works and I’ve come back to it eventually to clean things up. That’s partly my reasoning for writing this post though, to remember to use these things (appropriately).

So, modify the call which returns the UIViewController to return a generic. Easy:

fileprivate extension UIStoryboard {
    func instantiate<T>(withIdentifier identifier: StoryboardIdentifier) -> T {
        return (instantiateViewController(withIdentifier: identifier.rawValue) as? T)!
    }
}

Fairly straight forward, still have that ugly ‘cast to optional, force unwrap’ to appease SwiftLint. Wait a second, SwiftLint also has a rule about force unwrapping! Optional, but maybe there’s a better way.

guard let viewController = instantiateViewController(withIdentifier: identifier.rawValue) as? T else {
    fatalError("Could not instantiate viewController")
 }
return viewController

There we go. Now it’s explicit what the behaviour is meant to be. It’s a little more code but no-one scanning the code should be confused by the intent.

So the whole thing looks like this now. Much better. The repeated logic is now in a single place. I'm happier with how it looks and, whilst there might be more lines, it feels more compact. I think there are some other improvements that can be made but that goes outside the scope of this post.

enum StoryboardIdentifier: String {
    case splashViewController
    case mainMenuViewController
    case levelSelectViewController
    case optionsViewController
    case gameViewController
}

fileprivate extension UIStoryboard {
    func instantiate<T>(withIdentifier identifier: StoryboardIdentifier) -> T {
        guard let viewController = instantiateViewController(withIdentifier: identifier.rawValue) as? T else {
            fatalError("Could not instantiate viewController")
        }
        return viewController
    }
}

fileprivate class NavigationAssistant {
    private static let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)

    private static var splashViewController: SplashViewController {
        return mainStoryboard.instantiate(withIdentifier: .splashViewController)
    }

    private static var mainMenuViewController: MainMenuViewController {
        return mainStoryboard.instantiate(withIdentifier: .mainMenuViewController)
    }

    private static var levelSelectViewController: LevelSelectViewController {
        return mainStoryboard.instantiate(withIdentifier: .levelSelectViewController)
    }

    private static var optionsViewController: OptionsViewController {
        return mainStoryboard.instantiate(withIdentifier: .optionsViewController)
    }

    private static var gameViewController: GameViewController {
        return mainStoryboard.instantiatZ(withIdentifier: .gameViewController)
    }