105
88

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[Swift] DIって何? 実践編

Last updated at Posted at 2016-08-11

はじめに

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

依存性とは?

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

依存性の注入とは?

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

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

具体的にどうするか?

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

実装例

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

1. DIを利用しない場合

(1) Twitterと、Facebook投稿用の構造体を定義します

冗長ですが、本題ではないため、ご容赦ください。

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)
    }
}
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単体では動作しない。

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に投稿してみます

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) プロトコルを定義します

プロトコルを定義するところから始めます。

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を実装しています。

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)
    }
}
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も動かせます。

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です。

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) モックを実装します

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) ユニットテストを実装します

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とは、インスタンス生成時にオブジェクトを渡し、クラス間の依存関係なくすことのようです。
(セッターでオブジェクトを渡してもよい)

以上です

105
88
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
105
88

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?