Swift

Swift/プロトコル

オブジェクト指向言語の継承の概念は、クラス継承とインターフェイス継承からなる。 インターフェイス継承をSwiftはプロトコルと呼ぶ。

プロトコル (protocol) は実装を伴わない宣言の集合である。 クラス、構造体、列挙型などを定義するときにプロトコルを指定して、 そのプロトコルが宣言しているメソッドを実装する。 これにより継承関係のない型でも、プロトコルで宣言された同じ機能を提供することを保証できる。

public protocol CustomStringConvertible {
  public var description: String { get }
}

プロトコルを使ってクラス(や構造体や列挙型など)を定義することを 「プロトコルを採用 (adopt) する」といい、そのクラス(や構造体や列挙型など)は 「プロトコルに適合 (conform) している」という。

Swiftでプロトコルに含めることができるのは、次の宣言である。

  • インスタンスメソッド、タイプメソッド
  • 演算子による関数の宣言
  • インスタンスプロパティ、タイププロパティ
  • イニシャライザ
  • 添字付け
  • typealias宣言

プロトコル名は大文字開始のキャメルネームにする。 プロトコルは他のプロトコルを継承することができる。

// { }内はいずれもオプションで順番や個数に制限はない
// [ ]の部分はさらに省略可能
protocol プロトコル名 : プロトコル列 {
  [static] func メソッド名 ([仮引数]) [-> 型]
  [prefix | postfix] func 演算し ([仮引数]) [->型]
  [static] var プロパティ名 : 型 { get [set] }
  init([仮引数])
  subscript(仮引数) -> 型 { get [set] }
  typealias 型名 [= 型]
}

プロトコル Person の例

protocol Personal {
  var name: String { get }
  init(name: String)
  func sayHelloTo(p: Personal)
  func saySomething() -> String
}

メソッドの宣言と定義

プロトコルでのメソッドの宣言は、コードブロックは記述しない。 (プロトコルの拡張定義ではコードを記述して、プロトコルのデフォルトの動作を定義できる。)

タイプメソッドを定義するときは static 修飾子を記述する。

プロパティの宣言と定義

プロパティが読み書き可能の場合はsetとgetを、読み出し専用の場合は set のみを記述する。 読み出し専用プロパティでも、プロトコルでは var と宣言する(実装では let を使う)。

プロトコルで 読み出し専用として宣言したプロパティを、実装で読み書き可能に変更しても問題ない。

実装が格納型か計算型かはプロトコル側では記述しない。

イニシャライザの宣言と定義

イニシャライザを指定することもできる。クラスの必須イニシャライザになるので、 クラスの実装では required キーワードをつける必要があり、 そのクラスを継承したサブクラスでも同じイニシャライザを定義しなくてはいけない。 (ただし、クラス定義でfinalを指定している場合は、もうサブクラスは定義できなため required を記述する必要はない。)

designated initializer か convenience initalizer の区別はプロトコルでは記述しない。

class Citizen : Personal {
  let name: String        // プロトコルを定数で実装する
  required init(name: String) {   // required が必要
    self.name = name
  }
  func sayHelloTo(p: Personal) {
    print("\(p.name)さん、こんにちは")
  }
  func saySomething() -> String { return "なるほど" }
}

クラス定義のためのプロトコル

クラス(参照型)での実装だけを想定してプロトコルを定義する場合がある。 そのような場合は、クラスだけが対象であることを明示するために ':' の後に class キーワードを置く。

protocol TripleTappable : class { // 以下、略
protocol Nerberality : class, 別のプロトコル { // 以下、略

クラス限定のプロトコルの場合、メソッドに mutating というキーワードは記述できない。

プロトコルと型

プロトコルは型として使うことができる。

func printNames(list: [Personal]) {
  for p in list {
    print(p.name + ": " + p.saySomething())
  }
}

typealias による付属型の指定や Self を使って宣言されたプロトコルは型として使えない。

プロトコルの継承

enum Sex { cse Male, Female }     // 性別を表す列挙型
protocol HealthInfo : Personal {  // プロトコルを継承する
  var weight: Double { get set }  // 読み書き可能なプロパティ
  var height: Double { get set }
  var sex: Sex? { get }           // read onlyなプロパティ
}

プロトコルの合成

複数のプロトコルのUnionのプロトコルを作成できる。 次の例では、NewProtocolという新しいプロトコルを定義している。 一時的に使いたいだけならば protocol<> という記述もできる。

protocol NewProtocol : プロトコル1, プロトコル2 {}
var teller: NewProtocol
または
protocol<プロトコル1, プロトコル2> teller

プロトコルへの適合を調べる

Pをプロトコル, 式を exp とする。

  • exp is P --- 式 exp がプロトコル P に適合したインスタンスの場合は true。
  • exp as P --- 式 exp がプロトコル P にキャストされる。適合していない場合はエラーとなる。ダウンキャストには使用できない。
  • exp as? P --- 式 exp がプロトコル P のオプショナル型にダウンキャストされる。適合していない場合はnilが返る。
  • exp as! P --- 式 exp がプロトコル P のオプショナル型にダウンキャストされる。適合していない場合はエラーとなる。

プロトコルと付属型

ネスト型とプロトコル

次の例では、Elementの定義がなく、プロパティxとyがElement型であることだけが記述されている。

protocol VectorType {   // 2次元ベクトル型
  typealias Element     // 付属型の宣言
  var x : Element { get set }
  var y : Element { get set }
}

プロトコル内で typealias を使って定義される付属型はジェネリクス機能の一つで、 型の定義にプロトコルが採用されたときその型で実際に使われる型にマッチする。 型パラメータや付属型はコンパイル時に静的に解析される。

struct VectorFloat : VectorType {
  var x, y: Float     // 付属型に具体的な型を指定した
  func onvToDouble() -> VectorDouble {
    return VectorDouble(
      x: VectorDouble.Element(x),   // 型として利用
      y: VectorDouble.Element(y)
    )
  }
}
struct VectorDouble : VectorType, CustomStringConvertible {
 var x, y: Double       // 付属型に具体的な型を指定した
 var description: String { return "[\(x), \(y)]" }
}
struct VectorGrade : VectorType {
  enum Element { case A, B, C, D, X }   // ネスト型を定義する
  var x, y: Element
}
var a = VectorFloat(x: 10.0, y: 12.0)
let b = a.convToDouble()
print(b)       // [10.0, 12.0] と表示される

付属型が適合するプロトコルを指定する

プロトコルを採用した型で動作する共通のメソッドを宣言しようとすると、 付属型で表される型にどのような操作や演算が可能かを指定できる必要があるが、 そのためには適合するパラメータを指定すればよい。

protocol VectorIntegerType {
  typealias Element: IntegerType    // 付属型に適合するプロトコルを指定する
  var x : Element { get set }
  var y : Element { get set }
}
struct VectorInt: VectorIntegerType {
  var x, y: Int
  mutating func add(x:Int, y:Int) {
    self.x += x; self.y += y
  }
}
struct VectorUInt16: VectorIntegerType {
  var x, y: UInt16
}
let mx: VectorInt.Element = 10   // VectorInt.ElementはInt型
var a = VectorInt(x:mx, y:-7)
a.add(x:10, y:9)     // (x: 20, y: 2) になる。

プロトコルに適合する型の定義方法

プロトコル Equatable と Comparable


トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2016-05-26 (木) 16:30:45 (1473d)