Swift や SwiftUI を用いて開発を進めていく中で、 Swift 固有のお作法や機能に直面することもあります。 プロトコル もその一部ではないでしょうか。「変数」「構造体」「クラス」といった他の オブジェクト 志向言語にも同様の概念がある一方で プロトコル については 他の言語ではあまり馴染みがないかもしれません。 本記事では プロトコル とはどういったもので、 どのような メリット や 活用事例 があるのかを 具体例を使って紹介していこうと思います。
Swift: 5.5
Swift Community の プロトコル に関する記載
まず Swift community での プロトコル の定義 を確認してみます。
A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. The protocol can then be adopted by a class, structure, or enumeration to provide an actual implementation of those requirements. Any type that satisfies the requirements of a protocol is said to conform to that protocol.
引用元: Protocols — The Swift Programming Language (Swift 5.5)
プロトコル は、特定の タスク や機能の一部に適した メソッド 、 プロパティ 、およびその他の必要な要素を定義するものです。 クラス 、 構造体 、列挙体 を適用することができ、これらを使って構成することができます。 また プロトコル の必要事項を満たしている型は、その プロトコル に準拠していると呼ぶことができます。
日本語訳: 本ブログ
機械翻訳では正直意味が伝わりづらいため、少し大胆に意訳してみました。 つまり プロパティ や メソッド を用いて 固有の値や振る舞いを定義したものが プロトコル で、 その 値や振る舞い に準拠している 型 は “プロトコル に準拠 している” と呼ぶ という事ですね。
プロトコル を構成している要素
次に プロトコル を構成している要素について少し整理していきます。
上述の通り、 プロトコル の構成要素としては以下が挙げられます。 端的にいうと、 クラス や 構造体 同様の要素ということになります。
- プロパティ: 値 を定義する
- メソッド: 振る舞い を定義する
- その他の要素
それでは それぞれの要素ごとに プロトコル としてどのように実装し 活用 していくのかをみていきます。
プロトコル の プロパティ
最初に プロパティ からみていきます。 swift.org では Property Requirements として以下のように記載されています。
The protocol doesn’t specify whether the property should be a stored property or a computed property—it only specifies the required property name and type. The protocol also specifies whether each property must be gettable or gettable and settable.
引用元: Protocols — The Swift Programming Language (Swift 5.5)
プロトコル は プロパティ が ストアドプロパティ なのか コンピューテッドプロパティ なのかを区別せず、 単純に プロパティ の名前 と 型 を気にします。 また プロトコルは それぞれの プロパティ が 読取り可能 か 読書き可能 かを指定します。
日本語訳: 本ブログ
つまり、 プロトコル における プロパティ は 以下を指定して定義すれば良いことがわかります。
- プロパティ 名称
- プロパティ の型
- 読取り可能 か 読書き可能 か (“gettable” or “gettable and settable”)
概要が把握できたところで 実際の例で見ていきます。
プロトコル の プロパティ を定義し活用してみる
それでは 最初に プロトコル を宣言していきます。
ここでは とある学校を例題として考えていきます。この学校では 生徒は必ず クラブ 活動に所属する必要があり、 一度入ったら卒業するまで変更できないこととします。 なかなか厳しい ルール ですが、これを プロトコル を使って表現していきます。
最初に クラブ 活動 に関する 情報を studentClub プロトコル として定義していきます。 情報と言っても クラブ 名 だけの簡易的なものです。
protocol studentClub { // クラブ活動 var clubName: String { get } }
一度入ったら 変更できないため 読取り専用 を意味する { get } をつけて宣言します。 続いて、 この プロトコル に準拠した Student 構造体 を定義します。
プロトコル に準拠するために clubName を定義していることに加え、 Student 構造体 独自のプロパティ となる name (学生の名前) も定義しています。
struct Student: studentClub { var name: String var clubName: String }
それでは、 この Student 構造体 を使って 学生の オブジェクト を生成してみます。
let robert = Student(name: "Robert", clubName: "Soccer") print("The Student Name is \(robert.name), belongs to the \(robert.clubName) Club")
この コード を実行してみます。 プロトコル に特に準拠していない 普通の 構造体 と同じように オブジェクト を生成し その プロパティ を参照することができています。
The Student Name is Robert, belongs to the Soccer Club
複数 プロトコル に準拠
もっとも シンプル な プロトコル の利用方法がわかったところで より プロトコル ならでは の使い方を みていきます。 ここでは プロトコル を複数 定義し 必要に応じて適宜 オブジェクト に準拠させてみることにします。 以下のような シーン を想定してみます。
- 「科目」には「理系教科群」と「文系教科群」があり、個別の教科ごとに テスト を実施し採点される
- 生徒には 理系学生 と 文系学生 の種別があり、 それぞれ 「理系科目群」「文系科目群」を履修している。
- 理系学生、文系学生 ともに クラブ活動 に所属する必要がある
早速、これらを プロトコル を活用して表現してみます。 理系科目群として sciences を、 文系科目群として liberalArts を定義します。 それぞれの プロトコル には 該当する教科の テスト の点数を表現する プロパティ が存在していて、 読書き可能な { get set } で宣言しています。
protocol sciences { // 理系科目 var math: Int { get set } // 数学 var physics: Int { get set } // 物理 } protocol liberalArts { // 文系科目 var history: Int { get set } // 歴史 var art: Int { get set } // 芸術 }
続いて、 理系学生、文系学生 を 構造体で定義していきます。 双方共通で 前項で作成した studentClub に準拠していて、 理系学生 は sciences プロトコル に、 文系学生 は liberalArs プロトコル に準拠させています。
struct ScienceStudent: studentClub, sciences { // 理系学生 var name: String var clubName: String var math: Int var physics: Int } struct LiberalArtsStudent: studentClub, liberalArts { // 文系学性 var name: String var clubName: String var history: Int var art: Int }
実施に オブジェクト を生成してみます。
let hanako = ScienceStudent(name: "Hanako", clubName: "Brass Band", math: 90, physics: 78) let taro = LiberalArtsStudent(name: "Taro", clubName: "Soccer", history: 87, art: 92) print(hanako) print(taro)
実行結果は以下のようになります。
ScienceStudent(name: "Hanako", clubName: "Brass Band", math: 90, physics: 78) LiberalArtsStudent(name: "Taro", clubName: "Soccer", history: 87, art: 92)
プロパティ を プロトコル で定義し 準拠する メリット
今回実施した内容を ダイアログ で表現すると 以下のようになります。 理系・文系 だけだと メリット を感じづらいかもしれませんが、 N (例えば学生) 対 M(例えば教科) の関係を表現する際には 重複して定義する必要がなくなるので、 手間を省け、 メンテナンス性も保たれるということになります。
それでは プロパティ に続いて メソッド についても簡単に確認していきます。
プロトコル の メソッド
最初に swift.org の説明を読んでみます。
Protocols can require specific instance methods and type methods to be implemented by conforming types. These methods are written as part of the protocol’s definition in exactly the same way as for normal instance and type methods, but without curly braces or a method body.
引用元: Protocols — The Swift Programming Language (Swift 5.5)
プロトコル は 特定の インスタンスメソッド や タイプメソッド を プロトコル に準拠する型に対して要求することができます。 これらの メソッド は 通常の インスタンスメソッド や タイプメソッド と全く同様に プロトコル 定義の一部として記載しますが、 中括弧やメソッド本体は記載しません
日本語訳: 本ブログ
プロトコル では メソッド の定義は 名称のみで その実際の振る舞いを記載することはしないということです。 では、 実際の メソッド の振る舞いはどこに記載するのでしょうか? それは プロトコル に準拠させた (Conform している) それぞれの 型 で記載します。 実際の例で見ていきます。
プロトコル の メソッド を定義し活用してみる
それでは 最初に 口頭挨拶を想定した verbalCommunication プロトコル を定義します。
protocol verbalCommunication { func sayHello() -> String }
次に 口頭挨拶に準拠する Friend 構造体 を定義します。 友人に挨拶するイメージです。 プロトコル から メソッド sayHello に準拠し、 Friend 構造体 固有のプロパティ として name を持っています。 sayHello では 友人に挨拶をしているという感じです。 その後 Lisa という名前を持つ Friend 構造体 を生成し、 sayHello メソッドを呼び出しています。
struct Friend: verbalCommunication { var name: String func sayHello() -> String { return "Hi \(name)" } } let lisa = Friend(name: "Lisa") print(lisa.sayHello())
実行結果は以下のようになります。 無事 verbalCommunication プロトコル に準拠しつつ Friend 構造体 で定義した プロパティ と メソッド の内容で動作しています。
Hi Lisa
続いて プロトコル の動作の理解を深める意味で、 この verbalCommunication プロトコル を別の構造体に準拠させてみます。
プロトコル を 別構造体にも準拠させて メソッド の動作を別途定義してみる
verbalCommunication プロトコル の定義はそのままに、 新規で 以下の BusinessPartner 構造体 を定義します。 sayHello メソッド の内容が変わっている点に注目してください。
相手が誰であれ、挨拶はコミュニケーション の基本ですが、友人と ビジネスパートナー ではその表現方法が違う という状況を表現しています。
struct BusinessPartner: verbalCommunication { var name: String func sayHello() -> String { return("Hello \(name). Nice to meet you!") } } let naomi = BusinessPartner(name: "Naomi") print(naomi.sayHello())
実行結果は以下のようになります。
Hello Naomi. Nice to meet you!
メソッド を プロトコル で定義し 準拠する メリット
それでは、 メソッド を プロトコル に準拠する メリット について 上で作成していた verbalCommunication プロトコル の例で考えていきます。
以下のように プロトコル に準拠しているにもかかわらず プロトコル 要素を定義しない場合を考えてみます。 このコードは コンパイルエラー となります。
つまり、 「挨拶する」という行動を プロトコル で定義し、 「友達やビジネスパートナーやお医者さんには 忘れずに挨拶すること」という決め事を プロトコル に準拠し表現することで、このように 挨拶し忘れる ということを未然防げる ということになります。 挨拶のニュアンスは相手によって変えることができる 点も含めて プロトコル のメ リット と言えます。
struct Doctor: verbalCommunication { var name: String }
この時の エラーメッセージ は以下の通りです。
"Doctor"型がプロトコル"verbalCommunication"に準拠していません
まとめ
- プロトコル は 構造体 や クラス と同様に プロパティ と メソッド を要素としてもつ
- プロパティ を定義する際には gettable { get } なのか settable { get set } なのかを指定して定義する
- プロパティ に準拠 (conform) した 型 (構造体 や クラス) では、 プロトコル の要素が必須となり、 定義を忘れると コンパイル エラー となる
- プロトコル は 複数 の 型 を準拠させる事ができる
- 型 は 複数の プロトコル に準拠する事ができる
- プロトコル と 型 が M 対 N のような関係の時に有効