Codable Tech Blog

iPhoneアプリケーション開発と AWS(Amazon Web Service)活用に関する記事を配信

型の拡張(extensions)

拡張は既に存在するクラスや構造体、列挙体に対して新しく機能を追加する仕組みです。また、拡張は継承とは異なり、オリジナルのソースコードをなしで、機能を拡張することが出来ます。拡張を利用することで次のような機能を新規に追加できます。

  1. コンピューティッドプロパティとコンピューティッドタイププロパティの追加
  2. 新しいイニシャライザの提供
  3. インスタンスメソッドとタイプメソッドの追加
  4. サブスクリプトの定義
  5. 新しいネスト型の定義
  6. 既存の型を特定のプロトコルに適合させる

拡張では、既存メソッドのオーバーライドはできませんので注意してください。

拡張構文

ある型の機能を拡張したい場合、extensionキーワードを利用します。extensionキーワードを使って既存の型を拡張する構文は次のようになります。

extension 拡張したい型名 {
    //指定した型に拡張したい内容を記述
}

extensionブロックの中には指定した型で新規に追加したいプロパティやメソッドの定義を記述していきます。
また、既存クラスに新しくプロトコルを適合させる拡張構文は次のようになります。

extension 拡張したい型名 : プロトコル名1, プロトコル名2 {
    //プロトコルで要求されている実装を記述
}

この構文は既存の型はプロトコル名1とプロトコル名2に適合させます。ブロックの中にはプロトコルで要求されているメソッドを実装します。以降では、拡張を利用したさまざまな機能追加をみていきます。

コンピューティッドプロパティの追加

拡張ではすでに存在する型に対してコンピューティッドプロパティプロパティ、コンピューティッドプロパティタイププロパティを追加できます。次の例ではPersonクラスを定義し、Personクラスに対して拡張を利用して新しくfullNameと呼ばれるコンピューティッドプロパティプロパティを追加しています。

Personクラスの拡張ではString型のfullNameプロパティを追加しています。このプロパティはゲッターしか提供しないため、getキーワードを利用しない簡潔な形でプロパティの内容を定義しています。12行目ではPersonクラスのインスタンスを変数personに代入しています。そして、次の行で拡張を利用して追加したfullNameプロパティにアクセスしています。ここで、注目してもらいたいのは、拡張を利用した場合、もともとのPersonクラスのコードを変更していないということです。拡張がない場合、クラスに対して新しい機能を追加しようとした場合、オリジナルのコードに機能を追加するか、継承を利用するしかありませんでした。しかし、拡張を利用すれば、既存のコードには何も手を加えず、機能を追加することができます。このように、拡張は既存のモジュールに対して一切影響を与えず、機能を拡張できるとても便利な機能なのです。

イニシャライザの追加

拡張はすでに存在する型に対してイニシャライザを追加できます。但し、追加できるのはコンビニエンスイニシャライザだけです。指定イニシャライザは常にオリジナルコードの中でのみ提供されます。次の例では、拡張を利用してPersonクラスにパラメータをもたないコンビニエンスイニシャライザを追加しています。

拡張で追加できるのはコンビニエンスイニシャライザですので、convinieceキーワードをinitメソッドに付けています。このイニシャライザはPersonクラスで実装されている指定イニシャライザをパラメータを設定して呼び出しているだけになります。このようにコンビニエンスイニシャライザを追加することでPersonクラスを利用する側ではパラメータなしでもPersonクラスのインスタンスを作成することが出来るようになります。パラメータなしでPersonクラスを作成した場合、firstNameにTaro、lastNameにYamadaが代入されますおでコンソールにはTaroYamadaと出力されます。

メソッドの追加

インスタンスメソッドとタイプメソッドの追加

拡張では既存の型に対してインスタンスメソッドとタイプメソッドを追加することができます。次の例では拡張を利用してintroMessageメソッドを追加しています。introMessageメソッドは自己紹介文の中で自分のフルネームを出力するメソッドです。

利用者側はインスタンスメソッドが拡張を利用して追加されたことをまったく意識せずに利用できていることが分かります。今回の例ではfirstNameにTaro、lastNameにYamadaが代入されていますので、コンソールには「私の名前はTaroYamadaです。よろしくお願いします」と出力されています。

可変インスタンスメソッド

構造体や列挙体に対してプロパティの値を変更するメソッドを定義させたい場合、mutatingキーワードを付けたメソッドを拡張で宣言します。次のような構文になります。

extension 拡張したい型名 {
    mutating func メソッド名( パラメータ,..){
        文
    }
}

次の例ではプロパティとして幅と高さをもつ構造体Squareを定義し、拡張を利用して指定された倍率分だけ幅と高さを変更するzoomメソッドを追加しています。

zoomメソッドは構造体で定義されているプロパティを変更しますのでmutatingキーワードをつけてメソッドを定義しています。呼び出し側ではSquare構造体を初期化した後、zoomメソッドを呼び出しています。zoomメソッドは指定された倍率分だけ面積をスケールさせるために、自身のプロパティwidthとheigthの値を更新してます。この結果、zoomメソッドを呼び出した後、プロパティの値はwidth、heigthともに約0.7になります。

サブスクリプト

拡張では、既存の型に対して新しくサブスクリプトを追加することができます。次の例ではsubscript構文を利用して、Person型に新しくサブスクリプトを追加しています。追加したサブスクリプトはパラメータに渡された値が0ならばfirstNameを返却し、渡された値が1ならばlastNameを返却します。また、もし、0と1以外が渡された場合はnilを返却するようにしています。

呼び出し側でサブスクリプトを利用する場合、オプショナル型の値が返ってきますので、エクスクラメーションマーク(!)を利用して、アンラッピングしています。今回はサブスクリプト呼び出し時に0を渡していますのでfirstNameの値が返却されます。この結果、コンソールには「Taro」と出力されます。

ネストした型

拡張を利用して、既存の型にネストした型を新しく定義することも可能です。次の例ではSquare構造体を拡張して、新しくFigure列挙体をSquare構造体にネストする形で定義しています。Figure列挙体は2つのケースをもっており、ひとつは正方形を表すSquare、もうひとつが長方形を表すRectangleになります。この拡張では、さらにコンピューティッドプロパティkindも新しく追加しています。コンピューティッドプロパティkindはSquare構造体のwidthとheightの値が一致している場合はFigure列挙体のSquareを返却し、異なっていればRectagleを返します。

利用者では、拡張で新規に追加されたネストした型Figureとkindプロパティを利用して、生成したSquareインスタンスが正方形か長方形かを判断して、コンソールに判断結果を出力しています。