見出し画像

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

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

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

Protocol Syntax

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

   protocol SomeProtocol {
       // protocol definition goes here
   }

カスタムタイプは、定義の一部として、コロンで区切られたタイプの名前の後にプロトコルの名前を配置することにより、特定のプロトコルを採用することを示しています。 複数のプロトコルをリストでき、コンマで区切ります。

   struct SomeStructure: FirstProtocol, AnotherProtocol {
       // structure definition goes here
   }

クラスにスーパークラスがある場合は、採用するプロトコルの前にスーパークラス名をリストし、その後にコンマを続けます。

   class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
       // class definition goes here
   }

Property Requirements

プロトコルは、インスタンスプロパティまたはタイププロパティに特定の名前と型を指定すること、任意の型に適合することを要求できます。プロトコルは、プロパティを保存プロパティにするか計算プロパティにするかを指定しません。必要なプロパティ名と型のみを指定します。プロトコルは、各プロパティが読み取り専用であるか、読み書きできる必要があるかも指定します。

プロトコルでプロパティが取得可能および設定可能である必要がある場合、そのプロパティ要件は、一定の格納されたプロパティまたは読み取り専用の計算プロパティでは満たすことができません。プロトコルがプロパティを取得可能にすることだけを要求する場合、その要件はあらゆる種類のプロパティで満たすことができます。これが独自のコードに役立つ場合は、プロパティも設定可能であることが有効です。

プロパティ要件は、常に変数プロパティとして宣言され、接頭辞として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 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の計算、読み取り専用プロパティとして実装します。 各Starshipクラスインスタンスには、必須の名前とoptionalのprefixが格納されます。 fullNameプロパティは、prefix値が存在する場合はそれを使用し、名前の先頭にprefix値を追加して、StarshipのfullNameを作成します。

Method Requirements

プロトコルでは、特定のインスタンスメソッドとタイプメソッドを、型に準拠して実装する必要があります。 これらのメソッドは、プロトコルの定義の一部として、通常のインスタンスおよび型メソッドの場合とまったく同じ方法で記述されていますが、中括弧やメソッド本体はありません。 通常の方法と同じルールに従って、可変個引数パラメータを使用できます。 ただし、プロトコルの定義内のメソッドパラメータにデフォルト値を指定することはできません。

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

   protocol SomeProtocol {
       static func someTypeMethod()
   }

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

   protocol RandomNumberGenerator {
       func random() -> Double
   }

このプロトコルRandomNumberGeneratorでは、準拠する型にrandomというインスタンスメソッドが必要です。このメソッドは、呼び出されるたびにDouble値を返します。 プロトコルの一部として指定されていませんが、この値は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.3746499199817101"
   print("And another one: \(generator.random())")
   // Prints "And another one: 0.729023776863283"

Mutating Method Requirements

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

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

プロトコルインスタンスメソッドの要件を変更としてマークする場合、クラスのそのメソッドの実装を作成するときに、変更キーワードを作成する必要はありません。 mutatingキーワードは、構造体と列挙によってのみ使用されます。

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

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

   protocol Togglable {
       mutating func toggle()
   }

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

以下の例では、OnOffSwitchと呼ばれる列挙型を定義しています。 このenumは、enum caseのオンとオフで示される2つの状態を切り替えます。 列挙型のトグル実装は、トグル可能プロトコルの要件に一致するように、変更としてマークされています。

   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

Initializer Requirements

プロトコルでは、タイプに準拠することにより、特定の初期化子を実装する必要があります。 これらの初期化子は、通常の初期化子の場合とまったく同じ方法でプロトコルの定義の一部として記述しますが、中括弧や初期化子本体は使用しません。

   protocol SomeProtocol {
       init(someParameter: Int)
   }

Class Implementations of Protocol Initializer Requirements

プロトコル初期化子要件は、指定された初期化子または便利な初期化子として、適合クラスに実装できます。 どちらの場合も、初期化子の実装に必要な修飾子を付ける必要があります。

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

Failable Initializer Requirements

プロトコルは、Failable Initializersで定義されているように、適合型としてFailableInitializer要件を定義できます。

失敗可能なInitializerの要件は、適合型の失敗可能または失敗不可能なInitializerによって満たすことができます。 失敗しないInitializerの要件は、失敗しないInitializerまたは暗黙的にアンラップされた失敗可能なInitializerによって満たすことができます。

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