# 値型と参照型

Swiftでは、データを表すためにクラス、構造体、列挙型と3つの型の種類があります。これらの違いの一つで、値の受渡し方法があります。構造体と列挙型は 値型 で、クラスが 参照型 になります。

  • 値型

    • 構造体
    • 列挙型
  • 参照型

    • クラス

# 値型

値型とは、変数の値が参照ではなく直接、値をもつ型のことをいいます。変数や定数に値が代入されたときや関数に渡されたときに、新たに値がコピーされてメモリ領域を確保します。複数のインスタンスで値を共有することはなく、一度代入された値は再代入をしない限りは不変です。そのため、値の変更が予測しやすいようになっています。

Swiftでは、基本的な型であるInt型や、Float型、Bool型、String型、Array型、Dictionary型などは全て値型になります。これらは内部では構造体として実装されています。

実際に例を見てみましょう。構造体の中にプロパティを定義し、インスタンスを生成します。その後インスタンスプロパティを変更しても、それぞれの値は独立しているのでお互いに影響を及ぼしません。

struct Person {
  var name: String
  var age: Int
}

var person1 = Person(name: "Taro", age: 20)

// インスタンスを変数に代入
var person2 = person1

person1.age = 30 

// person1 の変更は person2 に影響を与えない
person1.age // 30
person2.age // 20

TIP

配列、辞書、文字列などで定義されたコレクションは、コピーする際にパフォーマンスコストを削減するために最適化が行われています。これらのコレクションは、すぐにコピーを作るのではなく、元のインスタンスとコピー先のインスタンスの間で、値のメモリを共有します。そして、コピー先のコレクションの要素が1つでも変更された場合に、値はコピーされます。

実際に見てみましょう。次の例では、どのタイミングで値がコピーされるでしょうか。

var a = [1,2,3]
var b = a

print(b[0])
b[0] = 100
a[0] = 1000

Swiftは、コレクションの要素が1つでも変更された場合に、値はコピーされる ので、b[0] = 100 が変更されたタイミングで値のコピーが発生します。

var a = [1,2,3]
var b = a       // 1. 代入しただけなので、コピーは実行されていない

print(b[0])     // 2. 値を参照しているだけなので、コピーは実行されていない
b[0] = 100      // 3. 値が1つ変更されたので、コピーを実行
a[0] = 1000     // 4. すでにbに値がコピーされ、aの値は誰とも共有されていないのでコピーは実行されない

このような最適化をすることによって、Swiftは不要なコピーを省き、効率的なメモリ管理をしています。

# mutating

値型では、mutating キーワードを定義することによって自身の値を変更することができます。変更と言っても実際は、インスタンスのプロパティに対して暗黙的な再代入をしています。そのため、定数に対して変更を行うとコンパイラはエラーを出力します。

struct Person {
  var name: String
  var age: Int

  // mutating キーワードをつけてメソッドを定義する
  mutating func updateAge(age: Int) {
    self.age = age
  }
}

var person1 = Person(name: "Taro", age: 20)

// 値を変更する
person1.updateAge(age: 30)

person1.age // 30

mutating キーワードがないと、コンパイラはエラーを出力します。

struct Person {
  var name: String
  var age: Int

  // mutating キーワードをなしでメソッドを定義する
  func updateAge(age: Int) {
    self.age = age
  }
}

var person1 = Person(name: "Taro", age: 20)

person1.updateAge(age: 30)

/* 実行結果 */
// error: cannot assign to property: 'self' is immutable
//     self.age = age
// note: mark method 'mutating' to make 'self' mutable

# 参照型

値型が値を持つ型に対して、参照型は値への参照を持つ型です。値型とは異なり、参照型は変数や定数に代入されたときや関数に渡されたときはコピーされません。コピーではなく、既存のインスタンスへの参照が使用されます。

Swiftのクラスは参照型です。複数の変数で1つのインスタンスを共有することができます。

実際に例を見てみましょう。クラスの中にプロパティを定義し、インスタンスを生成します。インスタンスプロパティを変更すると共有先の値も変更されることがわかります。

class Person {
  var name: String
  var age: Int

  init(name: String, age: Int) {
    self.name = name
    self.age = age
  }
}

let person1 = Person(name: "Taro", age: 20)
let person2 = person1

// インスタンスの値を変更
person1.age = 30

// 代入された変数も値を共有しているので変更される
person2.age // 30

# アイデンティティ演算子

Swiftのクラスは参照型なので、複数の定数や変数が同じインスタンスを参照することができます。

複数の定数や変数が全く同じインスタンスを参照しているかを調べるにはアイデンティティ演算子を使うことができます。

===!== で同じインスタンスかどうかを調べることができます。

class Person {
  var name: String
  var age: Int

  init(name: String, age: Int) {
    self.name = name
    self.age = age
  }
}

let person1 = Person(name: "Taro", age: 20)
let person2 = person1

// 同じインスタンスが調べる
if person1 === person2 {
  print("同じインスタンスを参照しています")
}

let person3 = Person(name: "Taro", age: 20)

// 異なるインスタンスが調べる
if person1 !== person3 {
  print("異なるインスタンスを参照しています")
}

TIP

===== は同じ意味ではないことに注意してください。=== は、クラス型の変数が同じインスタンスを参照していることを意味します。== は、2つのインスタンスが等しい、または値が同等であることを意味します。

また、ObjectIdentifier を使うと、一意の識別子を調べることができます。

識別子を見ることによって、person1person2 が同じ値を共有していることがわかります。

class Person {
  var name: String
  var age: Int

  init(name: String, age: Int) {
    self.name = name
    self.age = age
  }
}

let person1 = Person(name: "Taro", age: 20)
let person2 = person1
let person3 = Person(name: "Taro", age: 20)

// person1とperson2が同じ値を共有していることがわかる
ObjectIdentifier(person1) // ObjectIdentifier(0x000060000387cf90)
ObjectIdentifier(person2) // ObjectIdentifier(0x000060000387cf90)
ObjectIdentifier(person3) // ObjectIdentifier(0x0000600003841b30)

# 値型のなかに参照型がある場合

値型の構造体のなかに、参照型であるクラスを定義した場合はどのような動きになるのでしょうか。

構造体の中にクラスを定義してみましょう。

class NestedClass {
  var name = "nestedClass"
}

struct A {
  var name = "A"
  // 構造体のプロパティにクラスのインスタンスを定義する
  var nestedClass = NestedClass()
}

var a = A()
var b = a

// 構造体のプロパティを変更する
a.name = "AAAA"

// 値型なので変更は反映されない
a.name // AAAA
b.name // A

// クラスのプロパティを変更する
a.nestedClass.name = "BBBB"

// 共有先のクラスは参照型なので変更は反映される
a.nestedClass.name // BBBB
b.nestedClass.name // BBBB

クラスのインスタンスの値を変更すると、共有先の値も変更されました。構造体のプロパティは値型として振舞いますが、クラスのインスタンスである var nestedClass は参照型として振舞うことがわかります。

# 値型と参照型の使い分け

Swiftの標準ライブラリで提供されている型は、ほぼすべて値型です。Swiftは値型中心の言語仕様であり、値型を扱いやすいように様々な機能が提供されています。( inout 引数や mutating など)

値型は不変であり、値はコピーされ、他の変数と共有されません。

参照型は、複数の変数で値が共有されます。不用意に多用すると意図しない変更がされる可能性もあります。

実際にどちらを使用すべきかは開発の現場でケースバイケースですが、上記の性質を考慮すると、安全にデータを保持するには値型を積極的に使うのがいいでしょう。データの変更性が予測しやすく、問題の発見をしやすいなどの利点があるためです。

参照型は局所的に使う(状態管理など)などで、なるべく最小限に留めて、不用意なデータの変更性を減らすようにするのがいいでしょう。