Swift 4.2 日本語化計画 : Swift 4.2
プロトコル
プロトコル は、特定のタスクや機能の一部に適するメソッド、プロパティ、およびその他の要件の青写真を定義します。プロトコルは、それらの要件の実際の実装を提供するために、クラス、構造体、または列挙型によって 採用 される事ができます。プロトコルの要件を満たす全ての型は、そのプロトコルに 準拠する と言われます。
準拠する型が実装しなければならない要件を指定する事に加え、プロトコルを拡張して、これらの要件の一部を実装するか、または準拠する型が利用できる追加の機能を実装できます。
プロトコルの構文
クラス、構造体、列挙型と非常に似た方法でプロトコルを定義できます。
- protocol SomeProtocol {
-         // protocol definition goes here
- }
カスタム型は、特定のプロトコルを採用すると述べるのに、それらの定義の一部として、プロトコルの名前をコロンで区切って型の名前の後に書きます。複数のプロトコルを一覧表示でき、カンマで区切られます。
- struct SomeStructure: FirstProtocol, AnotherProtocol {
-         // structure definition goes here
- }
クラスがスーパークラスを持っている場合、それが採用する全てのプロトコルの前にスーパークラス名を一覧表示し、コンマを続けます。
- class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
-         // class definition goes here
- }
プロパティの要件
プロトコルは、インスタンスプロパティや、特定の名前と型を持つ型プロパティを提供するために、全ての準拠する型を必要とします。プロトコルはプロパティが格納されたプロパティか計算されたプロパティかを指定する必要はありません–必要なプロパティ名と型だけを指定します。プロトコルはまた、各プロパティが取得可能か、取得可能 かつ 設定可能でなければならないかのいずれかと指定します。
プロトコルが取得可能かつ設定可能なプロパティを必要とする場合、そのプロパティの要件は、定数の格納されたプロパティまたは読み取り専用の計算されたプロパティで満たすことはできません。プロトコルはプロパティが取得可能であることのみを必要とする場合、要件は、プロパティの全ての種類によって満たされ、自分のコードに便利であればプロパティは設定可能でもありえます。
プロパティの要件は、常に var キーワードで始まる変数プロパティとして宣言されます。取得可能かつ設定可能なプロパティは、その型宣言の後に { get set } と書くことで示され、取得可能プロパティは { get } と書くことで示されます。
- protocol SomeProtocol {
-         var mustBeSettable: Int { get set }
-         var doesNotNeedToBeSettable: Int { get }
- }
プロトコルで定義した時には、static のキーワードの前に、必ず型プロパティの要件を付けます。この規則は、型プロパティ要件がクラスによって実装されて class や static キーワードが前に付けていてもあてはまります:
- protocol AnotherProtocol {
-         class var someTypeProperty: Int { get set }
- }
ここでは一つのインスタンス・プロパティ要件を持つプロトコルの例を示します。
- protocol FullyNamed {
-         var fullName: String { get }
- }
FullyNamed プロトコルは、完全に修飾された名前を提供するために準拠する型を必要とします。プロトコルは準拠する型の性質について他のものは指定しません–それはその型がフルネームをそれ自身で提供できなければならないことのみを指定します。プロトコルはどんな FullyNamed 型でも String 型の fullName と言う取得可能なインスタンス・プロパティを持っていなければならないことを述べています。
ここで FullyNamed プロトコルに準拠し、採用する簡単な構造体の例を示します。
- struct Person: FullyNamed {
-         var fullName: String
- }
- let john = Person(fullName: "John Appleseed")
- // john.fullName is "John Appleseed"
この例では、特定の名前の人を表す Person と言う構造体を定義しています。それは、その定義の最初の行の一部として FullyNamed プロトコルを採用していると述べています。
Person の各インスタンスには、String 型の fullName と言う一つの格納されたプロパティがあります。これは FullyNamed プロトコルの一つの要件に一致し、Person が正しくプロトコルに準拠していることを意味します。(プロトコル要件が満たされていない場合、Swift はコンパイル時にエラーを報告します。)
ここで 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 is "USS Enterprise"
このクラスは、宇宙船の、計算された読み取り専用のプロパティとして fullName プロパティの要件を実装しています。各 Starship クラスインスタンスは、必須の name と optional の prefix を格納します。fullName プロパティは、存在する場合は prefix 値を使用し、宇宙船のフルネームを作成するために、name の先頭にそれを付けます。
メソッドの要件
プロトコルは特定のインスタンスメソッドと型メソッドを型に準拠することで実装される事を要求できます。これらのメソッドは、通常のインスタンスと型メソッドとまったく同じ方法で、プロトコルの定義の一部として書かれますが、中括弧またはメソッド本体はありません。可変個のパラメータは、通常のメソッドの場合と同じ規則に従い、許可されています。しかし、デフォルト値はプロトコルの定義の中のメソッドパラメーターとしては指定できません。
型プロパティの要件と同様に、これらがプロトコル内で定義されている場合、static キーワードを型メソッドの要件の前に常に付けて下さい。型メソッド要件がクラスによって実装されていて class または static キーワードが前に付けていてもこれは当てはまります。
- protocol SomeProtocol {
-         static func someTypeMethod()
- }
以下の例では、一つのインスタンスメソッド要件を持つプロトコルを定義しています。
- protocol RandomNumberGenerator {
-         func random() -> Double
- }
このプロトコル、RandomNumberGenerator は、それが呼び出されるたびに Double 値を返す random と言うインスタンスメソッドを持つ、すべての準拠する型が必要です。それはプロトコルの一部として指定されていませんが、この値は最小 0.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())")
- // prints "Here's a random number: 0.37464991998171"
- print("And another one: \(generator.random())")
- // prints "And another one: 0.729023776863283"
変異メソッドの要件
メソッドにとって、それが属するインスタンスを変更 (または 変異) することが必要なことがあります。値型のインスタンスメソッド (つまり、構造体と列挙型) の場合、メソッドは、そのインスタンスの全てのプロパティおよびそれが属するインスタンスを変更することが許可されていることを示すために、メソッドの func キーワードの前に、mutating キーワードを配置します。このプロセスは、インスタンスメソッド内から値型を変更 に記載されています。
プロトコルを採用する、全ての型のインスタンスを変異させることを意図しているプロトコル・インスタンス・メソッドの要件を定義する場合、プロトコルの定義の一部として mutating キーワードでメソッドをマークします。これは、プロトコルを採用し、そのメソッドの要件を満たすのを構造体や列挙型に可能にします。
以下の例は、toggle と言う一つのインスタンス・メソッドの要件を定義する、Togglable と言うプロトコルを定義しています。その名前が示唆するように、toggle() メソッドは、典型的には、その型のプロパティを変更することによって、トグルまたは全ての準拠型の状態を反転することを意図しています。
toggle() メソッドは Togglable プロトコル定義の一部として mutating キーワードでマークされていますが、それが呼び出された時、メソッドが準拠するインスタンスの状態を変異させることが期待されていることを示すためです:
- protocol Togglable {
-         mutating func toggle()
- }
構造体または列挙型のため Togglable プロトコルを実装する場合、その構造体または列挙型も mutating としてマークされている toggle() メソッドの実装を提供することにより、プロトコルに準拠することができます。
以下の例は、OnOffSwitch と呼言う列挙型を定義します。この列挙型は、on と off 列挙型の case で示される二つの状態の間を切り替えます。列挙型の 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 is now equal to .on
イニシャライザの要件
プロトコルは準拠する型によって実装されるべき特定のイニシャライザを必要とします。中括弧またはイニシャライザ本体なしで、通常のイニシャライザと完全に同じ方法で、プロトコルの定義の一部としてこれらのイニシャライザを記述して下さい。
- protocol SomeProtocol {
-         init(someParameter: Int)
- }
プロトコルイニシャライザ要件のクラス実装
指定イニシャライザやコンビニエンスイニシャライザのいずれかとして準拠するクラスのプロトコル・イニシャライザの要件を実装できます。どちらの場合でも、required 修飾子でイニシャライザの実装をマークすしなければなりません。
- class SomeClass: SomeProtocol {
-         required init(someParameter: Int) {
-                 // initializer implementation goes here
-         }
- }
required 修飾子の使用は、それらがまた、プロトコルに準拠するように、準拠するクラスのすべてのサブクラス上のイニシャライザ要件の明示的または継承された実装を提供することを保証します。
必須イニシャライザの詳細については、必須イニシャライザ を参照してください。
サブクラスがスーパークラスからの指定イニシャライザを上書きし、また、プロトコルから一致するイニシャライザの要件を実装する場合、required と override 修飾子の両方で、イニシャライザの実装をマークして下さい。
- protocol SomeProtocol {
-         init()
- }
- class SomeSuperClass {
-         init() {
-                 // initializer implementation goes here
-         }
- }
- class SomeSubClass: SomeSuperClass, SomeProtocol {
-         // "required" from SomeProtocol conformance; "override" from SomeSuperClass
-         required override init() {
-                 // initializer implementation goes here
-         }
- }
失敗可能イニシャライザの要件
失敗可能イニシャライザ で定義されているように、プロトコルは、準拠する型の失敗可能イニシャライザの要件を定義できます。
失敗可能イニシャライザの要件は準拠する型での失敗可能または失敗不可能なイニシャライザによって満たされます。失敗不可能イニシャライザの要件は失敗不可能イニシャライザまたは暗黙的に開封された失敗可能イニシャライザによって満たされます。
型としてのプロトコル
プロトコル自体は、実際には、何も機能を実装しません。それにもかかわらず、作成した全てのプロトコルは、コードで使用するための本格的な型になります。
それは型であるため、他の型は以下の物を含む、多くの場所でプロトコルを使用することができます。
- 関数、メソッド、またはイニシャライザ内のパラメータ型または戻り値の型として
- 定数、変数、またはプロパティの型として
- 配列、辞書、または他のコンテナ内の項目の型として
ここでは型として使用されるプロトコルの例を示します。
- class Dice {
-         let sides: Int
-         let generator: RandomNumberGenerator
-         init(sides: Int, generator: RandomNumberGenerator) {
-                 self.sides = sides
-                 self.generator = generator
-         }
-         func roll() -> Int {
-                 return Int(generator.random() * Double(sides)) + 1
-         }
- }
この例では、ボードゲームで使用するための n 面のサイコロを表す Dice と言う新しいクラスを定義しています。Dice インスタンスは、それらに何面あるかを表す sides と言う整数プロパティ、およびサイコロを転がして出る値を作成する、乱数発生器を提供する generator と言うプロパティがあります。
generator プロパティは、RandomNumberGenerator 型です。したがって、RandomNumberGenerator プロトコルを採用する 全ての 型のインスタンスにそれを設定できます。このインスタンスが RandomNumberGenerator プロトコルを採用しなければならないことを除いて、このプロパティに代入するインスタンスの要件は他にありません。
Dice にはまた、その初期状態を設定するために、イニシャライザがあります。このイニシャライザには、RandomNumberGenerator 型でもある generator と言うパラメータがあります。新しい Dice インスタンスを初期化するときには、このパラメータに、任意の準拠型の値を渡す事ができます。
Dice は 1 とサイコロの面の数の間の整数値を返す一つのインスタンス・メソッド、roll を提供します。このメソッドは、0.0 と 1.0 の間の新しい乱数を作成する発生器の random() メソッドを呼び出して、正しい範囲内のサイコロの値を作成するために、この乱数を使用します。generator が RandomNumberGenerator を採用する事がわかっているので、呼び出すべき random() メソッドがあることが保証されています。
ここで Dice クラスは、その乱数発生器として LinearCongruentialGenerator インスタンスで6面のサイコロを作成するために使用できる方法を挙げます。
- var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
- for _ in 1...5 {
-         print("Random dice roll is \(d6.roll())")
- }
- // Random dice roll is 3
- // Random dice roll is 5
- // Random dice roll is 4
- // Random dice roll is 5
- // Random dice roll is 4
デリゲート
デリゲート は、別の型のインスタンスへの責任の一部を渡す (又は デリゲート する) 事をクラスや構造体に可能にするデザインパターンです。このデザインパターンは、デリゲートされた責任をカプセル化するプロトコルを定義することによって実装され、準拠する型 (デリゲートとして知られる) がデリゲートされた機能を提供することが保証されるようにします。デリゲートは、特定のアクションに応答するために、またはそのソースの基本型を知らなくても、外部ソースからデータを取得するために使用できます。
以下の例では、サイコロベースのボードゲームで使われる2つのプロトコルを定義しています。
- protocol DiceGame {
-         var dice: Dice { get }
-         func play()
- }
- protocol DiceGameDelegate: AnyObject {
-         func gameDidStart(_ game: DiceGame)
-         func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
-         func gameDidEnd(_ game: DiceGame)
- }
DiceGame プロトコルは、サイコロを使う全てのゲームによって採用されうるプロトコルです。
DiceGameDelegate プロトコルは DiceGame の進行を追跡するために採用される事ができます。強い循環参照を防ぐために、デリゲートは弱い参照として宣言されています。弱い参照については、クラスインスタンス間の強い循環参照 を見てください。プロトコルをクラス専用としてマークすると、この章の後半の SnakesAndLadders クラスで、そのデリゲートが弱い参照を使用しなければならないことを宣言できます。クラス専用のプロトコルは、クラス専用プロトコル で説明するように、AnyObject からの継承によってマークされます。
ここで、もともと フロー制御 で導入された 蛇と梯子 のゲームのバージョンを示します。このバージョンは、そのサイコロを振るための Dice インスタンスを使用するように、DiceGame プロトコルを採用するように適合されています。また、その進行状況について DiceGameDelegate に通知します:
- class SnakesAndLadders: DiceGame {
-         let finalSquare = 25
-         let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
-         var square = 0
-         var board: [Int]
-         init() {
-                 board = Array(repeating: 0, count: finalSquare + 1)
-                 board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
-                 board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
-         }
-         weak var delegate: DiceGameDelegate?
-         func play() {
-                 square = 0
-                 delegate?.gameDidStart(self)
-                 gameLoop: while square != finalSquare {
-                 let diceRoll = dice.roll()
-                 delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)
-                 switch square + diceRoll {
-                 case finalSquare:
-                         break gameLoop
-                 case let newSquare where newSquare > finalSquare:
-                         continue gameLoop
-                 default:
-                         square += diceRoll
-                         square += board[square]
-                         }
-                 }
-                 delegate?.gameDidEnd(self)
-         }
- }
蛇と梯子 ゲームの説明については、フロー制御 の章の Break のセクションを参照してください。
ゲームのこのバージョンは、DiceGame プロトコルを採用する SnakesAndLadders というクラスとして包ま込まれています。これは、プロトコルに準拠するために、取得可能な dice プロパティ及び play() メソッドを提供します。(dice プロパティは、初期化後に変更する必要がないため、定数のプロパティとして宣言され、プロトコルは、それが取得可能であることのみを要求します。)
蛇と梯子 ゲームボードのセットアップは、クラスの init() イニシャライザの中で行われます。全てのゲームロジックは、プロトコルの play メソッドの中に移動し、そのサイコロを振った値を提供するプロトコルの必須 dice プロパティを使用します。
デリゲートは、ゲームをプレイするために必須でないため、delegate プロパティは、optional の DiceGameDelegate として定義されていることに注意してください。それは、optional の型なので、delegate プロパティは nil の初期値に自動的に設定されます。その後、ゲームの例示には、適切なデリゲートにプロパティを設定するオプションがあります。
DiceGameDelegate は、ゲームの進行状況を追跡するために3つのメソッドを提供します。これら3つのメソッドは、上記の play() メソッド内のゲームロジックに組み込まれており、新しいゲームが始まる時、また新しい順番が始まる時、またはゲームが終了したときに呼び出されます。
delegate プロパティは optional の DiceGameDelegate であるため、play() メソッドは、デリゲート上のメソッドを呼び出すたびに、optional の連鎖を使用します。delegate プロパティが nil の場合、これらのデリゲートの呼び出しは、嬉しくも失敗し、エラーを起こしません。delegate プロパティが nil でない場合、デリゲートメソッドが呼び出され、パラメータとして SnakesAndLadders インスタンスが渡されます。
次の例では、DiceGameDelegate プロトコルを採用した DiceGameTracker というクラスを示しています。
- class DiceGameTracker: DiceGameDelegate {
-         var numberOfTurns = 0
-         func gameDidStart(_ game: DiceGame) {
-                 numberOfTurns = 0
-                 if game is SnakesAndLadders {
-                 print("Started a new game of Snakes and Ladders")
-                 }
-                 print("The game is using a \(game.dice.sides)-sided dice")
-         }
-         func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
{ -                 numberOfTurns += 1
-                 print("Rolled a \(diceRoll)")
-         }
-         func gameDidEnd(_ game: DiceGame) {
-                 print("The game lasted for \(numberOfTurns) turns")
-         }
- }
DiceGameTracker は DiceGameDelegate が必須とする3つすべてのメソッドを実装します。これは、ゲームが取った順番の数を追跡するために、これらのメソッドを使用します。ゲームが始まると numberOfTurns プロパティをゼロにリセットし、新しい順番が始まるたびにそれを増分し、ゲームが終了した時点での順番の数の合計を出力します。
上に示した gameDidStart(_:) の実装は、プレイされようとしているゲームについてのいくつかの初歩的な情報を印刷する game パラメータを使用しています。game パラメータには DiceGame の型があり、 SnakesAndLadders の型ではなく、それで gameDidStart(_:) は DiceGame プロトコルの一部として実装されるメソッドとプロパティのみにアクセスし、使用できます。しかし、このメソッドはまだ、基礎となるインスタンスの型を照会するために型キャストを使用できます。この例では、game が実際に舞台裏で SnakesAndLadders のインスタンスであるかどうかをチェックし、そうであれば、適切なメッセージを印刷します。
gameDidStart(_:) メソッドはまた、渡された game パラメータの dice プロパティにもアクセスします。game は、DiceGame プロトコルに準拠することが知られているので、dice プロパティを持つことが保証され、従って gameDidStart(_:) メソッドは、プレイされているゲームの種類にかかわらず、サイコロの sides プロパティにアクセスし、印刷できます。
DiceGameTracker は動作中にどのように見えるかをここに示します:
- let tracker = DiceGameTracker()
- let game = SnakesAndLadders()
- game.delegate = tracker
- game.play()
- // Started a new game of Snakes and Ladders
- // The game is using a 6-sided dice
- // Rolled a 3
- // Rolled a 5
- // Rolled a 4
- // Rolled a 5
- // The game lasted for 4 turns
拡張機能を持つプロトコル準拠の追加
既存の型のソースコードへのアクセス権を持っていない場合でも、新しいプロトコルに準拠し、採用するために、既存の型を拡張することができます。拡張機能は、既存の型に新しいプロパティ、メソッド、およびサブスクリプトを追加し、そのためプロトコルが要求する全ての要件を追加できます。拡張機能の詳細については、拡張機能 を参照してください。
例えば、TextRepresentable と言うこのプロトコルは、テキストとして表現される方法がある、全ての型で実装される事ができます。これは、それ自体の説明、または現在の状態のテキストバージョンです。
- protocol TextRepresentable {
-         var textualDescription: String { get }
- }
以前の Dice クラスは TextRepresentable を採用し、準拠するように拡張できます:
- extension Dice: TextRepresentable {
-         var textualDescription: String {
-                 return "A \(sides)-sided dice"
-         }
- }
この拡張機能は、Dice がその元の実装で提供していたかのようにまったく同じ方法で新しいプロトコルを採用します。プロトコル名は、コロンで区切られ、型名の後に提供され、プロトコルのすべての要件の実装は、拡張機能の中括弧内に提供されます。
全ての Dice インスタンスは、TextRepresentable として扱えるようになりました:
- let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
- print(d12.textualDescription)
- // prints "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)
- // prints "A game of Snakes and Ladders with 25 squares"
条件付きでプロトコルに準拠
汎用型は、その型の汎用パラメーターがプロトコルに準拠している場合など、特定の条件下でのみプロトコルの要件を満たすことができます。型を拡張するときに制約をリストアップすることによって、汎用型を条件付きでプロトコルに準拠させることができます。汎用の where 句を書くことで、採用しているプロトコルの名前の後にこれらの制約を書いてください。汎用の where 句について詳しくは、汎用の where 句 を参照してください。
以下の機能拡張は、それらが TextRepresentable に準拠する型の要素を格納するときはいつでも、Array インスタンスを 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)
- // Prints "[A 6-sided dice, A 12-sided dice]"
拡張機能を持つプロトコルの採用を宣言
型がすでにプロトコル要件のすべてに準拠していても、まだそれは、そのプロトコルを採用すると述べていない場合、空の拡張機能を持つプロトコルを採用できます。
- 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)
- // prints "A hamster named Simon"
プロトコル型のコレクション
型としてのプロトコル で述べたように、プロトコルは、型として配列や辞書などのようにコレクションに格納されて使用できます。この例では、TextRepresentable の things の配列を作成します:
配列内の項目を反復処理し、各項目のテキスト表現を印刷することが可能になりました。
- 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 型であることに注意してください。舞台裏では実際のインスタンスはそれらの型であっても、Dice、DiceGame、または Hamster 型のいずれでもありません。それにもかかわらず、それは TextRepresentable 型なので、TextRepresentable な全てには texualDescription プロパティがあることを知られているため、ループを通るたび thing.texualDescription にアクセスするのは安全です。
プロトコルの継承
プロトコルは、1つ以上の他のプロトコルを 継承 でき、それが継承する要件の上にさらに要件を追加できます。プロトコルの継承の構文は、クラス継承の構文に似ていますが、オプションで、カンマで区切られた複数の継承プロトコルを一覧表示します:
- protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
-         // protocol definition goes here
- }
ここでは上に挙げた例から TextRepresentable プロトコルを継承するプロトコルの例を示します。
- protocol PrettyTextRepresentable: TextRepresentable {
-         var prettyTextualDescription: String { get }
- }
この例では、TextRepresentable から継承する新しいプロトコル、PrettyTextRepresentable を定義しています。 PrettyTextRepresentable を採用する全てのものは TextRepresentable によって強制される要件を全て満たさねばならず、また PrettyTextRepresentable によって強制される追加要件を満たさねばなりません。この例では、 PrettyTextRepresentable は String を返す prettyTextualDescription という取得可能なプロパティを提供するための一つの要件を追加します。
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) は、それが PrettyTextRepresentable プロトコルを採用し、SnakesAndLadders 型のため prettyTextualDescription プロパティの実装を提供することを述べています。PrettyTextRepresentable である全てのものは TextRepresentable でもなければならず、それで prettyTextualDescription の実装は、出力文字列を開始する TextRepresentable プロトコルから textualDescription プロパティにアクセスすることによって開始します。これは、コロンと改行を付加し、そのかなりなテキスト表現の開始としてこれを使用しています。その後、ボードの正方形の配列を反復処理し、各正方形の内容を表現する幾何学的な形を追加します:
- 正方形の値が 0 より大きい場合には、それは梯子の基部であり、▲ で表わされます。
- 正方形の値が 0 より小さい場合には、それは蛇の頭であり、▼ で表わされます。
- そうでない場合は、正方形の値は 0 で、"自由な" 正方形であり、○ で表わされます。
prettyTexualDescription プロパティは、今やあらゆる SnakesAndLadder インスタンスのかなりなテキストの説明を印刷するために使用できます。
- print(game.prettyTextualDescription)
- // A game of Snakes and Ladders with 25 squares:
- // ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○
クラス専用プロトコル
プロトコルの継承リストに、AnyObject プロトコルを追加することによって、プロトコルの採用をクラス型 (構造体または列挙型ではない) に限定することができます。
- protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
-         // class-only protocol definition goes here
- }
上記の例では、SomeClassOnlyProtocol はクラス型によってのみ採用できます。SomeClassOnlyProtocol を採用しようとして構造体や列挙型の定義を書くと、コンパイル時エラーになります。
プロトコルの構成
一度に複数のプロトコルに準拠する型を要求するのが便利な事があります。プロトコルの構成 で一つの要件に複数のプロトコルを組み合わせることができます。プロトコルの構成は、構成内のすべてのプロトコルの要件を組み合わせた一時的なローカルプロトコルを定義したかのように動作します。プロトコル構成は、新しいプロトコル型を定義しません。
プロトコルの構成は、someprotocol & AnotherProtocol の形です。アンパサンド(&) で区切って必要なだけ多くのプロトコルをリストできます。プロトコルのそのリストに加えて、プロトコル構成には 1 つのクラス型を含めることもでき、これを使用して必須スーパークラスを指定することができます。
ここで関数パラメータ上の一つのプロトコル構成要件に Named と Aged という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)
- // prints "Happy birthday Malcolm, you're 21!"
この例では、取得可能な String プロパティの name と言う一つの要件を持つ、Named プロトコルがあります。取得可能な Int 型プロパティの age と言う一つの要件がある、Aged プロトコルもあります。これらのプロトコルの両方が Person と言う構造体によって採用されています。
また、この例では、wishHappyBirthday(to:) 関数も定義しています。celebrstor パラメータの型は、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)
- // Prints "Hello, Seattle!"
beginConcert(in:) 関数は Location & Named 型のパラメータをとります。これは "Location のサブクラスで Named プロトコルに準拠するすべての型" を意味します。この場合には、City は両方の要件を満たします。
Person は Location のサブクラスではないため、birthdayPerson を beginConcert(in:) 関数に渡すことは無効です。同様に、Named プロトコルに準拠していない Location のサブクラスを作成した場合、その型のインスタンスを使用して beginConcert(in:) を呼び出すことも無効です。
プロトコル準拠の確認
型キャスト で説明したように is と as 演算子を、プロトコルの準拠をチェックするため、また特定のプロトコルにキャストするため使用できます。プロトコルへのキャスト及びチェックは、型のチェックとキャストとまったく同じ構文に従います。
- is 演算子は、インスタンスが、プロトコルに準拠していれば true そうでない場合は false を返します。
- ダウンキャスト演算子の as? バージョンは、プロトコル型の optional の値を返し、インスタンスがそのプロトコルに準拠していない場合、この値は nil です。
- ダウンキャスト演算子の as! バージョンは、プロトコル型にダウンキャストするのを強制し、ダウンキャストが成功しなかった場合、実行時エラーを引き起こします。
この例では、area と言う Double の一つの取得可能なプロパティ要件を持つ HasArea と言うプロトコルを定義しています。
- protocol HasArea {
-         var area: Double { get }
- }
ここに2つのクラス、Circle と Country があり、両方とも 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 }
- }
Circle、Country と Animal クラスは、共有する基本クラスを持っていません。それにもかかわらず、それらはすべてクラスであり、そして3つ全ての型のインスタンスは、型 AnyObject の値を格納する配列を初期化するために使えます:
- let objects: [AnyObject] = [
-         Circle(radius: 2.0),
-         Country(area: 243_610),
-         Animal(legs: 4)
- ]
objects 配列は、2ユニットの半径の Circle インスタンスを含む配列リテラルで初期化されます。Country インスタンスは、平方キロメートルで表したイギリスの表面積で初期化されます。そして Animal インスタンスは、4つの足で初期化されます。
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? 演算子で返される optional の値は、objectWithArea と言う定数に optional 結合されて開封されます。objectWithArea 定数は HasArea 型であることが知られており、したがって、その area プロパティには、アクセスでき、安全な型の方法で印刷できます。
基礎となるオブジェクトは、キャストするプロセスによっては変更されないことに注意してください。それらは、Circle、Country そして Animal であり続けます。しかし、それらが objectWithArea 定数に格納された時点で、それらは HasArea 型であるとのみ知られるので、それらの area プロパティのみにアクセスできます。
Optional のプロトコル要件
プロトコルには optional の要件 を定義できます。これらの要件は、プロトコルに準拠する型で実装する必要はありません。optional の要件は、プロトコルの定義の一部として、optional の修飾子を前に付けます。optional の要件は、Objective-C と相互運用するコードを書けるように、利用できます。プロトコルおよび optional の要件は両方とも @objc 属性でマークされなければなりません。その @objc プロトコルは、Objective-C のクラスや他の @objc クラスから継承するクラスのみに採用されることに注意してください。これらは、構造体または列挙型によっては採用できません。
optional の要件内にメソッドやプロパティを使用する場合、その型は自動的に、optional になります。例えば、型メソッド(Int) -> String は ((Int) -> Strng)? になります。メソッドの戻り値ではなく、関数全体の型が optional に包まれていることに注意してください。
要件がプロトコルに準拠した型によって実装されなかった可能性を考慮して、optional のプロトコル要件は optional の連鎖で呼び出すことができます。someOptionalMethod?(someArgument) のように、呼び出されたときにメソッドの名前の後に疑問符を書き込むことによって、optional のメソッドの実装を確認してください。optional の連鎖の詳細については、Optional の連鎖 を参照してください。
以下の例では、その増分量を提供するために外部データソースを使用する Counter と言う整数をカウントするクラスを定義します。このデータソースは2つの optional の要件である CounterDataSource プロトコルによって定義されます。
- @objc protocol CounterDataSource {
-         @objc optional func increment(forCount count: Int) -> Int
-         @objc optional var fixedIncrement: Int { get }
- }
CounterDataSource プロトコルは increment(forCount:) と言う optional のメソッド要件と fixedIncrement と言う optional のプロパティ要件を定義しています。これらの要件は、Counter インスタンスの適切な増分量を提供するために、データ·ソースの2つの異なる方法を定義しています。
以下に定義された Counter クラスには、CounterDataSource? 型の optional の 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 クラスは、また increment と言うメソッドも定義しており、そのメソッドが呼び出されるたびに count プロパティを増分します。
increment() メソッドは、まずそのデータソースに increment(forCount:) メソッドの実装を探して増分量を取得しようとします。increment() メソッドは increment(forCount:) を呼び出そうとして、optional の連鎖を使用し、メソッドの一つの引数として現在の count 値を渡します。
ここで optional の連鎖の 2つの レベルが演じている事に注意してください。第一に、dataSource が nil である可能性があり、したがって dataSource が nil でない場合にのみ increment(forCount:) を呼び出す必要があることを示すために、dataSource はその名前の後に疑問符がついています。第二に、 dataSource が 存在し ていても、それは optional の要件なので、increment(forCount:) を実装していることを保証するものではありません。ここで、increment(forCount:) が実装されていない可能性は、optional の連鎖によって処理されます。 increment(forCount:) への呼び出しは increment(forCount:) が存在する時にのみ起こり、すなわちそれが nil でない場合にのみ起こります。increment(forCount:) も、その名前の後に疑問符が書かれているのはこのためです。
increment(forCount:) への呼び出しは、これらの2つの理由のいずれかで失敗する可能性があるため、呼び出しは、optional の Int 値を返します。これは increment(forCount:) が CounterDataSource の定義で optional でない Int 値を返すように定義されていても同様です。2つの optional の連鎖の操作が、次々とあっても、結果はまだ一つの optional で包み込まれています。複数の optional の連鎖の操作の使用については、連鎖の複数レベルのリンク を参照して下さい。
increment(forCount:) を呼び出した後、それが返す optional の Int は、optional の結合を使用して、amount と言う定数に開封されます。optional の Int に値がある場合、すなわち、デリゲートとメソッドの両方が存在し、そしてメソッドが値を返す場合、開封された amount は格納された count プロパティに追加され、そして増分は完了します。
increment(forCount:) メソッドから値を取得でき ない 場合、dataSource が nil か、またはデータソースが increment(forCount:) を実装していないためのいずれかであり、その後代わりに increment() メソッドが、データ·ソースの fixedIncrement プロパティから値を取得しようとします。fixedIncrement プロパティも optional の要件であり、したがって、その値も CounterDataSource プロトコル定義の一部として fixedIncrement が optional でない Int プロパティとして定義されているにもかかわらず、optional の Int 値です。
ここで、データソースが、それが照会されるたびに定数の 3 の値を返す単純な CounterDataSource を実装しましょう。optional の fixedIncrement プロパティの要件を実装することでこれを行います:
- class ThreeSource: NSObject, CounterDataSource {
-         let fixedIncrement = 3
- }
新しい Counter インスタンスのデータソースとして ThreeSource のインスタンスを使用できます。
- var counter = Counter()
- counter.dataSource = ThreeSource()
- for _ in 1...4 {
-         counter.increment()
-         print(counter.count)
- }
- // 3
- // 6
- // 9
- // 12
上記のコードは新しい Counter インスタンスを作成します。そのデータソースを新しい ThreeSource インスタンスに設定します。そして counter の increment() メソッドを4回呼び出します。予想されるように、3回 increment() が呼ばれ、counter の count プロパティが増加します。
ここで 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 プロトコルから、optional の increment(forCount:) メソッドを実装し、どの方向にカウントが行われるかを示すために count 引数を使います。もし count がすでにゼロなら、メソッドは更なるカウントが起こらない事を示すために 0 を返します。
-4 からカウントをゼロにするために、既存の Counter インスタンスで TowardsZeroSource のインスタンスを使用できます。カウンタがゼロに達すると、それ以上のカウントは行われません。
- counter.count = -4
- counter.dataSource = TowardsZeroSource()
- for _ in 1...5 {
- counter.increment()
- print(counter.count)
- }
- // -3
- // -2
- // -1
- // 0
- // 0
プロトコル拡張機能
プロトコルは、メソッド、イニシャライザ、サブスクリプト、および計算されたプロパティの実装を準拠型に提供するように拡張できます。これにより、各型の個々の準拠性やグローバル関数ではなく、プロトコル自体の動作を定義できます。
例えば、RandomNumberGenerator プロトコルは、randomBool() メソッドを提供するように拡張でき、これはランダムな Bool 値を返すために必要な randm() メソッドの結果を使用します。
- extension RandomNumberGenerator {
-         func randomBool() -> Bool {
-                 return random() > 0.5
-         }
- }
プロトコル上で拡張機能を作成することにより、すべての準拠型は何も追加の変更なしに、このメソッドの実装を自動的に得ることができます。
- let generator = LinearCongruentialGenerator()
- print("Here's a random number: \(generator.random())")
- // Prints "Here's a random number: 0.37464991998171"
- print("And here's a random Boolean: \(generator.randomBool())")
- // Prints "And here's a random Boolean: true"
プロトコル拡張では、準拠型に実装を追加できますが、プロトコルを拡張したり他のプロトコルから継承したりすることはできません。プロトコルの継承は、常にプロトコル宣言自体で指定されます。
デフォルトの実装の提供
そのプロトコルの全てのメソッドまたは計算されたプロパティ要件に、デフォルトの実装を提供するために、プロトコルの拡張機能を使用できます。準拠する型が、必要なメソッドやプロパティの独自の実装を提供している場合、その実装は、拡張機能が提供するものの代わりに使用されます。
例えば、PrettyTextRepresentable プロトコルは、その必須 prettyTextualDescription プロパティのデフォルトの実装を提供できる TextRepresentable プロトコルを継承しており、単に textualDescription プロパティにアクセスした結果を返します:
- extension PrettyTextRepresentable {
-         var prettyTextualDescription: String {
-                 return textualDescription
-         }
- }
プロトコル拡張機能に制約を追加
プロトコル拡張機能を定義するときは、拡張機能のメソッドとプロパティが利用可能になる前に、準拠した型が満たさなければならない制約を指定できます。汎用の Where 句 で説明したように、汎用の where 句を使用して、拡張しようとしているプロトコルの名前の後に、これらの制約を記述します。
たとえば、その要素が Equatable プロトコルに準拠する任意のコレクションに適用される Collection プロトコルへの拡張を定義できます。コレクションの要素を標準ライブラリの一部である Equatable プロトコルに制約することで、== と != 演算子を使用して、2 つの要素間の等価性と不等価性を確認できます。
- extension Collection where Element: Equatable {
-         func allEqual() -> Bool {
-                 for element in self {
-                         if element != self.first {
-                                 return false
-                         }
-                 }
-                 return true
-         }
- }
allEqual() メソッドは、コレクション内のすべての要素が等しい場合にのみ true を返します。
整数の 2 つの配列を考えてみましょう。1 つはすべての要素が同じで、もう 1 つはそうでないものです。
- let equalNumbers = [100, 100, 100, 100, 100]
- let differentNumbers = [100, 100, 200, 100, 200]
配列は Collection に準拠し、整数は Equatable に準拠するため、equalNumbers および differentNumbers は allEqual() メソッドを使用できます。
- print(equalNumbers.allEqual())
- // Prints "true"
- print(differentNumbers.allEqual())
- // Prints "false"
前:機能拡張 次:ジェネリック(汎用)
トップへ
トップへ
トップへ
トップへ
トップへ
トップへ
トップへ
トップへ
トップへ
トップへ