Swiftのmutatingって何?基本を理解しよう
Swift プログラミングで出てくる mutating
キーワード。初心者の方には「なぜ必要なの?」と疑問に思うかもしれません。
一言で言うと 「構造体(struct)のメソッドが、自分自身のプロパティを変更する時に必要なキーワード」です。
mutating は「変更する」「変異させる」という意味の英語で、Swiftでは構造体が自分自身を変更することを明示的に示すために使われます。
なぜmutatingが必要なの?
まず、なぜSwiftにこのようなキーワードが必要なのか、理解しましょう。
Swiftの値型と参照型
Swiftには2種類のデータ型があります。
値型(Value Type)
- 構造体(struct)
- 列挙型(enum)
- コピーが作られる
参照型(Reference Type)
- クラス(class)
- メモリ上の同じ場所を参照する
構造体の特徴:デフォルトで変更不可
Swiftの構造体は、安全性を保つためにデフォルトでプロパティを変更できません。
struct Point {
var x: Int
var y: Int
// エラー!構造体のメソッドはデフォルトで自身を変更できない
func moveRight() {
x += 1 // ❌ コンパイルエラー
}
}
このエラーが出るのは、Swiftが「意図しない変更」を防ぐためです。
mutatingで変更を明示する
mutating
キーワードを付けることで、「このメソッドは自分自身を変更します」と明示的に宣言できます。
struct Point {
var x: Int
var y: Int
// ✅ mutatingを付ければOK
mutating func moveRight() {
x += 1 // 変更できる
}
}
日常生活で理解するmutating
例1:ノートに書き込む
mutating(変更する)
- 既存のノートに直接書き込む
- ノートそのものが変わる
- ノートは同じだが、内容が変わる
non-mutating(変更しない)
- 元のノートはそのまま
- 新しいページやノートに書く
- 元のノートは変わらない
例2:銀行口座の残高
mutating(変更する)
// 口座残高を直接変更
口座.残高 = 口座.残高 + 1000円
non-mutating(変更しない)
// 新しい口座情報を作成
新しい口座 = 口座をコピーして残高を1000円増やしたもの
Swiftの構造体では、どちらの動作をするのか明確にする必要があります。それが mutating
キーワードの役割です。
mutatingの基本的な使い方
例1:座標を表す構造体
struct Point {
var x: Int
var y: Int
// mutatingメソッド:自分自身を変更
mutating func move(x deltaX: Int, y deltaY: Int) {
x += deltaX
y += deltaY
}
// non-mutatingメソッド:新しいインスタンスを返す
func moved(x deltaX: Int, y deltaY: Int) -> Point {
return Point(x: x + deltaX, y: y + deltaY)
}
}
// 使い方
var point = Point(x: 0, y: 0)
// mutatingメソッドを使用
point.move(x: 5, y: 10)
print(point) // Point(x: 5, y: 10) - 元の値が変わる
// non-mutatingメソッドを使用
let newPoint = point.moved(x: 3, y: 3)
print(point) // Point(x: 5, y: 10) - 元は変わらない
print(newPoint) // Point(x: 8, y: 13) - 新しいインスタンス
例2:銀行口座の構造体
struct BankAccount {
var balance: Double
var accountNumber: String
// mutatingメソッド:残高を直接変更
mutating func deposit(amount: Double) {
if amount > 0 {
balance += amount
print("入金成功: \(amount)円")
}
}
mutating func withdraw(amount: Double) -> Bool {
if amount > 0 && balance >= amount {
balance -= amount
print("出金成功: \(amount)円")
return true
}
print("出金失敗: 残高不足")
return false
}
// non-mutatingメソッド:情報を取得するだけ
func getBalance() -> Double {
return balance
}
func displayInfo() {
print("口座番号: \(accountNumber)")
print("残高: \(balance)円")
}
}
// 使い方
var account = BankAccount(balance: 10000, accountNumber: "123-456")
account.deposit(amount: 5000) // 入金成功: 5000円
print(account.balance) // 15000.0
account.withdraw(amount: 3000) // 出金成功: 3000円
print(account.balance) // 12000.0
account.displayInfo()
// 出力:
// 口座番号: 123-456
// 残高: 12000.0円
varとletの違いとmutating
mutatingメソッドを呼び出すには、インスタンスが var
で宣言されている必要があります。
var で宣言(変更可能)
var point = Point(x: 0, y: 0)
point.move(x: 5, y: 10) // ✅ OK - varなので変更できる
print(point) // Point(x: 5, y: 10)
let で宣言(変更不可)
let point = Point(x: 0, y: 0)
point.move(x: 5, y: 10) // ❌ エラー!letは変更できない
エラーメッセージ
error: cannot use mutating member on immutable value: 'point' is a 'let' constant
理由
let
は定数を意味する- 定数は変更できない
- mutatingメソッドは変更を行うため、
let
では呼び出せない
non-mutatingメソッドはletでもOK
let point = Point(x: 0, y: 0)
let newPoint = point.moved(x: 5, y: 10) // ✅ OK
print(point) // Point(x: 0, y: 0) - 元は変わらない
print(newPoint) // Point(x: 5, y: 10)
mutatingが必要な場面
場面1:プロパティの値を変更する
struct Counter {
var count: Int = 0
// countを変更するのでmutatingが必要
mutating func increment() {
count += 1
}
mutating func decrement() {
count -= 1
}
mutating func reset() {
count = 0
}
}
var counter = Counter()
counter.increment() // count = 1
counter.increment() // count = 2
counter.decrement() // count = 1
counter.reset() // count = 0
場面2:複数のプロパティを変更する
struct Rectangle {
var width: Double
var height: Double
// 複数のプロパティを変更
mutating func scale(by factor: Double) {
width *= factor
height *= factor
}
// 面積の計算(変更しないのでmutating不要)
func area() -> Double {
return width * height
}
}
var rect = Rectangle(width: 10, height: 20)
print(rect.area()) // 200.0
rect.scale(by: 2.0)
print(rect) // Rectangle(width: 20.0, height: 40.0)
print(rect.area()) // 800.0
場面3:自分自身を完全に置き換える
struct Player {
var name: String
var score: Int
var level: Int
// 自分自身を新しいインスタンスで置き換える
mutating func levelUp() {
self = Player(name: name, score: score, level: level + 1)
print("\(name)がレベルアップ!レベル\(level)になりました")
}
mutating func addScore(_ points: Int) {
score += points
// 1000点ごとにレベルアップ
if score >= level * 1000 {
levelUp()
}
}
}
var player = Player(name: "太郎", score: 0, level: 1)
player.addScore(500) // score = 500
player.addScore(600) // score = 1100, レベルアップ!
// 出力: 太郎がレベルアップ!レベル2になりました
mutatingが不要な場面
場面1:プロパティを読み取るだけ
struct Book {
var title: String
var author: String
var pages: Int
// 読み取るだけなのでmutating不要
func getInfo() -> String {
return "\(title) by \(author) (\(pages)ページ)"
}
func isLongBook() -> Bool {
return pages > 300
}
}
let book = Book(title: "Swiftの本", author: "山田太郎", pages: 450)
print(book.getInfo()) // Swiftの本 by 山田太郎 (450ページ)
print(book.isLongBook()) // true
場面2:計算結果を返すだけ
struct Circle {
var radius: Double
// 計算するだけ(変更しない)
func area() -> Double {
return Double.pi * radius * radius
}
func circumference() -> Double {
return 2 * Double.pi * radius
}
func diameter() -> Double {
return radius * 2
}
}
let circle = Circle(radius: 5.0)
print(circle.area()) // 78.53981633974483
print(circle.circumference()) // 31.41592653589793
print(circle.diameter()) // 10.0
クラスではmutatingが不要な理由
クラスは参照型なので、mutating
キーワードは必要ありません。
class PointClass {
var x: Int
var y: Int
init(x: Int, y: Int) {
self.x = x
self.y = y
}
// mutating不要!クラスのメソッドは自由にプロパティを変更できる
func move(x deltaX: Int, y deltaY: Int) {
x += deltaX
y += deltaY
}
}
let pointClass = PointClass(x: 0, y: 0)
pointClass.move(x: 5, y: 10) // letでも変更できる
print(pointClass.x, pointClass.y) // 5 10
なぜletでも変更できる?
- クラスは参照型
let
は「参照先を変更できない」という意味- 参照先のオブジェクトの中身は変更できる
structとclassの比較
特徴 | struct(構造体) | class(クラス) |
---|---|---|
型 | 値型 | 参照型 |
mutating | 必要 | 不要 |
let での変更 | 不可 | 可能(中身のみ) |
コピー | 完全なコピー | 参照のコピー |
継承 | 不可 | 可能 |
// 構造体の例
struct StructPoint {
var x: Int
var y: Int
mutating func moveRight() {
x += 1
}
}
var sp1 = StructPoint(x: 0, y: 0)
var sp2 = sp1 // 完全なコピーが作られる
sp2.moveRight()
print(sp1.x) // 0 - sp1は変わらない
print(sp2.x) // 1
// クラスの例
class ClassPoint {
var x: Int
var y: Int
init(x: Int, y: Int) {
self.x = x
self.y = y
}
func moveRight() {
x += 1
}
}
let cp1 = ClassPoint(x: 0, y: 0)
let cp2 = cp1 // 参照のコピー(同じオブジェクトを指す)
cp2.moveRight()
print(cp1.x) // 1 - cp1も変わる
print(cp2.x) // 1
実践的なmutatingの使用例
例1:ショッピングカート
struct ShoppingCart {
private var items: [Item] = []
private var discountRate: Double = 0.0
struct Item {
let name: String
let price: Double
var quantity: Int
}
// 商品を追加(mutating)
mutating func addItem(name: String, price: Double, quantity: Int) {
if let index = items.firstIndex(where: { $0.name == name }) {
// 既にある商品なら数量を増やす
items[index].quantity += quantity
} else {
// 新しい商品を追加
items.append(Item(name: name, price: price, quantity: quantity))
}
print("\(name) x \(quantity)を追加しました")
}
// 商品を削除(mutating)
mutating func removeItem(name: String) {
items.removeAll { $0.name == name }
print("\(name)を削除しました")
}
// 割引を適用(mutating)
mutating func applyDiscount(_ rate: Double) {
if rate >= 0 && rate <= 1 {
discountRate = rate
print("\(Int(rate * 100))%の割引を適用しました")
}
}
// カートをクリア(mutating)
mutating func clear() {
items.removeAll()
discountRate = 0.0
print("カートを空にしました")
}
// 合計金額を計算(non-mutating)
func totalPrice() -> Double {
let subtotal = items.reduce(0.0) { $0 + ($1.price * Double($1.quantity)) }
return subtotal * (1.0 - discountRate)
}
// カートの内容を表示(non-mutating)
func displayItems() {
print("=== カートの内容 ===")
for item in items {
print("\(item.name): \(item.price)円 x \(item.quantity)")
}
if discountRate > 0 {
print("割引: \(Int(discountRate * 100))%")
}
print("合計: \(totalPrice())円")
print("==================")
}
}
// 使い方
var cart = ShoppingCart()
cart.addItem(name: "リンゴ", price: 150, quantity: 3)
// 出力: リンゴ x 3を追加しました
cart.addItem(name: "バナナ", price: 100, quantity: 5)
// 出力: バナナ x 5を追加しました
cart.applyDiscount(0.1)
// 出力: 10%の割引を適用しました
cart.displayItems()
// 出力:
// === カートの内容 ===
// リンゴ: 150.0円 x 3
// バナナ: 100.0円 x 5
// 割引: 10%
// 合計: 855.0円
// ==================
cart.removeItem(name: "リンゴ")
// 出力: リンゴを削除しました
cart.displayItems()
例2:タイマー機能
struct Timer {
private var seconds: Int = 0
private var isRunning: Bool = false
// タイマーを開始(mutating)
mutating func start() {
if !isRunning {
isRunning = true
print("タイマーを開始しました")
} else {
print("タイマーは既に動作中です")
}
}
// タイマーを停止(mutating)
mutating func stop() {
if isRunning {
isRunning = false
print("タイマーを停止しました")
} else {
print("タイマーは停止しています")
}
}
// 1秒経過(mutating)
mutating func tick() {
if isRunning {
seconds += 1
}
}
// リセット(mutating)
mutating func reset() {
seconds = 0
isRunning = false
print("タイマーをリセットしました")
}
// 時間を取得(non-mutating)
func getFormattedTime() -> String {
let hours = seconds / 3600
let minutes = (seconds % 3600) / 60
let secs = seconds % 60
return String(format: "%02d:%02d:%02d", hours, minutes, secs)
}
// 秒数を取得(non-mutating)
func getSeconds() -> Int {
return seconds
}
// 動作状態を確認(non-mutating)
func running() -> Bool {
return isRunning
}
}
// 使い方
var timer = Timer()
timer.start()
// 出力: タイマーを開始しました
// 5秒経過をシミュレート
for _ in 1...5 {
timer.tick()
}
print(timer.getFormattedTime())
// 出力: 00:00:05
timer.stop()
// 出力: タイマーを停止しました
timer.reset()
// 出力: タイマーをリセットしました
例3:ゲームのキャラクター
struct Character {
var name: String
private var health: Int
private var maxHealth: Int
private var attackPower: Int
private var level: Int
init(name: String) {
self.name = name
self.health = 100
self.maxHealth = 100
self.attackPower = 10
self.level = 1
}
// ダメージを受ける(mutating)
mutating func takeDamage(_ damage: Int) {
health -= damage
if health < 0 {
health = 0
}
print("\(name)は\(damage)のダメージを受けた!残りHP: \(health)")
if health == 0 {
print("\(name)は倒れた...")
}
}
// 回復する(mutating)
mutating func heal(_ amount: Int) {
health += amount
if health > maxHealth {
health = maxHealth
}
print("\(name)は\(amount)回復した!HP: \(health)")
}
// レベルアップ(mutating)
mutating func levelUp() {
level += 1
maxHealth += 20
health = maxHealth
attackPower += 5
print("\(name)はレベルアップした!レベル\(level)!")
print("最大HP: \(maxHealth), 攻撃力: \(attackPower)")
}
// 攻撃する(non-mutating、相手にダメージを与える)
func attack(_ target: inout Character) {
print("\(name)の攻撃!")
target.takeDamage(attackPower)
}
// ステータス表示(non-mutating)
func displayStatus() {
print("=== \(name) ===")
print("レベル: \(level)")
print("HP: \(health)/\(maxHealth)")
print("攻撃力: \(attackPower)")
print("==============")
}
// 生存確認(non-mutating)
func isAlive() -> Bool {
return health > 0
}
}
// 使い方
var hero = Character(name: "勇者")
var monster = Character(name: "スライム")
hero.displayStatus()
// 出力:
// === 勇者 ===
// レベル: 1
// HP: 100/100
// 攻撃力: 10
// ==============
hero.attack(&monster)
// 出力:
// 勇者の攻撃!
// スライムは10のダメージを受けた!残りHP: 90
monster.attack(&hero)
// 出力:
// スライムの攻撃!
// 勇者は10のダメージを受けた!残りHP: 90
hero.heal(20)
// 出力: 勇者は20回復した!HP: 100
hero.levelUp()
// 出力:
// 勇者はレベルアップした!レベル2!
// 最大HP: 120, 攻撃力: 15
mutatingを使う際の注意点
注意点1:letでは呼び出せない
let point = Point(x: 0, y: 0)
// point.move(x: 5, y: 10) // ❌ エラー
// 解決策1: varで宣言する
var mutablePoint = Point(x: 0, y: 0)
mutablePoint.move(x: 5, y: 10) // ✅ OK
// 解決策2: non-mutatingメソッドを使う
let newPoint = point.moved(x: 5, y: 10) // ✅ OK
注意点2:計算プロパティのsetterはmutating
struct Temperature {
var celsius: Double
var fahrenheit: Double {
get {
return celsius * 9 / 5 + 32
}
// setterは暗黙的にmutating
set {
celsius = (newValue - 32) * 5 / 9
}
}
}
var temp = Temperature(celsius: 0)
print(temp.fahrenheit) // 32.0
temp.fahrenheit = 100 // setterを呼び出す(mutating)
print(temp.celsius) // 37.77...
注意点3:配列や辞書のメソッドもmutating
struct TodoList {
var items: [String] = []
// 配列を変更するのでmutating
mutating func add(_ item: String) {
items.append(item) // append()はmutating
}
mutating func remove(at index: Int) {
items.remove(at: index) // remove()はmutating
}
mutating func clear() {
items.removeAll() // removeAll()はmutating
}
}
よくある質問(FAQ)
Q1:いつmutatingを付けるべきですか?
A:プロパティを変更する時は必ずmutatingを付けます
✅ mutatingが必要
- プロパティの値を変更する
- 配列や辞書に要素を追加・削除する
self
を置き換える
❌ mutatingが不要
- プロパティを読み取るだけ
- 計算して結果を返すだけ
- 新しいインスタンスを作って返す
Q2:クラスでもmutatingは使えますか?
A:いいえ、クラスではmutatingは使えません(そして不要です)
class MyClass {
var value: Int = 0
// mutatingは付けられない(エラーになる)
func increment() {
value += 1 // クラスなら自由に変更できる
}
}
Q3:mutatingメソッドの中でnon-mutatingメソッドを呼べますか?
A:はい、呼べます
struct Counter {
var count: Int = 0
func getCurrentCount() -> Int {
return count
}
mutating func incrementAndPrint() {
count += 1
// non-mutatingメソッドを呼び出せる
print("現在のカウント: \(getCurrentCount())")
}
}
Q4:プロトコルでmutatingを指定できますか?
A:はい、できます
protocol Incrementable {
mutating func increment()
}
struct Counter: Incrementable {
var value: Int = 0
mutating func increment() {
value += 1
}
}
class CounterClass: Incrementable {
var value: Int = 0
// クラスの場合はmutatingを付けない
func increment() {
value += 1
}
}
Q5:inout パラメータとmutatingの違いは?
A:用途が異なります
- mutating:自分自身(self)を変更する
- inout:引数として渡された外部の値を変更する
struct Point {
var x: Int
var y: Int
// mutating: 自分自身を変更
mutating func move(x deltaX: Int, y deltaY: Int) {
self.x += deltaX
self.y += deltaY
}
// inout: 引数を変更
func moveOther(_ other: inout Point, x deltaX: Int, y deltaY: Int) {
other.x += deltaX
other.y += deltaY
}
}
まとめ:mutatingキーワードをマスターしよう
Swiftの mutating
キーワードは、構造体の安全性を保ちながら変更を可能にする重要な機能です。
重要ポイントまとめ
mutating
は構造体のメソッドが自身を変更する時に必要- クラスでは
mutating
は不要(参照型のため) var
で宣言されたインスタンスでのみ呼び出し可能let
で宣言されたインスタンスでは呼び出せない- プロパティを読み取るだけなら
mutating
は不要 - Swiftの値型の安全性を保つための仕組み
使い分けのガイドライン
動作 | キーワード | 使用例 |
---|---|---|
プロパティを変更 | mutating | mutating func increment() |
値を読み取るだけ | 不要 | func getValue() -> Int |
新しいインスタンスを返す | 不要 | func moved() -> Point |
クラスのメソッド | 不要 | func update() |
初心者へのアドバイス
- まずは
mutating
を付けずに書いてみる - コンパイルエラーが出たら
mutating
を追加 var
とlet
の違いを意識する- 必要に応じて non-mutating 版も用意する
- 実際にコードを書いて動作を確認する
Swiftの mutating
キーワードを理解することで、より安全で予測可能なコードが書けるようになります。値型と参照型の違いを理解し、適切に mutating
を使いこなせるSwiftプログラマーを目指しましょう!