Swift 4.2 日本語化計画 : Swift 4.2


プロトコル


プロトコル は、特定のタスクや機能の一部に適するメソッド、プロパティ、およびその他の要件の青写真を定義します。プロトコルは、それらの要件の実際の実装を提供するために、クラス、構造体、または列挙型によって 採用 される事ができます。プロトコルの要件を満たす全ての型は、そのプロトコルに 準拠する と言われます。


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


プロトコルの構文


クラス、構造体、列挙型と非常に似た方法でプロトコルを定義できます。


  1. protocol SomeProtocol {
  2.         // protocol definition goes here
  3. }


カスタム型は、特定のプロトコルを採用すると述べるのに、それらの定義の一部として、プロトコルの名前をコロンで区切って型の名前の後に書きます。複数のプロトコルを一覧表示でき、カンマで区切られます。


  1. struct SomeStructure: FirstProtocol, AnotherProtocol {
  2.         // structure definition goes here
  3. }


クラスがスーパークラスを持っている場合、それが採用する全てのプロトコルの前にスーパークラス名を一覧表示し、コンマを続けます。


  1. class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
  2.         // class definition goes here
  3. }


プロパティの要件


プロトコルは、インスタンスプロパティや、特定の名前と型を持つ型プロパティを提供するために、全ての準拠する型を必要とします。プロトコルはプロパティが格納されたプロパティか計算されたプロパティかを指定する必要はありません–必要なプロパティ名と型だけを指定します。プロトコルはまた、各プロパティが取得可能か、取得可能 かつ 設定可能でなければならないかのいずれかと指定します。


プロトコルが取得可能かつ設定可能なプロパティを必要とする場合、そのプロパティの要件は、定数の格納されたプロパティまたは読み取り専用の計算されたプロパティで満たすことはできません。プロトコルはプロパティが取得可能であることのみを必要とする場合、要件は、プロパティの全ての種類によって満たされ、自分のコードに便利であればプロパティは設定可能でもありえます。


プロパティの要件は、常に var キーワードで始まる変数プロパティとして宣言されます。取得可能かつ設定可能なプロパティは、その型宣言の後に { get set } と書くことで示され、取得可能プロパティは { get } と書くことで示されます。


  1. protocol SomeProtocol {
  2.         var mustBeSettable: Int { get set }
  3.         var doesNotNeedToBeSettable: Int { get }
  4. }


プロトコルで定義した時には、static のキーワードの前に、必ず型プロパティの要件を付けます。この規則は、型プロパティ要件がクラスによって実装されて classstatic キーワードが前に付けていてもあてはまります:


  1. protocol AnotherProtocol {
  2.         class var someTypeProperty: Int { get set }
  3. }


ここでは一つのインスタンス・プロパティ要件を持つプロトコルの例を示します。


  1. protocol FullyNamed {
  2.         var fullName: String { get }
  3. }


FullyNamed プロトコルは、完全に修飾された名前を提供するために準拠する型を必要とします。プロトコルは準拠する型の性質について他のものは指定しません–それはその型がフルネームをそれ自身で提供できなければならないことのみを指定します。プロトコルはどんな FullyNamed 型でも String 型の fullName と言う取得可能なインスタンス・プロパティを持っていなければならないことを述べています。


ここで FullyNamed プロトコルに準拠し、採用する簡単な構造体の例を示します。


  1. struct Person: FullyNamed {
  2.         var fullName: String
  3. }
  4. let john = Person(fullName: "John Appleseed")
  5. // john.fullName is "John Appleseed"


この例では、特定の名前の人を表す Person と言う構造体を定義しています。それは、その定義の最初の行の一部として FullyNamed プロトコルを採用していると述べています。


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


ここで FullyNamed プロトコルに準拠し採用した、より複雑なクラスを挙げます。


  1. class Starship: FullyNamed {
  2.         var prefix: String?
  3.         var name: String
  4.         init(name: String, prefix: String? = nil) {
  5.                 self.name = name
  6.                 self.prefix = prefix
  7.         }
  8.         var fullName: String {
  9.                 return (prefix != nil ? prefix! + " " : "") + name
  10.         }
  11. }
  12. var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
  13. // ncc1701.fullName is "USS Enterprise"


このクラスは、宇宙船の、計算された読み取り専用のプロパティとして fullName プロパティの要件を実装しています。各 Starship クラスインスタンスは、必須の name と optional の prefix を格納します。fullName プロパティは、存在する場合は prefix 値を使用し、宇宙船のフルネームを作成するために、name の先頭にそれを付けます。


メソッドの要件


プロトコルは特定のインスタンスメソッドと型メソッドを型に準拠することで実装される事を要求できます。これらのメソッドは、通常のインスタンスと型メソッドとまったく同じ方法で、プロトコルの定義の一部として書かれますが、中括弧またはメソッド本体はありません。可変個のパラメータは、通常のメソッドの場合と同じ規則に従い、許可されています。しかし、デフォルト値はプロトコルの定義の中のメソッドパラメーターとしては指定できません。



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


  1. protocol SomeProtocol {
  2.         static func someTypeMethod()
  3. }


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


  1. protocol RandomNumberGenerator {
  2.         func random() -> Double
  3. }


このプロトコル、RandomNumberGenerator は、それが呼び出されるたびに Double 値を返す random と言うインスタンスメソッドを持つ、すべての準拠する型が必要です。それはプロトコルの一部として指定されていませんが、この値は最小 0.0 から最大 1.0 (を含まない) までの数であると仮定されています。


RandomNumberGenerator プロトコルは、各乱数がどう発生されるかについての仮定は全くしていません。単に新しい乱数を発成するための標準的な方法を提供する発生プログラムが必要です。


ここで RandomNumberGenerator プロトコルを採用し準拠したクラスの実装を示します。このクラスは、線形合同発生法 として知られている擬似乱数生成アルゴリズムを実装しています。


  1. class LinearCongruentialGenerator: RandomNumberGenerator {
  2.         var lastRandom = 42.0
  3.         let m = 139968.0
  4.         let a = 3877.0
  5.         let c = 29573.0
  6.         func random() -> Double {
  7.                 lastRandom = ((lastRandom * a + c).truncatingRemainder(dividingBy:m))
  8.                 return lastRandom / m
  9.         }
  10. }
  11. let generator = LinearCongruentialGenerator()
  12. print("Here's a random number: \(generator.random())")
  13. // prints "Here's a random number: 0.37464991998171"
  14. print("And another one: \(generator.random())")
  15. // prints "And another one: 0.729023776863283"


変異メソッドの要件


メソッドにとって、それが属するインスタンスを変更 (または 変異) することが必要なことがあります。値型のインスタンスメソッド (つまり、構造体と列挙型) の場合、メソッドは、そのインスタンスの全てのプロパティおよびそれが属するインスタンスを変更することが許可されていることを示すために、メソッドの func キーワードの前に、mutating キーワードを配置します。このプロセスは、インスタンスメソッド内から値型を変更 に記載されています。


プロトコルを採用する、全ての型のインスタンスを変異させることを意図しているプロトコル・インスタンス・メソッドの要件を定義する場合、プロトコルの定義の一部として mutating キーワードでメソッドをマークします。これは、プロトコルを採用し、そのメソッドの要件を満たすのを構造体や列挙型に可能にします。


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


以下の例は、toggle と言う一つのインスタンス・メソッドの要件を定義する、Togglable と言うプロトコルを定義しています。その名前が示唆するように、toggle() メソッドは、典型的には、その型のプロパティを変更することによって、トグルまたは全ての準拠型の状態を反転することを意図しています。


toggle() メソッドは Togglable プロトコル定義の一部として mutating キーワードでマークされていますが、それが呼び出された時、メソッドが準拠するインスタンスの状態を変異させることが期待されていることを示すためです:


  1. protocol Togglable {
  2.         mutating func toggle()
  3. }


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


以下の例は、OnOffSwitch と呼言う列挙型を定義します。この列挙型は、onoff 列挙型の case で示される二つの状態の間を切り替えます。列挙型の toggle の実装は Togglable プロトコルの要件に一致させるため、mutating としてマークされています:


  1. enum OnOffSwitch: Togglable {
  2.         case off, on
  3.         mutating func toggle() {
  4.                 switch self {
  5.                 case .off:
  6.                         self = .on
  7.                 case .on:
  8.                         self = .off
  9.                 }
  10.         }
  11. }
  12. var lightSwitch = OnOffSwitch.off
  13. lightSwitch.toggle()
  14. // lightSwitch is now equal to .on


イニシャライザの要件


プロトコルは準拠する型によって実装されるべき特定のイニシャライザを必要とします。中括弧またはイニシャライザ本体なしで、通常のイニシャライザと完全に同じ方法で、プロトコルの定義の一部としてこれらのイニシャライザを記述して下さい。


  1. protocol SomeProtocol {
  2.         init(someParameter: Int)
  3. }


プロトコルイニシャライザ要件のクラス実装


指定イニシャライザやコンビニエンスイニシャライザのいずれかとして準拠するクラスのプロトコル・イニシャライザの要件を実装できます。どちらの場合でも、required 修飾子でイニシャライザの実装をマークすしなければなりません。


  1. class SomeClass: SomeProtocol {
  2.         required init(someParameter: Int) {
  3.                 // initializer implementation goes here
  4.         }
  5. }


required 修飾子の使用は、それらがまた、プロトコルに準拠するように、準拠するクラスのすべてのサブクラス上のイニシャライザ要件の明示的または継承された実装を提供することを保証します。


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



注意: 最後 (final) のクラスはサブクラス化することはできないので、final 修飾子でマークされているクラスを required 修飾子でプロトコル・イニシャライザの実装をマークする必要はありません。final 修飾子の詳細については、オーバーライドの防止 を参照してください。


サブクラスがスーパークラスからの指定イニシャライザを上書きし、また、プロトコルから一致するイニシャライザの要件を実装する場合、requiredoverride 修飾子の両方で、イニシャライザの実装をマークして下さい。


  1. protocol SomeProtocol {
  2.         init()
  3. }
  4. class SomeSuperClass {
  5.         init() {
  6.                 // initializer implementation goes here
  7.         }
  8. }
  9. class SomeSubClass: SomeSuperClass, SomeProtocol {
  10.         // "required" from SomeProtocol conformance; "override" from SomeSuperClass
  11.         required override init() {
  12.                 // initializer implementation goes here
  13.         }
  14. }


失敗可能イニシャライザの要件


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


失敗可能イニシャライザの要件は準拠する型での失敗可能または失敗不可能なイニシャライザによって満たされます。失敗不可能イニシャライザの要件は失敗不可能イニシャライザまたは暗黙的に開封された失敗可能イニシャライザによって満たされます。


型としてのプロトコル


プロトコル自体は、実際には、何も機能を実装しません。それにもかかわらず、作成した全てのプロトコルは、コードで使用するための本格的な型になります。


それは型であるため、他の型は以下の物を含む、多くの場所でプロトコルを使用することができます。


注: プロトコルは型であるため、大文字でそれらの名前を始めます (例えば FullyNamedRandomNumberGenerator など) が、これは Swift の他の型の名前と一致するようにです (Int,String,Double など)。


ここでは型として使用されるプロトコルの例を示します。


  1. class Dice {
  2.         let sides: Int
  3.         let generator: RandomNumberGenerator
  4.         init(sides: Int, generator: RandomNumberGenerator) {
  5.                 self.sides = sides
  6.                 self.generator = generator
  7.         }
  8.         func roll() -> Int {
  9.                 return Int(generator.random() * Double(sides)) + 1
  10.         }
  11. }


この例では、ボードゲームで使用するための n 面のサイコロを表す Dice と言う新しいクラスを定義しています。Dice インスタンスは、それらに何面あるかを表す sides と言う整数プロパティ、およびサイコロを転がして出る値を作成する、乱数発生器を提供する generator と言うプロパティがあります。


generator プロパティは、RandomNumberGenerator 型です。したがって、RandomNumberGenerator プロトコルを採用する 全ての 型のインスタンスにそれを設定できます。このインスタンスが RandomNumberGenerator プロトコルを採用しなければならないことを除いて、このプロパティに代入するインスタンスの要件は他にありません。


Dice にはまた、その初期状態を設定するために、イニシャライザがあります。このイニシャライザには、RandomNumberGenerator 型でもある generator と言うパラメータがあります。新しい Dice インスタンスを初期化するときには、このパラメータに、任意の準拠型の値を渡す事ができます。


Dice1 とサイコロの面の数の間の整数値を返す一つのインスタンス・メソッド、roll を提供します。このメソッドは、0.01.0 の間の新しい乱数を作成する発生器の random() メソッドを呼び出して、正しい範囲内のサイコロの値を作成するために、この乱数を使用します。generatorRandomNumberGenerator を採用する事がわかっているので、呼び出すべき random() メソッドがあることが保証されています。


ここで Dice クラスは、その乱数発生器として LinearCongruentialGenerator インスタンスで6面のサイコロを作成するために使用できる方法を挙げます。


  1. var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
  2. for _ in 1...5 {
  3.         print("Random dice roll is \(d6.roll())")
  4. }
  5. // Random dice roll is 3
  6. // Random dice roll is 5
  7. // Random dice roll is 4
  8. // Random dice roll is 5
  9. // Random dice roll is 4


デリゲート


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


以下の例では、サイコロベースのボードゲームで使われる2つのプロトコルを定義しています。


  1. protocol DiceGame {
  2.         var dice: Dice { get }
  3.         func play()
  4. }
  5. protocol DiceGameDelegate: AnyObject {
  6.         func gameDidStart(_ game: DiceGame)
  7.         func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
  8.         func gameDidEnd(_ game: DiceGame)
  9. }


DiceGame プロトコルは、サイコロを使う全てのゲームによって採用されうるプロトコルです。


DiceGameDelegate プロトコルは DiceGame の進行を追跡するために採用される事ができます。強い循環参照を防ぐために、デリゲートは弱い参照として宣言されています。弱い参照については、クラスインスタンス間の強い循環参照 を見てください。プロトコルをクラス専用としてマークすると、この章の後半の SnakesAndLadders クラスで、そのデリゲートが弱い参照を使用しなければならないことを宣言できます。クラス専用のプロトコルは、クラス専用プロトコル で説明するように、AnyObject からの継承によってマークされます。


ここで、もともと フロー制御 で導入された 蛇と梯子 のゲームのバージョンを示します。このバージョンは、そのサイコロを振るための Dice インスタンスを使用するように、DiceGame プロトコルを採用するように適合されています。また、その進行状況について DiceGameDelegate に通知します:


  1. class SnakesAndLadders: DiceGame {
  2.         let finalSquare = 25
  3.         let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
  4.         var square = 0
  5.         var board: [Int]
  6.         init() {
  7.                 board = Array(repeating: 0, count: finalSquare + 1)
  8.                 board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
  9.                 board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
  10.         }
  11.         weak var delegate: DiceGameDelegate?
  12.         func play() {
  13.                 square = 0
  14.                 delegate?.gameDidStart(self)
  15.                 gameLoop: while square != finalSquare {
  16.                 let diceRoll = dice.roll()
  17.                 delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)
  18.                 switch square + diceRoll {
  19.                 case finalSquare:
  20.                         break gameLoop
  21.                 case let newSquare where newSquare > finalSquare:
  22.                         continue gameLoop
  23.                 default:
  24.                         square += diceRoll
  25.                         square += board[square]
  26.                         }
  27.                 }
  28.                 delegate?.gameDidEnd(self)
  29.         }
  30. }


蛇と梯子 ゲームの説明については、フロー制御 の章の Break のセクションを参照してください。


ゲームのこのバージョンは、DiceGame プロトコルを採用する SnakesAndLadders というクラスとして包ま込まれています。これは、プロトコルに準拠するために、取得可能な dice プロパティ及び play() メソッドを提供します。(dice プロパティは、初期化後に変更する必要がないため、定数のプロパティとして宣言され、プロトコルは、それが取得可能であることのみを要求します。)


蛇と梯子 ゲームボードのセットアップは、クラスの init() イニシャライザの中で行われます。全てのゲームロジックは、プロトコルの play メソッドの中に移動し、そのサイコロを振った値を提供するプロトコルの必須 dice プロパティを使用します。


デリゲートは、ゲームをプレイするために必須でないため、delegate プロパティは、optionalDiceGameDelegate として定義されていることに注意してください。それは、optional の型なので、delegate プロパティは nil の初期値に自動的に設定されます。その後、ゲームの例示には、適切なデリゲートにプロパティを設定するオプションがあります。


DiceGameDelegate は、ゲームの進行状況を追跡するために3つのメソッドを提供します。これら3つのメソッドは、上記の play() メソッド内のゲームロジックに組み込まれており、新しいゲームが始まる時、また新しい順番が始まる時、またはゲームが終了したときに呼び出されます。


delegate プロパティは optionalDiceGameDelegate であるため、play() メソッドは、デリゲート上のメソッドを呼び出すたびに、optional の連鎖を使用します。delegate プロパティが nil の場合、これらのデリゲートの呼び出しは、嬉しくも失敗し、エラーを起こしません。delegate プロパティが nil でない場合、デリゲートメソッドが呼び出され、パラメータとして SnakesAndLadders インスタンスが渡されます。


次の例では、DiceGameDelegate プロトコルを採用した DiceGameTracker というクラスを示しています。


  1. class DiceGameTracker: DiceGameDelegate {
  2.         var numberOfTurns = 0
  3.         func gameDidStart(_ game: DiceGame) {
  4.                 numberOfTurns = 0
  5.                 if game is SnakesAndLadders {
  6.                 print("Started a new game of Snakes and Ladders")
  7.                 }
  8.                 print("The game is using a \(game.dice.sides)-sided dice")
  9.         }
  10.         func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
    {
  11.                 numberOfTurns += 1
  12.                 print("Rolled a \(diceRoll)")
  13.         }
  14.         func gameDidEnd(_ game: DiceGame) {
  15.                 print("The game lasted for \(numberOfTurns) turns")
  16.         }
  17. }


DiceGameTrackerDiceGameDelegate が必須とする3つすべてのメソッドを実装します。これは、ゲームが取った順番の数を追跡するために、これらのメソッドを使用します。ゲームが始まると numberOfTurns プロパティをゼロにリセットし、新しい順番が始まるたびにそれを増分し、ゲームが終了した時点での順番の数の合計を出力します。


上に示した gameDidStart(_:) の実装は、プレイされようとしているゲームについてのいくつかの初歩的な情報を印刷する game パラメータを使用しています。game パラメータには DiceGame の型があり、 SnakesAndLadders の型ではなく、それで gameDidStart(_:)DiceGame プロトコルの一部として実装されるメソッドとプロパティのみにアクセスし、使用できます。しかし、このメソッドはまだ、基礎となるインスタンスの型を照会するために型キャストを使用できます。この例では、game が実際に舞台裏で SnakesAndLadders のインスタンスであるかどうかをチェックし、そうであれば、適切なメッセージを印刷します。


gameDidStart(_:) メソッドはまた、渡された game パラメータの dice プロパティにもアクセスします。game は、DiceGame プロトコルに準拠することが知られているので、dice プロパティを持つことが保証され、従って gameDidStart(_:) メソッドは、プレイされているゲームの種類にかかわらず、サイコロの sides プロパティにアクセスし、印刷できます。


DiceGameTracker は動作中にどのように見えるかをここに示します:


  1. let tracker = DiceGameTracker()
  2. let game = SnakesAndLadders()
  3. game.delegate = tracker
  4. game.play()
  5. // Started a new game of Snakes and Ladders
  6. // The game is using a 6-sided dice
  7. // Rolled a 3
  8. // Rolled a 5
  9. // Rolled a 4
  10. // Rolled a 5
  11. // The game lasted for 4 turns



拡張機能を持つプロトコル準拠の追加


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



注意: その準拠が拡張機能におけるインスタンスの型に追加されたとき、型の既存のインスタンスは自動的にプロトコルを採用し、準拠します。


例えば、TextRepresentable と言うこのプロトコルは、テキストとして表現される方法がある、全ての型で実装される事ができます。これは、それ自体の説明、または現在の状態のテキストバージョンです。


  1. protocol TextRepresentable {
  2.         var textualDescription: String { get }
  3. }


以前の Dice クラスは TextRepresentable を採用し、準拠するように拡張できます:


  1. extension Dice: TextRepresentable {
  2.         var textualDescription: String {
  3.                 return "A \(sides)-sided dice"
  4.         }
  5. }


この拡張機能は、Dice がその元の実装で提供していたかのようにまったく同じ方法で新しいプロトコルを採用します。プロトコル名は、コロンで区切られ、型名の後に提供され、プロトコルのすべての要件の実装は、拡張機能の中括弧内に提供されます。


全ての Dice インスタンスは、TextRepresentable として扱えるようになりました:


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


同様に、SnakesAndLadders ゲームクラスは TextRepresentable プロトコルを採用し、準拠するように拡張できます:


  1. extension SnakesAndLadders: TextRepresentable {
  2.         var textualDescription: String {
  3.                 return "A game of Snakes and Ladders with \(finalSquare) squares"
  4.         }
  5. }
  6. print(game.textualDescription)
  7. // prints "A game of Snakes and Ladders with 25 squares"


条件付きでプロトコルに準拠


汎用型は、その型の汎用パラメーターがプロトコルに準拠している場合など、特定の条件下でのみプロトコルの要件を満たすことができます。型を拡張するときに制約をリストアップすることによって、汎用型を条件付きでプロトコルに準拠させることができます。汎用の where 句を書くことで、採用しているプロトコルの名前の後にこれらの制約を書いてください。汎用の where 句について詳しくは、汎用の where 句 を参照してください。


以下の機能拡張は、それらが TextRepresentable に準拠する型の要素を格納するときはいつでも、Array インスタンスを TextRepresentable プロトコルに準拠させます。


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


拡張機能を持つプロトコルの採用を宣言


型がすでにプロトコル要件のすべてに準拠していても、まだそれは、そのプロトコルを採用すると述べていない場合、空の拡張機能を持つプロトコルを採用できます。


  1. struct Hamster {
  2.         var name: String
  3.         var textualDescription: String {
  4.                 return "A hamster named \(name)"
  5.         }
  6. }
  7. extension Hamster: TextRepresentable {}


TextRepresentable が必須の型ならどこでも Hamster のインスタンスは、使用できるようになりました。


  1. let simonTheHamster = Hamster(name: "Simon")
  2. let somethingTextRepresentable: TextRepresentable = simonTheHamster
  3. print(somethingTextRepresentable.textualDescription)
  4. // prints "A hamster named Simon"


注意: 型はその要件を満たすことだけで自動的にプロトコルを採用する訳ではありません。それらは、常に明示的にプロトコルの採用を宣言しなければなりません。


プロトコル型のコレクション


型としてのプロトコル で述べたように、プロトコルは、型として配列や辞書などのようにコレクションに格納されて使用できます。この例では、TextRepresentable の things の配列を作成します:


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



配列内の項目を反復処理し、各項目のテキスト表現を印刷することが可能になりました。


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


thing 定数が TextRepresentable 型であることに注意してください。舞台裏では実際のインスタンスはそれらの型であっても、DiceDiceGame、または Hamster 型のいずれでもありません。それにもかかわらず、それは TextRepresentable 型なので、TextRepresentable な全てには texualDescription プロパティがあることを知られているため、ループを通るたび thing.texualDescription にアクセスするのは安全です。



プロトコルの継承


プロトコルは、1つ以上の他のプロトコルを 継承 でき、それが継承する要件の上にさらに要件を追加できます。プロトコルの継承の構文は、クラス継承の構文に似ていますが、オプションで、カンマで区切られた複数の継承プロトコルを一覧表示します:


  1. protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
  2.         // protocol definition goes here
  3. }


ここでは上に挙げた例から TextRepresentable プロトコルを継承するプロトコルの例を示します。


  1. protocol PrettyTextRepresentable: TextRepresentable {
  2.         var prettyTextualDescription: String { get }
  3. }


この例では、TextRepresentable から継承する新しいプロトコル、PrettyTextRepresentable を定義しています。 PrettyTextRepresentable を採用する全てのものは TextRepresentable によって強制される要件を全て満たさねばならず、また PrettyTextRepresentable によって強制される追加要件を満たさねばなりません。この例では、 PrettyTextRepresentableString を返す prettyTextualDescription という取得可能なプロパティを提供するための一つの要件を追加します。


SnakesAndLadders クラスは PrettyTextRepresentable を採用し、準拠するように拡張できます:


  1. extension SnakesAndLadders: PrettyTextRepresentable {
  2.         var prettyTextualDescription: String {
  3.                 var output = textualDescription + ":\n"
  4.                 for index in 1...finalSquare {
  5.                 switch board[index] {
  6.                 case let ladder where ladder > 0:
  7.                         output += "▲ "
  8.                 case let snake where snake < 0:
  9.                         output += "▼ "
  10.                 default:
  11.                         output += "○ "
  12.                 }
  13.                 }
  14.                 return output
  15.         }
  16. }


この機能拡張 (extension) は、それが PrettyTextRepresentable プロトコルを採用し、SnakesAndLadders 型のため prettyTextualDescription プロパティの実装を提供することを述べています。PrettyTextRepresentable である全てのものは TextRepresentable でもなければならず、それで prettyTextualDescription の実装は、出力文字列を開始する TextRepresentable プロトコルから textualDescription プロパティにアクセスすることによって開始します。これは、コロンと改行を付加し、そのかなりなテキスト表現の開始としてこれを使用しています。その後、ボードの正方形の配列を反復処理し、各正方形の内容を表現する幾何学的な形を追加します:


prettyTexualDescription プロパティは、今やあらゆる SnakesAndLadder インスタンスのかなりなテキストの説明を印刷するために使用できます。


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


クラス専用プロトコル


プロトコルの継承リストに、AnyObject プロトコルを追加することによって、プロトコルの採用をクラス型 (構造体または列挙型ではない) に限定することができます。


  1. protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
  2.         // class-only protocol definition goes here
  3. }


上記の例では、SomeClassOnlyProtocol はクラス型によってのみ採用できます。SomeClassOnlyProtocol を採用しようとして構造体や列挙型の定義を書くと、コンパイル時エラーになります。


注意: そのプロトコルの要件によって定義された動作が、準拠型が値の意味ではなく、参照の意味を持つことを前提としまたは必要とするとき、クラス専用のプロトコルを使用してください。参照と値の意味の詳細については、構造体と列挙型は値型 と、クラスは参照型 を参照して下さい。


プロトコルの構成


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


プロトコルの構成は、someprotocol & AnotherProtocol の形です。アンパサンド(&) で区切って必要なだけ多くのプロトコルをリストできます。プロトコルのそのリストに加えて、プロトコル構成には 1 つのクラス型を含めることもでき、これを使用して必須スーパークラスを指定することができます。


ここで関数パラメータ上の一つのプロトコル構成要件に NamedAged という2つのプロトコルを組み合わせた例を示します。


  1. protocol Named {
  2.         var name: String { get }
  3. }
  4. protocol Aged {
  5.         var age: Int { get }
  6. }
  7. struct Person: Named, Aged {
  8.         var name: String
  9.         var age: Int
  10. }
  11. func wishHappyBirthday(to celebrator: Named & Aged) {
  12.         print("Happy birthday \(celebrator.name) , you're \(celebrator.age)!")
  13. }
  14. let birthdayPerson = Person(name: "Malcolm", age: 21)
  15. wishHappyBirthday(to: birthdayPerson)
  16. // prints "Happy birthday Malcolm, you're 21!"


この例では、取得可能な String プロパティの name と言う一つの要件を持つ、Named プロトコルがあります。取得可能な Int 型プロパティの age と言う一つの要件がある、Aged プロトコルもあります。これらのプロトコルの両方が Person と言う構造体によって採用されています。


また、この例では、wishHappyBirthday(to:) 関数も定義しています。celebrstor パラメータの型は、Named & Aged であり、これは、"NamedAged プロトコルの両方に準拠するすべての型" を意味します。必須プロトコルの両方に適合している限り、どの特定の型が関数に渡されたかは関係ありません。


この例は次に、birthdayPerson という新しい Person インスタンスを作成し、この新しいインスタンスを wishHappyBirthday(to:) 関数に渡します。Person は両方のプロトコルに準拠しているので、この呼び出しは有効で、wishHappyBirthday(to:) 関数はその誕生日の挨拶を印刷できます。


以前の例の Named プロトコルと Location クラスを組み合わせた例を次に示します。


  1. class Location {
  2.         var latitude: Double
  3.         var longitude: Double
  4.         init(latitude: Double, longitude: Double) {
  5.                 self.latitude = latitude
  6.                 self.longitude = longitude
  7.         }
  8. }
  9. class City: Location, Named {
  10.         var name: String
  11.         init(name: String, latitude: Double, longitude: Double) {
  12.                 self.name = name
  13.                 super.init(latitude: latitude, longitude: longitude)
  14.         }
  15. }
  16. func beginConcert(in location: Location & Named) {
  17.         print("Hello, \(location.name)!")
  18. }
  19. let seattle = City(name: "Seattle", latitude: 47.6, longitude: -122.3)
  20. beginConcert(in: seattle)
  21. // Prints "Hello, Seattle!"


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


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


プロトコル準拠の確認


型キャスト で説明したように isas 演算子を、プロトコルの準拠をチェックするため、また特定のプロトコルにキャストするため使用できます。プロトコルへのキャスト及びチェックは、型のチェックとキャストとまったく同じ構文に従います。


この例では、area と言う Double の一つの取得可能なプロパティ要件を持つ HasArea と言うプロトコルを定義しています。


  1. protocol HasArea {
  2.         var area: Double { get }
  3. }


ここに2つのクラス、CircleCountry があり、両方とも HasArea プロトコルに準拠しています


  1. class Circle: HasArea {
  2.         let pi = 3.1415927
  3.         var radius: Double
  4.         var area: Double { return pi * radius * radius }
  5.         init(radius: Double) { self.radius = radius }
  6. }
  7. class Country: HasArea {
  8.                 var area: Double
  9.         init(area: Double) { self.area = area }
  10. }


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


ここで、HasArea プロトコルに準拠していない、 Animal と言うクラスがあります。


  1. class Animal {
  2.         var legs: Int
  3.         init(legs: Int) { self.legs = legs }
  4. }


CircleCountryAnimal クラスは、共有する基本クラスを持っていません。それにもかかわらず、それらはすべてクラスであり、そして3つ全ての型のインスタンスは、型 AnyObject の値を格納する配列を初期化するために使えます:


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


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


objects 配列は、反復処理することができるようになり、配列内の各オブジェクトは、それが HasArea プロトコルに準拠するかどうかを確認できます。


  1. for object in objects {
  2.         if let objectWithArea = object as? HasArea {
  3.                 print("Area is \(objectWithArea.area)")
  4.         } else {
  5.                 print("Something that doesn't have an area")
  6.         }
  7. }
  8. // Area is 12.5663708
  9. // Area is 243610.0
  10. // Something that doesn't have an area


配列内のオブジェクトが HasArea プロトコルに準拠している時はいつでも、as? 演算子で返される optional の値は、objectWithArea と言う定数に optional 結合されて開封されます。objectWithArea 定数は HasArea 型であることが知られており、したがって、その area プロパティには、アクセスでき、安全な型の方法で印刷できます。


基礎となるオブジェクトは、キャストするプロセスによっては変更されないことに注意してください。それらは、CircleCountry そして 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 プロトコルによって定義されます。


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


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



注意: 厳密に言えば、プロトコル要件の いずれ をも実装することなく CounterDataSource に準拠したカスタムクラスを書くことができます。それらは結局、両方とも optional です。技術的には許可されていますが、これは非常に良いデータソースとは言えません。


以下に定義された Counter クラスには、CounterDataSource? 型の optional の dataSource プロパティがあります:


  1. class Counter {
  2.         var count = 0
  3.         var dataSource: CounterDataSource?
  4.         func increment() {
  5.                 if let amount = dataSource?.increment? (forCount: count) {
  6.                         count += amount
  7.                 } else if let amount = dataSource?.fixedIncrement {
  8.                         count += amount
  9.                 }
  10.         }
  11. }


Counter クラスは、count と言う変数プロパティに現在の値を格納します。Counter クラスは、また increment と言うメソッドも定義しており、そのメソッドが呼び出されるたびに count プロパティを増分します。


increment() メソッドは、まずそのデータソースに increment(forCount:) メソッドの実装を探して増分量を取得しようとします。increment() メソッドは increment(forCount:) を呼び出そうとして、optional の連鎖を使用し、メソッドの一つの引数として現在の count 値を渡します。


ここで optional の連鎖の 2つの レベルが演じている事に注意してください。第一に、dataSourcenil である可能性があり、したがって dataSourcenil でない場合にのみ increment(forCount:) を呼び出す必要があることを示すために、dataSource はその名前の後に疑問符がついています。第二に、 dataSource存在し ていても、それは optional の要件なので、increment(forCount:) を実装していることを保証するものではありません。ここで、increment(forCount:) が実装されていない可能性は、optional の連鎖によって処理されます。 increment(forCount:) への呼び出しは increment(forCount:) が存在する時にのみ起こり、すなわちそれが nil でない場合にのみ起こります。increment(forCount:) も、その名前の後に疑問符が書かれているのはこのためです。


increment(forCount:) への呼び出しは、これらの2つの理由のいずれかで失敗する可能性があるため、呼び出しは、optionalInt 値を返します。これは increment(forCount:)CounterDataSource の定義で optional でない Int 値を返すように定義されていても同様です。2つの optional の連鎖の操作が、次々とあっても、結果はまだ一つの optional で包み込まれています。複数の optional の連鎖の操作の使用については、連鎖の複数レベルのリンク を参照して下さい。


increment(forCount:) を呼び出した後、それが返す optional の Int は、optional の結合を使用して、amount と言う定数に開封されます。optional の Int に値がある場合、すなわち、デリゲートとメソッドの両方が存在し、そしてメソッドが値を返す場合、開封された amount は格納された count プロパティに追加され、そして増分は完了します。


increment(forCount:) メソッドから値を取得でき ない 場合、dataSourcenil か、またはデータソースが increment(forCount:) を実装していないためのいずれかであり、その後代わりに increment() メソッドが、データ·ソースの fixedIncrement プロパティから値を取得しようとします。fixedIncrement プロパティも optional の要件であり、したがって、その値も CounterDataSource プロトコル定義の一部として fixedIncrement が optional でない Int プロパティとして定義されているにもかかわらず、optional の Int 値です。


ここで、データソースが、それが照会されるたびに定数の 3 の値を返す単純な CounterDataSource を実装しましょう。optional の fixedIncrement プロパティの要件を実装することでこれを行います:


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


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


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


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


ここで TowardsZeroSource と言う、より複雑なデータソースがあり、これは Counter インスタンスを現在の count 値からゼロに向かってカウントダウンまたはアップします。


  1. class TowardsZeroSource: NSObject, CounterDataSource {
  2.         func increment(forCount count: Int) -> Int {
  3.                 if count == 0 {
  4.                         return 0
  5.                 } else if count < 0 {
  6.                         return 1
  7.                 } else {
  8.                         return -1
  9.                 }
  10.         }
  11. }


TowardsZeroSource クラスは、CounterDataSource プロトコルから、optional の increment(forCount:) メソッドを実装し、どの方向にカウントが行われるかを示すために count 引数を使います。もし count がすでにゼロなら、メソッドは更なるカウントが起こらない事を示すために 0 を返します。


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


  1. counter.count = -4
  2. counter.dataSource = TowardsZeroSource()
  3. for _ in 1...5 {
  4. counter.increment()
  5. print(counter.count)
  6. }
  7. // -3
  8. // -2
  9. // -1
  10. // 0
  11. // 0


プロトコル拡張機能


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


例えば、RandomNumberGenerator プロトコルは、randomBool() メソッドを提供するように拡張でき、これはランダムな Bool 値を返すために必要な randm() メソッドの結果を使用します。


  1. extension RandomNumberGenerator {
  2.         func randomBool() -> Bool {
  3.                 return random() > 0.5
  4.         }
  5. }


プロトコル上で拡張機能を作成することにより、すべての準拠型は何も追加の変更なしに、このメソッドの実装を自動的に得ることができます。


  1. let generator = LinearCongruentialGenerator()
  2. print("Here's a random number: \(generator.random())")
  3. // Prints "Here's a random number: 0.37464991998171"
  4. print("And here's a random Boolean: \(generator.randomBool())")
  5. // Prints "And here's a random Boolean: true"


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


デフォルトの実装の提供


そのプロトコルの全てのメソッドまたは計算されたプロパティ要件に、デフォルトの実装を提供するために、プロトコルの拡張機能を使用できます。準拠する型が、必要なメソッドやプロパティの独自の実装を提供している場合、その実装は、拡張機能が提供するものの代わりに使用されます。


注意: 拡張機能によって提供されるデフォルトの実装とプロトコル要件は、optional のプロトコル要件とは区別されます。準拠する型はいずれかの独自の実装を提供する必要はなく、デフォルトの実装の要件は、optional の連鎖なしで呼び出すことができます。


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


  1. extension PrettyTextRepresentable {
  2.         var prettyTextualDescription: String {
  3.                 return textualDescription
  4.         }
  5. }


プロトコル拡張機能に制約を追加


プロトコル拡張機能を定義するときは、拡張機能のメソッドとプロパティが利用可能になる前に、準拠した型が満たさなければならない制約を指定できます。汎用の Where 句 で説明したように、汎用の where 句を使用して、拡張しようとしているプロトコルの名前の後に、これらの制約を記述します。


たとえば、その要素が Equatable プロトコルに準拠する任意のコレクションに適用される Collection プロトコルへの拡張を定義できます。コレクションの要素を標準ライブラリの一部である Equatable プロトコルに制約することで、==!= 演算子を使用して、2 つの要素間の等価性と不等価性を確認できます。


  1. extension Collection where Element: Equatable {
  2.         func allEqual() -> Bool {
  3.                 for element in self {
  4.                         if element != self.first {
  5.                                 return false
  6.                         }
  7.                 }
  8.                 return true
  9.         }
  10. }


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


整数の 2 つの配列を考えてみましょう。1 つはすべての要素が同じで、もう 1 つはそうでないものです。


  1. let equalNumbers = [100, 100, 100, 100, 100]
  2. let differentNumbers = [100, 100, 200, 100, 200]


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


  1. print(equalNumbers.allEqual())
  2. // Prints "true"
  3. print(differentNumbers.allEqual())
  4. // Prints "false"


注意: 準拠する型が同じメソッドまたはプロパティの実装を提供する複数の制約された拡張機能の要件を満たしている場合、Swift は、最も特殊な制約に対応した実装を使用します。


前:機能拡張 次:ジェネリック(汎用)

















トップへ












トップへ












トップへ












トップへ












トップへ












トップへ












トップへ












トップへ












トップへ












トップへ
目次
Xcode 10 の新機能

Swift について
Swift と Cocoa と Objective-C (obsolete)
Swift Blog より (obsolete)

SwiftLogo
  • Swift 4.2 全メニュー


  • Swift へようこそ
  • Swift について
  • Swift 言語のガイド
  • Swift の基本
  • 基本演算子
  • 文字列と文字
  • コレクション型
  • フロー制御
  • 関数
  • クロージャ
  • 列挙型
  • クラスと構造体
  • プロパティ
  • メソッド
  • サブスクリプト
  • 継承
  • 初期化
  • デイニシャライザ
  • Optional の連鎖
  • エラー処理
  • 型キャスト
  • ネストした型
  • 拡張機能
  • プロトコル
  • ジェネリック(汎用)
  • 自動参照カウント
  • メモリの安全性
  • アクセス制御
  • 高度な演算子

  • 言語リファレンス

  • マニュアルの変更履歴













  • トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ