【Swift】プロトコルの使い方。クラスに実装しなけれならないプロパティ、メソッドを指定する。(Swift 2.1、XCode 7.2)
プロトコルとは
プロトコルとは、クラスに実装するプロパティとメソッドを指定する機能である。他言語ではインタフェースなどと呼ばれている。プロトコルを適用したクラスはプロトコルに定義されているプロパティとメソッドを必ず実装しなければならない。
プロトコルが役に立つ例を一つ挙げる。
例えば、プログラム開発をAチームとBチームで分担して行い、 Aチームはメンバー管理クラス、Bチームは会員クラスを作ることになった。メンバー管理クラスで会員クラスのインスタンスを生成してメソッドを呼び出すが、Bチームはまだ会員クラスを作り始めていなく、作成するプロパティやメソッドも確定していない。
そんなときは、メンバー管理クラスが利用するプロパティとメソッドを列挙したプロトコルをAチームが作成してBチームに渡す。これでBチームが会員クラスを作るときにメンバー管理クラスが必要なものが実装されることが保証された。
AチームはBチームの会員クラスが完成するまでは、プロトコルを適用した空の会員クラスを利用しながらメンバー管理クラスを作る。
プロトコルを定義する
プロトコルは以下のように定義する。プロパティ名の横には{ get, set }または{ get }を記述する。{ get, set }は読み書き可能、{ get }は読み込みのみを意味している。{ set }は定義できない。
1 2 3 4 |
protocol プロトコル名 { プロパティ名{ get set } メソッド名(引数名:型) -> 戻り値の型 } |
以下は会員クラス用のプロトコルを定義し、それを適用した会員クラスを作成したコードである。プロトコルを適用するときは、クラス宣言で「クラス名:プロトコル名」と記述する。プロトコルに定義されていないプロパティやメソッドも自由に記述することができる。
例では、プロトコルの定義で総合ポイント(totalPoint)を読み込みのみにしたが、変数(var)で実装してもエラーにならず、値も変更できるのが不思議だ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
/* ** プロトコルを適用する。 */ //会員クラス用プロトコル protocol MemberProtocol { var totalPoint:Int { get } //総合ポイント func resetPoint() //ポイントをリセット } //会員クラス(プロトコルを実装) class Member:MemberProtocol { var bonusPoint = 100 //ボーナスポイント var totalPoint = 1000 //トータルポイント //すべてのポイントをリセット func resetPoint(){ bonusPoint = 0 totalPoint = 0 } //ボーナスポイントを半分にする。 func changeHalfBonus(){ bonusPoint = bonusPoint/2 } } //会員クラスのインスタンスを生成 var test = Member() test.totalPoint = 500 print(test.totalPoint) //実行結果 //500 |
ちなみに、統合ポイントはプロトコルでvar(変数)で宣言されているが、実装するときに定数(let)にできる。逆に、{ get set }で定義した場合は、定数(let)で実装しようとするとエラーになる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
/* ** 読み込みのみのプロパティを定数(let)で宣言する。 */ //会員クラス用プロトコル protocol MemberProtocol { var totalPoint:Int { get } //総合ポイント func resetPoint() //ポイントをリセット } //会員クラス(プロトコルを実装) class Member:MemberProtocol { var bonusPoint = 100 //ボーナスポイント //プロトコルではvarで定義したが実装はlet let totalPoint = 1000 //すべてのポイントをリセット func resetPoint(){ bonusPoint = 0 } } //会員クラスのインスタンスを生成 var test = Member() print(test.totalPoint) //実行結果 //1000 |
クラスの継承は1つのクラスにつき1個しか継承できないが、プロトコルは1つのクラスに複数のプロトコルを適用できる。クラス宣言で「クラス名:プロトコル名, プロトコル名, プロトコル名,..」のようにカンマで区切りながら定義する。
以下のコードは3個のプロトコルを1つのクラスに適用する例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
/* ** 複数のプロトコルを適用する。 */ //プロトコル1 protocol TestProtocol1 { func test1() } //プロトコル2 protocol TestProtocol2 { func test2() } //プロトコル3 protocol TestProtocol3 { func test3() } //複数のプロトコルを適用したクラス class Test:TestProtocol1,TestProtocol2,TestProtocol3 { func test1(){ print("おはようございます。") } func test2(){ print("こんにちは") } func test3(){ print("こんばんは") } } //Testクラスのインスタンスを生成 var test = Test() test.test2() //実行結果 //こんにちは |
プロトコルを継承する
プロトコルを継承したプロトコルを作成することができる。プロトコルの宣言で「プロトコル名:スーパープロトコル名,スーパープロトコル名, …」と記述する。クラスの継承の制限「1クラスで継承できるクラスは1個まで」が無いのがクラスと異なる。
上記コードと同じ機能をプロトコルの継承で実現したのが以下のコードである。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
/* ** プロトコルを継承する。 */ //プロトコル1 protocol TestProtocol1 { func test1() } //プロトコル2 protocol TestProtocol2{ func test2() } //プロトコル3 protocol TestProtocol3:TestProtocol2,TestProtocol1 { func test3() } //複数のプロトコルを適用したクラス class Test:TestProtocol3 { func test1(){ print("おはようございます。") } func test2(){ print("こんにちは") } func test3(){ print("こんばんは") } } //Testクラスのインスタンスを生成 var test = Test() test.test2() //実行結果 //こんにちは |
クラスを継承したクラスにプロトコルを適用するには、クラス宣言で「クラス名:スーパークラス名, プロトコル名,プロトコル名, …」と定義する。必ずプロトコル名より前にスーパークラス名を記述すること。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
/* ** クラスを継承したクラスにプロトコルを適用する。 */ //プロトコル protocol TestProtocol1 { func test1() } //スーパークラス class TestClass1 { var name:String = "お疲れ様でした。" } //サブクラス class TestClass2:TestClass1,TestProtocol1 { func test1(){ print("おはようございます。") } } //TestClass2クラスのインスタンスを生成 var test = TestClass2() test.test1() //実行結果 //おはようございます。 |
Enum(列挙型)や構造体にプロトコルを適用する
プロトコルは、クラスだけではなくEnum(列挙型)や構造体にも適用することができる。以下のコードはEnumにプロトコルを適用する例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
/* ** Enumにプロトコルを適用する。 */ //プロトコル protocol TestEnumProtocol { func allScore() -> Int } //Enumを定義(プロトコルを適用) enum TestEnum:Int,TestEnumProtocol { case TEST1 = 1 case TEST2 = 2 case TEST3 = 3 func allScore() -> Int{ return TestEnum.TEST1.rawValue + TestEnum.TEST2.rawValue + TestEnum.TEST3.rawValue } } //Enumを利用する。 var test = TestEnum.TEST3 print(test.allScore()) //実行結果 //6 |
以下のコードは構造体にプロトコルを適用する例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
/* ** 構造体にプロトコルを適用する。 */ //プロトコル protocol TestStructProtocol { var test1:Int { get set } var test2:Int { get set } func allScore() -> Int } //構造体を定義(プロトコルを適用) struct TestStruct:TestStructProtocol { var test1 = 10 var test2 = 20 func allScore() -> Int{ return test1 + test2 } } //構造体を利用する。 var test = TestStruct() print(test.allScore()) //実行結果 //30 |