プロトコル(Protocols)

最終更新日: 2024/3/18 原文: https://docs.swift.org/swift-book/LanguageGuide/Protocols.html

準拠型が実装する必要がある要件を定義する。

_プロトコル_は、特定のタスクまたは機能に準拠するメソッド、プロパティ、およびその他の要件の設計図を定義します。クラス、構造体、または列挙型がプロトコルに準拠でき、それらの要件の実装を提供します。プロトコルの要件を満たす全ての型は、そのプロトコルに_準拠している_といいます。

プロトコルは、準拠する型が実装する必要がある要件を指定することに加えて、プロトコルを拡張して、これらの要件の一部を実装したり、準拠する型が利用できる追加機能を実装できます。

プロトコル構文(Protocol Syntax)

クラス、構造体、および列挙型と非常によく似た方法でプロトコルを定義します:

protocol SomeProtocol {
    // プロトコルの定義をここに
}

カスタム型は、定義の一部として、型名の後にプロトコル名をコロン(:)で区切って配置することにより、特定のプロトコルに準拠することを示します。複数のプロトコルにも準拠でき、カンマ(,)区切りで並べます:

struct SomeStructure: FirstProtocol, AnotherProtocol {
    // 構造体の定義をここに
}

クラスにスーパークラスがある場合は、準拠するプロトコルの前にスーパークラス名を記述し、その後にカンマを続けます:

class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
    // クラスの定義をここに
}

NOTE: プロトコルは型のため、 Swiftの他の型に一致するように(IntStringDouble など) 名前は大文字から始めてください。(FullyNamedRandomNumberGenerator など)

プロパティ要件(Property Requirements)

プロトコルは、特定の名前と型のインスタンスプロパティまたは型プロパティを要件にできます。プロトコルでは、格納プロパティか計算プロパティかを指定しません。必要なプロパティの名前と型を指定するだけです。また、プロトコルは、各プロパティが get のみか、get/set どちらも必要かどうかも指定します。

プロトコルが get/set を要求する場合、そのプロパティ要件は、定数格納プロパティまたは読み取り専用の計算プロパティでは満たされません。プロトコルが get のみを要求する場合でも、その要件はあらゆる種類のプロパティによって満たされ、必要ならば実装の方でプロパティの set を追加することもできます。

プロパティ要件は常に変数プロパティとして宣言され、前に var キーワードが付きます。get/set は型宣言の後に { get set } を記述することで示し、get は { get } で示します。

protocol SomeProtocol {
    var mustBeSettable: Int { get set }
    var doesNotNeedToBeSettable: Int { get }
}

プロトコルで、型プロパティの要件の前には、必ず static キーワードを付けてください。クラスによって実装されるときは、class または static キーワードをプレフィックスを付けても要件を満たします:

protocol AnotherProtocol {
    static var someTypeProperty: Int { get set }
}

単一のインスタンスプロパティを要件に持つプロトコルの例を次に示します:

protocol FullyNamed {
    var fullName: String { get }
}

完全修飾名を提供するために、FullyNamed プロトコルに準拠する型が必要です。プロトコルは、準拠する型の性質について他に何も指定していません。型が完全修飾名を提供する必要があることを指定するだけです。プロトコルでは、FullyNamed 型には、String 型の fullName という get インスタンスプロパティが宣言されています。

FullyNamed プロトコルに準拠するシンプルな構造体の例を次に示します:

struct Person: FullyNamed {
    var fullName: String
}
let john = Person(fullName: "John Appleseed")
// john.fullName は "John Appleseed"

この例では、特定の名前を持つ人物を表す Person という構造体を定義しています。その定義の最初の行の一部として FullyNamed プロトコルに準拠することが記述されています。

Person の各インスタンスには、String 型の fullName という 1 つの格納プロパティがあります。これは、FullyNamed プロトコルの単一の要件と一致し、Person がプロトコルに正しく準拠していることを意味します。(プロトコル要件が満たされていない場合、コンパイルエラーが発生します)

下記も FullyNamed プロトコルに準拠する、より複雑なクラスです:

class Starship: FullyNamed {
    var prefix: String?
    var name: String
    init(name: String, prefix: String? = nil) {
        self.name = name
        self.prefix = prefix
    }
    var fullName: String {
        return (prefix != nil ? prefix! + " " : "") + name
    }
}
var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
// ncc1701.fullName は "USS Enterprise"

このクラスは、宇宙船の読み取り専用計算プロパティとして fullName プロパティを実装します。各 Starship クラスのインスタンスは、必須の name とオプショナルの prefix を格納します。fullName プロパティは、プレフィックス値が存在する場合はそれを使用し、名前の先頭に追加して宇宙船のフルネームを作成します。

メソッド要件(Method Requirements)

プロトコルはインスタンスメソッドと型メソッドを要件にすることもできます。これらのメソッドは、通常のインスタンスおよび型メソッドとまったく同じ方法でプロトコルの定義の一部として記述されますが、中括弧({})やメソッド本文はありません。通常のメソッドと同じ規則に従って、可変長パラメータを使用できます。ただし、プロトコルの定義内のメソッドのパラメータにデフォルト値を指定することはできません。

型プロパティの要件と同様に、型メソッドをプロトコルで定義するときは、常に要件の前に static キーワードを付けます。これは、クラスによって実装されるときに、型メソッドの要件に class または static キーワードが付いている場合にも当てはまります:

protocol SomeProtocol {
    static func someTypeMethod()
}

次の例では、単一のインスタンスメソッド要件を持つプロトコルを定義しています:

protocol RandomNumberGenerator {
    func random() -> Double
}

このプロトコル RandomNumberGenerator に準拠するには random と呼ばれるインスタンスメソッドが必要です。このインスタンスメソッドは、呼び出される度に Double 値を返します。プロトコルでは指定されていませんが、この値は 0.0 から 1.0(ただし、1.0 を含まない) までの数値が想定されています。

RandomNumberGenerator プロトコルは、各乱数がどのように生成されるかについて何も指定していません。単に、ジェネレータが新しい乱数を生成する標準的な方法を要求するだけです。

下記は、RandomNumberGenerator プロトコルに準拠するクラスの実装です。このクラスは、線形合同法発生器として知られる疑似乱数発生器アルゴリズムを実装しています:

class LinearCongruentialGenerator: RandomNumberGenerator {
    var lastRandom = 42.0
    let m = 139968.0
    let a = 3877.0
    let c = 29573.0
    func random() -> Double {
        lastRandom = ((lastRandom * a + c)
            .truncatingRemainder(dividingBy:m))
        return lastRandom / m
    }
}
let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
// Here's a random number: 0.3746499199817101
print("And another one: \(generator.random())")
// And another one: 0.729023776863283

mutating メソッド要件(Mutating Method Requirements)

メソッドが属するインスタンスを変更する必要がある場合があります。値型(つまり、構造体と列挙型)のインスタンスメソッドの場合、メソッドの func キーワードの前に mutating キーワードを配置して、メソッドが属するインスタンスとそのインスタンスの全てのプロパティを変更できることを示します。このプロセスについては、Modifying Value Types from Within Instance Methods(インスタンスメソッド内からの値型の変更)で説明されています。

任意の型のインスタンスを変更することを目的としたプロトコルのインスタンスメソッドの要件を定義する場合は、mutating キーワードをメソッドにマークします。これにより、構造体と列挙型がそのメソッド要件を満たしてプロトコルに準拠することができます。

NOTE プロトコルのインスタンスメソッドの要件を mutating としてマークする場合、クラスがそのメソッドを実装するときに、mutating キーワードを記述する必要はありません。mutating キーワードは、構造体と列挙型のみ使用できます。

下記の例では、Togglable というプロトコルが定義されています。これは、toggle という単一のインスタンスメソッドの要件を定義しています。その名前が示すように、toggle() メソッドは、通常、その型のプロパティを変更することによって、準拠する型の状態をトグル(反転)することを目的としています。

toggle() メソッドは、Togglable プロトコル定義の一部として mutating キーワードがマークされており、メソッドが呼び出されたときに準拠するインスタンスの状態を変更する可能性を示しています。

protocol Togglable {
    mutating func toggle()
}

構造体または列挙型に対して Togglable プロトコルを実装する場合、その構造体または列挙型は、mutating がマークされている toggle() メソッドの実装を提供することにより、プロトコルに準拠できます。

下記の例では、OnOffSwitch という列挙型を定義しています。この列挙型は、onoff の 2 つのケースによって示される 2 つの状態を切り替えます。列挙型の toggle 実装は、Togglable プロトコルの要件を満たすように、mutating としてマークされています:

enum OnOffSwitch: Togglable {
    case off, on
    mutating func toggle() {
        switch self {
        case .off:
            self = .on
        case .on:
            self = .off
        }
    }
}
var lightSwitch = OnOffSwitch.off
lightSwitch.toggle()
// lightSwitch は .on

イニシャライザ要件(Initializer Requirements)

プロトコルでは、型に準拠するために特定のイニシャライザが必要な場合があります。このイニシャライザは、通常のイニシャライザとまったく同じ方法でプロトコルの定義の一部として記述できますが、中括弧({})やイニシャライザ本文はありません:

protocol SomeProtocol {
    init(someParameter: Int)
}

プロトコルイニシャライザ要件のクラス実装(Class Implementations of Protocol Initializer Requirements)

プロトコルのイニシャライザの要件は、準拠するクラスで指定イニシャライザまたは convenience イニシャライザとして実装できます。どちらの場合も、required 修飾子でイニシャライザの実装をマークする必要があります:

class SomeClass: SomeProtocol {
    required init(someParameter: Int) {
        // イニシャライザの実装をここに
    }
}

required 修飾子を使用すると、準拠するクラスの全てのサブクラスで、イニシャライザ要件を明示的に実装または継承して、プロトコルに準拠する必要があります。

required イニシャライザの詳細については、Required Initializers(必須イニシャライザ)を参照ください。

NOTE final クラスはサブクラス化できないため、 final 修飾子でマークされているクラスでは、プロトコルのイニシャライザの実装を required 修飾子でマークする必要はありません。final 修飾子の詳細については、Preventing Overrides(オーバーライドを防ぐ)を参照ください。

サブクラスがスーパークラスからの指定イニシャライザをオーバーライドし、そのイニシャライザがプロトコル要件も満たす場合、required 修飾子と override 修飾子の両方でイニシャライザの実装をマークします:

protocol SomeProtocol {
    init()
}

class SomeSuperClass {
    init() {
        // イニシャライザの実装をここに
    }
}

class SomeSubClass: SomeSuperClass, SomeProtocol {
    // "required" は SomeProtocol から、 "override" は SomeSuperClass から
    required override init() {
        // イニシャライザの実装をここに
    }
}

失敗可能イニシャライザ要件(Failable Initializer Requirements)

プロトコルは、Failable Initializers(失敗可能イニシャライザ)で定義されているように、失敗可能イニシャライザ要件を定義できます。

失敗可能イニシャライザ要件は、準拠する型の失敗可能または失敗しないイニシャライザによって満たされます。失敗しないイニシャライザ要件は、失敗しないイニシャライザまたは暗黙的にアンラップされた失敗可能イニシャライザによって満たされます。

型としてのプロトコル(Protocols as Types)

プロトコルは、実際には機能を実装しません。 それにもかかわらず、コード内で型としてプロトコルを使用できます。

プロトコルを型として使用する最も一般的な方法は、プロトコルをジェネリック制約として使用することです。ジェネリック制約を持つコードは、プロトコルに準拠する任意の型を扱うことができ、特定の型は API を使用する側のコードで選択されます。例えば、引数を取る関数を呼び出したとき、その引数の型がジェネリックであれば、呼び出し元がその型を選びます。

Opaque 型を持つコードは、プロトコルに準拠した何らかの型を使って機能します。基本的な型はコンパイル時に判明し、API 実装はその型を選択しますが、その型の正体は API のクライアントから隠されています。例えば、ある関数の戻り値の型を隠し、その値があるプロトコルに準拠していることだけを保証します。

Box プロトコル型を持つコードは、プロトコルに準拠する、実行時に選択された任意の型で動作します。この実行時の柔軟性をサポートするために、Swift は必要なときに間接的なレイヤーを追加します。これは、ボックスとして知られており、これはパフォーマンスコストを伴います。この柔軟性が理由で、Swift はコンパイル時に基礎となる型を知らなりません。つまり、プロトコルによって必要とされるメンバのみにアクセスできることを意味します。基礎となる型で定義された他の API にアクセスするには、実行時にキャストが必要です。

プロトコルをジェネリック制約として使用することについては、ジェネリクス(Generics)を参照してください。Opaque 型と Box プロトコル型については、Opaque 型とBox プロトコル型(Opaque Types and Boxed Types)を参照してください。

委譲(Delegation)

_委譲_は、クラスまたは構造体がその責任の一部を別の型のインスタンスに引き渡す(または委譲する)ことを可能にするデザインパターンです。このデザインパターンは、委譲される責任をカプセル化するプロトコルを定義することによって実装され、準拠する型 (_デリゲート_と呼ばれる)が委譲された機能を提供することを保証します。委譲を使用して、特定のアクションに応答したり、その準拠した具体的な型を知らなくても外部ソースからデータを取得したりできます。

下記の例では、サイコロゲームと、ゲームの進行を追跡するデリゲートのネストされたプロトコルを定義しています:

class DiceGame {
    let sides: Int
    let generator = LinearCongruentialGenerator()
    weak var delegate: Delegate?

    init(sides: Int) {
        self.sides = sides
    }

    func roll() -> Int {
        return Int(generator.random() * Double(sides)) + 1
    }

    func play(rounds: Int) {
        delegate?.gameDidStart(self)
        for round in 1...rounds {
            let player1 = roll()
            let player2 = roll()
            if player1 == player2 {
                delegate?.game(self, didEndRound: round, winner: nil)
            } else if player1 > player2 {
                delegate?.game(self, didEndRound: round, winner: 1)
            } else {
                delegate?.game(self, didEndRound: round, winner: 2)
            }
        }
        delegate?.gameDidEnd(self)
    }

    protocol Delegate: AnyObject {
        func gameDidStart(_ game: DiceGame)
        func game(_ game: DiceGame, didEndRound round: Int, winner: Int?)
        func gameDidEnd(_ game: DiceGame)
    }
}

DiceGame クラスは、各プレイヤーが順番にサイコロを振り、一番高い数字を出したプレイヤーがそのラウンドに勝つゲームを実装しています。このクラスでは、サイコロを振るための乱数を生成するために、この章の前の例で使った線形合同生成器を使います。

DiceGame.Delegate プロトコルは、サイコロゲームの進行を追跡するために導入できます。DiceGame.Delegate プロトコルは常にサイコロゲームのコンテキストで使用されるため、DiceGame クラスの内部にネストされます。プロトコルは、外側の宣言がジェネリックでない限り、構造体やクラスのような型宣言の内部にネストできます。型のネストについては、ネスト型(Nested Types) を参照してください。

_強参照_循環を防ぐために、デリゲートは_弱参照_として宣言されます。弱参照については、Strong Reference Cycles Between Class Instances(クラスインスタンス間の強循環参照)を参照ください。プロトコルをクラス専用としてマークすると、DiceGame クラスがそのデリゲートを弱参照で使用しなければならないことを宣言できるようになります。クラス専用プロトコルは、Class-Only Protocols(クラス専用プロトコル)で説明したように、AnyObject を継承します。

DiceGame.Delegate は、ゲームの進行状況を追跡するための 3 つのメソッドを提供しています。これらの 3 つのメソッドは、上記の play(rounds:) メソッド内のゲームロジックに組み込まれています。DiceGame クラスが、新しいゲームの開始、新しいターンの開始、またはゲームの終了時に、このデリゲートメソッドを呼びます。

delegate プロパティはオプショナルの DiceGame.Delegate のため、play(rounds:) メソッドはデリゲートメソッドを呼び出す度にオプショナルチェーンを使用します。delegate プロパティが nil の場合、これらのデリゲートの呼び出しはエラーを出力せずに失敗します。delegate プロパティが nil ではない場合、デリゲートメソッドが呼び出され、パラメータとして DiceGame インスタンスが渡されます。

次の例は、DiceGame.Delegate プロトコルに準拠する DiceGameTracker というクラスを示しています:

class DiceGameTracker: DiceGame.Delegate {
    var playerScore1 = 0
    var playerScore2 = 0
    func gameDidStart(_ game: DiceGame) {
        print("Started a new game")
        playerScore1 = 0
        playerScore2 = 0
    }
    func game(_ game: DiceGame, didEndRound round: Int, winner: Int?) {
        switch winner {
            case 1:
                playerScore1 += 1
                print("\(round)回戦はPlayer 1の勝ち")
            case 2: playerScore2 += 1
                print("\(round)回戦はPlayer 2の勝ち")
            default:
                print("The round was a draw")
        }
    }
    func gameDidEnd(_ game: DiceGame) {
        if playerScore1 == playerScore2 {
            print("ゲームはドローで終わりました。")
        } else if playerScore1 > playerScore2 {
            print("Player 1の勝利!")
        } else {
            print("Player 2の勝利!")
        }
    }
}

DiceGameTracker クラスは、DiceGame.Delegate プロトコルが要求する 3 つのメソッドをすべて実装しています。これらのメソッドを使用して、新しいゲームの開始時に両プレイヤーのスコアをゼロにし、各ラウンドの終了時にスコアを更新し、ゲームの終了時に勝者を発表します。

DiceGameTracker の実際の挙動は次のとおりです:

let tracker = DiceGameTracker()
let game = DiceGame(sides: 6)
game.delegate = tracker
game.play(rounds: 3)
// 新しいゲームを開始します
// 1回戦は Player2 の勝ち
// 2回戦は Player2 の勝ち
// 3回戦は Player1 の勝ち
// Player2 の勝利!

拡張機能を使ったプロトコル準拠の追加(Adding Protocol Conformance with an Extension)

既存の型のソースコードにアクセスできない場合でも、既存の型を拡張して新しいプロトコルに準拠させることができます。extension は、新しいプロパティ、メソッド、およびサブスクリプトを既存の型に追加できるため、プロトコルの要件を追加できます。拡張機能の詳細については、Extensions(拡張)を参照ください。

NOTE 型の既存のインスタンスは、extension でそのインスタンスの型がプロトコルに準拠すると、自動的にプロトコルに準拠します。

例えば、TextRepresentable と呼ばれるプロトコルは、テキストとして表現できる任意の型で実装できます。これは、それ自体の説明や、現在の状態の説明などの可能性があります:

protocol TextRepresentable {
    var textualDescription: String { get }
}

上記の Dice クラスを拡張して、TextRepresentable に準拠させることができます:

extension Dice: TextRepresentable {
    var textualDescription: String {
        return "A \(sides)-sided dice"
    }
}

この extension は、Dice の元の実装とまったく同じ方法で新しいプロトコルに準拠しています。プロトコル名を型名の後にコロン(:)で区切って指定し、プロトコルの全ての要件の実装は extension の中括弧内({})に指定します。

これで、どの Dice インスタンスも TextRepresentable として扱えるようになりました:

let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
print(d12.textualDescription)
// A 12-sided dice

同様に、SnakesAndLadders クラスを拡張して、TextRepresentable プロトコルに準拠させることができます:

extension SnakesAndLadders: TextRepresentable {
    var textualDescription: String {
        return "A game of Snakes and Ladders with \(finalSquare) squares"
    }
}
print(game.textualDescription)
// A game of Snakes and Ladders with 25 squares

条件付きでのプロトコルへの準拠(Conditionally Conforming to a Protocol)

ジェネリック型は、型のジェネリックパラメータがプロトコルに準拠している場合など、特定の条件下でのみプロトコルの要件を満たすことができるようにします。型を拡張するときに制約を並べることで、ジェネリック型を条件付きでプロトコルに準拠させることができます。where 句を記述して、準拠するプロトコルの名前の後にこれらの制約を記述します。ジェネリック where 句の詳細については、Generic Where Clauses(ジェネリック where 句)を参照ください。

次の拡張により、Array インスタンスが TextRepresentable に準拠する型の要素を格納する場合は、常に TextRepresentable プロトコルに準拠するようになります:

extension Array: TextRepresentable where Element: TextRepresentable {
    var textualDescription: String {
        let itemsAsText = self.map { $0.textualDescription }
        return "[" + itemsAsText.joined(separator: ", ") + "]"
    }
}
let myDice = [d6, d12]
print(myDice.textualDescription)
// [A 6-sided dice, A 12-sided dice]

拡張機能を使ったプロトコル準拠の宣言(Declaring Protocol Adoption with an Extension)

型がすでにプロトコルの全ての要件を満たしているものの、そのプロトコルに準拠することを表明していない場合は、空の extension でプロトコルに準拠することができます:

struct Hamster {
    var name: String
    var textualDescription: String {
        return "A hamster named \(name)"
    }
}
extension Hamster: TextRepresentable {}

これで TextRepresentable が必要な型へ、Hamster のインスタンスを使用できるようになりました:

let simonTheHamster = Hamster(name: "Simon")
let somethingTextRepresentable: TextRepresentable = simonTheHamster
print(somethingTextRepresentable.textualDescription)
// A hamster named Simon

NOTE 要件を満たすだけで、型が自動的にプロトコルに準拠するわけではありません。プロトコルへの準拠を常に明示的に宣言する必要があります。

デフォルト実装を使用したプロトコル準拠(Adopting a Protocol Using a Synthesized Implementation)

Swift は、多くのシンプルなケースで、EquatableHashable、および Comparable のプロトコルへの準拠を自動的に提供できます。このデフォルト実装を使用すると、プロトコル要件を自分で実装するために、繰り返しコードを記述する必要がなくなります。

Swift は、次の種類の独自の型に対して Equatable のデフォルト実装を提供します。

  • Equatable プロトコルに準拠した型の格納プロパティのみで構成される構造体

  • 関連値が Equatable プロトコルに準拠する型のみの列挙型

  • 関連値のない列挙型

== のデフォルト実装を受け取るには、自分で == 演算子を実装せずに、元の宣言を含むファイルで Equatable への準拠を宣言します。Equatable プロトコルは、!= のデフォルトの実装を提供しています。

下記の例では、Vector2D 構造体と同様に、3 次元位置ベクトル (x、y、z)Vector3D 構造体を定義しています。xyz プロパティは全て Equatable に準拠した型なので、Vector3D は等価演算子のデフォルト実装を受け取ります。

struct Vector3D: Equatable {
    var x = 0.0, y = 0.0, z = 0.0
}

let twoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)
let anotherTwoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)
if twoThreeFour == anotherTwoThreeFour {
    print("These two vectors are also equivalent.")
}
// These two vectors are also equivalent.

Swift は、次の種類の独自の型に対して Hashable のデフォルト実装を提供します。

  • Hashable プロトコルに準拠した型の格納プロパティのみで構成される構造体

  • 関連値が Hashable プロトコルに準拠する型のみの列挙型

  • 関連値のない列挙型

hash(into:) のデフォルト実装を受け取るには、hash(into:) メソッドを自分で実装せずに、元の宣言を含むファイルで Hashable への準拠を宣言します。

Swift は、Raw Value を持たない列挙型に Comparable のデフォルト実装を提供します。列挙型に関連値がある場合、それらは全て Comparable プロトコルに準拠している必要があります。< のデフォルト実装を受け取るには、自分で < 演算子を実装せずに、元の列挙宣言を含むファイルで Comparable への準拠を宣言します。残りの比較演算子(<=>、および >=)は Comparable プロトコルがデフォルトで実装を提供してます。

下記の例では、初心者、中級者、および専門家向けのケースを含む SkillLevel 列挙型を定義しています。エキスパートは、持っている星の数によってさらにランク付けされます。

enum SkillLevel: Comparable {
    case beginner
    case intermediate
    case expert(stars: Int)
}
var levels = [SkillLevel.intermediate, SkillLevel.beginner,
              SkillLevel.expert(stars: 5), SkillLevel.expert(stars: 3)]
for level in levels.sorted() {
    print(level)
}
// beginner
// intermediate
// expert(stars: 3)
// expert(stars: 5)

プロトコル型のコレクション(Collections of Protocol Types)

プロトコルは、Protocols as Types(型としてのプロトコル)で説明されているように、配列や辞書などのコレクションに格納される型として使用できます。この例では、TextRepresentable の配列を作成します。

let things: [TextRepresentable] = [game, d12, simonTheHamster]

配列内のアイテムを繰り返し処理し、各アイテムの説明をテキストで出力できるようになりました:

for thing in things {
    print(thing.textualDescription)
}
// A game of Snakes and Ladders with 25 squares
// A 12-sided dice
// A hamster named Simon

thing 定数は TextRepresentable 型なことに注目してください。内部の実際のインスタンスがそれらの型の 1 つの場合でも、型は DiceDiceGame、または Hamster ではありません。これは TextRepresentable 型で、TextRepresentable は全て textualDescription プロパティを持つことがわかっているため、ループ処理の中で安全に thing.textualDescription にアクセスできます。

プロトコル継承(Protocol Inheritance)

プロトコルは 1 つ以上の他のプロトコルを継承でき、継承する要件の上にさらに要件を追加できます。プロトコル継承の構文はクラス継承の構文に似ていますが、継承された複数のプロトコルをカンマ(,)で区切って並べます:

protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
    // プロトコルの実装をここに
}

上の TextRepresentable プロトコルを継承するプロトコルの例を次に示します:

protocol PrettyTextRepresentable: TextRepresentable {
    var prettyTextualDescription: String { get }
}

この例では、TextRepresentable から継承する新しいプロトコル PrettyTextRepresentable を定義しています。PrettyTextRepresentable に準拠するものは全て、TextRepresentable の全ての要件に加えて、PrettyTextRepresentable の追加の要件を満たす必要があります。この例では、PrettyTextRepresentable は、String を返す prettyTextualDescription というプロパティの get を提供するための 1 つの要件を追加します。

SnakesAndLadders クラスを拡張して、PrettyTextRepresentable に準拠させることができます:

extension SnakesAndLadders: PrettyTextRepresentable {
    var prettyTextualDescription: String {
        var output = textualDescription + ":\n"
        for index in 1...finalSquare {
            switch board[index] {
            case let ladder where ladder > 0:
                output += "▲ "
            case let snake where snake < 0:
                output += "▼ "
            default:
                output += "○ "
            }
        }
        return output
    }
}

この extension は、SnakesAndLadders 型が PrettyTextRepresentable プロトコルに準拠し、 prettyTextualDescription のプロパティの実装を提供していることを示しています。PrettyTextRepresentable に準拠するものは、必ず全て TextRepresentable なので、prettyTextualDescription の実装は、TextRepresentable プロトコルの textualDescription プロパティにアクセスして出力文字列を開始します。コロン(:)と改行()を追加し、これをテキストの開始表現として使用します。次に、ボードの正方形の配列を繰り返し処理し、各正方形の内容を表す幾何学的形状を追加します:

  • 正方形の値が 0 より大きい場合、それははしごの根本で、 で表されます

  • マスの値が 0 未満の場合、それはヘビの頭で、 で表されます

  • それ以外の場合、正方形の値は 0 で、それは で表される「自由な」正方形です

prettyTextualDescription プロパティを使用して、SnakesAndLadders インスタンスの説明をきれいに表示できるようになりました:

print(game.prettyTextualDescription)
// A game of Snakes and Ladders with 25 squares:
// ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○

クラス専用プロトコル(Class-Only Protocols)

AnyObject プロトコルをプロトコル継承の一覧に追加することで、プロトコルへの準拠を(構造体や列挙型ではなく)クラス型に制限できます:

protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
    // クラス専用プロトコルの実装をここに
}

上記の例では、SomeClassOnlyProtocol はクラス型でのみ準拠できます。SomeClassOnlyProtocol に準拠しようとする構造体または列挙型の定義を作成すると、コンパイルエラーになります。

NOTE そのプロトコルの要件によって定義された動作が、準拠する型が値型のセマンティクスではなく参照型のセマンティクスであることを想定(要求)する場合は、クラス専用プロトコルを使用してください。参照型と値型のセマンティクスの詳細については、Structures and Enumerations Are Value Types(構造体と列挙型は値型)Classes Are Reference Types(クラスは参照型)を参照ください。

プロトコル合成(Protocol Composition)

同時に複数のプロトコルに準拠すると便利な場合があります。プロトコル合成を使用して、複数のプロトコルを 1 つの要件に組み合わせることができます。プロトコル合成は、合成内の全てのプロトコルの要件を組み合わせた一時的なローカルプロトコルを定義したかのように動作します。プロトコル合成は、新しいプロトコル型を定義しません。

プロトコル合成は SomeProtocol & AnotherProtocol という形式で記述します。アンパサンド (&) で区切って、必要な数のプロトコルを並べることができます。プロトコルに加えて、プロトコル合成には、必要なスーパークラスを 1 つ含めることもできます。

NamedAged という 2 つのプロトコルを組み合わせて、単一の関数パラメータに指定した例を次に示します:

protocol Named {
    var name: String { get }
}
protocol Aged {
    var age: Int { get }
}
struct Person: Named, Aged {
    var name: String
    var age: Int
}
func wishHappyBirthday(to celebrator: Named & Aged) {
    print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
let birthdayPerson = Person(name: "Malcolm", age: 21)
wishHappyBirthday(to: birthdayPerson)
// Happy birthday, Malcolm, you're 21!

この例では、Named プロトコルには、name という String 型のプロパティの get 要件が 1 つあります。Aged プロトコルには、age と呼ばれる Int 型のプロパティの get 要件が 1 つあります。どちらのプロトコルも、Person と呼ばれる構造体に準拠されています。

この例では、wishHappyBirthday(to:) 関数も定義されています。celebrator パラメータの型は Named & Aged です。これは、「Named プロトコルと Aged プロトコルの両方に準拠する任意の型」を意味します。必要なプロトコルの両方に準拠している限り、関数に渡される特定の型は問題ではありません。

この例では、birthdayPerson という名前の新しい Person インスタンスを作成し、この新しいインスタンスを wishHappyBirthday(to:) 関数に渡しています。Person は両方のプロトコルに準拠しているため、wishHappyBirthday(to:) 関数は誕生日の挨拶を出力できます。

先ほどの例の Named プロトコルと Location クラスを組み合わせた例を次に示します:

class Location {
    var latitude: Double
    var longitude: Double
    init(latitude: Double, longitude: Double) {
        self.latitude = latitude
        self.longitude = longitude
    }
}
class City: Location, Named {
    var name: String
    init(name: String, latitude: Double, longitude: Double) {
        self.name = name
        super.init(latitude: latitude, longitude: longitude)
    }
}
func beginConcert(in location: Location & Named) {
    print("Hello, \(location.name)!")
}

let seattle = City(name: "Seattle", latitude: 47.6, longitude: -122.3)
beginConcert(in: seattle)
// Hello, Seattle!

beginConcert(in:) 関数は、Location & Named 型のパラメータを受け取ります。これは、「Location のサブクラスで、Named プロトコルに準拠する任意の型」を意味します。この場合、City は両方の要件を満たしています。

PersonLocation のサブクラスではないため、beginConcert(in:) 関数に birthdayPerson を渡すことは無効です。同様に、Named プロトコルに準拠していない Location のサブクラスを作成した場合、その型のインスタンスで beginConcert(in:) を呼び出すことも無効です。

プロトコル準拠チェック(Checking for Protocol Conformance)

Type Casting(型キャスト)で説明されている is および as 演算子を使用して、プロトコルの準拠を確認し、特定のプロトコルにキャストできます。プロトコルのチェックと型へのキャストは、型のチェックやキャストとまったく同じ構文を使用します。

  • is 演算子は、インスタンスがプロトコルに準拠している場合は true を返し、準拠していない場合は false を返します

  • ダウンキャスト演算子の as? は、プロトコルの型のオプショナル値を返します。インスタンスがそのプロトコルに準拠していない場合、この値は nil です

  • ダウンキャスト演算子の as! は、強制的にダウンキャストし、ダウンキャストが成功しない場合は実行時エラーを引き起こします

この例では、HasArea というプロトコルを定義しています。このプロトコルには、area という Double 型の単一のプロパティの get 要件があります:

protocol HasArea {
    var area: Double { get }
}

CircleCountry の 2 つのクラスがあり、どちらも HasArea プロトコルに準拠しています:

class Circle: HasArea {
    let pi = 3.1415927
    var radius: Double
    var area: Double { return pi * radius * radius }
    init(radius: Double) { self.radius = radius }
}
class Country: HasArea {
    var area: Double
    init(area: Double) { self.area = area }
}

Circle クラスは、radius という格納プロパティに基づいて、計算プロパティ area プロパティの要件を実装します。Country クラスは、格納プロパティとして area プロパティを直接実装します。どちらのクラスも、HasArea プロトコルに正しく準拠しています。

下記は、HasArea プロトコルに準拠していない Animal というクラスです:

class Animal {
    var legs: Int
    init(legs: Int) { self.legs = legs }
}

CircleCountry、および Animal クラスには、共通の基本クラスがありません。とはいえ、それらは全てクラスなため、3 つ全ての型のインスタンスを使用して、AnyObject 型の値を格納する配列を初期化できます:

let objects: [AnyObject] = [
    Circle(radius: 2.0),
    Country(area: 243_610),
    Animal(legs: 4)
]

objects の配列は、半径 2 の Circle インスタンス、英国の表面積を平方キロメートル単位で初期化した Country インスタンス、そして 4 本の足を持つ Animal インスタンスを含む配列リテラルで初期化されています。

objects 配列を繰り返し処理して、配列内の各オブジェクトをチェックして、HasArea プロトコルに準拠しているかどうかを確認できます:

for object in objects {
    if let objectWithArea = object as? HasArea {
        print("Area is \(objectWithArea.area)")
    } else {
        print("Something that doesn't have an area")
    }
}
// Area is 12.5663708
// Area is 243610.0
// Something that doesn't have an area

配列内のオブジェクトが HasArea プロトコルに準拠している場合、as? 演算子によって返されるオプショナル値はオプショナルバインディングを使用して objectWithArea という名前の定数にアンラップされます。objectWithArea 定数は HasArea 型であることが分かっているので、その area プロパティに型安全にアクセスして、表示することができます。

プロトコルに準拠したオブジェクトは、キャストで変更されないことに注目してください。それらはそれぞれ CircleCountryAnimal です。ただし、オブジェクトが objectWithArea 定数に格納されている時点では、HasArea 型とのみ認識されているため、アクセスできるのは area プロパティのみです。

オプショナルのプロトコル要件(Optional Protocol Requirements)

プロトコルにオプショナルの要件を定義できます。これらの要件は、プロトコルに準拠する型によって実装される必要はありません。オプショナルの要件には、プロトコルの定義の一部として optional 修飾子が付けられます。Objective-C と相互運用するコードを作成できるように、オプショナルの要件が利用可能です。プロトコルとオプショナルの要件の両方が @objc 属性でマークされている必要があります。@objc プロトコルは、構造体や列挙型で準拠することはできず、クラスでのみ準拠できることに注目してください。

オプショナルの要件でメソッドまたはプロパティを使用すると、その型は自動的にオプショナルになります。たとえば、(Int) -> String 型のメソッドは ((Int) -> String)? になります。メソッドの戻り値ではなく、関数型全体がオプショナルになっていることに注目してください。

プロトコルに準拠する型によって要件が実装されていない可能性を考慮して、オプショナルのプロトコル要件をオプショナルチェーンで呼び出すことができます。オプショナルメソッドの実装を確認するには、メソッドの呼び出し時にメソッド名の後に疑問符(?)を記述します(someOptionalMethod?(someArgument) など)。オプショナルチェーンについては、Optional Chaining(オプショナルチェーン)を参照ください。

次の例では、Counter という整数をカウントするクラスを定義しています。これは、外部データソースを使用して増分量を提供します。このデータソースは、次の 2 つのオプショナルの要件がある CounterDataSource プロトコルによって定義されます:

@objc protocol CounterDataSource {
    @objc optional func increment(forCount count: Int) -> Int
    @objc optional var fixedIncrement: Int { get }
}

CounterDataSource プロトコルは、increment(forCount:) と呼ばれるオプショナルメソッドの要件と、fixedIncrement と呼ばれるオプショナルのプロパティの要件を定義しています。これらの要件は、データソースが Counter インスタンスに適切な増分量を提供するための 2 つの異なる方法を定義します。

NOTE 厳密に言えば、いずれのプロトコル要件を実装していなくても、CounterDataSource に準拠する独自クラスを作成できます。結局のところ、どちらもオプショナルです。技術的には許可されていますが、これはあまり有用なデータソースではありません。

以下で定義される Counter クラスには、CounterDataSource? 型のオプショナル値の dataSource プロパティがあります:

class Counter {
    var count = 0
    var dataSource: CounterDataSource?
    func increment() {
        if let amount = dataSource?.increment?(forCount: count) {
            count += amount
        } else if let amount = dataSource?.fixedIncrement {
            count += amount
        }
    }
}

Counter クラスは、現在の値を count という変数プロパティに保存します。Counter クラスは、メソッドが呼び出されるたびに count プロパティを増加する increment というメソッドも定義します。

increment() メソッドは、最初に、データソースで increment(forCount:) メソッドの実装を呼び出して、増分量を取得しようとします。increment() メソッドは、オプショナルチェーンを使用して increment(forCount:) の呼び出しを試み、現在の count をメソッドの単一の引数として渡します。

ここでは、2 つの階層のオプショナルチェーンが行われていることに注目してください。まず、dataSourcenil の可能性があるため、dataSource にはその名前の後に疑問符(?)があり、dataSourcenil でない場合にのみ increment(forCount:) を呼び出す必要があることを示しています。次に、dataSource が存在する場合でも、increment(forCount:) がオプショナルのため、必ず実装されている保証はありません。ここで、increment(forCount:) が実装されない可能性も、オプショナルチェーンによって処理されます。increment(forCount:) の呼び出しは、increment(forCount:) が存在する場合、つまり nil でない場合にのみ発生します。これが increment(forCount:) にも名前の後に疑問符が付いている理由です。

increment(forCount:) の呼び出しはこれら 2 つの理由のいずれかで失敗する可能性があるため、呼び出しはオプショナルの Int 値を返します。これは、CounterDataSource の定義で increment(forCount:) がオプショナルではない Int 値を返すように定義されている場合でも当てはまります。オプショナルチェーンは 2 回行われていますが、結果は 1 つのオプショナルにラップされます。複数のオプショナルチェーンの詳細については、Linking Multiple Levels of Chaining(複数階層のチェーンへのリンク)を参照ください。

increment(forCount:) を呼び出した後、それが返すオプショナルの Int は、オプショナルバインディングを使用して、amount という定数にアンラップされます。オプショナルの Int に値が含まれている場合、つまり、デリゲートとメソッドの両方が存在し、メソッドが値を返した場合、count 格納プロパティに値が追加され、増加の動作が完了します。

increment(forCount:) メソッドから値を取得できない場合(dataSourcenil か、データソースが increment(forCount:) を実装していないため)、 increment() メソッドはデータソースの fixedIncrement プロパティから値を取得しようとします。fixedIncrement プロパティもオプショナルの要件のため、その値はオプショナルの Int 値です。ただし、fixedIncrement は、CounterDataSource プロトコル定義の一部として非オプショナルの Int プロパティとして定義されています。

下記は、fixedIncrement にアクセスされる度にデータソースが定数 3 を返す簡単な CounterDataSource の実装です。これは、オプショナルの fixedIncrement プロパティ要件を実装することでこれを行います。

class ThreeSource: NSObject, CounterDataSource {
    let fixedIncrement = 3
}

ThreeSource のインスタンスを新しい Counter インスタンスのデータソースとして使用できます:

var counter = Counter()
counter.dataSource = ThreeSource()
for _ in 1...4 {
    counter.increment()
    print(counter.count)
}
// 3
// 6
// 9
// 12

上記のコードは、新しい Counter インスタンスを作成します。データソースを新しい ThreeSource インスタンスに設定します。そして、カウンタの increment() メソッドを 4 回呼び出します。予想どおり、カウンタの count プロパティは、increment() が呼び出されるたびに 3 ずつ増加します。

下記は TowardsZeroSource と呼ばれるより複雑なデータソースで、Counter インスタンスを現在の count からゼロに向かってカウントアップまたはカウントダウンします:

class TowardsZeroSource: NSObject, CounterDataSource {
    func increment(forCount count: Int) -> Int {
        if count == 0 {
            return 0
        } else if count < 0 {
            return 1
        } else {
            return -1
        }
    }
}

TowardsZeroSource クラスは、CounterDataSource プロトコルからオプショナルの increment(forCount:) メソッドを実装し、count 引数を使用して、カウントする方向を決定します。count が既にゼロの場合、メソッドは 0 を返し、それ以上のカウントが行われないことを示します。

TowardsZeroSource のインスタンスを既存の Counter インスタンスとともに使用して、-4 から 0 までカウントできます。カウンタがゼロに達すると、それ以上のカウントは行われません:

counter.count = -4
counter.dataSource = TowardsZeroSource()
for _ in 1...5 {
    counter.increment()
    print(counter.count)
}
// -3
// -2
// -1
// 0
// 0

プロトコル Extension(Protocol Extensions)

プロトコル Extension を使用して、準拠する型にメソッド、イニシャライザ、サブスクリプト、および計算プロパティの実装を提供できます。これにより、準拠する個々の型やグローバル関数ではなく、プロトコル自体に動作を定義できます。

例えば、RandomNumberGenerator プロトコルを拡張して、必須要件の random() メソッドを使用してランダムな Bool 値を返す randomBool() メソッドを提供できます:

extension RandomNumberGenerator {
    func randomBool() -> Bool {
        return random() > 0.5
    }
}

プロトコル Extension を作成することにより、全ての準拠する型は、追加の変更なしでこのメソッドの実装を自動的に使用することができます。

let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
// Here's a random number: 0.3746499199817101
print("And here's a random Boolean: \(generator.randomBool())")
// And here's a random Boolean: true

プロトコル Extension は、準拠する型に実装を追加できますが、プロトコルにさらに定義を追加して拡張したり、別のプロトコルを継承することはできません。プロトコルの継承は、常にプロトコル宣言内で指定されます。

デフォルト実装の提供(Providing Default Implementations)

プロトコル Extension を使用して、そのプロトコルのメソッドまたは計算プロパティ要件にデフォルトの実装を提供できます。準拠する型が要件のメソッドまたはプロパティの実装を提供している場合、プロトコル Extension によって提供されるものの代わりに、準拠する型の実装が使用されます。

NOTE extension によって提供されるプロトコル要件のデフォルト実装は、オプショナルのプロトコル要件とは異なります。準拠する型は、独自の実装を提供する必要はありませんが、デフォルトの実装を持つ要件は、オプショナルチェーンなしで呼び出すことができます。

例えば、TextRepresentable プロトコルを継承する PrettyTextRepresentable プロトコルは、要件の prettyTextualDescription プロパティのデフォルト実装を提供して、textualDescription プロパティにアクセスした結果を返すことができます:

extension PrettyTextRepresentable  {
    var prettyTextualDescription: String {
        return textualDescription
    }
}

プロトコル Extensionに制約の追加(Adding Constraints to Protocol Extensions)

プロトコル Extension を定義するとき、準拠する型が拡張したメソッドとプロパティを使用できる前に、その型が満たす必要がある制約を指定できます。これらの制約は、拡張するプロトコルの名前の後のジェネリックの where 句によって記述されます。ジェネリック where 句の詳細については、Generic Where Clauses(ジェネリック where 句)を参照ください。

例えば、要素が Equatable プロトコルに準拠しているコレクションに適用される Collection プロトコルの拡張を定義できます。コレクションの要素を Swift 標準ライブラリの Equatable プロトコルに制約することで、== および != 演算子を使用して、2 つの要素間の等価性と不等価性をチェックできます:

extension Collection where Element: Equatable {
    func allEqual() -> Bool {
        for element in self {
            if element != self.first {
                return false
            }
        }
        return true
    }
}

allEqual() メソッドは、コレクション内の全ての要素が等しい場合にのみ true を返します。

1 つは全ての要素が同じで、もう 1 つは要素が同じでない、整数の 2 つの配列を考えます:

let equalNumbers = [100, 100, 100, 100, 100]
let differentNumbers = [100, 100, 200, 100, 200]

配列は Collection に準拠し、整数は Equatable に準拠しているため、 equalNumbers および differentNumbersallEqual() メソッドを使用できます:

print(equalNumbers.allEqual())
// true
print(differentNumbers.allEqual())
// false

NOTE 準拠する型が、複数の制約が付いた拡張の要件を満たす同じメソッドまたはプロパティを実装している場合、Swift は最も厳しい制約がついている実装を選択して使用します。

最終更新