Swiftで値型と参照型の違いを理解する

Swiftで値型と参照型の違いを理解する

↓の挙動の違いの理由を説明できますか？

### struct （値型）の場合

```swift
struct Foo {
    var a: UInt8
    var b: UInt8
}
let foo = Foo(a: 3, b: 127)
foo.a = 5 // コンパイルエラー！！
```

### class （参照型）の場合

```swift
class Bar {
    var a: UInt8
    var b: UInt8
    init(a: UInt8, b: UInt8) { self.a = a; self.b = b }
}
let bar = Bar(a: 3, b: 127)
bar.a = 5 // OK
```

## ことのはじめ

仕事でコードレビューをしていて、次のようなコードを見かけました（ `Bar` は↑の `Bar` とします）。

```swift
var bar = Bar(a: 2, b: 3)

...

bar.a = 5
```

「どうして `let` じゃなくて `var` にしたの？」と聞いてみたところ、「 `bar` を変更するから `let` じゃいけないと思った」という返事が返ってきました。値型と参照型を混同してしまっているようです。

そういうわけで、値型と参照型の違いについて説明することにしました。

## 値型の場合

変数を定義すると、その変数の値を格納するためにメモリ上に領域が確保されます。上記の `Foo` であれば、 1 バイトの `UInt8` を二つ持っているので 2 バイト分の領域が確保されます。

```swift
let foo = Foo(a: 3, b: 127)
```

とすると、その 2 バイトの領域の最初の 1 バイトには `3` が、次の 1 バイトには `127` が格納され、 16 進数で書くと `037F` という 2 バイトが格納されることになります。

```
# メモリ上のバイト列のイメージ
|    | 03 | 7F |    |    |
       ^    ^
       foo のための領域
```


`let` とは、その変数のために確保された領域を、後から変更できないようにするという意味の宣言です。 `037F` という値は、変数 `foo` が存在している限り変更されません。

```swift
foo.a = 5
```

を実行するためには `037F` を変更して `057F` に書き換えなければなりません。それは `let` に違反するのでコンパイルエラーとなります。

```
# メモリ上のバイト列のイメージ
|    | 03 | 7F |    |    |    |
       ^
       ここを 05 に書き換えないといけない
```


## 参照型の場合

### 参照型とは何か

値型が値を直接変数の領域に格納するのに対して、参照型では別の場所に値を格納し、そのアドレス（どこに値を格納したか）を変数に格納します。

```swift
let bar = Bar(a: 3, b: 127)
```

とすると、メモリ上のどこか（変数のための領域とは別の場所）に `037F` という 2 バイトのデータが格納されます。ここではメモリ上の 7FAB52C4 番地からの 2 バイトに格納されたとしましょう。 7FAB52C4 番地には `03` が、 7FAB52C5 番地には `7F` が格納されます。

では、変数 `bar` のために確保された領域には何が書かれるのでしょうか。それは `7FAB52C4` という値（アドレス、番地）です[^1]。

```
# メモリ上のバイト列のイメージ
|    | 7F | AB | 52 | C4 | ... | 03 | 7F |    |
       ^    ^    ^    ^          ^
       bar のための領域            7FAB52C4番地
```


```swift
println(bar.a)
```

とすると何が起こるでしょうか。 `Bar` は参照型なので、まず `bar` に格納された値 `7FAB52C4` が読み出されます。次に、そのアドレス 7FAB52C4 番地にアクセスして、 `03` という値が読み出されます。その結果 `3` という値が取得され表示されます。

このように、参照型ではアドレスを介して間接的に値にアクセスすることになります。

### 参照型と let

参照型である `Bar` 型の変数 `bar` を `let` にする意味は、 `bar` に格納された `7FAB52C4` を変更できないようにするという意味です。

もし、

```swift
bar = Bar(a: 5, b: 255)
```

とするとどうなるでしょうか。 `Bar(a: 5, b: 255)` は新たにメモリ上のどこか（ここでは 8B3E4CA9 番地としましょう）に 2 バイトの領域を確保し、そこに値 `05FF` を書き込みます。そして、 `bar` に格納された `7FAB52C4` を `8B3E4CA9` に書き換えようとします。しかし、これは `bar` が `let` で宣言されていることに違反するのでコンパイルエラーとなります。

```
# メモリ上のバイト列のイメージ
|    | 7F | AB | 52 | C4 | ... | 03 | 7F | ... | 05 | FF |    |
       ^    ^    ^    ^          ^               ^
       8B 3E 4C A9 に書き換えたい  7FAB52C4番地     8B3E4CA9番地
```

しかし、

```swift
bar.a = 5
```

としても、 7FAB52C4 番地に格納された `03` から `05` に書き換えるだけで、 `bar` に格納された `7FAB52C4` を書き換えるわけではありません。そのため、 `bar` が `let` で宣言されていても `bar.a = 5` は許されるのです。

```
# メモリ上のバイト列のイメージ
      bar
|    | 7F | AB | 52 | C4 | ... | 05 | 7F |    |
       ^    ^    ^    ^          ^
       ここはそのまま              ここを 03 から 05 に書き換えた
```

なお、 `7FAB52C4` 番地の値が書き換えられるかどうかは、 `a` が `var` で宣言されているか `let` で宣言されているかによります。今は `var` なので書き換えられますが、 `let` で宣言されていると `bar.a = 5` はコンパイルエラーとなります。

## 参照型がうれしい理由

参照型に慣れてない人にとっては、一見参照型は複雑なだけで何がうれしいのかわかりづらいと思います。参照型がうれしい理由の一つは代入のコストが小さいことです。

代入とは、代入先の変数のために確保された領域に、値をまるごとコピーする操作です。

```swift
let foo1 = Foo(a: 3, b: 127)
let foo2 = foo1
```

とすると、 `foo1` と `foo2` のために 2 バイトずつの領域が確保されます。最初の行で `foo1` の領域には `037F` が格納されます。次の行で、 `foo2` の 2 バイトの領域に、 `foo1` の領域に格納されている `037F` をまるごとコピーします。

```
# メモリ上のバイト列のイメージ
|    | 03 | 7F |    | ... |    | 03 | 7F |    |
       ^    ^                    ^    ^
       foo1 のための領域           foo2 のための領域に 03 7F をコピー
```


では、もし次のような `struct` だとどうなるでしょうか。

```swift
struct Baz {
	let v1: UInt8
	let v2: UInt8
	let v3: UInt8
	...
	let v100: UInt8
}
let baz1 = Baz(...)
let baz2 = baz1
```

`Baz` 型の変数を宣言すると 100 バイトの領域が確保されます。 `baz1` から `baz2` へ代入すると、 100 バイト分もデータをコピーしないといけません。

プログラミングをしているともっと大きなデータを扱わなければならないことがあります。例えば、配列で 100 万個の要素が必要なこともあるでしょう。その代入（引数に渡したり、戻り値で受け取ったりも含みます）の度にすべてをコピーしていると、プログラムのパフォーマンスはひどいことになってしまいます。

では、参照型だとどうでしょうか。

```swift
class Qux {
	let v1: UInt8
	let v2: UInt8
	let v3: UInt8
	...
	let v1000: UInt8
	init(...)
}
let qux1 = Qux(...)
let qux2 = qux1
```

この場合、 `qux1` から `qux2` にコピーされるのは `qux1` に格納されたアドレスの値だけです。アドレスは、 32 bit アーキテクチャなら 4 バイト、 64 bit アーキテクチャなら 8 バイトのデータです。 `qux1` から `qux2` への代入は、たとえそのアドレスが参照する値がどれだけ巨大でも、わずか 4 バイト（または 8 バイト）のコピーで済みます。

```
# メモリ上のバイト列のイメージ
| 7F | AB | 52 | C4 | ... | 7F | AB | 52 | C4 |    |
  ^    ^    ^    ^          ^    ^
  qux1 のための領域           qux2 のための領域に 7F AB 52 C4 をコピー
```

## 参照型の代償（または、参照型がうれしい理由その２）

参照型は、次のような現象を引き起こします。

```swift
// 値型の場合
var foo1 = Foo(a: 3, b: 127)
var foo2 = foo1
foo2.a = 5
println(foo1.a) // 3 ( foo2 を変更しても foo1 は変更されない)
```

```swift
// 参照型の場合
var bar1 = Bar(a: 3, b: 127)
var bar2 = bar1
bar2.a = 5
println(bar1.a) // 5 ( bar2 を変更したら bar1 も変更された）
```

これは、 `bar1` と `bar2` には同じアドレスの値が格納されており、その実体となる値はメモリ上に一つしか存在しないためです。

例えば、 `bar1` と `bar2` に格納されている値（アドレス）が `098E6F1A` だったとします。すると、 2 行目が終了した時点では 098E6F1A 番地からの 2 バイトには `037F` が格納されているはずです。次に 3 行目で `bar2.a = 5` が実行されると、 098E6F1A 番地からの 2 バイトは `057F` に書き換えられてしまいます。 `bar1` も同じ 098E6F1A 番地を参照しているので、 `bar1.a` も `5` になってしまうわけです。

```
# メモリ上のバイト列のイメージ

# bar2.a = 5 の実行前
| 09 | 8E | 6F | 1A | ... | 09 | 8E | 6F | 1A | ... | 03 | 7F |    |
  ^    ^    ^    ^          ^    ^    ^    ^          ^
  bar1 のための領域           bar2 のための領域           098E6F1A 番地

# bar2.a = 5 の実行後
| 09 | 8E | 6F | 1A | ... | 09 | 8E | 6F | 1A | ... | 05 | 7F |    |
  ^    ^    ^    ^          ^    ^    ^    ^          ^
  bar1 のための領域           bar2 のための領域           05 に書き換えた
```

このような挙動は、参照型に慣れていない人にとってはわかりづらく感じられるでしょうし、参照型の挙動を正しく理解していないと思わぬバグを生む可能性があります。

ただし、参照型のそのような挙動は悪いことだとは限りません。プログラム全体で状態を共有して、ある箇所で値を書き換えたら他の箇所にも反映されてほしいということもあります。大切なのは、値型と参照型の違いを理解して、正しく使い分けることです。

[^1]: エンディアンについては本題とは関係なく話が複雑になるだけなので意図的に無視しています。