詳解Swift#7(Chapter10 プロトコル)
前回に続き、詳解Swiftの写経を続ける。
- 作者: 荻原剛志
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2015/12/25
- メディア: 単行本
- この商品を含むブログを見る
chapter10 プロトコル
コレより前の章でもちょこちょこ出てきていたプロトコルについての話。プロトコルは 実装を伴わない宣言の集合 と書いている。インタフェースによく似ている。インタフェースの継承のようにインスタンスがどのような機能を持つか、ということをSwiftではプロトコルで実現していて
- クラス
- 構造体
- 列挙型
のそれぞれにプロトコルを使って標準ライブラリが持つ機能を継承できるらしい。
プロトコルの宣言
プロトコル名はキャメルケースの大文字開始で宣言する。
CustomStringConvertible
の定義stdlib/public/core/OutputStream.swift
に定義されてる- https://github.com/apple/swift/blob/55be501ad6d4d103db7b4ba303aa54343acaa779/stdlib/public/core/OutputStream.swift#L54
/// `CustomStringConvertible` public protocol CustomDebugStringConvertible { /// A textual representation of the instance, suitable for debugging. var debugDescription: String { get } }
protocol Personal { var name: String { get } init(name: String) func sayHelloto(p:Personal) func saySomething() -> String }
メソッドは宣言だけして、コードブロック {}
は記述しない。
- イニシャライザ
プロトコルのイニシャライザは、継承するクラスでは必須イニシャライザとなるので、 required
修飾子を付ける必要がある。
protocol Personal { var name: String { get } init(name: String) func sayHelloTo(p:Personal) func saySomething() -> String } class Citizen: Personal { let name: String required init(name: String) { self.name = name } func sayHelloTo(p: Personal) { print("どうも、 \(p.name) さん") } func saySomething() -> String { return "なるほど" } } var ct = Citizen(name: "kentana") var ct2 = Citizen(name: "tanaken") ct.saySomething() // なるほど ct.sayHelloTo(ct2) // どうも、 tanaken さん ct2.sayHelloTo(ct) // どうも、 kentana さん
プロトコルと型
プロトコルは型としても使える。(というか、↑の例で既に引数の型としてPersonalを使ってる)
enum Sex { case Male, Female } protocol HealthInfo: Personal { // Personalを継承 var weight: Double { get set } var height: Double { get set } var sex: Sex? { get } }
// 新しく合成したプロトコルを定義 protocol HelthyPerson: Healthy, Personal {} // 定義せずに合成して型とする var teller: protocol<Healthy, Personal>
- プロトコルの適合の評価
exp is P // 適合している場合はtrue, していない場合はfalse exp as P // プロトコルP型にキャストされる。適合してない場合は実行時エラー exp as? P // プロトコルP型にダウンキャストされる。適合してない場合はnil exp as! P // プロトコルP型にダウンキャストされる。適合してない場合は実行時エラー
- オプション項目のあるプロトコル
@objc protocol PersonalData { var name: String { get } optional avr bloodtype: String { get } optional func age() -> Int } var p: PersonalData = ... if let btype = p.bloodtype { print(btype) } if let a = p.age?() { print("hoge") }
プロトコルと付属型
protocol VectorType { typealias Element var x : Element { get set } var y : Element { get set } } struct VectorFloat : VectorType { var x, y: Float func convToDouble() -> 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) print("x:\(a.x), y:\(a.y)") let b = a.convToDouble() print(b) // [10.0, 12.0] var g = VectorGrade(x: .A, y: .C) print(g) // VectorGrade(x: VectorGrade.Element.A, y: VectorGrade.Element.C)
プロトコルに適合する型の定義方法
Swiftのデータ型の多くは構造体で定義されているが、この構造体も標準のプロトコルを使っている。ここに合わせる形で定義するデータ型も標準のプロトコルに適合させると、既存のデータ型と同じように使える。これは確かによいかもしれない。
- Comparable / Equatable
- 比較演算子を使えるようになる
- ForwardIndexType
for-in
で繰り返しできるように範囲を作れる
enum WeekDay: Int, ForwardIndexType { case Sun, Mon, Tue, Wed, Thu, Fri, Sat case _dummy func successor() -> WeekDay { if self == ._dummy { return ._dummy } return WeekDay(rawValue: self.rawValue + 1)! } } var d = WeekDay.Sun print(d.rawValue) // 0 print(d.successor().rawValue) // 1 var r = Range<WeekDay>(start:.Mon, end:.Fri) for i in WeekDay.Sun ... WeekDay.Sat { print(i.rawValue, terminator:" ") // 0 1 2 3 4 5 6 } for i in WeekDay.Mon ... .Sat { print(i.rawValue, terminator:" ") // 1 2 3 4 5 6 }
- 継承絡みのプロトコル
protocol Copyable { func copy() -> Self } class GeoPoint : Copyable, Equatable, CustomStringConvertible { let latitude: Double // 緯度 let longitude: Double // 経度 required init(lat:Double, lon:Double) { // 必須イニシャライザ self.latitude = lat self.longitude = lon } func copy() -> Self { let nw = self.dynamicType.init(lat:latitude, lon:longitude) return nw } var description: String { return "GeoPoint:\(latitude), \(longitude)" } } func == (lhs:GeoPoint, rhs:GeoPoint) -> Bool { return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude }
継承が絡んで来る場合、Selfではなく self.dynamicType
を使わないとサブクラス側から呼び出された場合にスーパークラスのインスタンスを参照してしまうので注意が必要。
所感・雑感
- プロトコルはC#, Javaなどでいうとこのインタフェース。特に違和感なく使えそう
- Swift標準を意識してプログラムを書くとプロトコルに適合するようになるのかもしれない
- プロトコルの合成はインタフェースの組み合わせを自在に定義できるので、シンプルなプロトコルをいくつか定義していおいて、それらを組み合わせてかなり便利に使えそう
@objc
修飾子は Objective-C と組み合わせる意図はなく、オプショナルの項目を作りたい場合でも定義できるのは名前が直感的でないのでイマイチ感ある。そして@objc
修飾したプロトコルはクラスでしか使えない。これは覚えられる気がしないので、実戦で掴んでいかないといかん。- 付属型はちょっとよくわからない。というか、複雑なコードになったときに活躍しそうなので、いまはまだ評価できないという印象
- Self難しい。