見出し画像

Swiftでプログラミング-Protocols 3

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

既存の型にアクセスできない場合でも、既存の型を拡張して新しいプロトコルを採用し、準拠させることができます。 拡張機能は、既存の型に新しいプロパティ、メソッド、および添え字を追加し、プロトコルが必要とする要件を追加できます。 

今あるインスタンスの型は、拡張されたインスタンスの型が追加されると、プロトコルに適合したものを自動的に採用します。

たとえば、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"

Conditionally Conforming to a Protocol プロトコルに条件付きで準拠

ジェネリック型は、型のジェネリックパラメータがプロトコルに準拠している場合など、特定の条件下でのみプロトコルの要件を満たすことができる場合があります。 型を拡張するときに制約をリスト化することにより、ジェネリック型を条件付きでプロトコルに準拠させることができます。 採用するプロトコルの名前の後に、一般的なwhere句を記述して、これらの制約を記述します。 

次の拡張により、配列インスタンスは、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)
// Prints "[A 6-sided dice, A 12-sided dice]"

Declaring Protocol Adoption with an 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)
   // Prints "A hamster named Simon"
型は、要件を満たすだけでプロトコルを自動的に採用するわけではありません。 それらは常にプロトコルの採用を明示的に宣言しなければなりません。

Adopting a Protocol Using a Synthesized Implementation 合成された実装を使用したプロトコルの採用

Swiftは、多くの単純なケースで、自動的にEquatable、Hashable、およびComparableのプロトコル準拠します。これらを実装することで、プロトコル要件を自分で実装するために繰り返しBoilerplate code (実際のテキストや画像を提供する。コピー&ペーストしたら使える)を記述する必要がなくなります。

Swiftは、次の種類のカスタムタイプに対してEquatableの実装を提供します。

・Equatableプロトコルに準拠するプロパティのみが保存されている構造
・Equatableプロトコルに準拠するタイプのみが関連付けられている列挙
・関連付けられた型を持たない列挙

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

以下の例では、Vector2D構造と同様に、3次元位置ベクトル(x、y、z)のVector3D構造を定義しています。 x、y、およびzプロパティはすべて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.")
   }
   // Prints "These two vectors are also equivalent."

Swiftは、次の種類のカスタムタイプ用にHashableの実装ができます。

・Hashableプロトコルに準拠する一つの保存プロパティのみもつ構造体
・Hashableプロトコルに準拠する一つのassociated typeを持つ列挙体
・associated typeを持たない列挙体

hash(into :)の実装する場合は、hash(into :)メソッドを自身で定義せず、元の宣言を含むファイルでHashableの準拠を宣言します。

Swiftは、raw valueを持たない列挙体に対してComparableの合成実装を提供します。列挙体にassociated typeがある場合、それらはすべてComparableプロトコルに準拠している必要があります。 "<"の合成実装を受け取るには、"<"演算子を自分で実装せずに、元の列挙宣言を含むファイルでComparableへの適合を宣言します。 Comparableプロトコルのデフォルトの実装である<=、>、および> =は、残りの比較演算子を提供します。

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

   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)
   }
   // Prints "beginner"
   // Prints "intermediate"
   // Prints "expert(stars: 3)"
   // Prints "expert(stars: 5)"

Collections of Protocol 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型であることに注意してください。実際のインスタンスがこれらの型のいずれかであっても、Dice、DiceGame、またはHamsterではありません。 それでも、型はTextRepresentableであり、TextRepresentableはプロパティtextualDescriptionを持ち、ループを回すことでthing.textualDescriptionに安全にアクセスできます。

Protocol Inheritance プロトコルの継承

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

    protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
       // protocol definition goes here
   }

上記からTextRepresentableプロトコルを継承するプロトコルの例を次に示します。

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

この例では、TextRepresentableから継承する新しいプロトコルPrettyTextRepresentableを定義します。 PrettyTextRepresentableを採用するものはすべて、TextRepresentableの要件に加えて、PrettyTextRepresentableによって定義される追加の要件を満たす必要があります。 この例では、PrettyTextRepresentableは、文字列を返すprettyTextualDescriptionというgettableプロパティを提供するための単一の要件を追加します。

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
       }
   }

この拡張機能は、PrettyTextRepresentableプロトコルを採用し、SnakesAndLadders型の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 {
       // class-only protocol definition goes here
   }

上記の例では、SomeClassOnlyProtocolはクラスタイプでのみ採用できます。 構造体または列挙型でSomeClassOnlyProtocolを採用しようとするとコンパイルエラーが発生します。

Protocol Composition

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

Protocol Compositionは、SomeProtocolおよびAnotherProtocolを持っています。 アンパサンド(&)で区切って、必要な数のプロトコルをリストできます。 プロトコルのリストに加えて、Protocol Compositionには1つのクラスタイプを含めることもできます。これを使用して、必要なスーパークラスを指定できます。

これは、NamedとAgedという2つのプロトコルを組み合わせ、Protocol Compositionで関数パラメーターの要件としています。

   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!"

この例では、Namedプロトコルには、nameと呼ばれるgetできるStringプロパティが定義されています。 Agedプロトコルには、ageと呼ばれるgetできるIntプロパティが定義されています。 どちらのプロトコルも、構造体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)
   // Prints "Hello, Seattle!"

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

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

Checking for Protocol Conformance

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

is演算子は、インスタンスがプロトコルに準拠している場合はtrueを返し、準拠していない場合はfalseを返します。
として? ダウンキャスト演算子のバージョンは、プロトコルの型のoptionalの値を返します。インスタンスがそのプロトコルに準拠していない場合、この値はnilになります。
として! ダウンキャスト演算子のバージョンは、ダウンキャストをプロトコルタイプに強制し、ダウンキャストが成功しない場合はランタイムエラーをトリガーします。

この例では、HasAreaと呼ばれるプロトコルを定義し、areaと呼ばれるgettableDoubleプロパティの単一のプロパティ要件を使用します。

   protocol HasArea {
       var area: Double { get }
   }

以下は、CircleとCountryの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クラスは、領域要件を格納されたプロパティとして直接実装します。 どちらのクラスも、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)
   ]

オブジェクト配列は、半径2単位のCircleインスタンスを含む配列リテラルで初期化されます。 英国の表面積(平方キロメートル)で初期化されたCountryインスタンス。 そして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プロパティにアクセスして、型に安全な方法で出力できます。

基になるオブジェクトはキャストプロセスによって変更されないことに注意してください。 彼らはサークル、国、そして動物であり続けます。 ただし、objectWithArea定数に格納された時点では、タイプはHasAreaであることがわかっているため、アクセスできるのはareaプロパティのみです。

Optional Protocol Requirements

プロトコルのoptional の要件を定義できます。これらの要件は、プロトコルに準拠する型で実装する必要はありません。optionalの要件には、プロトコルの定義の一部としてoptionalの修飾子が接頭辞として付けられます。 Objective-Cと相互運用するコードを記述できるように、optionalの要件を利用できます。プロトコルとオプションの要件の両方に@objc属性を付ける必要があります。 @objcプロトコルは、Objective-Cクラスまたは他の@objcクラスから継承するクラスでのみ採用できることに注意してください。構造や列挙では採用できません。

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

optionalのプロトコル要件は、プロトコルに準拠する型によって要件が実装されなかった可能性を説明するために、optionalチェーンとともに呼び出すことができます。optionalのメソッドの実装を確認するには、メソッドの名前の後に、someOptionalMethod?(someArgument)など、メソッドが呼び出されたときに疑問符を書き込みます。

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

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

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

厳密に言えば、どちらのプロトコル要件も実装せずに、CounterDataSourceに準拠するカスタムクラスを作成できます。 結局のところ、どちらもoptionalです。 技術的には許可されていますが、これでは非常に優れたデータソースにはなりません。

以下で定義されている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クラスは、incrementと呼ばれるメソッドも定義します。このメソッドは、メソッドが呼び出されるたびにcountプロパティをインクリメントします。

incremental()メソッドは、最初に、データソースでincrement(forCount :)メソッドの実装を探すことにより、増分量を取得しようとします。 incremental()メソッドは、optionalチェーンを使用してincrement(forCount :)を呼び出そうとし、現在のカウント値をメソッドの単一の引数として渡します。

ここでは、2つのレベルのoptionalチェーンが機能していることに注意してください。まず、dataSourceがnilである可能性があるため、dataSourceの名前の後に疑問符が付いており、dataSourceがnilでない場合にのみincrement(forCount :)を呼び出す必要があることを示しています。次に、dataSourceが存在する場合でも、それがoptionalの要件であるため、increment(forCount :)を実装する保証はありません。ここで、increment(forCount :)が実装されない可能性も、optionalチェーンによって処理されます。 incremental(forCount :)の呼び出しは、increment(forCount :)が存在する場合、つまりnilでない場合にのみ発生します。これが、increment(forCount :)も名前の後に疑問符が付いて書かれている理由です。

インクリメント(forCount :)の呼び出しは、これら2つの理由のいずれかで失敗する可能性があるため、呼び出しはoptionalのInt値を返します。これは、increment(forCount :)がCounterDataSourceの定義でオプションではないInt値を返すように定義されている場合でも当てはまります。 2つのoptionalチェーン操作が次々にありますが、結果は1つのoptionalにラップされます。複数のoptionalチェーン操作の使用の詳細については、複数レベルの連鎖のリンクを参照してください。

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

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

これは、データソースがクエリされるたびに定数値3を返す単純なCounterDataSource実装です。これは、optionalの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回呼び出します。 予想どおり、increment()が呼び出されるたびに、カウンターのcountプロパティは3ずつ増加します。

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

    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

Protocol Extensions

プロトコルを拡張して、メソッド、初期化子、添え字、および計算されたプロパティの実装を適合型に提供できます。 これにより、各タイプの個別の適合性やグローバル関数ではなく、プロトコル自体の動作を定義できます。

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

   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.3746499199817101"
   print("And here's a random Boolean: \(generator.randomBool())")
   // Prints "And here's a random Boolean: true"

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

Providing Default Implementations

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

拡張機能によって提供されるデフォルトの実装を使用したプロトコル要件は、optionalのプロトコル要件とは異なります。 準拠タイプはどちらも独自の実装を提供する必要はありませんが、デフォルトの実装の要件は、optionalチェーンなしで呼び出すことができます。

たとえば、TextRepresentableプロトコルを継承するPrettyTextRepresentableプロトコルは、必要なprettyTextualDescriptionプロパティのデフォルトの実装を提供して、textualDescriptionプロパティへのアクセス結果を返すだけです。

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

Adding Constraints to Protocol Extensions

プロトコル拡張を定義するとき、拡張のメソッドとプロパティが使用可能になる前に、適合タイプが満たさなければならない制約を指定できます。 これらの制約は、拡張するプロトコルの名前の後に、一般的なwhere句を記述して記述します。 ジェネリック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"
適合型が、同じメソッドまたはプロパティの実装を提供する複数の制約付き拡張の要件を満たしている場合、Swiftは特殊な制約に対応することが出来ます。

この記事が気に入ったらサポートをしてみませんか?