戌咬音かんぬき(inugaminé)'s avatar

戌咬音かんぬき(inugaminé)

構造体とクラスの比較

共通点 - どちらでもできること

構造体とクラスは、実はかなり似ている。以下のことはどちらでもできる:

機能説明
プロパティを定義値を格納する変数・定数を持てる
メソッドを定義機能を提供する関数を持てる
サブスクリプトを定義[] でアクセスする機能を持てる
イニシャライザを定義初期化処理をカスタマイズできる
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


      構造体を使う ← デフォルト!
構造体とクラスの比較のまとめ
観点構造体クラス
キーワードstructclass
型の種類値型参照型
継承❌ 不仮✅ 可能
デイニシャライザ❌ なし✅ あり
推奨度デフォルト必要な時だけ

定義構文 (Definition Syntax)

構造体は struct 、クラスは class で定義する。

struct SomeStructure {
    // 構造体の定義
}

class SomeClass {
    // クラスの定義
}
命名規則
  • 型名 (構造体名・クラス名) → UpperCamelCase (例: VideoModeResolution)
  • プロパティ名・メソッド名 → lowerCamelCase (例: frameRateincrementCount)
実例

公式ドキュメントの例:

struct Resolution {
    var width = 0
    var height = 0
}

class VideoMode {
    var resolution = Resolution()
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}

Resolution (構造体)

  • widthheight の2つの格納プロパティを持つ
  • 初期値 0 を与えているため、型推論で Int になる

VideoMode (クラス)

  • 4つの格納プロパティを持つ
  • resolutionResolution() で初期化しているので、型は Resolution である
  • interlacedfalse で初期化 → 型推論により Bool となる
  • frameRate0.0 で初期化 → 型推論により Double となる
  • nameString? (オプショナル) → デフォルトで 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

someVideoModelet (定数) で宣言されているのに、中のプロパティを変更できる。これは VideoModeクラス (参照型) だからである。
定数が保持しているのは「インスタンスそのもの」ではなく、あくまでも「インスタンスへの参照」なので、参照先の中身は変更することができる。そのため、別のインスタンスの参照に変更することはできない。