Swift: Swiftクラスと構造体:値型と参照型の選択

クラ��と構造体はSwiftでデータモデルを構築するための2つの中核型です。多くの類似点を共有しますが、メモリ管理と値の受け渡し方法が根本的に異なります。このレッスンでは、それぞれの選択戦略を明確にします。

1. 学習目標


2. iOS開発者の実話

(1) 課題:ある場所でユーザーアカウントを変更すると別の場所にも影響する

Aliceはユーザーアカウントのモデルにクラスを選択しました:

SWIFT
class UserAccount {
    var name: String
    var balance: Double
    init(name: String, balance: Double) {
        self.name = name
        self.balance = balance
    }
}
let account1 = UserAccount(name: "Alice", balance: 1000)
let account2 = account1  // References the same instance
account2.name = "Bob"
print(account1.name)  // "Bob" — account1 was also changed!

Aliceは新しいユーザーを作成したかっただけなのに、元のデータを誤って変更してしまいました。参照型の副作用がコードベース全体に気付かれずに伝播しました。

(2) 解決策:値型としての構造体

SWIFT
struct UserAccount {
    var name: String
    var balance: Double
}
var account1 = UserAccount(name: "Alice", balance: 1000)
var account2 = account1  // Copies an independent set of data
account2.name = "Bob"
print(account1.name)  // "Alice" — account1 is unaffected

構造体の代入は値のコピーを行います — 各インスタンスは独立しています。

(3) 利点:予測可能なデータ動作

観点 class(参照型) struct(値型)
代入の動作 同じインスタンスを共有 独立したコピー
誤変更のリスク 高い(エイリアシング) なし
メモリ割り当て ヒープ割り当て スタック割り当て(より効率的)
スレッド安全性 手動同期が必要 自然に分離
適している場面 共有可変状態 データモデル、値セマンティクス

3. クラスと構造体の定義

(1) 基本構文の比較

100%
graph TB
    subgraph class
        A["class Person {"]
        B["    var name: String"]
        C["    var age: Int"]
        D["    func greet() { }"]
        E["}"]
    end
    subgraph struct
        F["struct Person {"]
        G["    var name: String"]
        H["    var age: Int"]
        I["    func greet() { }"]
        J["}"]
    end
特徴 class struct 備考
プロパティ ストアドプロパティとコンピューテッドプロパティ
メソッド インスタンスメソッドと型メソッド
イニシャライザ classはメンバーワイズinitを自動生成しない
継承 不可 structは継承不可
デイニシャライザ 不可 structにdeinitはない
参照カウント 不可 structはARC不要
型キャスト 不可 as?/as! はclassのみ

▶ サンプル: クラスと構造体の定義

SWIFT
// ============================================
// Class and struct definition comparison
// ============================================
// Class definition — must write init manually
class PersonClass {
    var name: String
    var age: Int
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
    func introduce() {
        print("Hi, I'm \(name), \(age) years old")
    }
}
// Struct definition — automatically gets a memberwise initializer
struct PersonStruct {
    var name: String
    var age: Int
    func introduce() {
        print("Hi, I'm \(name), \(age) years old")
    }
}
let p1 = PersonClass(name: "Alice", age: 28)
p1.introduce()
let p2 = PersonStruct(name: "Bob", age: 22)  // Auto-generated init
p2.introduce()

出力:

TEXT 📖 参照専用
Hi, I'm Alice, 28 years old
Hi, I'm Bob, 22 years old

4. 値型 vs 参照型

(1) 核心的な違い

100%
graph LR
    subgraph "値型 (struct)"
        A["var a = Point(x: 1, y: 2)"]
        B["var b = a"]
        C["b.x = 5"]
        D["a.x → 1 (独立)"]
        A --> B --> C --> D
    end
    subgraph "参照型 (class)"
        E["var a = Point(x: 1, y: 2)"]
        F["var b = a"]
        G["b.x = 5"]
        H["a.x → 5 (共有)"]
        E --> F --> G --> H
    end
観点 値型 (struct) 参照型 (class)
代入のセマンティクス 内容をコピー 参照をコピー
等価性チェック == が値を比較 === が同一性を比較
格納場所 スタック(高速) ヒープ(ARC管理)
let定数 プロパティが不変 参照は不変だがプロパティは変更可能
関数パラメータ デフォルトでコピー デフォルトで参照を共有

▶ サンプル: 代入動作の比較

SWIFT
// ============================================
// Value type vs reference type assignment behavior
// ============================================
// Struct — value type
struct Position {
    var x: Int
    var y: Int
}
var posA = Position(x: 10, y: 20)
var posB = posA
posB.x = 99
print("posA: (\(posA.x), \(posA.y))")  // Unchanged
print("posB: (\(posB.x), \(posB.y))")  // Only affects posB
// Class — reference type
class Location {
    var x: Int
    var y: Int
    init(x: Int, y: Int) { self.x = x; self.y = y }
}
var locA = Location(x: 10, y: 20)
var locB = locA
locB.x = 99
print("locA: (\(locA.x), \(locA.y))")  // Also changed!
print("locB: (\(locB.x), \(locB.y))")

出力:

TEXT 📖 参照専用
posA: (10, 20)
posB: (99, 20)
locA: (99, 20)
locB: (99, 20)

(2) 同一性演算子 ===

参照型は === を使って2つの変数が同じインスタンスを指しているかをチェックできます:

SWIFT
let loc1 = Location(x: 5, y: 10)
let loc2 = loc1
let loc3 = Location(x: 5, y: 10)  // Different instance
print(loc1 === loc2)  // true — same instance
print(loc1 === loc3)  // false — different instances

ヒント: === は同一性演算子で、==(等価性演算子)とは異なります。値型は == のみ使用可能(Equatableを実装する必要あり)で、=== は使用できません。


5. mutatingメソッドと選択戦略

(1) mutating キーワード

構造体のインスタンスメソッドはデフォルトではプロパティを変更できません。変更するには mutating をマークする必要があります:

メソッドタイプ class struct(非mutating) struct(mutating)
プロパテ��変更 許可 コンパイルエラー 許可
self変更 許可 不可 許可
呼び出し要件 制限なし let定数で呼び出し可能 varで呼び出す必要あり

▶ サンプル: mutatingメソッド

SWIFT
// ============================================
// Struct mutating methods
// ============================================
struct Counter {
    var count = 0
    // Non-mutating — read-only method
    func display() {
        print("Count: \(count)")
    }
    // mutating — modifies property
    mutating func increment() {
        count += 1
    }
    // mutating — replaces the entire instance
    mutating func reset() {
        self = Counter()  // Replace self
    }
}
var counter = Counter()  // Must be var to call mutating methods
counter.increment()
counter.increment()
counter.display()   // Count: 2
counter.reset()
counter.display()   // Count: 0

出力:

TEXT 📖 参照専用
Count: 2
Count: 0

(2) 選択戦略

シナリオ 推奨 理由
データモデル(Point, Size, User) struct 値セマンティクス、安全で予測可能
継承が必要(UIViewサブクラス, BaseViewController) class 継承はクラスのみ
共有可変状態が必要 class 参照型は自動的に共有
ObjC相互運用が必要 class @objc はクラスが必要
軽量ラッパー(少数の値をラップ) struct スタック割り当て、オーバーヘッドゼロ
deinitクリーンアップが必要 class structにdeinitはない

▶ サンプル: モデルに構造体をデフォルトで使う

SWIFT
// ============================================
// Why modern Swift prefers struct for models
// ============================================
struct User {
    let id: Int
    var name: String
    var email: String
    var score: Int
    mutating func addScore(_ points: Int) {
        score += points
    }
}
var user = User(id: 1, name: "Alice", email: "alice@example.com", score: 100)
user.addScore(50)
// Copy-and-mutate pattern — safely create changed versions
let admin = User(id: 2, name: "Admin", email: "admin@example.com", score: 0)
// admin.addScore(10)  // Compile error! let constant cannot call mutating methods
var editor = admin
editor.name = "Editor"  // Independent modification, doesn't affect admin

出力:

TEXT 📖 参照専用
(No console output — demonstrates value semantics: editor.name = "Editor" does not affect admin.name)

6. 完全な例:銀行口座システム

SWIFT
// ============================================
// Complete example: Bank account system
// Features: class for account management, struct for transaction records
// ============================================
import Foundation
// 1. Transaction record — value type struct
struct Transaction {
    let id: Int
    let amount: Double
    let type: String  // "deposit" or "withdraw"
    let date: Date
    func description() -> String {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd"
        return "[\(formatter.string(from: date))] \(type): $\(amount)"
    }
}
// 2. Account — reference type class (shared state)
class BankAccount {
    let accountNumber: String
    var balance: Double
    private var transactions: [Transaction] = []
    private var nextId = 1
    init(accountNumber: String, initialBalance: Double = 0) {
        self.accountNumber = accountNumber
        self.balance = initialBalance
    }
    func deposit(amount: Double) {
        guard amount > 0 else { return }
        balance += amount
        let tx = Transaction(id: nextId, amount: amount, type: "deposit", date: Date())
        transactions.append(tx)
        nextId += 1
    }
    func withdraw(amount: Double) -> Bool {
        guard amount > 0 && amount <= balance else { return false }
        balance -= amount
        let tx = Transaction(id: nextId, amount: amount, type: "withdraw", date: Date())
        transactions.append(tx)
        nextId += 1
        return true
    }
    func printStatement() {
        print("=== Account \(accountNumber) ===")
        print("Balance: $\(balance)")
        print("Transactions:")
        for tx in transactions {
            print("  \(tx.description())")
        }
    }
}
// 3. Usage
let account = BankAccount(accountNumber: "ACC-001", initialBalance: 1000)
account.deposit(amount: 500)
account.withdraw(amount: 200)
account.deposit(amount: 100)
account.printStatement()

出力:

TEXT 📖 参照専用
=== Account ACC-001 ===
Balance: $1400.0
Transactions:
  [2026-07-30] deposit: $500.0
  [2026-07-30] withdraw: $200.0
  [2026-07-30] deposit: $100.0

❓ よくある質問

Q Appleが構造体を優先するよう推奨する理由は?
A 値型はより安全で予測可能です。共有参照による副作用がなく、循環参照もなく、参照カウントのオーバーヘッドもありません。String、Array、DictionaryなどのSwift標準ライブラリの型はすべて構造体です。
Q 構造体にはパフォーマンス上の利点がありますか?
A はい。構造体はスタックに割り当てられ、参照カウントのオーバーヘッドがなく、ヒープ割り当てのコストもあり��せん。ただし、大きな構造体を関数間で受け渡すと、参照を渡す場合よりもコピーコストが高くなる可能性があります。Swiftのコピーオンライト機構がこれを最適化します。
Q クラスのinitは常に手動で書く必要がありますか?
A クラスの場合、すべてのプロパティにデフォルト値があり、イニシャライザが定義されていない場合、Swiftはデフォルトの引数なしinitを生成します。しかし、構造体のようにメンバーワイズinitが自動生成されることは決してありません。
Q 構造体にlazyプロパティは持てますか?
A はい。lazy var は構造体でも動作しますが、lazyプロパティは不変の構造体でアクセスできないため、実用的な使用は制限されます。lazyはクラスでより自然です。
Q mutatingはクラスで使えますか?
A いいえ。mutating は値型(構造体と列挙型)にのみ適用されます。クラスのメソッドはデフォルトでプロパティを変更できます — mutating キーワードは不要です。

📖 まとめ


📝 練習問題

  1. 基本: titleauthorpages プロパティと summary() メソッドを持つ Book 構造体を定義してください。2つのインスタンスを作成し、値型のコピー動作をテストしてください。
  2. 中級: currentTemp プロパティと readings: [Double] 履歴配列を持つ TemperatureSensor クラスを定義して��ださい。recordTemp(_:)average() メソッドを実装してください。インスタンスを作成し、5つの温度を記録し、平均を出力してください。
  3. 発展: 「チームコラボレーションシステム」を設計してください:共有メンバーリストを管理する Team クラスと、個別プロファイル用の Member 構造体を使用します。TeamaddMember(_:)listMembers() メソッドを提供します。Member はロールを更新する mutating メソッドを持ちます。クラスの共��参照動作を検証してください。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%