Needleを使った簡単なDIの検証

Uberが出しているDIライブラリ「needle」を使って簡単なDIの検証をしてみました。
https://github.com/uber/needle

# インストール
needleを利用するためには、コードジェネレーターとアプリに組み込むライブラリの2点のインストールが必要になります。
Carthageを使うと、双方が一気に使えるようになるので、今回Carthageを用いたインストールを行いました。
Cartfileを作成し、以下を追記します。
このタイミングでの最新バージョンがv0.17.2だったため、そのバージョンを指定しています。
```
github "https://github.com/uber/needle.git" ~> 0.17.2
```
実際試して使っていく中で、Carthageでインストールしたコードジェネレーターがうまく動作せず、コードジェネレーター側はHomebrewに切り替えたため、Homebrewを用いたインストールも行いました。
```
brew install needle
```

# アプリ側ライブラリの導入
Carthageを用いた導入を行います。
上記Cartfileを作成した状態で、ターミナルから以下コマンドを実行します。
```
carthage update --use-xcframeworks --platform iOS
```
Carthage/Build/以下に`NeedleFoundation.xcframework`が作成されるので、Xcodeから導入したいターゲットのGeneral > Frameworks, Libraries, and Embedded Content 項目に移動し、上で作成された`NeedleFoundation.xcframework`をドラッグアンドドロップします。

# アプリの雛形作成
今回のケースでは、Main > Root > Home の3画面構成で、Root > Home の画面内でneedleの機能を利用してみます。
Xcodeで新規プロジェクト作成、テンプレートでiOS > Appを選択、InterfaceはStoryboardを選択したプロジェクトの初期状態から始めます。

# データ用の構造体作成
今回の検証では、ユーザーを示す構造体を作成し、ユーザー名だけを保持できるようにします。
```Swift
struct User {
    var name: String
    
    init(name: String) {
        self.name = name
    }
}
```

# Root画面の作成
Root画面の作成のために、DIの構成情報を定義するComponentクラス、ViewControllerクラス、xibファイルの3点を作成します。
ComponentクラスとViewControllerクラスの実装は以下のようにします。
xibファイルはHome画面への遷移用にUIButton1点だけを配置したシンプルなViewを用意します。
```Swift
import NeedleFoundation

final class RootComponent: BootstrapComponent {
    var user: User {
        shared{ User(name: "Taro") }
    }
    
    var rootViewController: RootViewController {
        return RootViewController(homeBuilder: homeBuilder)
    }
    
    var homeBuilder: HomeBuilder {
        return HomeComponent(parent: self)
    }
}
```
```Swift
import UIKit

class RootViewController: UIViewController {
    
    private let homeBuilder: HomeBuilder
    
    required init?(coder: NSCoder) {
        fatalError()
    }
    
    init(homeBuilder: HomeBuilder) {
        self.homeBuilder = homeBuilder
        super.init(nibName: String(describing: RootViewController.self), bundle: nil)
    }

    @IBAction private func homeButtonDidTapped() {
        present(homeBuilder.homeViewController, animated: true)
    }
}
```

# Home画面の作成
Home画面も同様、DIの構成情報を定義するComponentクラス、ViewControllerクラス、xibファイルの3点を作成します。
ComponentクラスとViewControllerクラスの実装は以下のようにします。
xibファイルはユーザーの名前表示用にUILabel1点だけを配置したシンプルなViewを用意します。
```Swift
import NeedleFoundation

protocol HomeBuilder {
    var homeViewController: HomeViewController { get }
}

protocol HomeDependency: Dependency {
    var user: User { get }
}

final class HomeComponent: Component<HomeDependency>, HomeBuilder {
    var homeViewController: HomeViewController {
        return HomeViewController(user: dependency.user)
    }
}
```
```Swift
import UIKit

class HomeViewController: UIViewController {
    
    @IBOutlet private var nameLabel: UILabel?
    
    private let user: User
    
    required init?(coder: NSCoder) {
        fatalError()
    }
    
    init(user: User) {
        self.user = user
        super.init(nibName: String(describing: HomeViewController.self), bundle: nil)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // ユーザー名表示
        nameLabel?.text = user.name
    }

}
```

# Main > Rootの遷移作成
プロジェクト新規作成時に作成されているMain.storyboardとViewControllerからRoot画面に繋がる遷移を作成します。
Main.storyboardにRoot遷移用のUIButtonを1点配置します。
ViewControllerの実装は以下の通りです。
```Swift
import UIKit

class ViewController: UIViewController {
    
    @IBAction private func rootButtonDidTapped() {
        let rootComponent = RootComponent()
        present(rootComponent.rootViewController, animated: true)
    }
}
```

# コードジェネレーターの実行
上記で検証に用いる構成要素が揃ったので、コードジェネレーターでDIの依存関係を解決してくれるファイルの作成を行います。
ターミナルで、プロジェクトフォルダ配下のソースコードが入ってるフォルダ(新規プロジェクト作成後、AppDelegate.swiftがある階層)に移動し以下コマンドを実行します。
```
needle generate NeedleGenerated.swift ./
```
このコマンドを実行すると、同じ階層にNeedleGenerated.swiftというファイルが作成されます。

# 依存関係の解決
上記で生成したNeedleGenerated.swiftをXcode上で、Add Files to "プロジェクト名"の操作を行い、利用できる状態にします。
その後、AppDelegate.swiftの`application(_:didFinishLaunchingWithOptions:)`内で`registerProviderFactories()`の呼び出しを追加します。
```Swift
import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        registerProviderFactories()
        return true
    }
}
```

# アプリ実行
実行すると以下のようにHome画面でユーザー名で登録した「Taro」が表示されており、検証想定していた表示が実現できています。

<img width="300" alt="実行結果" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122007/6f327736-9211-2626-1e5e-8a61b309bcbc.gif">