Skip to content

構造体とクラス

データをカプセル化するカスタム型のモデル。

構造体とクラスは、プログラムコードの構成要素となる、汎用的で柔軟な構造です。定数、変数、関数の定義に使用するのと同じ構文を使用して、構造体とクラスにプロパティとメソッドを定義し、機能を追加します。

他のプログラミング言語とは異なり、Swiftではカスタムの構造体とクラスに対して、インターフェースファイルと実装ファイルを分けて作成する必要はありません。Swiftでは、構造体やクラスを1つのファイルで定義でき、そのクラスや構造体への外部インターフェースは自動的に他のコードで使用可能になります。

注意
クラスのインスタンスは、伝統的にオブジェクトとして知られています。しかし、Swiftでは構造体とクラスの機能は他の言語よりも近く、この章の多くの説明は、クラスまたは構造体型のインスタンスのどちらにも適用されます。そのため、より一般的な用語である「インスタンス」を使用しています。

構造体とクラスの比較

Swiftにおける構造体とクラスには、多くの共通点があります。両者ともに以下が可能です:

  • 値を格納するプロパティを定義する
  • 機能を提供するメソッドを定義する
  • サブスクリプト構文でその値へのアクセスを提供するサブスクリプトを定義する
  • 初期状態を設定するイニシャライザを定義する
  • デフォルトの実装を超えて機能を拡張する
  • 特定の種類の標準機能を提供するプロトコルに準拠する

詳細については、プロパティメソッドサブスクリプト初期化拡張プロトコルを参照してください。

クラスには、構造体にはない追加の機能があります:

  • 継承により、あるクラスが別のクラスの特性を継承できる
  • 型キャストにより、実行時にクラスインスタンスの型を確認および解釈できる
  • デイニシャライザにより、クラスインスタンスに割り当てられたリソースを解放できる
  • 参照カウントにより、1つのクラスインスタンスへの複数の参照が可能

詳細については、継承型キャストデイニシャライザ自動参照カウントを参照してください。

クラスが提供する追加機能には、複雑性が増すというコストが伴います。一般的なガイドラインとして、構造体の方が理解しやすいので優先的に使用し、クラスは適切な場合や必要な場合に使用してください。実際には、定義するカスタム型の大部分は構造体と列挙型になります。詳細な比較については、構造体とクラスの選択を参照してください。

注意
クラスとアクターは多くの特性と動作を共有しています。アクターについては、並行処理を参照してください。

定義の構文

構造体とクラスの定義構文は似ています。構造体はstructキーワードで、クラスはclassキーワードで導入し、どちらも定義全体を中括弧で囲みます:

swift
struct SomeStructure {
    // 構造体の定義をここに記述
}
class SomeClass {
    // クラスの定義をここに記述
}

注意
新しい構造体やクラスを定義するたびに、新しいSwift型を定義することになります。型には、標準のSwift型(StringIntBoolなど)の大文字キャメルケースの名前に合わせて、SomeStructureSomeClassのように大文字キャメルケースの名前を付けます。プロパティやメソッドには、型名と区別するために、小文字キャメルケースの名前(frameRateincrementCountなど)を付けます。

ここに構造体の定義とクラスの定義の例があります:

swift
struct Resolution {
    var width = 0
    var height = 0
}
class VideoMode {
    var resolution = Resolution()
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}

上記の例では、ピクセルベースのディスプレイ解像度を記述するためにResolutionという新しい構造体を定義しています。この構造体には、widthheightという2つの格納プロパティがあります。格納プロパティは、構造体やクラスの一部としてまとめられて格納される定数や変数です。これらの2つのプロパティは、初期整数値0を設定することでInt型であると推測されます。

上記の例では、ビデオ表示の特定のビデオモードを記述するためにVideoModeという新しいクラスも定義しています。このクラスには4つの変数格納プロパティがあります。最初のresolutionは、新しいResolution構造体インスタンスで初期化され、プロパティ型がResolutionであると推測されます。他の3つのプロパティについては、新しいVideoModeインスタンスは、インターレース設定がfalse(「非インターレースビデオ」を意味する)、再生フレームレートが0.0、およびnameというオプションのString値で初期化されます。nameプロパティはオプション型であるため、デフォルト値としてnil(「名前の値がない」)が自動的に設定されます。

構造体とクラスのインスタンス

Resolution構造体の定義とVideoModeクラスの定義は、ResolutionVideoModeがどのように見えるかを記述するだけです。それ自体は特定の解像度やビデオモードを記述していません。それを行うには、構造体やクラスのインスタンスを作成する必要があります。

インスタンスを作成する構文は、構造体とクラスの両方で非常に似ています:

swift
let someResolution = Resolution()
let someVideoMode = VideoMode()

構造体とクラスはどちらも、新しいインスタンスのためにイニシャライザ構文を使用します。最も単純な形式のイニシャライザ構文は、クラスや構造体の型名の後に空の括弧を付けたものです(例:Resolution()VideoMode())。これにより、クラスや構造体の新しいインスタンスが作成され、プロパティはデフォルト値に初期化されます。クラスと構造体の初期化については、初期化で詳しく説明しています。

プロパティへのアクセス

インスタンスのプロパティには、ドット構文を使用してアクセスできます。ドット構文では、インスタンス名の直後にプロパティ名をピリオド(.)で区切って記述します(スペースは入れません):

swift
print("someResolutionの幅は\(someResolution.width)です")
// "someResolutionの幅は0です"と表示されます

この例では、someResolution.widthsomeResolutionwidthプロパティを指し、そのデフォルト初期値である0を返します。

サブプロパティにドリルダウンすることもできます。例えば、VideoModeresolutionプロパティのwidthプロパティにアクセスする場合:

swift
print("someVideoModeの幅は\(someVideoMode.resolution.width)です")
// "someVideoModeの幅は0です"と表示されます

また、ドット構文を使用して変数プロパティに新しい値を割り当てることもできます:

swift
someVideoMode.resolution.width = 1280
print("someVideoModeの幅は現在\(someVideoMode.resolution.width)です")
// "someVideoModeの幅は現在1280です"と表示されます

構造体型のメンバーワイズイニシャライザ

すべての構造体には、自動的に生成されるメンバーワイズイニシャライザがあり、新しい構造体インスタンスのメンバープロパティを初期化するために使用できます。新しいインスタンスのプロパティの初期値は、名前でメンバーワイズイニシャライザに渡すことができます:

swift
let vga = Resolution(width: 640, height: 480)

構造体とは異なり、クラスインスタンスはデフォルトのメンバーワイズイニシャライザを受け取りません。イニシャライザについては、初期化で詳しく説明しています。

構造体と列挙型は値型

値型とは、変数や定数に代入されたり、関数に渡されたりすると、その値がコピーされる型のことです。

実際、前の章で値型を広範囲に使用してきました。実際、Swiftの基本型(整数、浮動小数点数、ブール値、文字列、配列、辞書)はすべて値型であり、内部的には構造体として実装されています。

Swiftのすべての構造体と列挙型は値型です。つまり、作成したすべての構造体と列挙型のインスタンス、およびそれらがプロパティとして持つすべての値型は、コード内で渡されるたびに常にコピーされます。

注意
配列、辞書、文字列など、Swift標準ライブラリで定義されたコレクションは、コピーのパフォーマンスコストを削減するための最適化を使用します。すぐにコピーを作成する代わりに、これらのコレクションは、元のインスタンスとすべてのコピーの間で要素が格納されているメモリを共有します。コレクションのコピーの1つが変更されると、変更の直前に要素がコピーされます。コードで見る動作は常に、コピーがすぐに行われたかのようになります。

前の例で使用したResolution構造体を使用した例を考えてみましょう:

swift
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd

この例では、hdという定数を宣言し、フルHDビデオの幅と高さ(1920ピクセル幅、1080ピクセル高さ)で初期化されたResolutionインスタンスに設定します。

次に、cinemaという変数を宣言し、それをhdの現在の値に設定します。Resolutionは構造体であるため、既存のインスタンスのコピーが作成され、この新しいコピーがcinemaに割り当てられます。hdcinemaは同じ幅と高さを持っていますが、内部的には完全に異なるインスタンスです。

次に、cinemawidthプロパティを、デジタルシネマプロジェクションに使用されるわずかに広い2K標準の幅(2048ピクセル幅、1080ピクセル高さ)に変更します:

swift
cinema.width = 2048

cinemawidthプロパティを確認すると、確かに2048に変更されていることがわかります:

swift
print("cinemaの幅は現在\(cinema.width)ピクセルです")
// "cinemaの幅は現在2048ピクセルです"と表示されます

しかし、元のhdインスタンスのwidthプロパティはまだ1920のままです:

swift
print("hdの幅はまだ\(hd.width)ピクセルです")
// "hdの幅はまだ1920ピクセルです"と表示されます

cinemahdの現在の値が与えられたとき、hdに格納されていた値は新しいcinemaインスタンスにコピーされました。最終的には、同じ数値を含む2つの完全に別々のインスタンスが作成されました。しかし、別々のインスタンスであるため、cinemaの幅を2048に設定しても、hdに格納されている幅には影響しません。

同じ動作は列挙型にも適用されます:

swift
enum CompassPoint {
    case north, south, east, west
    mutating func turnNorth() {
        self = .north
    }
}
var currentDirection = CompassPoint.west
let rememberedDirection = currentDirection
currentDirection.turnNorth()

print("現在の方向は\(currentDirection)です")
print("記憶された方向は\(rememberedDirection)です")
// "現在の方向はnorthです"
// "記憶された方向はwestです"と表示されます

rememberedDirectioncurrentDirectionの値が割り当てられると、それは実際にはその値のコピーに設定されます。その後、currentDirectionの値を変更しても、rememberedDirectionに格納された元の値のコピーには影響しません。

クラスは参照型

値型とは異なり、参照型は変数や定数に代入されたり、関数に渡されたりしてもコピーされません。コピーの代わりに、同じ既存のインスタンスへの参照が使用されます。

上記で定義したVideoModeクラスを使用した例を見てみましょう:

swift
let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0

この例では、tenEightyという新しい定数を宣言し、それをVideoModeクラスの新しいインスタンスを参照するように設定します。ビデオモードには、前述のHD解像度(1920 x 1080)のコピーが割り当てられます。インターレース設定が有効になり、名前が"1080i"に設定され、フレームレートが25.0フレーム/秒に設定されます。

次に、tenEightyを新しい定数alsoTenEightyに割り当て、alsoTenEightyのフレームレートを変更します:

swift
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0

クラスは参照型であるため、tenEightyalsoTenEightyは実際には同じVideoModeインスタンスを参照しています。実質的に、これは同じ単一のインスタンスに対する2つの異なる名前にすぎません。

tenEightyframeRateプロパティを確認すると、基になるVideoModeインスタンスから新しいフレームレート30.0が正しく報告されることがわかります:

swift
print("tenEightyのframeRateプロパティは現在\(tenEighty.frameRate)です")
// "tenEightyのframeRateプロパティは現在30.0です"と表示されます

この例は、参照型が理解しにくい場合があることも示しています。tenEightyalsoTenEightyがプログラムのコード内で離れている場合、ビデオモードが変更されるすべての方法を見つけるのが難しいかもしれません。tenEightyを使用する場所では、alsoTenEightyを使用するコードについても考慮する必要があります。対照的に、値型は同じ値とやり取りするすべてのコードがソースファイル内で近くにあるため、理解しやすいです。

tenEightyalsoTenEightyは定数として宣言されていますが、tenEighty.frameRatealsoTenEighty.frameRateを変更することはできます。これは、tenEightyalsoTenEighty定数自体の値が実際には変更されないためです。tenEightyalsoTenEighty自体はVideoModeインスタンスを「格納」しているわけではなく、内部的にはVideoModeインスタンスを参照しています。変更されるのは、基になるVideoModeframeRateプロパティであり、そのVideoModeへの定数参照の値ではありません。

同一性演算子

クラスは参照型であるため、複数の定数や変数が内部的に同じ単一のクラスインスタンスを参照することが可能です。(構造体や列挙型には同じことは当てはまりません。これらは常に定数や変数に代入されたり、関数に渡されたりするとコピーされます。)

2つの定数や変数が正確に同じクラスインスタンスを参照しているかどうかを確認することが有用な場合があります。これを可能にするために、Swiftは2つの同一性演算子を提供します:

  • 同一である(===
  • 同一でない(!==

これらの演算子を使用して、2つの定数や変数が同じ単一のインスタンスを参照しているかどうかを確認します:

swift
if tenEighty === alsoTenEighty {
    print("tenEightyとalsoTenEightyは同じVideoModeインスタンスを参照しています。")
}
// "tenEightyとalsoTenEightyは同じVideoModeインスタンスを参照しています。"と表示されます

同一である(3つの等号で表される===)は、等しい(2つの等号で表される==)とは意味が異なることに注意してください。同一であるとは、クラス型の2つの定数や変数が正確に同じクラスインスタンスを参照していることを意味します。等しいとは、型の設計者が定義した適切な意味で、2つのインスタンスが値として等しいまたは同等であると見なされることを意味します。

独自のカスタム構造体やクラスを定義する場合、2つのインスタンスが等しいと見なされる条件を決定するのはあなたの責任です。独自の==および!=演算子の実装を定義するプロセスについては、等価演算子で説明しています。

ポインタ

C、C++、またはObjective-Cの経験がある場合、これらの言語がメモリ内のアドレスを参照するためにポインタを使用することを知っているかもしれません。参照型のインスタンスを参照するSwiftの定数や変数は、Cのポインタに似ていますが、メモリ内のアドレスへの直接ポインタではなく、参照を作成することを示すためにアスタリスク(*)を書く必要もありません。代わりに、これらの参照はSwiftの他の定数や変数と同じように定義されます。Swift標準ライブラリは、ポインタを直接操作する必要がある場合に使用できるポインタ型とバッファ型を提供しています。詳細については、手動メモリ管理を参照してください。