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

【Swift】Appleの新言語「Swift」のリファレンスを読む(15) - Extensions

注意

  • あくまでメモ書きなので細かい部分を端折りますし、色々間違ってるかもしれません。ちゃんとした内容は原文を読んでね。
  • コード例は基本的に原文からそのまま引用していますが、ちょっとした注釈をつけたり、統合したりしています。
  • SyntaxHighlighterが対応してないので微妙に読みにくいです。SyntaxHighlighterはこちらのものを使用させて頂いてます。
  • 他言語にそっくりな部分でも指摘しない。(自戒)

Extensions(拡張)

あらゆるクラスや構造体、列挙体は拡張(Extension)することが出来ます。

具体的にどの辺を拡張できるかと言うと、以下の通りです。

とまぁ、継承とはなんだったのかってレベルで拡張可能です。逆に拡張できないのは、デイニシャライザとか、普通のプロパティ(stored property)ですかね。それとプロパティオブザーバも拡張できません。

この手の「型の動的拡張」とでも言うべき仕様は最近だとわりかし流行ってるんですが、ここまで派手に拡張出来る言語はちょっと珍しい気がしますね。

Extension Syntax

とりあえず文法を見ていきましょう。ここで言う「SomeType」とは、既に定義されている既存の型だと思ってください。

extension SomeType {
    // new functionality to add to SomeType goes here
}

プロトコルを追加する場合はこうします。

extension SomeType: SomeProtocol, AnotherProtocol {
    // implementation of protocol requirements goes here
}

Computed Properties

まずは計算型プロパティを追加してみましょう。既存の型は何でも拡張できるので、組み込みの型であるDoubleも拡張できます。

extension Double {
    var km: Double { return self * 1_000.0 }
    var m: Double { return self }
    var cm: Double { return self / 100.0 }
    var mm: Double { return self / 1_000.0 }
    var ft: Double { return self / 3.28084 }
}

let oneInch = 25.4.mm
println("One inch is \(oneInch) meters")
// prints "One inch is 0.0254 meters"
let threeFeet = 3.ft
println("Three feet is \(threeFeet) meters")
// prints "Three feet is 0.914399970739201 meters"

ここでのselfはDoubleなので、selfに四則演算を適用すれば返ってくるのも当然Doubleです。

Initializers

次はイニシャライザです。拡張だからと言ってSwiftのイニシャライザのルールを破るわけにはいかないので、その辺だけ気をつけましょう。

まずは拡張の対象となる型を作りましょう。今回は構造体を作成するので、自動でイニシャライザが提供されます。

struct Size {
    var width = 0.0, height = 0.0
}

struct Point {
    var x = 0.0, y = 0.0
}

struct Rect {
    var origin = Point()
    var size = Size()
}

let defaultRect = Rect()
let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0),
size: Size(width: 5.0, height: 5.0))

実際に拡張してみましょう。

extension Rect {
    init(center: Point, size: Size) {
        let originX = center.x - (size.width / 2)
        let originY = center.y - (size.height / 2)
        self.init(origin: Point(x: originX, y: originY), size: size)
    }
}

let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
size: Size(width: 3.0, height: 3.0))
// centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)

これでRectにはinit(origin: Point, size: Size)とinit(center: Point, size: Size)と言う二つのイニシャライザが定義されました。これ引数の型と数が一緒だからダメじゃね?って思った人はもう一度イニシャライザの章を読んできましょう。このコードは合法です。

Methods

じゃあ次はメソッドです。普通に型にメソッドを定義するように書きます。

extension Int {
    func repetitions(task: () -> ()) {
        for i in 0..self {
            task()
        }
    }
}

3.repetitions({
    println("Hello!")
})
// Hello!
// Hello!
// Hello!

Extensionとは何の関係もない、クロージャのおさらいですが、引数がクロージャなのでもっと省略して呼び出すことができます。

3.repetitions {
    println("Goodbye!")
}
// Goodbye!
// Goodbye!
// Goodbye!

これまたExtensionとはあんまり関係ない値型におけるメソッドのおさらいですが、自分自身の値を変更するメソッドを定義する時はmutatingキーワードが必須になります。

extension Int {
    mutating func square() {
        self = self * self
    }
}

var someInt = 3
someInt.square()
// someInt is now 9

Subscripts

サブスクリプトも普通に定義すればOKです。Intに定義するのは中々キモいですが。

extension Int {
    subscript(digitIndex: Int) -> Int {
        var decimalBase = 1
        for _ in 1...digitIndex {
            decimalBase *= 10
        }
        
        return (self / decimalBase) % 10
    }
}

746381295[0]
// returns 5
746381295[1]
// returns 9
746381295[2]
// returns 2
746381295[8]
// returns 7

Nested Types

最後にネストされる型を定義してみましょう。Characterに対してKindと言うネストした列挙型と、kindと言う計算型プロパティを追加します。

extension Character {

    enum Kind {
        case Vowel, Consonant, Other
    }

    var kind: Kind {
        switch String(self).lowercaseString {
        case "a", "e", "i", "o", "u":
            return .Vowel
        case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
            "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
            return .Consonant
        default:
            return .Other
        }
    }
}

func printLetterKinds(word: String) {
    println("'\(word)' is made up of the following kinds of letters:")
    
    for character in word {
        switch character.kind {
        case .Vowel:
            print("vowel ")
        case .Consonant:
            print("consonant ")
        case .Other:
            print("other ")
        }
    }
    
    print("\n")
}

printLetterKinds("Hello")
// 'Hello' is made up of the following kinds of letters:
// consonant vowel consonant consonant vowel

とまぁ、色々なことができます。

自分自身で定義した型にこれを使うことはあんまりないと思います。組み込みの型や何らかのライブラリの型を自分で拡張して使いやすくするのがほとんどでしょう。

慣れていないと案外すぐに思い出せない概念ですが、まぁObjective-Cにもカテゴリって言うメソッドレベルでの似たようなものがあるらしいので、きっと大丈夫なんでしょうね。