Swiftの値型と参照型の違い
Swiftには「値型(Value Type)」と「参照型(Reference Type)」の2種類のデータ型があり、それぞれ挙動が異なります。
変数を扱う際にこの違いを理解しておくことで、意図しないバグを防ぐことができます。
前提:変数と定数の基本
Swiftでは、データを格納するために「変数(var)」と「定数(let)」を使います。
- var(変数): 値を変更できる
- let(定数): 値を変更できない
var number = 10 // 変数なので値を変更できる
number = 20
let constantNumber = 10 // 定数なので値を変更できない
// constantNumber = 20 // これはエラー
この「変更できるかどうか」のルールは、値型と参照型の違いとも関連します。
値型(Value Type)とは
値型は、変数がデータそのものを保持している型です。
値型の変数を他の変数に代入すると、データはコピーされます。
値型に分類されるもの
- 構造体(Struct)
- 列挙型(Enum)
- タプル(Tuple)
- 標準ライブラリの型(String、Int、Boolなど)
値型の挙動
struct MyStruct {
var value: Int
}
var a = MyStruct(value: 10)
var b = a // `a`のコピーが作られる
b.value = 20
print(a.value) // 10
print(b.value) // 20
この例では、b
に a
を代入した時点で a
のコピーが作成されます。そのため、b
を変更しても a
には影響しません。
値型のメリット
- データの独立性が保たれる → ある変数の変更が、他の変数に影響を与えない。
- スレッドセーフ → マルチスレッド環境でも安全に扱える。
参照型(Reference Type)とは
参照型は、変数がデータのアドレス(メモリ上の位置)を保持している型です。
参照型の変数を他の変数に代入しても、データそのものはコピーされず、同じデータを指す別の参照が作られます。
参照型に分類されるもの
- クラス(Class)
- クロージャ(Closure)
- Objective-Cのオブジェクト(NSString, NSArray など)
参照型の挙動(同じデータを指す)
class MyClass {
var value: Int
init(value: Int) {
self.value = value
}
}
var a = MyClass(value: 10)
var b = a // `a`と`b`は同じインスタンスを参照
b.value = 20
print(a.value) // 20
print(b.value) // 20
この例では、b
に a
を代入してもコピーは発生せず、a
と b
は同じメモリアドレスを指します。そのため、b
を変更すると a
も変更されます。
参照型の注意点
- データが共有されるため、意図しない変更が起こる可能性がある
- マルチスレッド環境では同期処理が必要になる場合がある
よくあるバグの例
値型と参照型の誤解によるバグ
struct Counter {
var count: Int = 0
}
func increment(counter: Counter) {
var counter = counter
counter.count += 1
}
var myCounter = Counter()
increment(counter: myCounter)
print(myCounter.count) // 0(変更されていない!)
このバグの原因は、構造体(値型)はコピーされるため、関数内で変更しても元の変数には影響がないことにあります。
修正するには、inout
を使って変更を反映させる必要があります。
func increment(counter: inout Counter) {
counter.count += 1
}
var myCounter = Counter()
increment(counter: &myCounter)
print(myCounter.count) // 1(正しく変更される)
参照型の共有による意図しない変更
class Person {
var name: String
init(name: String) {
self.name = name
}
}
var personA = Person(name: "Alice")
var personB = personA
personB.name = "Bob"
print(personA.name) // "Bob"(意図しない変更が発生!)
このバグは、クラス(参照型)はコピーされずに共有される ために起こります。
値の独立性を確保したい場合は、struct
を使うべきです。
struct Person {
var name: String
}
var personA = Person(name: "Alice")
var personB = personA
personB.name = "Bob"
print(personA.name) // "Alice"(変更されない!)
まとめ
値型(Value Type) | 参照型(Reference Type) | |
---|---|---|
代入時の動作 | コピーが作成される | 参照が共有される |
データの独立性 | 保たれる | 保たれない |
var の影響範囲 | その変数のみ | すべての参照先に影響 |
let の制約 | プロパティも変更不可 | 参照は変更不可だが、プロパティ変更は可能 |
特に「よくあるバグ」の例などは、swiftを経験していれば一度は遭遇する事象だと思います。
その際に「参照先が共有されてるのかな?」などの考えにすぐに至ることができれば、バグの解消もスムーズにできるかと思います。