【Swift】プロトコル(protocol)の使い方

この記事は[レコチョク Advent Calendar 2021](https://qiita.com/advent-calendar/2021/recochoku)の18日目の記事となります。

## 自己紹介

はじめまして、株式会社レコチョクでiOSアプリの開発をしている新卒1年目の深山です！
邦ロック・ジャニーズ・ボカロが大好きで、たくさんのアーティストの情報を追うのに必死な毎日です！

そんな私が、10月に配属されてから2ヶ月の間でiOSアプリの開発に触れて学んだ**プロトコル**についてまとめたいと思います。

---

## 1. はじめに

Swiftには他言語でいうインタフェースに似た、**プロトコル(protocol)**という機能があります。

この記事では、

- プロトコルとは
- プロトコルの使用例
- プロパティ
- エクステンションの使い方

といった内容について解説していきます。

## 2. プロトコルとは

Swiftのプロトコルとは、クラスや構造体が保持するプロパティやメソッドなどの決まり事を定めるものです。
プロトコルを定義することによって、複数の型で共通の機能を実装することができます。

型宣言の時にプロトコルに従い、定められた機能を実装することをプロトコルに「準拠する」と言います。

## 3. プロトコルの使用例

プロトコルには、具体的な処理内容は書きません。
プロトコルに準拠しているクラスや構造体は、プロトコルに定義されているプロパティとメソッドなどを必ず実装しなければなりません。

プロトコルは、`protocol`キーワードで定義することができます。
次の例ではプロトコル`ArtistProtocol`を定義しています。この`ArtistProtocol`は制約として`play()`の実装を要求します。

```swift
protocol ArtistProtocol {
  func play()
}
```

型が`ArtistProtocol`に準拠するには、`ArtistProtocol`が要求しているメソッド`play()`を実装しなければなりません。

以下は、構造体をプロトコルに準拠させる例です。
`型名: プロトコル`でプロトコルを記述します。

```swift
protocol ArtistProtocol {
  func play() 
}

struct Singer: ArtistProtocol {
  func play() {
    print("美声を披露")
  }
}

struct Guitarist: ArtistProtocol {
  func play() {
    print("ギターソロ")
  }
}
```

上記の2つの型`Singer`と`Guitarist`は、`ArtistProtocol`に準拠していると言えます。
この記述により、`Singer`と`Guitarist`は共通のメソッド`play()`を実装する制約を文法レベルで担保することができます。

そして、`play()`を実装しないと、プロトコルの要求を満たしていないとして、コンパイルエラーになります。

```swift
// error: type 'Singer' does not conform to protocol 'ArtistProtocol'
// struct Singer: ArtistProtocol {

// note: protocol requires function 'play()' with type '() -> ()'; do you want to add a stub?
//  func play() 
```

ここまでを次のコードで実行してみましょう。

```swift
let singer = Singer()
let guitarist = Guitarist()

singer.play()
guitarist.play()
```

すると以下の結果が出力されます。

```
美声を披露
ギターソロ
```

`Singer`型のインスタンスである`singer`は、`play()`を呼び出すことで、`Singer`内の`play()`で定義されているように、`美声を披露`と出力します。

また、`Guitarist`型のインスタンスである`guitarist`は、`play()`を呼び出すことで、`Guitar`内の`play()`で定義されているように、`ギターソロ`を出力します。

同じ`play()`を呼び出しても、それぞれの型で実装した内容が実行される仕組みとなっています。

また、1つの型は複数のプロトコルに準拠することができます。
複数のプロトコルを準拠するには、次のように、カンマ区切りで宣言することができます。

```swift
protocol ArtistProtocol {
  func play() 
}

protocol SongwriterProtocol {
  func make() 
}

// カンマ区切りでプロトコルを記述
struct Singer: ArtistProtocol, SongwriterProtocol {
  func play() {
    print("美声を披露")
  }

  func make() {
    print("曲を作成")
  }
}
```

## 4. プロパティ

プロトコルは、制約として読み取り・書き込みのできるプロパティを定義することができます。

プロパティを定義するには、プロパティ名の型の横に、`{ get set }`または`{ get }`を記述します。

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

プロパティの値を取得する際に`get`メソッドが呼ばれ、プロパティに値を代入する際に`set`メソッドが呼ばれます。
`set`メソッドのみの定義はできません。

次の例では、`ArtistProtocol`で`get`メソッドを定義し、`AgeProtocol`で`get`メソッドと`set`メソッドを定義しています。

```swift
protocol ArtistProtocol {
  var name: String { get } 
}

protocol AgeProtocol {
  var age: Int { get set }
}

struct Singer: ArtistProtocol, AgeProtocol {
  var name: String
  var age: Int
}
```

次のコードを実行してみましょう。

```swift
var singer = Singer(name: "Mary", age: 25)

print(singer.name)
print(singer.age)
```

すると以下の結果が出力されます。

```
Mary
25
```

また、`singer`の`age`の値に30を代入し、同じようにprint文で出力してみましょう。

```swift
singer.age = 30

print(singer.name)
print(singer.age)
```

すると以下の結果が出力され、`age`の値が更新されたことが確認できます。

```swift
Mary
30
```

## 5. エクステンションの使い方

プロトコルを使うにあたって、[エクステンション(extension)](https://swift.codelly.dev/guide/%E5%9E%8B%E3%81%AE%E6%A7%8B%E6%88%90/%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%86%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%B3.html)には次の2つの役割があります。

1. プロトコルの準拠
2. プロトコルの拡張

これらついて詳しく紹介していきます。

### 5-1. プロトコルの準拠

3.の後半で、複数のプロトコルの準拠はカンマ区切りでできることを紹介しました。

しかし、カンマ区切りで複数のプロトコルを記述すると、どのプロパティやメソッドが、どのプロトコルに準拠させているかわかりづらいという点があります。

そこで、エクステンションを使ってプロトコルを準拠させてみましょう。
エクステンションを使うと、プロトコルを1つずつ準拠できるので、可読性があがるというメリットがあります。
次の例では、2つのプロトコルをそれぞれエクステンションを使って準拠させています。

```swift
protocol ArtistProtocol {
  func play() 
}

protocol SongwriterProtocol {
  func make() 
}

struct Singer {}

extension Singer: ArtistProtocol {
  func play() {
    print("美声を披露")
  }
}

extension Singer: SongwriterProtocol {
  func make() {
    print("曲を作成")
  }
}
```

カンマ区切りで定義するよりも明確にプロトコルが区別されているのがわかります。

### 5-2. プロトコルの拡張

プロトコルは、エクステンションを使って拡張させることができます。
これはインターフェースを拡張するのではなく、実装の処理を追加するのに使われ、メソッドのデフォルト実装を持つことができます。

次の例では、`ArtistProtocol`にメソッドを追加しています。
準拠先では、追加されたメソッドを実行することができます。

```swift
protocol ArtistProtocol {
  var name: String { get }
}

// extension を記述してプロトコルを拡張します
extension ArtistProtocol {
  func say() {
    print("Hello, I am \(self.name)!")
  }
}

struct Singer: ArtistProtocol {
  var name: String
}
```

次のコードを実行してみましょう。
`say()`はプロトコルに実装済みなので実行することができます。

```swift
let singer = Singer(name: "John")

singer.say()
```

すると以下の結果が出力されます。

```
Hello, I am John!
```

また、下記のように`protocol`内にも`say()`を定義することで、任意の型の`say()`に異なる処理を入れたいときにも対応できるようになります。

```swift
protocol ArtistProtocol {
  var name: String { get }
  func say()
}

extension ArtistProtocol {
  func say() {
    print("Hello, I am \(self.name)!")
  }
}

struct Singer: ArtistProtocol {
  var name: String
}

struct Guitarist: ArtistProtocol {
  var name: String
  func say() { // say()に異なる処理を入れる
    print("Hello, I am a super guitarist, \(self.name)!")
  }
}
```

次のコードを実行してみましょう。

```swift
let singer = Singer(name: "John")
let guitarist = Guitarist(name: "Michael")

singer.say()
guitarist.say()
```

すると以下の結果が出力されます。

```
Hello, I am John!
Hello, I am a super guitarist, Michael!
```

---

最後まで読んでいただき、ありがとうございました。

明日の[レコチョク Advent Calendar 2021](https://qiita.com/advent-calendar/2021/recochoku)は19日目「Androidアプリ開発2ヶ月弱の新卒が思う、初学者でも参考になったサイト8選」です。お楽しみに！

## 参考

- [プロトコルとは | Swiftの始め方](https://swift.codelly.dev/guide/%E3%83%97%E3%83%AD%E3%83%88%E3%82%B3%E3%83%AB/#%E3%83%97%E3%83%AD%E3%83%88%E3%82%B3%E3%83%AB%E3%81%AE%E5%AE%9A%E7%BE%A9%E6%96%B9%E6%B3%95)
- [【Swift入門】プロトコル(protocol)を使ってみよう！](https://www.sejuku.net/blog/35550)
- [【Swift】セッター(Setter)、ゲッター(Getter)の処理を書く](https://qiita.com/yuinchirn/items/c1bc87494cb758a6b0ca)

---
この記事はレコチョクエンジニアブログの記事を転載したものとなります。

https://techblog.recochoku.jp/8051