構造体とクラス
データをカプセル化するカスタム型のモデル。
構造体とクラスは、プログラムコードの構成要素となる、汎用的で柔軟な構造です。定数、変数、関数の定義に使用するのと同じ構文を使用して、構造体とクラスにプロパティとメソッドを定義し、機能を追加します。
他のプログラミング言語とは異なり、Swiftではカスタムの構造体とクラスに対して、インターフェースファイルと実装ファイルを分けて作成する必要はありません。Swiftでは、構造体やクラスを1つのファイルで定義でき、そのクラスや構造体への外部インターフェースは自動的に他のコードで使用可能になります。
注意
クラスのインスタンスは、伝統的にオブジェクトとして知られています。しかし、Swiftでは構造体とクラスの機能は他の言語よりも近く、この章の多くの説明は、クラスまたは構造体型のインスタンスのどちらにも適用されます。そのため、より一般的な用語である「インスタンス」を使用しています。
構造体とクラスの比較
Swiftにおける構造体とクラスには、多くの共通点があります。両者ともに以下が可能です:
- 値を格納するプロパティを定義する
- 機能を提供するメソッドを定義する
- サブスクリプト構文でその値へのアクセスを提供するサブスクリプトを定義する
- 初期状態を設定するイニシャライザを定義する
- デフォルトの実装を超えて機能を拡張する
- 特定の種類の標準機能を提供するプロトコルに準拠する
詳細については、プロパティ、メソッド、サブスクリプト、初期化、拡張、プロトコルを参照してください。
クラスには、構造体にはない追加の機能があります:
- 継承により、あるクラスが別のクラスの特性を継承できる
- 型キャストにより、実行時にクラスインスタンスの型を確認および解釈できる
- デイニシャライザにより、クラスインスタンスに割り当てられたリソースを解放できる
- 参照カウントにより、1つのクラスインスタンスへの複数の参照が可能
詳細については、継承、型キャスト、デイニシャライザ、自動参照カウントを参照してください。
クラスが提供する追加機能には、複雑性が増すというコストが伴います。一般的なガイドラインとして、構造体の方が理解しやすいので優先的に使用し、クラスは適切な場合や必要な場合に使用してください。実際には、定義するカスタム型の大部分は構造体と列挙型になります。詳細な比較については、構造体とクラスの選択を参照してください。
注意
クラスとアクターは多くの特性と動作を共有しています。アクターについては、並行処理を参照してください。
定義の構文
構造体とクラスの定義構文は似ています。構造体はstruct
キーワードで、クラスはclass
キーワードで導入し、どちらも定義全体を中括弧で囲みます:
struct SomeStructure {
// 構造体の定義をここに記述
}
class SomeClass {
// クラスの定義をここに記述
}
注意
新しい構造体やクラスを定義するたびに、新しいSwift型を定義することになります。型には、標準のSwift型(String
、Int
、Bool
など)の大文字キャメルケースの名前に合わせて、SomeStructure
やSomeClass
のように大文字キャメルケースの名前を付けます。プロパティやメソッドには、型名と区別するために、小文字キャメルケースの名前(frameRate
やincrementCount
など)を付けます。
ここに構造体の定義とクラスの定義の例があります:
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
上記の例では、ピクセルベースのディスプレイ解像度を記述するためにResolution
という新しい構造体を定義しています。この構造体には、width
とheight
という2つの格納プロパティがあります。格納プロパティは、構造体やクラスの一部としてまとめられて格納される定数や変数です。これらの2つのプロパティは、初期整数値0を設定することでInt
型であると推測されます。
上記の例では、ビデオ表示の特定のビデオモードを記述するためにVideoMode
という新しいクラスも定義しています。このクラスには4つの変数格納プロパティがあります。最初のresolution
は、新しいResolution
構造体インスタンスで初期化され、プロパティ型がResolution
であると推測されます。他の3つのプロパティについては、新しいVideoMode
インスタンスは、インターレース設定がfalse
(「非インターレースビデオ」を意味する)、再生フレームレートが0.0
、およびname
というオプションのString
値で初期化されます。name
プロパティはオプション型であるため、デフォルト値としてnil
(「名前の値がない」)が自動的に設定されます。
構造体とクラスのインスタンス
Resolution
構造体の定義とVideoMode
クラスの定義は、Resolution
やVideoMode
がどのように見えるかを記述するだけです。それ自体は特定の解像度やビデオモードを記述していません。それを行うには、構造体やクラスのインスタンスを作成する必要があります。
インスタンスを作成する構文は、構造体とクラスの両方で非常に似ています:
let someResolution = Resolution()
let someVideoMode = VideoMode()
構造体とクラスはどちらも、新しいインスタンスのためにイニシャライザ構文を使用します。最も単純な形式のイニシャライザ構文は、クラスや構造体の型名の後に空の括弧を付けたものです(例:Resolution()
やVideoMode()
)。これにより、クラスや構造体の新しいインスタンスが作成され、プロパティはデフォルト値に初期化されます。クラスと構造体の初期化については、初期化で詳しく説明しています。
プロパティへのアクセス
インスタンスのプロパティには、ドット構文を使用してアクセスできます。ドット構文では、インスタンス名の直後にプロパティ名をピリオド(.)で区切って記述します(スペースは入れません):
print("someResolutionの幅は\(someResolution.width)です")
// "someResolutionの幅は0です"と表示されます
この例では、someResolution.width
はsomeResolution
のwidth
プロパティを指し、そのデフォルト初期値である0を返します。
サブプロパティにドリルダウンすることもできます。例えば、VideoMode
のresolution
プロパティのwidth
プロパティにアクセスする場合:
print("someVideoModeの幅は\(someVideoMode.resolution.width)です")
// "someVideoModeの幅は0です"と表示されます
また、ドット構文を使用して変数プロパティに新しい値を割り当てることもできます:
someVideoMode.resolution.width = 1280
print("someVideoModeの幅は現在\(someVideoMode.resolution.width)です")
// "someVideoModeの幅は現在1280です"と表示されます
構造体型のメンバーワイズイニシャライザ
すべての構造体には、自動的に生成されるメンバーワイズイニシャライザがあり、新しい構造体インスタンスのメンバープロパティを初期化するために使用できます。新しいインスタンスのプロパティの初期値は、名前でメンバーワイズイニシャライザに渡すことができます:
let vga = Resolution(width: 640, height: 480)
構造体とは異なり、クラスインスタンスはデフォルトのメンバーワイズイニシャライザを受け取りません。イニシャライザについては、初期化で詳しく説明しています。
構造体と列挙型は値型
値型とは、変数や定数に代入されたり、関数に渡されたりすると、その値がコピーされる型のことです。
実際、前の章で値型を広範囲に使用してきました。実際、Swiftの基本型(整数、浮動小数点数、ブール値、文字列、配列、辞書)はすべて値型であり、内部的には構造体として実装されています。
Swiftのすべての構造体と列挙型は値型です。つまり、作成したすべての構造体と列挙型のインスタンス、およびそれらがプロパティとして持つすべての値型は、コード内で渡されるたびに常にコピーされます。
注意
配列、辞書、文字列など、Swift標準ライブラリで定義されたコレクションは、コピーのパフォーマンスコストを削減するための最適化を使用します。すぐにコピーを作成する代わりに、これらのコレクションは、元のインスタンスとすべてのコピーの間で要素が格納されているメモリを共有します。コレクションのコピーの1つが変更されると、変更の直前に要素がコピーされます。コードで見る動作は常に、コピーがすぐに行われたかのようになります。
前の例で使用したResolution
構造体を使用した例を考えてみましょう:
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
この例では、hd
という定数を宣言し、フルHDビデオの幅と高さ(1920ピクセル幅、1080ピクセル高さ)で初期化されたResolution
インスタンスに設定します。
次に、cinema
という変数を宣言し、それをhd
の現在の値に設定します。Resolution
は構造体であるため、既存のインスタンスのコピーが作成され、この新しいコピーがcinema
に割り当てられます。hd
とcinema
は同じ幅と高さを持っていますが、内部的には完全に異なるインスタンスです。
次に、cinema
のwidth
プロパティを、デジタルシネマプロジェクションに使用されるわずかに広い2K標準の幅(2048ピクセル幅、1080ピクセル高さ)に変更します:
cinema.width = 2048
cinema
のwidth
プロパティを確認すると、確かに2048に変更されていることがわかります:
print("cinemaの幅は現在\(cinema.width)ピクセルです")
// "cinemaの幅は現在2048ピクセルです"と表示されます
しかし、元のhd
インスタンスのwidth
プロパティはまだ1920のままです:
print("hdの幅はまだ\(hd.width)ピクセルです")
// "hdの幅はまだ1920ピクセルです"と表示されます
cinema
にhd
の現在の値が与えられたとき、hd
に格納されていた値は新しいcinema
インスタンスにコピーされました。最終的には、同じ数値を含む2つの完全に別々のインスタンスが作成されました。しかし、別々のインスタンスであるため、cinema
の幅を2048に設定しても、hd
に格納されている幅には影響しません。
同じ動作は列挙型にも適用されます:
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です"と表示されます
rememberedDirection
にcurrentDirection
の値が割り当てられると、それは実際にはその値のコピーに設定されます。その後、currentDirection
の値を変更しても、rememberedDirection
に格納された元の値のコピーには影響しません。
クラスは参照型
値型とは異なり、参照型は変数や定数に代入されたり、関数に渡されたりしてもコピーされません。コピーの代わりに、同じ既存のインスタンスへの参照が使用されます。
上記で定義したVideoMode
クラスを使用した例を見てみましょう:
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
のフレームレートを変更します:
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
クラスは参照型であるため、tenEighty
とalsoTenEighty
は実際には同じVideoMode
インスタンスを参照しています。実質的に、これは同じ単一のインスタンスに対する2つの異なる名前にすぎません。
tenEighty
のframeRate
プロパティを確認すると、基になるVideoMode
インスタンスから新しいフレームレート30.0が正しく報告されることがわかります:
print("tenEightyのframeRateプロパティは現在\(tenEighty.frameRate)です")
// "tenEightyのframeRateプロパティは現在30.0です"と表示されます
この例は、参照型が理解しにくい場合があることも示しています。tenEighty
とalsoTenEighty
がプログラムのコード内で離れている場合、ビデオモードが変更されるすべての方法を見つけるのが難しいかもしれません。tenEighty
を使用する場所では、alsoTenEighty
を使用するコードについても考慮する必要があります。対照的に、値型は同じ値とやり取りするすべてのコードがソースファイル内で近くにあるため、理解しやすいです。
tenEighty
とalsoTenEighty
は定数として宣言されていますが、tenEighty.frameRate
とalsoTenEighty.frameRate
を変更することはできます。これは、tenEighty
とalsoTenEighty
定数自体の値が実際には変更されないためです。tenEighty
とalsoTenEighty
自体はVideoMode
インスタンスを「格納」しているわけではなく、内部的にはVideoMode
インスタンスを参照しています。変更されるのは、基になるVideoMode
のframeRate
プロパティであり、そのVideoMode
への定数参照の値ではありません。
同一性演算子
クラスは参照型であるため、複数の定数や変数が内部的に同じ単一のクラスインスタンスを参照することが可能です。(構造体や列挙型には同じことは当てはまりません。これらは常に定数や変数に代入されたり、関数に渡されたりするとコピーされます。)
2つの定数や変数が正確に同じクラスインスタンスを参照しているかどうかを確認することが有用な場合があります。これを可能にするために、Swiftは2つの同一性演算子を提供します:
- 同一である(
===
) - 同一でない(
!==
)
これらの演算子を使用して、2つの定数や変数が同じ単一のインスタンスを参照しているかどうかを確認します:
if tenEighty === alsoTenEighty {
print("tenEightyとalsoTenEightyは同じVideoModeインスタンスを参照しています。")
}
// "tenEightyとalsoTenEightyは同じVideoModeインスタンスを参照しています。"と表示されます
同一である(3つの等号で表される===
)は、等しい(2つの等号で表される==
)とは意味が異なることに注意してください。同一であるとは、クラス型の2つの定数や変数が正確に同じクラスインスタンスを参照していることを意味します。等しいとは、型の設計者が定義した適切な意味で、2つのインスタンスが値として等しいまたは同等であると見なされることを意味します。
独自のカスタム構造体やクラスを定義する場合、2つのインスタンスが等しいと見なされる条件を決定するのはあなたの責任です。独自の==
および!=
演算子の実装を定義するプロセスについては、等価演算子で説明しています。
ポインタ
C、C++、またはObjective-Cの経験がある場合、これらの言語がメモリ内のアドレスを参照するためにポインタを使用することを知っているかもしれません。参照型のインスタンスを参照するSwiftの定数や変数は、Cのポインタに似ていますが、メモリ内のアドレスへの直接ポインタではなく、参照を作成することを示すためにアスタリスク(*
)を書く必要もありません。代わりに、これらの参照はSwiftの他の定数や変数と同じように定義されます。Swift標準ライブラリは、ポインタを直接操作する必要がある場合に使用できるポインタ型とバッファ型を提供しています。詳細については、手動メモリ管理を参照してください。