LoginSignup
6
4

More than 1 year has passed since last update.

protocolについて

Posted at

備忘録

はじめに

ふわっとしか理解していないのでQiitaに書こうと思いました。

protocol

日本語に訳すと規定約束事という意味合いになります。

Swiftにおいては、プロパティやメソッドを定義することができて
クラスや構造体に準拠されること前提で使われます。

protocol HogeProtocol {
    var hoge: String { get }
    func doHoge()
}

・使い方

protocolは冒頭でも述べたようにクラスや構造体などに
準拠されること前提で使われます。

なので使用する場合は準拠することから始まります。

そして、protocolで定義したものは必ず実装しなければなりません

class Hoge: HogeProtocol {
    // 実装しなければエラーが発生する
    // var hoge: String = ""

    func doHoge() {
        print("doHoge")
    }
}

・複数準拠できる

クラスの場合、複数の継承はエラーが発生してしまいます。
ですが、protocolでは複数準拠が可能です。

protocol FugaProtocol {
    var fuga: String { get }
    func doFuga()
}

class HogeFuga: HogeProtocol, FugaProtocol {
    var hoge: String = ""
    var fuga: String = ""

    func doHoge() {
        print("doHoge")
    }

    func doFuga() {
        print("doFuga")
    }
}

・protocolもprotocolを準拠できる

タイトル通り、protocol自身もprotocolを準拠できます

// 先ほど定義したProtocol達をまとめる
protocol HogeFugaProtocol: HogeProtocol, FugaProtocol {
    var hogeFuga: String { get }
}

class HogeFuga: HogeFugaProtocol {
    var hogeFuga: String = ""
    var hoge: String = ""
    var fuga: String = ""

    func doHoge() {
        print("doHoge")
    }

    func doFuga() {
        print("doFuga")
    }
}

・protocol extension

基本的にprotocolはデフォルト実装ができません
なので準拠したクラスなどで具体的な処理内容を記述しなければいけません。

ですが例外があり、extentionによってデフォルト実装が可能になります。

// デフォルト実装を追加する
extension HogeProtocol {
    func doHoge() {
        print("doHoge")
    }
}

// デフォルト実装しているので実装しなくてもエラーが発生しない
class HogeFuga: HogeFugaProtocol {
    var hogeFuga: String = ""
    var hoge: String = ""
    var fuga: String = ""

//    func doHoge() {
//        print("doHoge")
//    }

    func doFuga() {
        print("doFuga")
    }
}

let hogeFuga = HogeFuga()
hogeFuga.doHoge() // "doHoge"

protocolのメリット

protocolについてよく分かったけど
結局何がいいの?どこで使うの??って感じですよね。

protocolのメリットを備忘録を含めて
つらつらと書いていこうと思います。

・共通化できる

例えば、各動物の走ったり泳いだりなどの速さを出力したいとします。

◯protocolを使わない場合

まずは大元のAnimalクラスを作ります。

// 動物クラス
class Animal {
    var runningSpeed: Int?
    var swimmingSpeed: Int?

    func run() {
        guard let runnningSpeed = runnningSpeed else { return }
        print("走るスピードは\(String(describing: runnningSpeed))km/h です")
    }

    func swim() {
        guard let swimmingSpeed = swimmingSpeed else { return }
        print("泳ぐスピードは\(String(describing: swimmingSpeed))km/h です")
    }
}

そして、Humanクラスを作って
Animalクラスを継承します。

// 人間クラス
class Human: Animal {
    override init() {
        super.init()
        runningSpeed = 36
        swimmingSpeed = 8
    }
}

では他にも、色んな動物クラスを追加していきましょう。

// 鳥クラス
class Birds: Animal {

}
// 犬クラス
class Dog: Animal {

}

新たに作ったBirdsクラスでは飛ぶ速さを出力したいと思います。

ということは飛ぶ速さを出力するメソッド等を
大元のAnimalクラスに追加しないといけませんね。

ついでにBirdsクラスDogクラスで速さが出力できるようにしていきましょう。

// 動物クラス
class Animal {
    var runningSpeed: Int?
    var swimmingSpeed: Int?
    var flyingSpeed: Int?

    func run() {
        guard let runnningSpeed = runnningSpeed else { return }
        print("走るスピードは\(String(describing: runnningSpeed))km/h です")
    }

    func swim() {
        guard let swimmingSpeed = swimmingSpeed else { return }
        print("泳ぐスピードは\(String(describing: swimmingSpeed))km/h です")
    }

    func fly() {
        guard let flyingSpeed = flyingSpeed else { return }
        print("飛ぶスピードは\(String(describing: flyingSpeed))km/h です")
    }
}
// 鳥クラス
class Birds: Animal {
    override init() {
        super.init()
        flyingSpeed = 340
    }
}
// 犬クラス
class Dog: Animal {
    override init() {
        super.init()
        runningSpeed = 50
        swimmingSpeed = 4
    }
}

これで出力できそうですね。

ただこれらのコードだと、いくつか欠点があります。

本来、呼び出す必要がないメソッド等が
外部で呼び出せてしまいます

let human =  Human()
let birds = Birds()
let dog = Dog()

human.run() // 走るスピードは36km/h です
human.fly() // 何も出力されない

birds.fly() // 飛ぶスピードは340km/h です

dog.fly() // 何も出力されない

後はAnimalクラスがどんどん肥大化してしまうという点です。

様々なクラスに対応させるために大元のクラスに色々、詰め込んでしまっては膨大なコード量になってしまって
可読性も良くない、あるクラスでは必要ないプロパティやメソッドなどが大量生産されてしまいます。

◯protocolを使った場合

では、protocolで実装していきます。

protocol RunProtocol {
    var runningSpeed: Int { get set }
    func run()
}
// 同じ処理の部分はextentionでデフォルト実装していく
extension RunProtocol {
    func run() {
        print("走るスピードは\(String(describing: runningSpeed))km/h です")
    }
}

protocol SwimProtocol {
    var swimmingSpeed: Int { get set }
    func swim()
}

extension SwimProtocol {
    func swim() {
        print("走るスピードは\(String(describing: swimmingSpeed))km/h です")
    }
}

protocol FlyProtocol {
    var flySpeed: Int { get set }
    func fly()
}

extension FlyProtocol {
    func fly() {
        print("走るスピードは\(String(describing: flySpeed))km/h です")
    }
}

そして、先ほどのクラス達に準拠していきます。

// 動物クラス
class Animal {

}
// 人間クラス
class Human: Animal, RunProtocol, SwimProtocol {
    var runningSpeed: Int = 36
    var swimmingSpeed: Int = 8
}
// 鳥クラス
class Birds: Animal, FlyProtocol {
    var flySpeed: Int = 340
}
// 犬クラス
class Dog: Animal, RunProtocol, SwimProtocol {
    var runningSpeed: Int = 50
    var swimmingSpeed: Int = 4
}

let human =  Human()
let birds = Birds()
let dog = Dog()

human.run() // 走るスピードは36km/h です
human.fly() // エラー

birds.fly() // 走るスピードは340km/h です

dog.fly() // エラー

見て分かるようにprotocolに定義されていないものはエラーとなります。

そして、protocolでは定義したものは必ず実装しなければいけないルールなので
どのプロパティに初期化が必要なのかを考慮しなくてもよくなります。

これで必要ないものが外部で呼び出されることはなくなりました

このように部分的に処理を共通化させる事によって
必要な処理のみを実装できて可読性が向上します。

・ファイル間の依存関係を薄くできる

例えば、TableViewCellをタップした時に
そのCellに付随する値を違うVCに受け渡し、そのVCに画面遷移するとします。

◯protocolを使わない場合

単純に実装すれば以下のようになると思います。

class SecondViewController: UIViewController {
  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        // 受け渡す値
        let model = models[indexPath.row]
        let firstVC = FirstViewController()
        // ここで他のVCに値を受け渡す
        firstVC.text = model.text
        // 画面遷移
        self.navigationController?.popViewController(animated: true)
    }
}

かなり雑ですが、こんな感じで他のVCに値を受け渡すことが出来ます。

ただこのコードだとSecondViewController
FirstViewControllerが存在していないとエラーになります。

いわゆる密結合になっている状態です。
つまり、VC間の依存関係が濃い訳ですね。

◯protocolを使った場合

ここでprotocolを使ったDelegateで依存関係を薄くしていきます。

protocol ToPassDataDelegate: AnyObject {
    func didSelectData(model: Model)
}

class SecondViewController: UIViewController {
  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let model = models[indexPath.row]
        // cellをタップした時に値を渡して、それ以降の処理は任せる
        delegate?.didSelectData(model: model)
        self.navigationController?.popViewController(animated: true)
    }
}

どこにもFirstViewControllerが記述されていないことが分かりますね。
もちろん、存在していなくてもエラーになることはありません

このように依存関係を薄くすることによって
たとえ違うVCに変更したとしても影響がなく、改修しやすいものとなります。

これがいわゆる疎結合ですね。

先ほど出てきたUITableViewクラスも内部でprotocolを使ったDelegateを持っています。
依存関係が薄いからこそ、私たちは様々な処理ができるようになっている訳ですね。

・構造体や列挙型に準拠できる

構造体や列挙型はクラスのように継承が出来ません。

ですが、protocolを使うことで
構造体や列挙型でも同じようなことができます。

protocol HogeProtocol {
  var hoge: String { get }
}

struct Fuga: HogeProtocol {
  var fuga: String
  var hoge: String
}

おわりに

Swiftは唯一の「Protocol指向」のプログラミング言語だそうです。

何か間違っている部分や気になる部分があればコメントして下さると有り難いです。

参考資料

mixi-inc/iOSTraining
Swiftのプロトコルについて
iOSアプリの基本設計を考える:疎結合の概念から構造化、MVVM、RxSwiftまで
Swiftのプロトコルとは?役割・使いどころ・存在意義やメリット。

6
4
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
6
4