Codable Tech Blog

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

構造体(Struct)

データと処理は別々に扱うのではなく、データとそのデータと関連する処理を一緒に扱うために用意されているのが構造体です。データと処理を一緒に扱うことのメリットは管理のし易さにあります。データと関連する処理がまとまっていることによって、プログラム構造が把握しやすくなります。また、グローバル変数を極力減らすことが可能になります。では、構造体の定義、利用方法をみていきましょう。

構造体の宣言

構造体は次のように定数・変数と関数をグループ化した形で定義されます。構造体内で定義する定数・変数はプロパティ、関数はメソッドと呼びます。

struct 構造体の名前 {
    //プロパティ
    //メソッド
}

次の例は、2つのInt型のプロパティを持つ構造体になります。

プログラミングの世界では、プロパティとメソッドを持つものは「オブジェクト」と呼ばれます。Swiftではオブジェクトを定義するものとして、構造体とクラスがあります。構造体やクラスはオブジェクトの定義情報ですが、この定義情報から値を生成することができます。Int型の値が生成できるように、構造体型やクラス型の値を生成できるのです。一般的にオブジェクトの定義情報である構造体やクラスから値を生成することをインスタンス化と呼び、値のことをインスタンスと呼びます。

構造体のインスタンス化

構造体のインスタンス作成方法としてもっとも簡単なのは構造体名のあとに空の括弧をつける方法です。次のような形です。

構造体名()

次の例はSquare構造体をインスタンス化した例です。

この例では、Square構造体型のインスタンスを生成してsquare変数に代入しています。

プロパティへのアクセス

プロパティにはドット構文を利用してアクセスします。

インスタンス名.プロパティ名

次の例は、ドット構文を使用してSquare構造体のインスタンスであるsquareからプロパティwidthとheightにアクセスしています。

値の代入

値の代入もドット構文をつかっておこなうことができます。

この例ではwidthに10、heightに20を代入して、その結果を出力しています。注意して欲しいのは構造体のプロパティの値が変更できるのは構造体のインスタンスをvarキーワードを指定して宣言した場合です。もし、letキーワードを利用して構造体のインスタンスを宣言し、値の変更を行おうとした場合はコンパイルエラーが発生します。

squareを定数として扱っているにも関わらず、値の再代入を行おうとしているため、コンパイルエラーが発生しています。

構造体の初期化

インスタンスを作成するために「構造体名()」の形でインスタンスを生成してきましたが、インスタンス生成と同時に初期化処理を行うことが可能です。実は、Swiftでは構造体を初期化するための初期化メソッドが自動で用意されています。構造体のインスタンスを作成するとき、プロパティ名を指定して値の初期化を行うことができます。構造体の初期化メソッドの呼び出しは次のような構文になります。

... = 構造体名(プロパティ名:値,...)

次の例はSquare構造体のインスタンスを初期化メソッドを利用して初期化した例です。

square構造体はwidthプロパティとheightプロパティをもっていますので、Square(width:xxx,height:xxx)の形でインスタンス生成と同時に初期化処理を行っています。

構造体のネスト

構造体では、構造体をネストして定義することが可能です。次の例は、Square構造体をネストしたCube構造体です。

Cube構造体のブロック内でstructキーワードを利用してSquare構造体を定義しています。また、プロパティとしてCube構造体はdepthプロパティとsquareプロパティを持っています。Cube構造を利用する側でSquare構造体をネストしたCube構造体の初期化する場合、次のように記述します。

Cube構造体を初期化する場合、事前にSquare構造体を初期化する必要があります。ネストされた構造体に対するアクセスはドット演算子をつなげることで可能になります。このため、Square構造体の初期化処理を呼び出す場合、Cube.Square(...)の形で初期化しています。

構造体の初期化のカスタマイズ

呼び出し側でネストされた構造体を初期化する処理では、depthプロパティとsquareプロパティを初期化しました。しかし、構造体の使い勝手の観点からネストした構造体の詳細は利用者側には極力、意識させたくない場合も多いです。つまり、Cube構造体がSquare構造体を内包している構造体であることを利用者側には意識させたくないということです。しかし、現時点ではCube構造体の初期化処理を行う時、Cube構造体の利用者はSquare構造体を作成して、Cube構造体を初期化する必要があります。Swiftでは構造体の初期化処理をカスタマイズすることが可能であり、以下のメソッドを実装することで初期化処理をカスタマイズすることができます。

init(パラメータ名:パラメータの型,...)

次の例はSquare構造体にinitメソッドを実装しています。

Square構造体では3つのパラメータを受け取るinitメソッドを実装しています。そして、呼び出し側から渡されたパラメータでdepthプロパティとsquareプロパティを初期化しています。注目して欲しいのは利用者側の初期化メソッドの呼び出し方です。初期化処理の呼び出しがプロパティ名ではなく、initメソッドのパラメータ名に変更されています。また、自分で初期化メソッドを定義した場合、定義時にデフォルトで用意された初期化メソッドは利用できなくなるので注意しましょう。

インスタンスメソッドの定義

インスタンスメソッドは特定のインスタンスに属する関数です。インスタンスメソッドの文法は関数と同じで、funcキーワードを利用して定義します。構造体にインスタンスメソッドを追加する構文は次のようになります。

struct 構造体名{
    func メソッド名(パラメータ名:パラメータの型,...){
        ...
    }
}

インスタンスメソッドは構造体ブロック{ }の中に記述します。インスタンスメソッドはインスタンスなしにはコールできません。また、構造体のインスタンスメソッドは構造体から生成されたインスタンスだけがコールすることができます。次の例では構造体Squareにareaメソッドを追加しています。

areaメソッドはwidthプロパティとheigthプロパティを利用して面積を求め、求めた面積を戻り値を返却するメソッドです。このareaメソッドは構造体Squareから生成されたインスタンスのみが利用できるメソッドになります。areaメソッドの例から分かるようにインスタンスメソッドは構造体で定義されているプロパティにアクセスすることが可能です。また、インスタンスメソッドからインスタンスメソッドを呼び出すことも可能です。次の例ではprintAreaメソッドを定義しています。このprintAreaメソッドは内部でareaメソッドを利用する形で定義されています。

インスタンスメソッドの呼び出し

インスタンスメソッドはプロパティ同様、ドット構文を利用することで呼び出し可能です。

インスタンス名.メソッド名(パラメータ,...)

次の例ではSquare構造体のインスタンスを生成して、printAreaメソッドを呼び出しています。

printAreaメソッドの中でさらにareaメソッドを呼び出しています。このように自分の型で定義されているインスタンスメソッドを呼び出すことができます。printAreaメソッドは面積の値をコンソール上に出力しますので結果として「area 50」という結果がコンソール上に出力されます。

メソッドのローカルパラメータと外部パラメータ

メソッドも関数と同様に外部パラメータを持つことができます。しかし、デフォルトの振舞いが関数とメソッドでは異なっています。Swiftではメソッドの最初のパラメータはローカルパラメータだけが与えられ、2番目以降のパラメータはローカルパラメータと外部パラメータの両方がデフォルトで与えらます。このようにするのはメソッドをコールする時、メソッドを一つの文として読みやすくするためです。次の例ではprintAreaメソッドを指定回数繰り返し呼び出すrepeatPrintAreaUntilメソッドを実装し、呼び出しています。

この場合、repeatPrintAreaUntilメソッドの第1パラメータであるcountはローカルパラメータだけが与えられます。一方、第2引数のdelimiterはローカルパラメータと外部パラメータの両方が与えられます。このため、repeatPrintAreaUntilメソッドの呼び出しは「repeatPrintAreaUntil(5, delimiter: "***")」のようになります。第1パラメータの意図はrepeatPrintAreaUntilというメソッド名から明らかです。一方で、第2パラメータは、外部パラメータがなければパラメータの意図が不明であるため、外部パラメータ名を付けて、パラメータの意図を明らかにしています。

補足:メソッドの外部パラメータの振舞いの変更

あまり使うことはありませんが、デフォルトの振舞いを変えることもできます。最初のパラメータに外部パラメータをつけたければ、最初のパラメータにシンボル名を記述します。また、2番目以降のパラメータに外部パラメータをつけたくなければ、_(アンダーバー)を利用します。

selfプロパティ

全てのインスタンスはインスタンス自身を表すselfプロパティをもっています。selfプロパティは現在のインスタンスを参照するために利用することができます。

selfプロパティの利用例

selfプロパティのよくある利用例の一つとしてプロパティ名とメソッドのパラメータが同じ場合に、両者を区別するのに利用します。次の例はSquare構造体のinitメソッドをselfプロパティを利用して定義しなおした例です。

initメソッドをselfプロパティを利用した形で書き直すことでパラメータ名にプロパティ名と同一の名前を指定できることに注目してください。selfプロパティを利用するまではプロパティとパラメータを区別するためにパラメータの名前をプロパティ名とは異なる名前で定義していました。この結果、若干、プログラムの可読性が下がっていました。しかし、selfプロパティを利用することでパラメータ名にプロパティ名と同一の名前をつけることができました。この結果、パラメータの意図を明確にすることに成功しています。呼び出し側でも(プロパティ名:値,...)という形で初期化できるので、パラメータ名に違和感を感じることがありません。

プロパティの値を変更

インスタンスメソッドから変更

構造体のインスタンスメソッドはそのままの状態ではプロパティの値を変更することができません。もし、構造体のインスタンスメソッドからプロパティの値を変更したい場合、次の構文のようにmutatingキーワードをfuncの前につける必要があります。

struct 構造体の名前 {
    mutating func メソッド名(パラメータ名,...)
}

次の例ではsetWidthメソッドを新しく定義しています。mutatingをつけない場合、メソッドの中でプロパティの値を変更しようとするとコンパイルエラーが発生します。しかし、mutatingをつけた場合は正常に値を変更することができます。

ただし、構造体のインスタンスを定数として宣言した場合、mutatingキーワードをつけたメソッドを呼び出すとコンパイルエラーが発生します。次の例では、Square構造体のインスタンスを定数として宣言しているにも関わらず、mutatingキーワードのついたsetWidthメソッドを呼び出しているため、コンパイルエラーが発生しています。このエラーを避けるためにはインスタンスをletキーワードではなく、varキーワードで宣言します。

selfプロパティの変更

mutatingキーワードが付いたメソッドであれば、selfプロパティに新しいインスタンスを代入することも可能になっています。

この例ではzoomUpメソッドを新しく定義しています。zoomUpメソッドは指定された倍率分だけ幅と高さを変更するメソッドです。zoopUpメソッドの中でSquare構造体のインスタンスを作成し、selfプロパティに代入しています。10行目でzoomUpメソッドを呼び出した後、11行目でprintAreaメソッドを呼び出し、結果を確認すると、selfプロパティの値が新規に作成したSquare構造体のインスタンスになっていることが確認できます。

タイププロパティとタイプメソッド

構造体型のインスタンスとは構造体型から生成されるオブジェクトのことでした。実はSwiftでは構造体型自身もオブジェクトとして認識されています。両者の違いは構造体型自身はプログラム中で1つしか存在しませんが、構造体型から生成されるインスタンスは複数個存在する点です。構造体型自身もオブジェクトとして認識されていますので、構造体型自身もプロパティとメソッドを持つことができます。この構造体型自身がもつプロパティはタイププロパティと呼ばれ、メソッドはタイプメソッドと呼ばれます。タイププロパティは次のようにプロパティ名の前にstaticキーワードを配置することで定義できます。

struct 構造体の名前 {
    static プロパティ名
}

タイプメソッドはfuncキーワードの前にstaticキーワードをつけることで定義することができます。

struct 構造体の名前 {
    static func メソッド名(パラメータ名,...)
}

タイププロパティやタイプメソッドにアクセスする時は、次のようにしてアクセスします。

構造体名.プロパティ名
構造体名.メソッド名(パラメータ,...)

次の例では構造体Studentを定義しています。この構造体はタイププロパティcountとタイプメソッドgetCountをもっています。countプロパティはStudent構造体のインスタンスがいくつ作成されたかを計測するために用意したプロパティです。このため、countプロパティはinitメソッドが呼び出されたタイミングで+1インクリメントされています。getCountメソッドはcountプロパティの現在値を返却するメソッドになります。

このStudent構造体のインスタンスを作成して、countプロパティの値を表示してみたのが次の例になります。

Student構造体のインスタンスを作成するたびにcountプロパティの値が増えていることが確認できます。