Swiftでプログラミング。-Structures and Classes 2
Structures and Enumerations Are Value Types 構造体と列挙体の値型
値型とは、変数や定数に代入したり、関数に渡したりする際に値がコピーされる型のことです。
これまでの章では、値の型を多用してきました。実際、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は同じ幅と高さを持つようになりましたが、舞台裏では全く異なる2つのインスタンスになっています。
次に、cinemaのwidthプロパティは、デジタルシネマの投影に使用されるわずかに広い2K規格(幅2048ピクセル、高さ1080ピクセル)の幅に修正されます。
cinema.width = 2048
cinemaのwidthプロパティを確認すると、確かに2048に変更されています。
print("cinema is now \(cinema.width) pixels wide")
// Prints "cinema is now 2048 pixels wide"
しかし、オリジナルのhdインスタンスのwidthプロパティは、以前の値である1920のままです。
print("hd is still \(hd.width) pixels wide")
// Prints "hd is still 1920 pixels wide"
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("The current direction is \(currentDirection)")
print("The remembered direction is \(rememberedDirection)")
// Prints "The current direction is north"
// Prints "The remembered direction is west"
rememberedDirectionにcurrentDirectionの値が割り当てられると、実際にはその値のコピーが設定されます。その後、currentDirectionの値を変更しても、rememberedDirectionに格納されていた元の値のコピーには影響しません。
Classes Are Reference Types クラスは参照型
値型とは異なり、参照型は、変数や定数に代入されたり、関数に渡されたりしてもコピーされません。コピーではなく、同じ既存のインスタンスへの参照が使用されます。
上で定義したVideoModeクラスを例に説明します。
let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0
この例では、tenEightyという新しい定数を宣言し、VideoModeクラスの新しいインスタンスを参照するように設定しています。このビデオモードには、先ほどのHD解像度1920×1080のコピーが割り当てられています。インターレースにして、名前を「1080i」にして、フレームレートを25.0フレーム/秒にしています。
次に、「tenEighty」が「alsoTenEighty」という新しい定数に割り当てられ、「alsoTenEighty」のフレームレートが変更されます。
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
クラスは参照型なので、tenEightyとalsoTenEightyはどちらも同じVideoModeインスタンスを参照しています。実際には、下の図のように、同じ1つのインスタンスを2つの異なる名前で表しているに過ぎません。
tenEightyのframeRateプロパティを確認すると、基礎となるVideoModeインスタンスから30.0という新しいフレームレートが正しく報告されています。
print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// Prints "The frameRate property of tenEighty is now 30.0"
この例は、参照型がいかに推論しにくいかということも示しています。tenEightyとalsoTenEightyがプログラムのコードの中で離れていたとしたら、ビデオモードが変更されるすべての方法を見つけるのは難しいでしょう。tenEightyを使うときは、alsoTenEightyを使うコードも考えなければなりませんし、その逆もまた然りです。これに対して、値の型は、同じ値を扱うコードがソースファイルの近くにあるので、考えやすいのです。
tenEightyとalsoTenEightyは変数ではなく、定数として宣言されていることに注意してください。tenEighty と alsoTenEighty は、変数ではなく定数として宣言されているので、tenEighty.frameRate と alsoTenEighty.frameRate を変更することができます。tenEighty と alsoTenEighty 自体は VideoMode インスタンスを「保存」しているわけではなく、裏で VideoMode インスタンスを参照しています。変更されるのは、基礎となる VideoMode の frameRate プロパティであり、その VideoMode を参照する定数の値ではありません。
Identity Operators
クラスは参照型なので、裏では複数の定数や変数が同じクラスのインスタンスを参照している可能性があります。(構造体や列挙体は、定数や変数に代入されたり、関数に渡されたりするときに、常にコピーされるので、同じことは言えません)。
2つの定数や変数が、クラスの全く同じインスタンスを参照しているかどうかを調べることは、時々役に立ちます。これを可能にするために、Swiftは2つの同一性演算子を提供します。
・同一である (===)
・同一ではない (!==)
これらの演算子を使用して、2つの定数または変数が同じ単一のインスタンスを参照しているかどうかを確認します。
if tenEighty === alsoTenEighty {
print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
}
// Prints "tenEighty and alsoTenEighty refer to the same VideoMode instance."
identical to(3つの等号で表す、=== )とequal to(2つの等号で表す、==)は同じ意味ではないことに注意してください。同一とは、クラス型の2つの定数や変数が、まったく同じクラスのインスタンスを参照していることを意味します。等しい」とは、型の設計者が定義した「等しい」という適切な意味において、2 つのインスタンスが等しい、または同等の値であるとみなされることを意味します。
独自のカスタム構造やクラスを定義する場合、何をもって2つのインスタンスが等しいと判断するかは各自の責任となります。独自の == および != 演算子の実装を定義するプロセスについては、「Equivalence Operators」で説明しています。
Pointers ポインタ
C、C++、またはObjective-Cの経験があれば、これらの言語がメモリ内のアドレスを参照するためにポインタを使用することを知っているかもしれません。いくつかの参照型のインスタンスを参照するSwiftの定数または変数は、C言語のポインタに似ていますが、メモリ内のアドレスへの直接のポインタではなく、参照を作成していることを示すためにアスタリスク(*)を記述する必要はありません。その代わり、これらの参照は、Swiftの他の定数や変数のように定義されます。標準ライブラリは、ポインタとバッファの型を提供しており、ポインタを直接操作する必要がある場合に使用することができます(「手動によるメモリ管理」を参照)。
この記事が気に入ったらサポートをしてみませんか?