構造体とクラスの比較
共通点 - どちらでもできること
構造体とクラスは、実はかなり似ている。以下のことはどちらでもできる:
| 機能 | 説明 |
|---|---|
| プロパティを定義 | 値を格納する変数・定数を持てる |
| メソッドを定義 | 機能を提供する関数を持てる |
| サブスクリプトを定義 | [] でアクセスする機能を持てる |
| イニシャライザを定義 | 初期化処理をカスタマイズできる |
| extension で拡張 | 後から機能を追加できる |
| プロトコルに準拠 | 共通のインターフェースを実装できる |
クラスだけの追加機能
クラスには、構造体にはない4つの追加機能がある:
| クラス専用の機能 | 説明 |
|---|---|
| 継承 (Inheritance) | 別のクラスの特性を引き継げる |
| 型キャスト (Type Casting) | 実行時にインスタンスの型を確認・変更できる |
| デイニシャライザ (Deinitializer) | インスタンス廃棄時にクリーンアップ処理ができる |
| 参照カウント (Reference Counting) | 同じインスタンスを複数箇所から参照できる |
便利さと複雑さのトレードオフ
シンプル・安全 柔軟・複雑
◀───────────────────────────────────▶
構造体 クラス
┌──────────────┐ ┌─────────────┐
│ ・予測しやすい │ │ ・継承できる │
│ ・コピーで独立 │ │ ・参照共有 │
│ ・スレッド安全 │ │ ・状態管理複雑 │
└──────────────┘ └─────────────┘
クラスの追加機能は便利だが、その分複雑さが増す。 特に、「参照型」であることは、予期しない副作用を生みやすい:
// クラスの罠
let modeA = VideoMode()
modeA.frameRate = 25.0
let modeB = modeA // 同じ実体を参照
modeB.frameRate = 30.0 // modeB を変えたつもりが...
print(modeA.frameRate) // 30.0 ← modeA も変わっている
Swift の設計思想
「構造体の方が扱いやすく推奨される。クラスは適切または必要な場合にのみ使用してください。」
判断フローチャート:
その型を定義したい
│
▼
継承が必要? ──Yes──▶ クラスを使う
│
No
│
▼
参照の共有が必要? ──Yes──▶ クラスを使う
│
No
│
▼
構造体を使う ← デフォルト!
構造体とクラスの比較のまとめ
| 観点 | 構造体 | クラス |
|---|---|---|
| キーワード | struct | class |
| 型の種類 | 値型 | 参照型 |
| 継承 | ❌ 不仮 | ✅ 可能 |
| デイニシャライザ | ❌ なし | ✅ あり |
| 推奨度 | デフォルト | 必要な時だけ |
定義構文 (Definition Syntax)
構造体は struct 、クラスは class で定義する。
struct SomeStructure {
// 構造体の定義
}
class SomeClass {
// クラスの定義
}
命名規則
- 型名 (構造体名・クラス名) →
UpperCamelCase(例:VideoMode、Resolution) - プロパティ名・メソッド名 →
lowerCamelCase(例:frameRate、incrementCount)
実例
公式ドキュメントの例:
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
Resolution (構造体)
widthとheightの2つの格納プロパティを持つ- 初期値
0を与えているため、型推論でIntになる
VideoMode (クラス)
- 4つの格納プロパティを持つ
resolution→Resolution()で初期化しているので、型はResolutionであるinterlaced→falseで初期化 → 型推論によりBoolとなるframeRate→0.0で初期化 → 型推論によりDoubleとなるname→String?(オプショナル) → デフォルトでnil
注目する点として、クラスの中に構造体のインスタンスをプロパティとして持てるという点。
型をネストして組み合わせるのは、Swift では普通のことである。
インスタンスの作成 (Structure and Class Instances)
構造体やクラスの定義は、あくまで「設計図」であり、実際に使うにはインスタンスを作る必要がある。
let someResolution = Resolution()
let someVideoMode = VideoMode()
インスタンスを作るには、型名の後ろに () を付けるだけ。これにより、デフォルト値で初期化された新しいインスタンスが生成される。
この () は、実はイニシャライザ (init) の呼び出しであるが、詳しくは後の「初期化」の章で説明する。
プロパティへのアクセス (Accessing Properties)
ドット構文でプロパティにアクセスする:
ドット構文では、インスタンス名のすぐ後ろに、スペースを入れずにピリオド(.)で区切ってプロパティ名を記述する。
print(someResolution.width) // 0
// ^^^^^^^ ← 「someResolution の width プロパティ」という意味
ネストしたプロパティにもドットを繋げてアクセスできる:
print(someVideoMode.resolution.width) // 0
代入もドット構文で行う:
someVideoMode.resolution.width = 1280
print(someVideo.resolution.width) // 1280
someVideoMode は let (定数) で宣言されているのに、中のプロパティを変更できる。これは VideoMode がクラス (参照型) だからである。
定数が保持しているのは「インスタンスそのもの」ではなく、あくまでも「インスタンスへの参照」なので、参照先の中身は変更することができる。そのため、別のインスタンスの参照に変更することはできない。