【Swift】拡張(エクステンション)の使い方。クラスからデータ型まであらゆる機能を拡張する。(Swift 2.1、XCode 7.2)
拡張(エクステンション)とは
拡張(エクステンション)とは、既存のクラスや構造体、Enumからデータ型まであらゆるものに対して、名前はそのままにプロパティやメソッドを追加する機能である。
プロパティやメソッドを追加する機能といえば「継承」をイメージするが、「継承」はクラスのプロパティやメソッドを引き継いで新たなクラスを別名で作るのに対し、「拡張」は名前はそのままにプロパティやメソッドを追加することができるのが特徴である。
拡張は以下のように定義する。
1 2 3 4 |
extension 拡張する型名 { プロパティ メソッド } |
以下のコードは作成した商品クラスを拡張して、税抜価格のプロパティと割引メソッドを追加した例である。残念なことにプロパティはComputedプロパティしか追加できない。Computedプロパティに関しては次の記事を参照されたし⇒「記事」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
/* ** 拡張を利用してクラスにプロパティとメソッドを追加する。 */ //商品クラス class Shouhin { var taxPrice = 980 //税込価格 //値上げメソッド func addPrice(price:Int) { taxPrice += price } } //商品クラスを拡張 extension Shouhin { //税抜価格 var noTaxPrice:Int { get { return Int(Double(taxPrice)/1.08) } } //割引メソッド func discountPrice(discount:Int) { taxPrice = taxPrice - discount } } //商品クラスのインスタンスを生成 var test = Shouhin() //税抜価格を確認 print(test.noTaxPrice) test.discountPrice(20) print(test.noTaxPrice) //実行結果 //907 //888 |
拡張は定義を追加することはできるが、継承で使われるメソッドのオーバーライドを使うことはできない。つまり、拡張前クラスのメソッドの処理内容を修正したくてもできない。新しいメソッドを追加して対応するべし。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
/* ** 拡張は元のクラスのプロパティやメソッドをオーバーライドできない。 */ //商品クラス class Shouhin { var taxPrice = 980 //税込価格 //値上げメソッド func addPrice(price:Int) { taxPrice += price } } //商品クラスを拡張 extension Shouhin { //値上げメソッドをオーバーライドしようとしてエラー override func addPrice(price:Int) { taxPrice += Int(Double(price) * 1.08) } } //コンパイルエラー //error: method does not override any method from its superclass |
イニシャライザを追加する
拡張はイニシャライザを追加することができる。ただし、コンビニエンスイニシャライザのみ追加することができる。以下のコードは拡張クラスにコンビニエンスイニシャライザを定義し、コンビニエンスイニシャライザから、商品クラスの指名イニシャライザを呼び出す例である。
ちなみに、コンビニエンスイニシャライザはプロパティの初期化をすることはできないイニシャライザであり、他の指名イニシャライザを呼び出すことでプロパティを初期化する。あまり重要なことができないイメージだ。以下の例では、引数が1個でイニシャライザが呼ばれたときに、もう一つのプロパティ(id)をつけて本来のイニシャライザを呼び出している。
このように、拡張ではStoreadプロパティの追加、指名イニシャライザの追加をできなくすることで、クラスの根幹部分を壊されないようにしている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
/* ** 拡張でイニシャライザを追加する。 */ //商品クラス class Shouhin { var taxPrice:Int //税込価格 var id:Int //商品ID //指名イニシャライザ init(taxPrice:Int, id:Int){ self.taxPrice = taxPrice self.id = id } } //商品クラスを拡張 extension Shouhin { //コンビニエンスイニシャライザ convenience init(taxPrice:Int){ self.init(taxPrice:taxPrice, id:0) } } var test = Shouhin(taxPrice:1000) print(test.taxPrice) print(test.id) //実行結果 //1000 //0 |
拡張で追加したメソッドの注意事項
以下のコードのように、拡張で追加したメソッドはサブクラスでオーバーライドすることができない。拡張で追加したメソッドは見えないfinalが書かれていると考えるといい。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
/* ** 拡張で追加したメソッドはサブクラスでオーバーライドすることはできない。 */ //商品クラス class Shouhin { var taxPrice = 980 //税込価格 } //商品クラスを拡張 extension Shouhin { //割引メソッド func discountPrice(discount:Int) { taxPrice = taxPrice - discount } } //商品クラスのサブクラスを作成 class Juice:Shouhin { //エラー 拡張によって追加したメソッドはサブクラスでオーバーライドできない。 override func discountPrice(discount:Int) { taxPrice = taxPrice - discount } } //コンパイルエラー //error: declarations from extensions cannot be overridden yet |
拡張とプロトコル
以下のコードのように、拡張にプロトコルを適用することができる。これにより、プロトコルに定義されているプロパティやメソッドを拡張で必ず追加させることができる。
なお、通常のプロトコルの適用と同じように、複数のプロトコルを指定する場合はカンマ区切りで列挙する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
/* ** クラスを拡張するときにプロトコルを指定する。 */ //プロトコル protocol TestProtocol { //割引メソッド func discountPrice(price:Int) } //商品クラス class Shouhin { var taxPrice = 980 //税込価格 } //商品クラスを拡張 extension Shouhin:TestProtocol { //割引メソッドを定義しなければエラーになる。 func discountPrice(discount:Int) { taxPrice = taxPrice - discount } } |
拡張は、クラスだけでなくプロトコルも拡張することができる。
プロトコルはクラスに追加すべきプロパティやメソッドの宣言のみ行い、実際の値や処理は記述できないイメージがあるが、何と、プロトコルの拡張ではメソッドの実装が書けるのである。というか、通常のプロトコルのようなプロパティやメソッドの宣言はできず、メソッドの実装のみできる。
これを利用すれば、プロトコルを適用した全クラスに共通したメソッドを持たせることができる。筆者は、これが拡張の嬉しいところと思っている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
/* ** プロトコルを拡張する。 */ //プロトコル protocol TestProtocol { var price:Int{ get set } //価格 } //プロトコルを拡張 extension TestProtocol { //割引メソッド func getTaxPrice() -> Int { return Int(Double(self.price) * 1.08) } } //プロトコルを適用した商品クラス class Shouhin:TestProtocol { var price = 900 } //商品クラスのインスタンスを生成して、メソッドを呼び出し var test = Shouhin() print(test.getTaxPrice()) |
データ型を拡張
拡張できるのは、自作したクラスだけでは無い。何と、Swiftに標準で備わっているクラスやデータ型をも拡張することができるのだ。
以下のコードはInt型を拡張して、数字を逆順にするメソッドを追加した例。新しく必要になった変数の値の加工処理を、変数のメソッドを追加するかたちで行えるとは新しい感覚だ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
/* ** Int型を拡張して、数字を逆順にするメソッドを追加する。 */ extension Int { //逆順メソッド func reverseNumber() -> Int { var resultStr:String = "" //数字を文字列に変換し、1文字ずつループで回して逆順に結合する。 for a in String(self).characters { resultStr = String(a) + resultStr } return Int(resultStr)! } } //Int型の逆順メソッドを呼び出す。 var test:Int = 123 print(test.reverseNumber()) //実行結果 //321 |