はじめに
DIとは、Dependency Injectionの略で、
日本語で『依存性の注入』ということらしいです。
依存性とは?
依存性とは、「クラスAのオブジェクトを動かすためにはクラスBが必要」ということ
クラス間で密結合である状態
依存性の注入とは?
クラスBがなくても、クラスAが動かせること
クラス間で疎結合になるため、下記のようなメリットがあります。
・カスタマイズしやすい
・テストしやすい
・オーナシップが明確
具体的にどうするか?
インスタンス変数にオブジェクトを与えるだけです。
コンストラクターにオブジェクトをセットするか、セッターでオブジェクトをセットするだけです。
実装例
Social.frameworkを利用して、
Twitterまたは、Facebookに投稿する例を利用してご説明します。
1. DIを利用しない場合
(1) Twitterと、Facebook投稿用の構造体を定義します
冗長ですが、本題ではないため、ご容赦ください。
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)
}
}
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単体では動作しない。
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に投稿してみます
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) プロトコルを定義します
プロトコルを定義するところから始めます。
import UIKit
protocol SocialPostOutput {
func postText(viewController: UIViewController, text: String)
func postTextAndImage(viewController: UIViewController, text: String, image: UIImage)
}
(2) Twitterと、Facebookに投稿用の構造体を定義します
DIを利用しない場合の処理は同じですが、SocialPostOutputを実装しています。
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)
}
}
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も動かせます。
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です。
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) モックを実装します
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) ユニットテストを実装します
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とは、インスタンス生成時にオブジェクトを渡し、クラス間の依存関係なくすことのようです。
(セッターでオブジェクトを渡してもよい)
以上です