4
5

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 3 years have passed since last update.

【プログラミング初心者】Swift基礎~プロトコル~

Last updated at Posted at 2020-06-21

#はじめに
今回はオブジェクト指向の考え方の1つであるポリモーフィズムの実現方法について紹介します。
ポリモーフィズムがどういうものかはこちらで簡単に説明しています。

Swiftでポリモーフィズムを実現するには**プロトコル(protocol)という機能を使います。
他言語でいうところの
インターフェース(interface)**に該当します。

#プロトコル
##プロトコルとは
プロトコルとは取り決め、約束事というような意味を持ちます。
Swiftのプロトコルとは、こんなメソッド、プロパティを必ず持つということを強制する技術です。

クラスや構造体では作成されたオブジェクトが持つプロパティやメソッドの具体的な実装が記述されています。

一方でプロトコルには具体的な処理の実装はなく、実装すべきプロパティ、メソッドが記述されているだけです。

クラスや構造体がオブジェクトの設計書とするならばプロトコルは仕様書といったところでしょうか。

まだピンとこないかと思いますが実装方法を見ていきましょう。

##プロトコル定義
プロトコルの定義方法は以下となります。

protocol プロトコル名 {
    var プロパティ名: 型名 { get set }

    func メソッド名(引数名: 型名)
}

クラス定義などと似ていますが、まずプロパティに{ get set }とあります。
{ get set }とした場合、そのプロパティが読み書き可能ということを表します。
{ get }の場合は読み込み専用ということを表します。

またメソッドがありますが具体的な処理が書かれていません。

このように実装すべきプロパティとメソッドを定義します。

プロトコルはこのように具体的な実装はない抽象的な定義です。
そのためクラスなどとは違いプロトコルからオブジェクトを作成することはできません。
プロトコルはクラスで継承させて使います。

実装例を見ていきます。
動物プロトコルを作ります。動物は必ず鳴くものとしbark()メソッドを持っているとします。

AnimalProtocol.swift
protocol AnimalProtocol {
    /// 鳴く
    func bark()
}

これでAnimalProtocolを作成できました。

ここから動物プロトコルを継承した犬クラスを作成してみます。
一度以下のように実装してみてください。

Dog.swift
class Dog: AnimalProtocol {
    
}

これで犬クラスは作成されましたが
Type 'Dog' does not conform to protocol 'AnimalProtocol'
というエラーが発生したかと思います。
犬クラスは動物プロトコルに準拠していませんという意味です。
つまりプロトコルとして定義されているものが実装されていませんと怒られています。
このようにして実装を強制します。

ではbark()メソッドを実装します。

Dog.swift
class Dog: AnimalProtocol {
    func bark() {
        print("ワン")
    }
}

これでエラーが消えました。
あとはいつも通りクラスからインスタンスを作成してあげるだけです。

let dog = Dog()
dog.bark()

プロパティもプロトコルに追加してあげます。

AnimalProtocol.swift
protocol AnimalProtocol {
    /// 名前
    var name: String { get set }
    
    /// 鳴く
    func bark()
}

するとDognameプロパティを追加してあげる必要があります。

Dog.swift
class Dog: AnimalProtocol {
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func bark() {
        print("ワン")
    }
}
let dog = Dog(name: "ポチ")
print(dog.name) // ポチ

これでプロパティもプロトコルに追加することができました。

基本的な定義の方法はこれだけです。

##プロトコルのメリット
さてこれで定義することはできましたがあまりメリットを感じないどころか、無駄な実装が増えたように感じます。
実際1つのクラスだけではメリットはあまりありません。

では動物プロトコルに準拠した猫クラスを追加してみましょう。

Cat.swift
class Cat: AnimalProtocol {
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func bark() {
        print("にゃー")
    }
}

さらに人間クラスを作成し、ペットというプロパティを持っているとします。
人間クラスはstrokePet()という撫でるメソッドを持ち、呼び出すとペットが鳴くという実装をします。
その場合以下のような実装になります。

Human.swift
class Human {
    var pet: AnimalProtocol
    
    init(pet: AnimalProtocol) {
        self.pet = pet
    }
    
    func strokePet() {
        self.pet.bark()
    }
}

すると以下のようになります。

print("---人間のペットが犬の場合---")
let human1 = Human(pet: Dog(name: "ポチ"))
human1.strokePet()

print("---人間のペットが猫の場合---")
let human2 = Human(pet: Cat(name: "タマ"))
human2.strokePet()
実行結果
---人間のペットが犬の場合---
ワン
---人間のペットが猫の場合---
にゃー

この結果が当たり前のように感じるかもしれませんが、クラスのみを扱ってきた今までとは以下の部分が決定的に異なります。

let human1 = Human(pet: Dog(name: "ポチ"))
let human2 = Human(pet: Cat(name: "タマ"))

human1human2を比較すると初期化で与えられているオブジェクトのクラスが異なります。
今まではクラスに一致するオブジェクトしか引数に指定できませんでした。
これは人間クラスの初期化でinit(pet: AnimalProtocol)と引数にクラスではなくプロトコルを指定しているためです。
このようにAnimalProtocolに準拠したオブジェクトであればどんなオブジェクトを入れることができます。

またstroke()では

func strokePet() {
    self.pet.bark()
}

という呼び出しをしています。

self.petは犬なのか猫なのかは人間クラスにはわかりません。
ですがpetAnimalProtocolに準拠したオブジェクトなのでbark()の実装を強制されていることはわかっているので犬なのか猫なのかを意識することなく呼び出すことができます。

このように人間クラスの実装を変えることなく具体的な処理の内容を変更することができるのがプロトコルのメリットであり、オブジェクト指向のポリモーフィズムという考え方です。

また、鳥クラスを追加したいとします。
この鳥クラスもAnimalProtocolに準拠させることでHumanクラスの実装を変えることなく扱うことができます。

Bird.swift
class Bird: AnimalProtocol {
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func bark() {
        print("チュン")
    }
}
let human3 = Human(pet: Bird(name: "鳥"))
human3.strokePet()
実行結果
チュン

このようにプロトコルとして扱うと呼び出し元は具体的なクラスを意識する必要がなくなるため変更に強いコードとなります。

##プロトコルを使わない場合は?
補足としてプロトコルを使わない場合の実装例を挙げます。

Dog.swift
class Dog {
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func bark() {
        print("ワン")
    }
}
Cat.swift
class Cat {
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func bark() {
        print("にゃー")
    }
}
Human.swift
class Human {
    var dog: Dog?
    var cat: Cat?
    
    init(pet: Any) {
        if let dog = pet as? Dog {
            self.dog = dog
        }
        if let cat = pet as? Cat {
            self.cat = cat
        }
    }
    
    func strokePet() {
        self.dog?.bark()
        self.cat?.bark()
    }
}
print("---人間のペットが犬の場合---")
let human1 = Human(pet: Dog(name: "ポチ"))
human1.strokePet()

print("---人間のペットが猫の場合---")
let human2 = Human(pet: Cat(name: "タマ"))
human2.strokePet()

このように人間クラスは具体的なクラスを指定したdogcatを持つ必要があります。
またイニシャライザではpetをキャストして犬なら犬、猫なら猫それぞれ判定してプロパティに入れる必要があります。
(Anyは全ての型を表します。)

strokePetではそれぞれがオプショナルとして定義しているのでnilではないオブジェクトのみ実行されるというような構成になっています。

さて、これはパッと見ただけであまり綺麗なコードとは思えません。
綺麗さだけならまだしも、さらに鳥クラスを追加するとなると人間クラスの実装も変更する必要があります。

このことからもプロトコルを使って実装した方がいいことがわかります。

#最後に
今回はプロトコルの実装方法について紹介しました。
少し難しかったり、どういったタイミングで実際に使えばいいのかイメージしづらいかったかもしれません。
このプロトコルをどう使っていくのかというところは正直かなり難しく、かなり経験が必要になるのかなと思います。
iOSアプリではプロトコルを使ったデリゲートという考えが多く使われています。
こちらは別途紹介しますが基本的な使い方とデリゲートを押さえておけばいいかと思います。

今回の内容は以上です。
本記事とは別でプログラミング未経験からiOSアプリ開発が行えるようになることを目的とした記事を連載しています。
連載は以下にまとめていますのでそちらも是非もご覧ください。
http://naoyalog.com/

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?