[Swift] DIって何？ 実践編

# はじめに
DIとは、Dependency Injectionの略で、
日本語で『依存性の注入』ということらしいです。

## 依存性とは？
依存性とは、「クラスAのオブジェクトを動かすためにはクラスBが必要」ということ
クラス間で密結合である状態

## 依存性の注入とは？
クラスBがなくても、クラスAが動かせること
クラス間で疎結合になるため、下記のようなメリットがあります。

・カスタマイズしやすい
・テストしやすい
・オーナシップが明確


## 具体的にどうするか？
インスタンス変数にオブジェクトを与えるだけです。
コンストラクターにオブジェクトをセットするか、セッターでオブジェクトをセットするだけです。

# 実装例
Social.frameworkを利用して、
Twitterまたは、Facebookに投稿する例を利用してご説明します。

## 1. DIを利用しない場合

### (1) Twitterと、Facebook投稿用の構造体を定義します
冗長ですが、本題ではないため、ご容赦ください。

```swift:TwitterClient.swift
import Foundation
import Social

struct TwitterClient {

    func postText(viewController: UIViewController, text: String) {
        post(viewController, text: text)
    }

    func postTextAndImage(viewController: UIViewController, text: String, image: UIImage) {
        post(viewController, text: text, image: image)
    }

    private func post(viewController: UIViewController, text: String, image: UIImage? = nil) {

        let composeViewController = SLComposeViewController(forServiceType: SLServiceTypeTwitter)
        composeViewController.setInitialText(text)
        composeViewController.addImage(image)
        viewController.presentViewController(composeViewController, animated: true, completion: nil)
    }
}
```

```swift:FacebookClient.swift
import Foundation
import Social

struct FacebookClient {

    func postText(viewController: UIViewController, text: String) {
        post(viewController, text: text)
    }

    func postTextAndImage(viewController: UIViewController, text: String, image: UIImage) {
        post(viewController, text: text, image: image)
    }

    private func post(viewController: UIViewController, text: String, image: UIImage? = nil) {

        let composeViewController = SLComposeViewController(forServiceType: SLServiceTypeFacebook)
        composeViewController.setInitialText(text)
        composeViewController.addImage(image)
        viewController.presentViewController(composeViewController, animated: true, completion: nil)
    }
}
```

### (2) Twitterまたは、Facebook用クライアントのインスタンスを生成後、対象のメソッドを呼びます。
*ここがポイント*
SocialPresenterクラスは、TwitterClientと依存関係があり、
SocialPresenter単体では動作しない。

```swift:SocialPresenter.swift
import Foundation

class SocialPresenter: NSObject {

    let client = TwitterClient()

    func postText(viewController: UIViewController, text: String) {
        client.postText(viewController, text: text)
    }

    func postTextAndImage(viewController: UIViewController, text: String, image: UIImage) {
        client.postTextAndImage(viewController, text: text, image: image)
    }
}
```

### (3) ViewControllerからTwitterに投稿してみます

```swift:ViewController.swift
import UIKit

class ViewController: UIViewController {

    var presenter: SocialPresenter?

    override func viewDidLoad() {
        super.viewDidLoad()
        presenter = SocialPresenter()
    }

    @IBAction func didTapButton(sender: UIButton) {
        presenter?.postText(self, text: "スマートデバイス・テクノロジー")
    }
}
```

## 2. DIを利用する場合
### (1) プロトコルを定義します
プロトコルを定義するところから始めます。

```swift:SocialPostOutput.swift
import UIKit

protocol SocialPostOutput {
    func postText(viewController: UIViewController, text: String)
    func postTextAndImage(viewController: UIViewController, text: String, image: UIImage)
}
```

### (2) Twitterと、Facebookに投稿用の構造体を定義します
DIを利用しない場合の処理は同じですが、SocialPostOutputを実装しています。

```swift:TwitterClient.swift
import Foundation
import Social

struct TwitterClient: SocialPostOutput {

    func postText(viewController: UIViewController, text: String) {
        post(viewController, text: text)
    }

    func postTextAndImage(viewController: UIViewController, text: String, image: UIImage) {
        post(viewController, text: text, image: image)
    }

    private func post(viewController: UIViewController, text: String, image: UIImage? = nil) {

        let composeViewController = SLComposeViewController(forServiceType: SLServiceTypeTwitter)
        composeViewController.setInitialText(text)
        composeViewController.addImage(image)
        viewController.presentViewController(composeViewController, animated: true, completion: nil)
    }
}
```

```swift:FacebookClient.swift
import Foundation
import Social

struct FacebookClient: SocialPostOutput {

    func postText(viewController: UIViewController, text: String) {
        post(viewController, text: text)
    }

    func postTextAndImage(viewController: UIViewController, text: String, image: UIImage) {
        post(viewController, text: text, image: image)
    }

    private func post(viewController: UIViewController, text: String, image: UIImage? = nil) {

        let composeViewController = SLComposeViewController(forServiceType: SLServiceTypeFacebook)
        composeViewController.setInitialText(text)
        composeViewController.addImage(image)
        viewController.presentViewController(composeViewController, animated: true, completion: nil)
    }
}
```

### (3) DIを使ってクラスを定義します
*ここがポイント*
『インスタンス生成時に、コンストラクタの引数にプロトコルを渡すこと』

これにより、TwitterClientやFacebookClientに依存せず、
SocialPresenterを動作することができます。

勿論、ユニットテスト用のMocSocialClientも動かせます。

```swift:SocialPresenter.swift
import Foundation

class SocialPresenter: NSObject {

    var output: SocialPostOutput?

    init(output: SocialPostOutput) {
        super.init()
        self.output = output
    }

    func postText(viewController: UIViewController, text: String) {
        output?.postText(viewController, text: text)
    }

    func postTextAndImage(viewController: UIViewController, text: String, image: UIImage) {
        output?.postTextAndImage(viewController, text: text, image: image)
    }
}
```
###  (4) ViewControllerからTwitterに投稿してみます
Twitterに投稿したいなら、TwitterClientを
Facebookに投稿したいなら、FacebookClientを引数に渡せばOKです。

```swift:ViewController.swift
import UIKit

class ViewController: UIViewController {

    var presenter: SocialPresenter?

    override func viewDidLoad() {
        super.viewDidLoad()

        // ここがポイント 
        presenter = SocialPresenter(output: TwitterClient())
    }

    @IBAction func didTapButton(sender: UIButton) {
        presenter?.postText(self, text: "スマートデバイス・テクノロジー")
    }
}
```

### (5) モックを実装します

```swift:MocSocialClient.swift
import UIKit

class MocSocialClient: SocialPostOutput {

    var post_wasCalled = false
    var post_wasCalled_withArgs: (viewController: UIViewController, text: String, image: UIImage?)? = nil

    func postText(viewController: UIViewController, text: String) {
        post_wasCalled = true
        post_wasCalled_withArgs = (viewController: viewController, text: text, image: nil)
    }

    func postTextAndImage(viewController: UIViewController, text: String, image: UIImage) {
        post_wasCalled = true
        post_wasCalled_withArgs = (viewController: viewController, text: text, image: image)
    }
}
```

### (6) ユニットテストを実装します

```swift:DIDemoTests.swift
import XCTest
@testable import DIDemo

class DIDemoTests: XCTestCase {
    
    override func setUp() {
        super.setUp()
    }
    
    override func tearDown() {
        super.tearDown()
    }
    
    func testPostText() {

        let client = MocSocialClient()
        client.postText(UIViewController(), text: "スマートデバイス・テクノロジー")

        XCTAssertTrue(client.post_wasCalled)
        XCTAssertEqual("スマートデバイス・テクノロジー", client.post_wasCalled_withArgs?.text)
        XCTAssertNil(client.post_wasCalled_withArgs?.image)
    }
}
```

# まとめ
DIの概念と、実装例についてご説明しましたが、如何だったでしょうか。

つまり、DIとは、インスタンス生成時にオブジェクトを渡し、クラス間の依存関係なくすことのようです。
（セッターでオブジェクトを渡してもよい）

以上です