Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
111
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

Organization

iOS13ではStoryboardでもDIができる件について

はじめに

これまでのiOS13未満のUIKitのAPIでは、Storyboardで定義したUIViewControllerではinitializerでのDI(Dependency Injection|依存性の注入)ができないという課題がありました。

※initializerでのDIはコンストラクタインジェクションとも呼ばれますが、Swiftでは慣習的にコンストラクタという用語はあまり使わないので、本記事では「initializerでのDI」とします。

UIViewControllerの実装方法とinitializerでのDIの可否をまとめると以下のようになります。
※黒魔術的なライブラリなどを利用しない前提

レイアウト実装方法 initializer DI可否(〜iOS12) initializer DI可否(iOS13〜)
コードベース
XIB
Storyboard ×

iOS13未満での実装方法の詳細については以下の記事で説明しています。
Qiita - iOSとコードベースレイアウト

勿論、プロパティインジェクションならばiOS12以下でもStoryboardで定義したUIViewControllerで可能ですが、initializerでのDIをする設計にすることで以下の恩恵が得られます。

  • DIする値をプロパティで保持する場合に、varでなくletで定義できるのでより安全
  • DIする値をプロパティで保持する場合に、Optional型でなく非Optional型で定義できるので無駄なUnwrapが不要
  • UIViewControllerの生成を行うために、必ずDIが必要になるので、プロパティインジェクションのように設定忘れが起こり得ない

iOS13でStoryboardでイニシャライザでDIする

iOS13では地味にこんなAPIが加わっています。

instantiateInitialViewController(creator:)

func instantiateInitialViewController<ViewController>(creator: ((NSCoder) -> ViewController?)? = nil) 
-> ViewController? where ViewController : UIViewController

このAPIでは、creatorというクロージャーを引数として渡すことで、これまで隠蔽されていたStoryboardからのUIViewControllerの生成過程に介入することができます。

以下のようにUIViewControllerの継承クラスで以下のようにNSCoderを引数にとるinitializerを実装します。

class ViewController: UIViewController {
    @IBOutlet fileprivate weak var textView: UITextView!

    private let dependency: Int

    // DI用のinitializer
    init?(coder: NSCoder, dependency: Int) {
        self.dependency = dependency
        super.init(coder: coder)
    }

    // 独自のinitializerを実装するときにrequiredとして実装が要求されるinitializer
    // 利用しないので、fatalError()として実装を省略する 
    required init?(coder: NSCoder) {
        fatalError()
    }
}

上記のように定義したinitializerをcreatorのクロージャーで利用します。

let storyboard = UIStoryboard(name: "ViewController", bundle: nil)
let viewController = storyboard.instantiateInitialViewController { coder in
    ViewController(coder: coder, dependency: 10)
}

以上のようにすることで、Storyboardで定義したUIViewControllerでもinitializerでのDIが可能です。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
111
Help us understand the problem. What are the problem?