エクステンションとは
Objective−Cでは「カテゴリ」という仕組みを通じて、既存のクラスにメソッドを定義したり、読み取り専用のプロパティを設定したりできました。Swiftでは、Objective-Cのカテゴリと同様に既存のクラスはもちろんのこと、ストラクチャや列挙型に対してもエクステンションという仕組みを通じて機能を拡張できます。
Objective-CのカテゴリとSwiftのエクステンションはほぼ同じ機能とみなせますが、エクステンションでは、エクステンション1つ1つに命名をすることができません。
extension 既存の型 (: プロトコル1, プロトコル2, ...) { メソッド、プロパティ、イニシャライザ... }
エクステンションで追加できる機能
エクステンションを用いることで、定義済みの型に対して以下の機能を追加できます。
- 計算プロパティ(クラス、ストラクチャに対して)
- タイププロパティ
- メソッド
- subscript
- イニシャライザ
- ネストされた型
- プロトコルに対する準拠
Swiftでは、自分で定義した型の中で型を定義することで「型のネスト」を行えます。例えば、「Person」というストラクチャを使うときにだけ「Gender」という性別を表すプロパティを用いる場合、Gender型をPerson型の中に定義することで、GenderがPersonに付随した型であることを明示できます。
struct Person { enum Gender { case Male case Female } var name: String var gender: Gender }
計算プロパティ
エクステンションでは、保持プロパティを追加することはできませんが、既存の型に対して計算プロパティを追加することはできます。次の例では、Int型(整数型)に計算プロパティabsoluteを追加して、Int型に絶対値を示す機能を与えています。
extension Int { var absolute: Int { return abs(self) } } -1.absolute // 1
タイププロパティ
タイププロパティ(変数などに代入したインスタンスに応じた値ではなく、定義した型に応じた値を示すプロパティ)も、エクステンションを用いて追加できます。次の例ではInt型に、ゼロを示すタイププロパティzeroを追加しています。
extension Int { static var zero: Int { return 0 } } Int.zero // 0
この例ではストラクチャにタイププロパティを追加していますが、定義済みクラスに対してもタイププロパティを追加できます。
Swiftの現在のバージョン(1.1)では計算プロパティの形でしかタイププロパティを追加できませんが、定義済みクラスに紐ついた値を新しく追加することができます。
メソッド
メソッドも、エクステンションを用いて追加できます。メソッドに関して、本連載第4回で取り上げたように、値型に対して自分自身や付随するプロパティを変更する場合には、mutatingキーワードをメソッドに付けます。
次の例では、powerとnegateという2つのメソッドをInt型に追加しています。power(times: Int)メソッドは、自身の値をtimes乗した(times回掛け合わせた)値を返すメソッドです。また、negate()メソッドは自分自身の符号を反転するメソッドです。
extension Int { func power(times: Int) -> Int { return Int(pow(Double(self), Double(times))) } mutating func negate() { self = -self } } 2.power(6) // 64 var three = 3 three.negate() // -3
pow関数は(Double, Double) -> Doubleという型を持つので、イニシャライザ経由でいったんDouble型に変換したのち、再びInt型に変換しています。
subscript
subscriptもエクステンションによって、既存クラスに追加できます。次に示すsubscriptは、Double型の値の1桁目を起点とし、digitIndex分左にずらした桁の番号を取得しています。
extension Double { subscript(digitIndex: Int) -> Int { let digitBase = pow(10, Double(digitIndex)) return Int(self / digitBase) % 10 } } 123456789.01234[0] // 9 123456789.01234[2] // 7 123456789.01234[-3] // 2
イニシャライザ
エクステンションを用いれば、イニシャライザも既存の型に追加できます。次の例ではFailableイニシャライザを追加して、Int型をStringから初期化できるようにしています。
extension Int { init?(_ string: String) { if let intValue = string.toInt() { self = intValue } else { return nil } } } Int("aaaa") // nil Int("1233") // Some(1233)
string変数に数値として解釈可能な文字列が入っている場合、初期化は成功し、イニシャライザを通じてInt値をオプショナル列挙型にくるんで返します。一方、数値として解釈できない文字列が入っている場合には、初期化は失敗し、nil(=Optional.None)を返します。
Failableイニシャライザは、初期化に失敗する可能性をはらんだイニシャライザです。このイニシャライザは、定義された型をオプショナル列挙型にくるんで初期化します。宣言はinitの後に「?」を付けて行います。初期化に失敗する場合には、イニシャライザの中でreturn nilして、初期化が失敗したことをオプショナル列挙型を通じて通知します。
ネストされた型
ネストされた型も、エクステンションによって既存の型に追加できます。次の例では、既存のInt値の符号を判定するsignプロパティを追加しています。
extension Int { enum Sign { case Plus case Zero case Minus } var sign: Sign { if self > 0 { return .Plus } else if self < 0 { return .Minus } else { return .Zero } } } 1.sign // Sign.Plus (-1).sign // Sign.Minus 0.sign // Sign.Zero
signプロパティは新しくエクステンションで追加されたネスト列挙型Signで、この型を通じてInt値の符号を判定します。