この記事はレコチョク Advent Calendar 2021の18日目の記事となります。
自己紹介
はじめまして、iOSアプリの開発をしている新卒1年目の深山です!
邦ロック・ジャニーズ・ボカロが大好きで、たくさんのアーティストの情報を追うのに必死な毎日です!
そんな私が、10月に配属されてから2ヶ月の間でiOSアプリの開発に触れて学んだプロトコルについてまとめたいと思います。
1. はじめに
Swiftには他言語でいうインタフェースに似た、プロトコル(protocol)という機能があります。
この記事では、
- プロトコルとは
- プロトコルの使用例
- プロパティ
- エクステンションの使い方
といった内容について解説していきます。
2. プロトコルとは
Swiftのプロトコルとは、クラスや構造体が保持するプロパティやメソッドなどの決まり事を定めるものです。
プロトコルを定義することによって、複数の型で共通の機能を実装することができます。
型宣言の時にプロトコルに従い、定められた機能を実装することをプロトコルに「準拠する」と言います。
3. プロトコルの使用例
プロトコルには、具体的な処理内容は書きません。
プロトコルに準拠しているクラスや構造体は、プロトコルに定義されているプロパティとメソッドなどを必ず実装しなければなりません。
プロトコルは、
protocolキーワードで定義することができます。
次の例ではプロトコル
ArtistProtocolを定義しています。この
ArtistProtocolは制約として
play()の実装を要求します。
protocol ArtistProtocol { func play() } |
型が ArtistProtocolに準拠するには、 ArtistProtocolが要求しているメソッド play()を実装しなければなりません。
以下は、構造体をプロトコルに準拠させる例です。
型名: プロトコルでプロトコルを記述します。
protocol ArtistProtocol { func play() } struct Singer: ArtistProtocol { func play() { print("美声を披露") } } struct Guitarist: ArtistProtocol { func play() { print("ギターソロ") } } |
上記の2つの型
Singerと
Guitaristは、
ArtistProtocolに準拠していると言えます。
この記述により、
Singerと
Guitaristは共通のメソッド
play()を実装する制約を文法レベルで担保することができます。
そして、 play()を実装しないと、プロトコルの要求を満たしていないとして、コンパイルエラーになります。
// 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() |
ここまでを次のコードで実行してみましょう。
let singer = Singer() let guitarist = Guitarist() singer.play() guitarist.play() |
すると以下の結果が出力されます。
美声を披露 ギターソロ |
Singer型のインスタンスである singerは、 play()を呼び出すことで、 Singer内の play()で定義されているように、 美声を披露と出力します。
また、 Guitarist型のインスタンスである guitaristは、 play()を呼び出すことで、 Guitar内の play()で定義されているように、 ギターソロを出力します。
同じ play()を呼び出しても、それぞれの型で実装した内容が実行される仕組みとなっています。
また、1つの型は複数のプロトコルに準拠することができます。
複数のプロトコルを準拠するには、次のように、カンマ区切りで宣言することができます。
protocol ArtistProtocol { func play() } protocol SongwriterProtocol { func make() } // カンマ区切りでプロトコルを記述 struct Singer: ArtistProtocol, SongwriterProtocol { func play() { print("美声を披露") } func make() { print("曲を作成") } } |
4. プロパティ
プロトコルは、制約として読み取り・書き込みのできるプロパティを定義することができます。
プロパティを定義するには、プロパティ名の型の横に、 { get set }または { get }を記述します。
protocol プロトコル名 { var プロパティ名: 型 { get set } } |
プロパティの値を取得する際に
getメソッドが呼ばれ、プロパティに値を代入する際に
setメソッドが呼ばれます。
setメソッドのみの定義はできません。
次の例では、 ArtistProtocolで getメソッドを定義し、 AgeProtocolで getメソッドと setメソッドを定義しています。
protocol ArtistProtocol { var name: String { get } } protocol AgeProtocol { var age: Int { get set } } struct Singer: ArtistProtocol, AgeProtocol { var name: String var age: Int } |
次のコードを実行してみましょう。
var singer = Singer(name: "Mary", age: 25) print(singer.name) print(singer.age) |
すると以下の結果が出力されます。
Mary 25 |
また、 singerの ageの値に30を代入し、同じようにprint文で出力してみましょう。
singer.age = 30 print(singer.name) print(singer.age) |
すると以下の結果が出力され、 ageの値が更新されたことが確認できます。
Mary 30 |
5. エクステンションの使い方
プロトコルを使うにあたって、エクステンション(extension)には次の2つの役割があります。
- プロトコルの準拠
- プロトコルの拡張
これらついて詳しく紹介していきます。
5-1. プロトコルの準拠
3.の後半で、複数のプロトコルの準拠はカンマ区切りでできることを紹介しました。
しかし、カンマ区切りで複数のプロトコルを記述すると、どのプロパティやメソッドが、どのプロトコルに準拠させているかわかりづらいという点があります。
そこで、エクステンションを使ってプロトコルを準拠させてみましょう。
エクステンションを使うと、プロトコルを1つずつ準拠できるので、可読性があがるというメリットがあります。
次の例では、2つのプロトコルをそれぞれエクステンションを使って準拠させています。
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にメソッドを追加しています。
準拠先では、追加されたメソッドを実行することができます。
protocol ArtistProtocol { var name: String { get } } // extension を記述してプロトコルを拡張します extension ArtistProtocol { func say() { print("Hello, I am \(self.name)!") } } struct Singer: ArtistProtocol { var name: String } |
次のコードを実行してみましょう。
say()はプロトコルに実装済みなので実行することができます。
let singer = Singer(name: "John") singer.say() |
すると以下の結果が出力されます。
Hello, I am John! |
また、下記のように protocol内にも say()を定義することで、任意の型の say()に異なる処理を入れたいときにも対応できるようになります。
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)!") } } |
次のコードを実行してみましょう。
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は19日目「Androidアプリ開発2ヶ月弱の新卒が思う、初学者でも参考になったサイト8選」です。お楽しみに!
参考
この記事を書いた人
最近書いた記事
- 2023.03.30【Swift】iOS 16で画面回転禁止処理を行っている箇所で無限ループが発生しクラッシュする
- 2023.03.27SwiftLintでカスタムルールを追加してみた
- 2022.09.30【Swift】CoreDataのユニットテストの環境構築
- 2022.09.30【Swift】CoreDataの概念を知る