Swiftには、構造体とクラス、列挙型という3つの型の種類があります。
それぞれプロパティやメソッドなどの共通した仕様がある一方で、
三者それぞれの固有の仕様も多く存在します。
これらの仕様の違いは、
単純な機能の有無だけではなく値の受け渡し時の挙動にも及びます。
#型の種類を使い分ける目的
先ほども記載しましたが、型には構造体とクラス、列挙型が存在します。
これらの型は共通の構成要素を所持しており、
たいていのデータは構造体やクラスで表現できるようになっています。
しかし、わざわざ別れているくらいなので察すると思いますが、
構造体とクラス、列挙型はそれぞれの目的に特化した機能を持っています。
なので、データの性質に応じて適切な種類を選択すれば、
単なる値と機能の組み合わせ以上の表現が可能になります。
#値の受け渡し方法による分類
Swiftの3つの型の種類は、値の受け渡しの方法によって、
値型と参照型の2つに大別することができます。
この2つの違いは、変更を他の変数や定数と共有するかどうかにあります。
変更を共有しない型が値型であるのに対し、
変更を共有する型が参照型になります。
Swiftでは、構造体と列挙型は値型、クラスは参照型として実装されています。
##値型
値型は、インスタンスが値への参照ではなく値そのものを表す型になります。
変数や定数への値型のインスタンスの代入は、
インスタンスが表す値そのものの代入を意味します。
なので、複数の変数や定数で一つの値型のインスタンスを共有することはできません。
そのため、一度代入したインスタンスは再代入をしない限り不変です。
このことから、
値型のメリットは、その値の予測が可能になることが挙げられます。
標準ライブラリで準備されている値型は多くありますが、
その中でも特に使用する値型はInt型が挙げられます。
下記のサプルコードをご覧ください。
変数bに対して変数aを代入していますが、この代入しているaとは、
aが持つ値への参照ではなく、aが持つ4という値そのものになります。
つまり、aとbは別々の4というインスタンスを所持しているため、
aに対して再代入を行ってもbの値は変わらず4のままになります。
var a = 4
var b = 4
a = 2
print(a)
print(b)
実行結果
2
4
###mutating
値型では、mutatingキーワードをメソッドの宣言に追加することで、
自身の値を変更する処理を実行できます。
mutatingキーワードが指定されたメソッドを実行してインスタンスの値を変更すると、
インスタンスが格納されている変数への暗黙的な再代入が行われます。
mutatingキーワードが指定されたメソッドは再代入のメソッドとして扱われるため、
定数に格納された値型のインスタンスに対して実行するとコンパイルエラーになります。
記述方法としては下記のようになります。
mutating func メソッド名(引数) -> 戻り値の型 {
メソッド呼び出し時に実行される文
}
サンプルコードでは、エクステンションを利用して、
Int型にincrement( )メソッドを追加しています。
このメソッドは、a.increment()
のように実行できるため
一見すると代入を伴わず値の変更をしているように見えます。
しかし、実際は再代入が行われているので、
定数であるbに対してincrementメソッドを実行するとコンパイルエラーになります。
extension Int {
mutating func increment() {
self += 1
}
}
var a = 1
a.increment() // 2
let b = 1
b.increment() // コンパイルエラー
エラー内容:Cannot use mutating member on immutable value: 'b' is a 'let' constant
和訳:不変の値に変更メンバーを使用することはできません:「b」は「let」定数です
mutatingキーワードをつけなければ、再代入として扱われないのですが、
先頭にmutatingをつけないでメソッドを定義すると
そもそも自身の値にアクセスできなくなります。
つまり、mutatingを利用する利点としては、
・自身の値を変更することができる。
・定数に対しては実行できなくなる。
が挙げられると思います。
2つ目の利点は、インスタンスが保持する値の変更を防ぎたい場合に役立ちます。
値の変更を防ぐということは、
予期せぬ値の代入を防ぐことができるということです。
つまり安全性が向上します。
##参照型
参照型とは、インスタンスが値への参照を表す型です。
Swiftにおけるクラスは参照型になります。
変数や定数への参照型の値の代入は、
インスタンスに対する参照の代入を意味するため、
複数の定数や変数で1つの参照型のインスタンスを共有できます。
###値の変更の共有
値型では、変数や定数が他の値の変更による影響を受けないことが保証されていました。
それに対して、参照型では1つのインスタンスが他の変数や定数と共有されているため、
ある値に対しての変更はインスタンスを共有している他の変数や定数にも影響します。
文字を書いていても伝わりにくと思いますので、
実際にサンプルコードを記述してみました。
class Sample型を定義しました。
var a = Sample(value: 1)
var b = a
変数aにSample(value: 1)を代入し変数bにはaを代入しています。
参照型のインスタンスであるaをbに代入するということは、
aが参照しているインスタンスをbも参照するということになります。
つまりaとbは、同じSample(value: 1)
への参照を持っていることになります。
そのためa.valueを変更するとb.valueも変更されます。
class Sample {
var value: Int
init(value: Int) {
self.value = value
}
}
var a = Sample(value: 1)
var b = a
a.value // 1
b.value // 1
a.value = 2
a.value // 2
b.value // 2
##値型と参照型の使い分け
値型は変数や定数への変更が共有されません。
したがって、一度代入された値は明示的に再代入しない限り不変になります。
一方で参照方はその逆であり、変数や定数への変更が共有されます。
したがって、一度代入された値が変更されないことの保証は難しくなります。
これらの性質を考慮すると、
安全にデータを扱うためには積極的に値型を使用し、
参照型は変更の共有が必要となる範囲のみにとどめるのがベストだと思います。
以上で型の基礎知識についての説明は終了します。
構造体とクラス、列挙型についての説明も記事にしていますので、
お時間がある際にぜひご覧ください!
・【Swift】型の種類〜構造体〜
・【Swift】型の種類〜クラス前編〜
・【Swift】型の種類〜列挙型〜
最後までご覧いただきありがとうございました。