こんにちは!フリーエンジニアのせきです。
Swiftには多言語でいうインタフェースに似た、プロトコル(protocol)という機能があります。
この記事では、
・プロトコルとは何か知りたい
・プロトコルを使うと何ができるのか知りたい
・プロトコルを使うメリットを知りたい
という基本的な内容から、
・デリゲートでプロトコルを使う方法を知りたい
・プロトコルを拡張する方法を知りたい
といった応用的な内容に関しても解説していきます。
今回はそんなプロトコルについて、わかりやすく解説します!
※この記事ではSwift4.0を使用しています。
プロトコルとは
プロトコルとは、具体的な処理内容は書かず、クラスや構造体が実装するプロパティとメソッドを定義する機能です。
JavaやC#でいうインタフェースにあたります。
プロトコルを適用したクラスや構造体は、プロトコルに定義されているプロパティとメソッドを必ず実装しなければなりません。
プロトコルを使用するメリット
プロトコルを使用するメリットには次のようなものがあります。
・実装を後から変更できる
プロトコルの定義だけしておき、実装は後から行うことができるため、プロパティやメソッドを使用する箇所は変更することなく、処理を変えたい場合に有効です。
・構造体で使用することができる
似た処理を行うクラスを複数定義する場合、スーパークラスと呼ばれる元となるクラスを作成し、それを継承するそれぞれのクラスで異なる部分を実装するという方法が考えられます。
Swiftの構造体は継承を行うことができません。
似た処理を持つ構造体を複数定義する場合、スーパークラスを使用する代わりにプロトコルで同じようなことが実現できます。
・複数のプロトコルを適用することができる
クラスは1つのクラスしか継承できませんが、プロトコルは複数適用することができます。
そのため、プロトコルを使うとクラスや構造体の関係を制限なく、わかりやすく書くことができます。
構造体については、以下の記事で詳しく解説しています!
プロトコルの使い方
プロトコルの定義
プロトコルは、以下のように定義します。
書き方:
protocol プロトコル名 { var プロパティ名: 型 { set get } func メソッド名(引数名: 型) -> 戻り値の型 }
プロパティ名の横には{ set get }または{ get }を記述します。
{ set get }は読み書き可能なプロパティ、{ get }は読み込み専用プロパティを意味します。
{ set }は定義することができません。
プロトコルの適合
プロトコルの具体的な処理は、クラスや構造体で実装していきます。
クラスや構造体がプロトコルの実装を行うことを「プロトコルを適合する」という言い方をします。
プロトコルを適合するには、以下のように記述します。
書き方:
struct 構造体名: プロトコル名 { // 処理の実装 }
構造体名の後に「: プロトコル名」を記述します。クラスの場合も同様です。
このように宣言した構造体は、プロトコルに定義されているメソッドを実装する必要があります。
必要に応じて、プロトコルに定義されていないメソッドは自由に記述することができます。
プロトコルの使用例
プロトコルを使用して、2つの数値の計算を行うサンプルを紹介します。
サンプルプログラム:
// プロトコルの定義 protocol Calculation { var num1: Int { set get } var num2: Int { set get } func calc() -> Int } // プロトコルの適合 struct Add: Calculation { var num1: Int var num2: Int func calc() -> Int { return num1 + num2 } } // num1とnum2に値を設定して実行 let add = Add(num1: 5, num2: 3) print(add.calc())
実行結果
8
プロトコルCalculationは、2つの数値をプロパティに持ち、計算を行うメソッドを定義したプロトコルです。
構造体AddはプロトコルCalculationを適合します。
そのため、プロトコルCalculationで定義されているcalcメソッドを実装しないと、コンパイルエラーが発生します。
calcメソッドでは、num1とnum2を足した値を返すように実装しています。
最後に「Add(num1: 5, num2: 3)」でnum1とnum2に値が設定し、calcメソッドを呼び出します。2つの数値が足した値が返されているのがわかります。
構造体Addのcalcメソッドの処理は後で変更することができますが、プロトコルCalculationを適合し、calcメソッドが実装されていることが保証されているので、呼び出す側は呼び出し方を変える必要はありません。
デリゲートにおけるプロトコルの使用
プロトコルはデリゲート(delegate)において、よく利用されます。
デリゲートとはデザインパターンの一つで、処理の流れをプロトコルで定義したメソッドを使って書いていきます。
このようにすると、実際の処理はプロトコルを適合する側で実装するため後から変更することができますが、処理を実行する側はどんな処理をするのか意識することなく、呼び出すことができます。
デリゲート(delegate)については、以下の記事で詳しく解説しています!
エクステンションによるプロトコルの拡張
プロトコルを拡張し、メソッドのデフォルト実装を持つことができます。
プロトコルを拡張するには、エクステンション(extention)を使用します。
エクステンションは以下のように記述します。
書き方:
extention プロトコル名 { // 処理の実装 }
プロトコルCalculationに定義されているcalcメソッドにデフォルト実装を持たせてみます。
サンプルプログラム:
extension Calculation { func calc() -> Int { return 0 } } struct NoCalc: Calculation { var num1: Int var num2: Int } let noCalc = NoCalc(num1: 5, num2: 3) print(noCalc.calc())
「extension Calculation」の中で実装したcalcメソッドが、デフォルト実装になります。
ここでは常に0を返すように実装しています。
このように拡張しておくと、プロトコルCalculationを適合するクラスは、calcメソッドを実装しなくてもよくなります。
構造体NoCalcはcalcメソッドを実装していませんが、エラーになりません。
NoCalcのcalcメソッドを実行すると、デフォルト実装の0が返されています。
エクステンション(extention)については、以下の記事で詳しく解説しています!
まとめ
今回は、
・プロトコルとは何か
・プロトコルを使うと何ができるのか
・プロトコルを使うメリット
・デリゲートでプロトコルを使う方法
・プロトコルを拡張する方法
などについて解説しました。
プロトコルを使いこなすと、クラス同士の依存をなくし、わかりやすいコードになっていきますので、ぜひ使ってみてください。
プロトコルについて忘れてしまったら、この記事を思い出してくださいね!