読者です 読者をやめる 読者になる 読者になる

kentana20 技忘録

技術ネタを中心に、セミナー、勉強会、書籍、会社での出来事を綴っていきます。不定期更新。

詳解Swift#8(Chapter11 拡張)

前回に続き、詳解Swiftの写経を続ける。

詳解 Swift 改訂版

詳解 Swift 改訂版

chapter11 拡張

継承や型そのもののコード変更を行わずに、既に存在している型にメソッドやプロパティを追加できる機能。Objective-Cでは「カテゴリ」と呼ばれていたらしい。強力だと言っている。

拡張の宣言

extension 型名: プロトコル {
    イニシャライザ
    計算型プロパティ
    メソッド
    その他の定義
}

という定義の仕方をする。

  • 既存の型に対する拡張
extension String {
    var length: Int {
        return self.characters.count
    }
}

クラスに対して拡張定義をした場合、サブクラスにも適用される。ただ、その拡張はfinalで定義されたものとみなされるのでサブクラス側で変更したりはできない。

拡張定義とプロトコルへの適合

truct Ounce {
    var mL:Double = 0.0
    static let ounceUS = 29.5735
    init(ounce:Double) {
        self.ounce = ounce
    }
    var ounce: Double {
        get { return mL / Ounce.ounceUS }
        set { mL = newValue * Ounce.ounceUS }
    }
}

このOunce構造体にプロトコル拡張を定義すると以下。

:walking: extension Ounce : FloatLiteralConvertible {
    init(floatLiteral value: Double) {
        self.init(ounce: value)
    }
}

プロトコル拡張

protocol Datable {
    var year: Int { get }
    var month: Int { get }
    var day: Int { get }
}

このプロトコルを拡張してみる。

protocol Datable {
    var year: Int { get }
    var month: Int { get }
    var day: Int { get }
}

// ツェラーの公式
func Zeller(var m:Int, _ d:Int, var _ y:Int) -> Int {
     if m < 3 {
          m += 12; --y
     }
     let leap = y + y / 4 - y / 100 + y / 400
     return (leap + (13 * m + 8) / 5 + d) % 7
}

extension Datable {
    var dayOfWeek: Int {
        return Zeller(month, day, year)
    }
}

struct Date : Datable {
    var year, month, day: Int
}

let d1 = Date(year:1992, month:7, day:19)
print(d1)
print(d1.dayOfWeek)

プロトコルを拡張することで、メソッド、添字付けなど実装を拡張することができる。プロトコル拡張で定義したメソッドなどの実装は 既定実装 と呼ぶ。

protocol DateType {
    var year: Int { get }
    var month: Int { get }
    var day: Int { get }
    var area: String? { get }
}

extension DateType {
    func toString() -> String {
        return "\(year). \(month). \(day)"
    }
}

protocol TimeType {
    var hour: Int { get }
    var minute: Int { get }
    var area: String? { get }
}

extension TimeType {
    func toString() -> String {
        var s = (hour < 10 ? " " : "") + "\(hour)"
        s += ":" + (minute < 10 ? "0" : "") + "\(minute)"
        if let a = area { s += " (\(a))" }
        return s
    }
}

struct Date : DateType, TimeType {
    let year, month, day: Int
    let hour, minute: Int
    let area: String?
    init(_ y:Int, _ m:Int, _ d:Int, _ t:(Int, Int), area:String? = nil) {
        year = y; month = m; day = d;
        (hour, minute) = t; self.area = area
    }
}

プロトコル拡張の既定実装は宣言(静的)であり、クラスの継承のように動的に動作するものではない。

所感・雑感

  • プロトコルの拡張は便利に使えそう
    • String系のプロトコルにログ出力系処理を拡張に書くとか
  • プロトコル拡張の既定実装は定義したプロトコルの使い方というか利用をある程度規定できる気がするので意図しない使い方を防げそう