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
97
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

Organization

[Swift] プロトコル指向って何? サンプルを見ながら勉強してみる

プロトコル指向プログラミングとは?

・プロトコルファーストということ?
・何を実装しなければならないかが明確になるからよい?? (共通化??)
・値型なので、別のオブジェクトから変更されないからセーフティ?? 

よく分からないので、プロトコル指向プログラミングが紹介されている
サンプルコードを見ながら写経してみます。

サンプル

今回は、下記の例を利用してご説明します。

ログイン画面にて、メールアドレスとパスワードを入力し、ログインボタンを押下する。
該当フォームが空の場合は、該当フォームをブルブル震わす。

スクリーンショット 2016-08-15 11.41.55.png

ダメなパターンも含め、思いつく実装案を色々挙げてみました。

(A案)

ブルブル震わすロジックを直接ViewControlerに組み込む

おそらく一番バッドパターン。
新人プログラマがやるパターン。「仮実装なので」といいながら、そのまま最後まで残るパターン。
一事が万事!(★最近良く聞く言葉です。)

ViewController.swift
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var mailAddressTextField: FormTextField!
    @IBOutlet weak var passWordTextField: FormTextField!

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func didTapShake(sender: UIButton) {

        if let mailAddress = mailAddressTextField.text where mailAddress.isEmpty {
            let animation = CABasicAnimation(keyPath: "position")
            animation.duration = 0.05
            animation.repeatCount = 5
            animation.autoreverses = true
            animation.fromValue = NSValue(CGPoint: CGPointMake(mailAddressTextField.center.x - 4.0, mailAddressTextField.center.y))
            animation.toValue = NSValue(CGPoint: CGPointMake(mailAddressTextField.center.x + 4.0, mailAddressTextField.center.y))
            mailAddressTextField.layer.addAnimation(animation, forKey: "position")
        }

        if let passWord = passWordTextField.text where passWord.isEmpty {
            let animation = CABasicAnimation(keyPath: "position")
            animation.duration = 0.05
            animation.repeatCount = 5
            animation.autoreverses = true
            animation.fromValue = NSValue(CGPoint: CGPointMake(passWordTextField.center.x - 4.0, passWordTextField.center.y))
            animation.toValue = NSValue(CGPoint: CGPointMake(passWordTextField.center.x + 4.0, passWordTextField.center.y))
            passWordTextField.layer.addAnimation(animation, forKey: "position")
        }
    }
}

(B案)

ブルブル震わすロジックを直接ViewControlerに組み込むのは変わらないが、
メールアドレスとパスワードが共通処理なので、一応メソッド分割はする。
A案より一歩進んだ段階。

ViewController.swift
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var mailAddressTextField: FormTextField!
    @IBOutlet weak var passWordTextField: FormTextField!

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func didTapShake(sender: UIButton) {

        if let mailAddress = mailAddressTextField.text where mailAddress.isEmpty {
            shake(mailAddressTextField)
        }

        if let passWord = passWordTextField.text where passWord.isEmpty {
            shake(passWordTextField)
        }
    }

    private func shake(view: UIView) {
        let animation = CABasicAnimation(keyPath: "position")
        animation.duration = 0.05
        animation.repeatCount = 5
        animation.autoreverses = true
        animation.fromValue = NSValue(CGPoint: CGPointMake(view.center.x - 4.0, view.center.y))
        animation.toValue = NSValue(CGPoint: CGPointMake(view.center.x + 4.0, view.center.y))
        view.layer.addAnimation(animation, forKey: "position")
    }
}

(C案)

サブクラスを作り、ブルブル震わすメソッドを定義する。
B案より一歩進み、ViewControllerからViewへ切り出そうという意識がある段階。

FormTextField.swift
class FormTextField: UITextField {
    func shake() {
        let animation = CABasicAnimation(keyPath: "position")
        animation.duration = 0.05
        animation.repeatCount = 5
        animation.autoreverses = true
        animation.fromValue = NSValue(CGPoint: CGPointMake(self.center.x - 4.0, self.center.y))
        animation.toValue = NSValue(CGPoint: CGPointMake(self.center.x + 4.0, self.center.y))
        layer.addAnimation(animation, forKey: "position")
    }
}
ViewController.swift
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var mailAddressTextField: FormTextField!
    @IBOutlet weak var passWordTextField: FormTextField!

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func didTapShake(sender: UIButton) {

        if let mailAddress = mailAddressTextField.text where mailAddress.isEmpty {
            mailAddressTextField.shake()
        }

        if let passWord = passWordTextField.text where passWord.isEmpty {
            passWordTextField.shake()
        }
    }
}

(D案)

UIViewのExtensionを作り、ブルブル震わすメソッドを定義する。
Obj-Cでカテゴリ、SwiftでExtensionを覚えたので、使ってみた段階。
個人的は、このパターン乱発してました。(汗)

UIView+Shake.swift
import UIKit

extension UIView {

    func shake() {
        let animation = CABasicAnimation(keyPath: "position")
        animation.duration = 0.05
        animation.repeatCount = 5
        animation.autoreverses = true
        animation.fromValue = NSValue(CGPoint: CGPointMake(self.center.x - 4.0, self.center.y))
        animation.toValue = NSValue(CGPoint: CGPointMake(self.center.x + 4.0, self.center.y))
        layer.addAnimation(animation, forKey: "position")
    }
}
ViewController.swift
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var mailAddressTextField: FormTextField!
    @IBOutlet weak var passWordTextField: FormTextField!

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func didTapShake(sender: UIButton) {

        if let mailAddress = mailAddressTextField.text where mailAddress.isEmpty {
            mailAddressTextField.shake()
        }

        if let passWord = passWordTextField.text where passWord.isEmpty {
            passWordTextField.shake()
        }
    }
}

(E案)

プロトコル指向を採用して、ブルブル震わすメソッドを定義する。
制約付きの拡張なので、UIView以外では使えないから安心。

Shakeable.swift
import UIKit

protocol Shakeable {}

extension Shakeable where Self: UIView {

    func shake() {
        let animation = CABasicAnimation(keyPath: "position")
        animation.duration = 0.05
        animation.repeatCount = 5
        animation.autoreverses = true
        animation.fromValue = NSValue(CGPoint: CGPointMake(self.center.x - 4.0, self.center.y))
        animation.toValue = NSValue(CGPoint: CGPointMake(self.center.x + 4.0, self.center.y))
        layer.addAnimation(animation, forKey: "position")
    }
}
FormTextField.swift
import UIKit

class FormTextField: UITextField, Shakeable {
}
ViewController.swift
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var mailAddressTextField: FormTextField!
    @IBOutlet weak var passWordTextField: FormTextField!

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func didTapShake(sender: UIButton) {

        if let mailAddress = mailAddressTextField.text where mailAddress.isEmpty {
            mailAddressTextField.shake()
        }

        if let passWord = passWordTextField.text where passWord.isEmpty {
            passWordTextField.shake()
        }
    }
}

まとめ

ボトムアップの観点で考えると、
クラス間で共通なプロパティや処理は、まずプロトコルを採用することを考えようということなのでしょうか。

トップダウンの観点で考えると、
ユースケースをプロトコルとして採用することを考えようということなのでしょうか。

有志の方、教えて下さい。

現時点では、御作法として、
プロトコルファーストで設計及び実装していきたいと思います。

・参考サイト
下記のサイトを参考にさせて頂きました。
大変勉強になりました。

https://realm.io/news/appbuilders-natasha-muraschev-practical-protocol-oriented-programming/
https://www.infoq.com/jp/news/2015/06/protocol-oriented-swift

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
97
Help us understand the problem. What are the problem?