# プロトコルとは

Swiftのプロトコルは、型のインターフェースを定義するものです。インターフェースを定義し、抽象化することによって、複数の型で共通の機能を実装することができます。プロトコルはクラス、構造体、列挙型などで使われ、プロトコルのインターフェースを満たす型は、プロトコルに準拠している と言われています。

例えば、次のプロトコルは message() というインターフェースを定義しています。このプロトコルに準拠するためには、message() を実装しなければなりません。

protocol MessageProtocol {
  func message() -> String
}

struct APIMessage: MessageProtocol {
  func message() -> String {
    return "APIMessage"
  }
}

struct DBMessage: MessageProtocol {
  func message() -> String {
    return "DBMessage"
  }
}

上記の二つの型は、MessageProtocol プロトコルを準拠していると言えます。インターフェースとして定義することによって、複数の型で共通の機能を持つという同値性を保証することができるのです。

message() を実装しないとコンパイラは、整合性がないとしてエラーを出力します。

protocol MessageProtocol {
  func message() -> String
}

struct DBMessage: MessageProtocol {}

/* 実行結果 */
// error: type 'DBMessage' does not conform to protocol 'Message'
// struct DBMessage: Message {
   
// note: protocol requires function 'message()' with type '() -> String'; do you want to add a stub?
//   func message() -> String

このような検知により、Swiftは型の安全性を提供しています。

# プロトコルの定義方法

プロトコルは、protocol キーワードで定義することができます。

protocol プロトコル名 {}

次の例では、ErrorProtocol プロトコルを定義しています。このプロトコルはインターフェースに printError を要求します。

protocol ErrorProtocal {
  func printError()
}

# 構造体

構造体を、プロトコルに準拠させるためには、型名のあとに 型名: プロトコル のフォーマットでプロトコルを記述します。

protocol ErrorProtocol {
  func printError()
}

// プロトコルを準拠する
struct APIError: ErrorProtocol {
  func printError() {
    print("error!")
  }
}

複数のプロトコルを準拠するには、カンマ区切りで書くことができます。

protocol ErrorProtocol {
  func printError()
}

protocol MessageProtocol {
  func message()
}

// カンマ区切りでプロトコルを記述する。
struct APIError: ErrorProtocol, MessageProtocol {
  func printError() {
    print("error!")
  }

  func message() {
    print("message!")
  }
}

# クラス

クラスでプロトコルを使用するには、構造体と同様に、型名: プロトコル のフォーマットでプロトコルを記述します。

protocol ErrorProtocol {
  func printError()
}

// プロトコルを準拠する
class APIError: ErrorProtocol {
  func printError() {
    print("error!")
  }
}

継承 をする場合は、スーパークラスを先に書いて、そのあとにプロトコルを記述します。

protocol ErrorProtocol {
  func printError()
}

class ErrorSuperClass {}

// スーパークラスを先に書く
class APIError: ErrorSuperClass, ErrorProtocol {
  func printError() {
    print("error!")
  }
}

# エクステンションで準拠する

Swiftでは、エクステンションでプロトコルを準拠させることもできます。

次のフォーマットで、プロトコルを準拠させることができます。

extension 型: プロトコル {}

次の例では、構造体にエクステンションを使ってプロトコルを準拠させています。

protocol ErrorProtocol {
  func printError()
}

struct APIError {}

// エクステンションでプロトコルを準拠させる
extension APIError: ErrorProtocol {
  func printError() {
    print("error!")
  }
}

カンマ区切りで複数のプロトコルを記述すると、どのプロパティやメソッドがどのプロトコルに準拠させているか分かりづらいという点があります。これに対して、エクステンションを使うと、個別に一つずつ準拠できるので可読性が上がるというメリットがあります。

次の例では、2つのプロトコルをカンマ区切りで準拠させています。

protocol ErrorProtocol {
  func printError()
}

protocol MessageProtocol {
  func message()
}

struct APIError: ErrorProtocol, MessageProtocol {
  func printError() {
    print("error!")
  }

  func message() {
    print("message!")
  }
}

現状は、2つしかないのでどのメソッドがどのプロトコルに属しているか判断できますが、これが増えていくとメンテナンスのコストが高くなっていきます。

一方、エクステンションでの準拠を見てみましょう。

次の例では、2つのプロトコルをそれぞれエクステンションを使って準拠させています。

protocol ErrorProtocol {
  func printError()
}

protocol MessageProtocol {
  func message()
}

struct APIError {}

// ErrorProtocolを準拠
extension APIError: ErrorProtocol {
  func printError() {
    print("error!")
  }
}

// MessageProtocolを準拠
extension APIError: MessageProtocol {
  func message() {
    print("message!")
  }
}

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

# 引数の型

プロトコルは型なので、引数の型としても使用することができます。

次の例では、関数の引数の型としてプロトコルを使用しています。

protocol NameProtocol {
  var name: String { get }
}

// NameProtocolを準拠
struct Person: NameProtocol {
  var name: String
}

// NameProtocolの型で引数を定義
func printPersonName(person: NameProtocol) {
  print(person.name)
}

let person1 = Person(person: "Taro")

// PersonはNameProtocolを準拠しているので、引数として渡すことができる
printPersonName(person: person1) // Taro

# プロトコルコンポジション | Protocol Composition

プロトコルコンポジションを使うことで、複数のプロトコルを組み合わせて準拠させることができます。

& を記述して、複数のプロトコルを組み合わせます。

protocol NameProtocol {
  var name: String { get }
}
protocol AgeProtocol {
  var age: Int { get }
}

struct Person: NameProtocol, AgeProtocol {
  var name: String
  var age: Int
}

// & でプロトコルを組み合わせる
func printPersonNameAndAge(person: NameProtocol & AgeProtocol) {
  print(person.name)
  print(person.age)
}

let person1 = Person(name: "Taro", age: 20)

printPersonNameAndAge(person: person1)

// Taro
// 20

NameProtocolAgeProtocol を引数の型として定義し、この2つを準拠させる型のみを許容しています。

# プロパティ

プロトコルには、インターフェースとしてプロパティを定義することができます。

プロパティを定義するには、ゲッタとセッタの有無を定義します。

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

次の例では、NameProtocol でゲッタを定義し、AgeProtocol でゲッタとセッタを定義しています。

protocol NameProtocol {
  // ゲッタのみを定義
  var name: String { get }
}

protocol AgeProtocol {
  // ゲッタとセッタを定義
  var age: Int { get set }
}

struct Person: NameProtocol, AgeProtocol {
  // NameProtocolのプロパティを準拠
  var name: String
    
  // AgeProtocolのプロパティを準拠
  var age: Int {
    get {
      return 10
    }

    set {}
  }
}

# 関連型 | Associated Type

関連型を使用すると、1つの型に依存しない抽象的なプロトコルを定義することができます。通常はプロトコルの定義時に型を定義しますが、関連型を使用すると、具体的な型はプロトコルを準拠させるタイミングで指定させることができます。

関連型の定義方法は、associatedtype キーワードを使います。

protocol プロトコル {
  associatedtype 関連型

  var プロパティ名: 関連型
  func メソッド名(引数: 関連型)
}

次の例では、associatedtype キーワードで関連型を定義しています。プロトコルを準拠させる構造体の方では、typealias キーワードを使って、具体的な型を指定しています。

protocol ItemProtocol {
  // associatedtype キーワードで関連型を定義して抽象化させる
  // この時点では、具体的な型は決まっていない
  associatedtype Item
  var items: [Item] { get }
}

struct ProductItem: ItemProtocol {
  // String型で具体的な型を指定する
  typealias Item = String
  var items = ["item1", "item2"]
}

struct CartItem: ItemProtocol {
  // Int型で具体的な型を指定する
  typealias Item = Int
  var items = [1,2,3]         
}

let productItem = ProductItem()
productItem.items               // ["item1", "item2"]

let cartItem = CartItem()
cartItem.items                  // [1, 2, 3]

ProductItem ではString型を使用し、CartItem ではInt型を使用しています。プロトコルに型を指定しなくても、準拠先で柔軟に変更することができました。

typealias キーワードを明示的に書かなくても、実装から型推論によって自動的に型を特定させることもできます。

protocol ItemProtocol {
  associatedtype Item
  var items: [Item] { get }
}

struct CartItem: ItemProtocol {
  // typealias を使わないで定義する
  var items = [1,2,3]
}

let cartItem = CartItem()
cartItem.items               // [1, 2, 3]

# 型の制約

関連型には、スーパークラスやプロトコルを指定して型の制約を追加することができます。コンパイラは型の制約が満たされているかチェックし、満たさない場合はエラーを出力します。

制約の追加は、: を記述してプロトコルかスーパークラスを指定します。

protocol プロトコル {
  associatedtype 関連型: プロトコル名 or スーパークラス名
}

次の例では、ItemClass をスーパークラスにして、プロトコルの関連型に制約を追加しています。ItemClass を満たす型のみプロトコルを準拠させることができます。

class ItemClass {}

protocol ItemProtocol {
  // ItemClass をスーパークラスにして、ItemClassを満たす型のみを許容する
  associatedtype Item: ItemClass
  var items: [Item] { get }
}

class ItemSubClass: ItemClass {}

struct ProductItem: ItemProtocol {
  // ItemSubClass は ItemClass を継承しているので制約を満たしている
  typealias Item = ItemSubClass
  var items = [ItemSubClass]()
}

String型を指定すると、ItemClass を満たしていないのでコンパイラはエラーを出力します。

class ItemClass {}

protocol ItemProtocol {
  associatedtype Item: ItemClass
  var items: [Item] { get }
}

struct ProductItem: ItemProtocol {
  // String型は ItemClass を満たしていない
  typealias Item = String
  var items = [String]()
}

/* 実行結果 */
// error: type 'ProductItem' does not conform to protocol 'ItemProtocol'
// struct ProductItem: ItemProtocol {

// note: possibly intended match 'ProductItem.Item' (aka 'String') does not inherit from  'ItemClass'
// typealias Item = String

// note: protocol requires nested type 'Item'; do you want to add it?

# 継承

プロトコルは他のプロトコルを継承させることができます。プロトコルを継承させることで、継承元のインターフェースを引き継ぐことができます。

継承させるには、: の後に、継承元のプロトコルをカンマ区切りで書きます。

次の例では、RectangleProtocolColorProtocol を継承するプロトコルを正義しています。

protocol RectangleProtocol {
  var width: Int { get }
  var height: Int { get }
}

protocol ColorProtocol {
  var color: String { get }
}

// RectangleProtocol と ColorProtocol を継承する
protocol RectangleColorProtocol: RectangleProtocol, ColorProtocol {}

struct RedRectangle: RectangleColorProtocol {
  // 全てのインターフェースを満たす
  var width = 200
  var height = 200
  var color = "red"
}

準拠させる構造体では、全てのインターフェースを満たす必要があります。

# クラス専用のプロトコル | Class-Only Protocol

プロトコルには、クラスのみを準拠させるように制約をかけることができます。

class キーワードを使うと準拠させる側は、クラスのみを使わなければなりません。

// class キーワードを指定する
protocol NameProtocol: class {
  var name: String { get }
}

// クラスに準拠させる
class Person: NameProtocol {
  var name: String

  init(name: String) {
    self.name = name
  }
}

構造体などを準拠させると、コンパイラはエラーを出力します。

protocol NameProtocol: class {
  var name: String { get }
}

// 構造体などを準拠させる
struct Person: NameProtocol {
  var name: String
}

/* 実行結果 */
// error: non-class type 'Person' cannot conform to class protocol 'NameProtocol'
// struct Person: NameProtocol {

# エクステンション

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

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

protocol NameProtocol {
  var name: String { get }
}

// extension を記述してプロトコルを拡張する
extension NameProtocol {
  func printName() {
    print(self.name)
  }
}

struct Person: NameProtocol {
  var name: String
}

var person1 = Person(name: "Taro")

// printName() はプロトコルに実装済みなので実行できる
person1.printName() //Taro