iOSエンジニアのつぶやき

毎朝8:30に iOS 関連の技術について1つぶやいています。まれに釣りについてつぶやく可能性があります。

Swift の Class と Struct で迷った時にみる記事

Swift を書いていると、Class を使うか Struct を使うか迷う時が結構あり、どちらを使うべきか悩んだ時に判断する指標となることを簡単にまとめていきたいと思います。

f:id:yum_fishing:20201101185228p:plain 参照: https://blog.usejournal.com/swift-struct-or-class-how-to-choose-d22d987bac3d

Class or Struct?

Class と Struct の最も大きな違いは、参照型か値型であるかということです。そのほかにも、オブジェクトを継承するかどうかなどの違いもありますが、両者の違いを詳しく理解するためには参照型と値型を理解することが重要になってきます。

参照型

参照型は代入時にオブジェクト自体が代入されます。つまり、AインスタンスB に代入して B の値を変更すると、A の値も更新されます。Swift では Class が主に参照型として扱われます。

値型

値型は代入時にコピーが代入されます。つまり AインスタンスB に代入して B の値を変更しても A の値が変更されることはありません。また、Swift の値型は Copy on Write なため、代入先で変更があった時に初めて値がコピーされます。

Swift ドキュメントの値型のセクションにもある通り、Swiftの基本的な型(integers, floating-point numbers, Booleans, strings, arrays, dictionaries) は全て値型で定義されていて、Struct や Enum で定義した型も値型になります。 docs.swift.org

これはなんで?

値型の場合、値の変更をする時に再代入が行われるため、let で定義した値型を更新することはできません。また、structenum などで自身を再代入する際に使われる mutating func なども同様に、let で定義した値型で呼び出すと Error になります。

// 参照型
class Fish {
    var name: String
    init(name: String) {
        self.name = name
    }
}
let fish = Fish(name: "sweetfish")
fish.name = "tuna" // OK

一方参照型の場合は、値型と異なり値を直接メモリ上の変数領域には格納せず、値があるアドレスを変数領域に格納するため、let で定義したオブジェクトでもプロパティの要素が持っている値のアドレス先を要素を変更することで、変数領域を直接変更せずに値を更新することができます。また、これらは代入コストの面でも大きな恩恵を受けることができます。値型では、値を代入する際にはコピーを作成するので、データ量が多い場合はそれだけメモリを消費しますが、参照型では、アドレスのみのコピーを取得するので、本体のデータが大きくなろうと参照先は一つなので、メモリを大量に消費するコストが下がります。これについては下記の記事が大変参考になります。

qiita.com

// 値型
struct Fish {
    var name: String
    init(name: String) {
        self.name = name
    }
}
let fish = Fish(name: "sweetfish")
fish.name = "tuna" // Error 

どう使い分ける?

僕が実務で使い分ける際にはざっくり下記の項目で判断しています。

内容 yes no
状態が必要 Class Struct
特定のオブジェクトを継承する Class Struct
代入時にコピーを渡す Struct Class

参考

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com