読み込み中...

Swiftで知るべき15のmutatingメソッドとその使い方

Swiftのmutatingメソッドのイラスト付き解説 Swift
この記事は約28分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

Swift言語を学ぶ上で欠かせない要素のひとつに、mutatingメソッドがあります。

mutatingというキーワードは一見難しそうに感じるかもしれませんが、この記事を読めば、Swiftのmutatingメソッドの基本から実用的な使い方、そして注意点までをしっかりと理解することができるようになります。

●Swiftのmutatingメソッドとは

Swiftのmutatingメソッドは、構造体や列挙型のインスタンスメソッドの中で、インスタンス自体やそのプロパティの変更を可能にする特殊なキーワードです。

クラスにおいては、インスタンス変数を変更するのは通常のメソッドでも可能ですが、構造体や列挙型ではインスタンスメソッド内での変更が制限されています。

このため、Swiftはこの制限を解除するためのmutatingキーワードを提供しています。

○mutatingメソッドの基本

mutatingメソッドは、特に構造体や列挙型でそのインスタンス自身やプロパティの値を変更したいときに使用します。

このmutatingキーワードを使うことで、構造体や列挙型のメソッド内で、selfやプロパティの変更が可能となります。

また、mutatingメソッド内で新しいインスタンスを生成して、そのインスタンスで元のインスタンスを置き換えることもできます。

例えば、整数をプロパティに持つ構造体があり、その整数をインクリメントするメソッドを実装したい場合、次のようにmutatingメソッドを使って実装することができます。

struct Counter {
    var value: Int = 0

    mutating func increment() {
        value += 1
    }
}

var myCounter = Counter()
myCounter.increment()
print(myCounter.value)  // このコードを実行すると、結果は1と表示されます。

このコードでは、Counterという構造体を定義しています。

そして、その構造体内にincrementというmutatingメソッドを定義しています。

このメソッドが呼ばれると、valueプロパティの値が1増加します。

このように、mutatingメソッドを使用することで、構造体のインスタンス自体やそのプロパティの値を変更することができます。

なお、このコードを実行すると、myCounter.valueの結果は1と表示されます。

これは、incrementメソッドが呼び出された後、valueプロパティの値が1増加したためです。

●mutatingメソッドの使い方

Swiftのmutatingメソッドは、特に構造体や列挙型で活躍します。

しかし、実際のコード上でどのように使われるのでしょうか?

ここでは、実際のコードを交えてその使い方を詳しくご紹介します。

○サンプルコード1:mutatingメソッドを使った簡単な構造体の変更

このコードでは、シンプルな構造体を定義し、その構造体の値を増加させるmutatingメソッドを実装しています。

struct SimpleCounter {
    var number: Int = 0

    mutating func addOne() {
        number += 1
    }
}

var counter = SimpleCounter()
counter.addOne()
print(counter.number)

このコードを実行すると、counter.numberの値は1となります。

最初の値が0から、addOneメソッドによって1が加算されたからです。

○サンプルコード2:mutatingメソッドと非mutatingメソッドの違い

次に、mutatingメソッドと非mutatingメソッドの違いについて見ていきましょう。

非mutatingメソッドは、インスタンスやプロパティの値を変更しないメソッドのことを指します。

struct Square {
    var sideLength: Double

    mutating func doubleSize() {
        sideLength *= 2
    }

    func calculateArea() -> Double {
        return sideLength * sideLength
    }
}

var mySquare = Square(sideLength: 3.0)
mySquare.doubleSize()
print(mySquare.sideLength)  // 6.0
print(mySquare.calculateArea())  // 36.0

このコードでは、Squareという構造体が定義されています。

その中にdoubleSizeというmutatingメソッドと、calculateAreaという非mutatingメソッドがあります。

doubleSizeメソッドは、sideLengthの値を2倍にし、calculateAreaメソッドは面積を計算して返します。

このコードを実行すると、最初のsideLengthの値は3.0ですが、doubleSizeメソッドによって6.0となります。

また、面積を計算すると36.0となります。このように、mutatingメソッドは構造体のプロパティの値を変更することができますが、非mutatingメソッドはその値を変更することはできません。

○サンプルコード3:配列や辞書の要素を変更する

Swiftのmutatingメソッドは、配列や辞書といったコレクション型の要素を変更する場面でも非常に有効です。

特に、値型である配列や辞書に対して変更を加える場合、mutatingメソッドの活用が推奨されます。

このコードでは、配列の要素を変更するための独自のメソッドを実装しています。

struct CustomArray {
    var numbers: [Int]

    mutating func multiply(by value: Int) {
        for index in numbers.indices {
            numbers[index] *= value
        }
    }
}

var myArray = CustomArray(numbers: [1, 2, 3, 4])
myArray.multiply(by: 2)
print(myArray.numbers)  // [2, 4, 6, 8]

このコードを実行すると、myArray.numbersの要素は元々の[1, 2, 3, 4]から、multiply(by:)メソッドによって各要素が2倍された[2, 4, 6, 8]となります。

次に、辞書の要素を変更するサンプルも見てみましょう。

struct CustomDictionary {
    var wordCount: [String: Int]

    mutating func addCount(for word: String) {
        if let count = wordCount[word] {
            wordCount[word] = count + 1
        } else {
            wordCount[word] = 1
        }
    }
}

var myDictionary = CustomDictionary(wordCount: ["apple": 1, "banana": 2])
myDictionary.addCount(for: "apple")
print(myDictionary.wordCount)  // ["apple": 2, "banana": 2]

このコードでは、CustomDictionaryという構造体内に、辞書の特定のキーの値を増やすaddCount(for:)というmutatingメソッドを実装しています。

コードを実行すると、最初は"apple": 1でしたが、メソッドを実行後は"apple": 2となります。

これにより、指定したキーの値を増やすことができました。

○サンプルコード4:mutatingメソッドを使用した計算プロパティの設定

計算プロパティは、実際の値を持たず、計算によって値を返すプロパティのことを指します。

この計算プロパティの中でも、mutatingメソッドを用いて値を設定することができます。

struct Rectangle {
    var width: Double
    var height: Double
    var area: Double {
        get {
            return width * height
        }
        mutating set(newArea) {
            width = sqrt(newArea)
            height = newArea / width
        }
    }
}

var myRectangle = Rectangle(width: 2.0, height: 3.0)
print(myRectangle.area)  // 6.0
myRectangle.area = 16.0
print(myRectangle.width)  // 4.0
print(myRectangle.height)  // 4.0

このコードを実行すると、初めにmyRectangle.areaは6.0と表示されます。

その後、myRectangle.areaに新しい値として16.0を設定します。その結果、widthheightの値もそれぞれ4.0となります。

これにより、面積を変更することで、縦と横の長さも変更することができました。

●mutatingメソッドの応用例

Swiftのmutatingメソッドは、基本的な使い方だけでなく、様々な応用シーンでもその真価を発揮します。

ここでは、複雑なデータ構造の更新や外部ライブラリとの組み合わせといった、実際の開発での応用例をいくつか取り上げて解説します。

○サンプルコード5:複雑なデータ構造の更新

このコードでは、ネストされた構造体内の値をmutatingメソッドを利用して更新しています。

struct Company {
    var name: String
    var employee: Employee

    struct Employee {
        var name: String
        var age: Int
        mutating func updateAge(to newAge: Int) {
            self.age = newAge
        }
    }

    mutating func updateEmployeeAge(to newAge: Int) {
        employee.updateAge(to: newAge)
    }
}

var techCompany = Company(name: "TechCorp", employee: Company.Employee(name: "Taro", age: 25))
techCompany.updateEmployeeAge(to: 26)
print(techCompany.employee.age)  // 26

このコードを実行すると、Taroの年齢が25から26に更新されることが確認できます。

○サンプルコード6:外部ライブラリとの組み合わせ

Swiftのライブラリやフレームワークの中には、mutatingメソッドを活用しているものも多いです。

ここでは、外部ライブラリとmutatingメソッドの組み合わせを示す簡単な例を紹介します。

想像してみてください。あるライブラリが提供するVector型があるとします。

このVector型には、ベクトルの大きさを変更するmutatingメソッドが提供されていると仮定します。

import ExternalLibrary  // 外部ライブラリの仮定

var v = Vector(x: 3.0, y: 4.0)
v.scale(by: 2.0)
print(v.length)  // 10.0

このコードでは、Vector型のベクトルvの長さが、scale(by:)メソッドによって2倍になります。

そして、その結果としてv.lengthは10.0と表示されます。

○サンプルコード7:mutatingメソッドを用いた動的な設定変更

Swiftのmutatingメソッドを使用することで、オブジェクトの内部の値や設定を動的に変更することができます。こ

れは、特に設定や状態の管理が必要な場面で非常に有効です。

ここでは、動的な設定変更を目的としたmutatingメソッドの一例を紹介します。

想像してみてください。

アプリケーションの設定やユーザーのプロフィールなどのデータを管理するための構造体があります。

この構造体の中には、ユーザーの名前や年齢、言語設定などの情報が含まれています。

そして、この情報はアプリケーションの動作中に変更されることがあるでしょう。

ここでは、そのような設定情報を管理する構造体と、その情報を変更するためのmutatingメソッドのサンプルコードを紹介します。

struct UserProfile {
    var name: String
    var age: Int
    var language: String

    // 名前を変更するメソッド
    mutating func changeName(to newName: String) {
        self.name = newName
    }

    // 年齢を変更するメソッド
    mutating func changeAge(to newAge: Int) {
        self.age = newAge
    }

    // 言語設定を変更するメソッド
    mutating func changeLanguage(to newLanguage: String) {
        self.language = newLanguage
    }
}

var user = UserProfile(name: "Taro", age: 30, language: "Japanese")
user.changeName(to: "Jiro")
user.changeAge(to: 31)
user.changeLanguage(to: "English")

print(user.name)  // Jiro
print(user.age)   // 31
print(user.language)  // English

このコードでは、UserProfileという構造体に3つのmutatingメソッドが定義されています。

それぞれのメソッドは、ユーザーの名前、年齢、言語設定を変更する役割を持っています。

コードを実行すると、userオブジェクトの名前が「Taro」から「Jiro」に、年齢が30から31に、言語設定が「Japanese」から「English」に変更されていることがわかります。

○サンプルコード8:配列や辞書の高度な操作

Swiftの配列や辞書は、mutatingメソッドを用いて様々な高度な操作を行うことができます。

例えば、配列内の特定の要素を取り出して加工したり、辞書内のキーと値を動的に更新することが可能です。

この次のコードは、配列内の整数を指定した倍数に変更するmutatingメソッドと、辞書内の文字列の値を大文字に変更するmutatingメソッドを表しています。

extension Array where Element == Int {
    // 配列内の整数を指定した倍数にする
    mutating func multiply(by factor: Int) {
        for i in 0..<self.count {
            self[i] *= factor
        }
    }
}

extension Dictionary where Value == String {
    // 辞書内の文字列の値を大文字にする
    mutating func makeValuesUppercase() {
        for key in self.keys {
            if let value = self[key] {
                self[key] = value.uppercased()
            }
        }
    }
}

var numbers = [1, 2, 3, 4, 5]
numbers.multiply(by: 3)
print(numbers)  // [3, 6, 9, 12, 15]

var cities = ["jp": "tokyo", "us": "new york", "uk": "london"]
cities.makeValuesUppercase()
print(cities)  // ["jp": "TOKYO", "us": "NEW YORK", "uk": "LONDON"]

このコードでは、ArrayDictionaryに対して拡張機能を使用してmutatingメソッドを追加しています。

それぞれのメソッドを実行すると、配列numbersの各要素は3倍に、辞書citiesの各値は大文字に変更されます。

●注意点と対処法

Swiftでmutatingメソッドを効果的に利用するためには、いくつかの注意点を理解しておくことが必要です。

適切に扱うことで、多くの問題やバグを防ぐことができるでしょう。

ここでは、mutatingメソッドの使用に関連する主要な注意点と、それらの問題を回避または解決するための対処法を紹介します。

○mutatingメソッドを過度に使用するリスク

mutatingメソッドは、構造体や列挙型のインスタンスを変更するための強力なツールですが、過度に使用するとコードの可読性や保守性が低下するリスクがあります。

具体的には、あるインスタンスの状態が頻繁に変わることで、そのインスタンスの現在の状態を追跡するのが難しくなる可能性があります。

また、多くのmutatingメソッドを持つ構造体や列挙型は、その機能や役割が不明瞭になることが考えられます。

struct Counter {
    var count = 0

    mutating func increment() {
        count += 1
    }

    mutating func decrement() {
        count -= 1
    }

    mutating func reset() {
        count = 0
    }

    mutating func add(value: Int) {
        count += value
    }

    mutating func subtract(value: Int) {
        count -= value
    }
}

var myCounter = Counter()
myCounter.increment()
myCounter.add(value: 5)
myCounter.decrement()
myCounter.subtract(value: 2)

print(myCounter.count)  // 3

このコードを実行すると、myCounterのカウント値は3になります。

しかし、多くの変更操作を行っているため、最終的な値がどのように計算されたのかを一目で理解するのは難しいかもしれません。

対処法として、不必要なmutatingメソッドは避け、必要最低限のメソッドのみを実装することが推奨されます。

また、メソッドの命名やコメントを工夫して、その機能や挙動を明確にすることも効果的です。

○参照型と値型の違いとは

Swiftでは、クラスは参照型、構造体や列挙型は値型として扱われます。

これはmutatingメソッドの使用においても重要なポイントとなります。

参照型の場合、オブジェクトの参照が渡されるため、そのオブジェクトの状態を変更することができます。

一方、値型の場合、オブジェクトの実際の値がコピーされて渡されるため、オリジナルのオブジェクトの状態は変更されません。

この違いを理解しておくことで、mutatingメソッドを適切に使用することができます。

struct ValueStruct {
    var value: Int

    mutating func updateValue(newValue: Int) {
        value = newValue
    }
}

class ReferenceClass {
    var value: Int

    init(value: Int) {
        self.value = value
    }

    func updateValue(newValue: Int) {
        value = newValue
    }
}

let valueInstance = ValueStruct(value: 10)
var referenceInstance = ReferenceClass(value: 10)

// 値型の場合、以下のコードはコンパイルエラーとなる
// valueInstance.updateValue(newValue: 20)

// 参照型の場合、オブジェクトの状態を変更できる
referenceInstance.updateValue(newValue: 20)

上記のコードでは、値型のインスタンスでmutatingメソッドを使用する際の制約を表しています。

このような制約を理解し、参照型と値型の違いを意識してコードを書くことが大切です。

○mutatingメソッドとスレッドセーフティ

マルチスレッド環境では、複数のスレッドが同時に同じオブジェクトの状態を変更しようとすると、予期しない動作やバグの原因となる可能性があります。

この問題は、mutatingメソッドを使用する際にも注意が必要です。

mutatingメソッドを使用してオブジェクトの状態を変更する際、同時に複数のスレッドからそのメソッドが呼び出されると、データの不整合や競合状態が発生する可能性があります。

対処法として、mutatingメソッドの実行をスレッドセーフにするための同期処理を導入することが考えられます。

Swiftでは、DispatchQueueNSLockなどのツールを使用して、スレッドセーフな処理を実装することができます。

ここでは、DispatchQueueを使用して、mutatingメソッドの実行をスレッドセーフにするサンプルコードを紹介します。

import Dispatch

struct ThreadSafeCounter {
    private var count = 0
    private let lock = DispatchQueue(label: "com.example.lock")
    
    mutating func increment() {
        lock.sync {
            count += 1
        }
    }
    
    func currentCount() -> Int {
        return lock.sync {
            return count
        }
    }
}

var counter = ThreadSafeCounter()

DispatchQueue.global().async {
    for _ in 1...1000 {
        counter.increment()
    }
}

DispatchQueue.global().async {
    for _ in 1...1000 {
        counter.increment()
    }
}

DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
    print(counter.currentCount())  // 2000
}

このコードでは、ThreadSafeCounter構造体において、incrementメソッドがスレッドセーフに実行されるようにしています。

複数のスレッドから同時にメソッドが呼び出されても、lock.syncを使用することで一度に一つのスレッドのみがメソッド内の処理を実行できるようになっています。

●カスタマイズ方法

mutatingメソッドはSwiftの強力な特徴の一つですが、時には標準の動作だけでは不十分な場合があります。

そんな時、カスタマイズする方法を学ぶことで、更に効果的にmutatingメソッドを使用することができます。

○サンプルコード9:オリジナルのmutatingメソッドの作成

最初に、自分自身のmutatingメソッドを作成する方法を見てみましょう。

例として、整数の配列を持つ構造体に、指定した倍数にするmutatingメソッドを追加します。

struct NumberList {
    var numbers: [Int]

    // 指定した倍数にするmutatingメソッド
    mutating func multiply(by factor: Int) {
        for index in numbers.indices {
            numbers[index] *= factor
        }
    }
}

var myList = NumberList(numbers: [1, 2, 3, 4, 5])
myList.multiply(by: 3)
print(myList.numbers)  // [3, 6, 9, 12, 15]

このコードでは、NumberList構造体のmultiply(by:)メソッドを使用して、各数字を指定した倍数に変更しています。

実行すると、[3, 6, 9, 12, 15]という結果が得られます。

○サンプルコード10:拡張機能を使用したmutatingメソッドのカスタマイズ

Swiftの拡張機能を使用すれば、既存の型にmutatingメソッドを追加することができます。

例えば、Int型に累乗を行うmutatingメソッドを追加してみましょう。

extension Int {
    mutating func power(of exponent: Int) {
        self = Int(pow(Double(self), Double(exponent)))
    }
}

var number = 3
number.power(of: 3)
print(number)  // 27

このコードでは、Int型にpower(of:)メソッドを追加しています。

このメソッドを使用して3の3乗を計算すると、27という結果が得られます。

○サンプルコード11:プロトコルとmutatingメソッドの組み合わせ

プロトコルを使用して、特定の型にmutatingメソッドを実装するように要求することもできます。

例として、配列に要素を逆順にするmutatingメソッドを実装するプロトコルを作成します。

protocol Reversible {
    mutating func reverseElements()
}

extension Array: Reversible {
    mutating func reverseElements() {
        self = self.reversed()
    }
}

var stringList: [String] = ["apple", "banana", "cherry"]
stringList.reverseElements()
print(stringList)  // ["cherry", "banana", "apple"]

このコードでは、Reversibleプロトコルを定義し、Array型にreverseElementsメソッドを実装しています。

このメソッドを使用して文字列の配列を逆順にすると、["cherry", "banana", "apple"]という結果が得られます。

●Swiftのmutatingメソッドの裏側

Swiftでのmutatingメソッドの動作は、表面上だけでは理解しにくい部分があります。

ここでは、その裏側、つまり内部での動作やデバッグ時の挙動について詳しく解説します。

○サンプルコード12:mutatingメソッドの内部動作の解析

Swiftのmutatingメソッドがどのように動作しているかを理解するため、簡単な例を取り上げて動作の裏側を見てみましょう。

struct SimpleCounter {
    var count: Int = 0

    mutating func increment() {
        self.count += 1
    }
}

var myCounter = SimpleCounter()
print(myCounter.count)  // 0
myCounter.increment()
print(myCounter.count)  // 1

このコードのSimpleCounterという構造体には、incrementというmutatingメソッドが定義されています。

このメソッドを使用するとcountプロパティが1ずつ増えます。

このとき、selfは構造体の新しいコピーを指しており、既存のインスタンスを直接変更するわけではありません。

その結果、myCounterのcountは1となります。

○サンプルコード13:デバッグ時のmutatingメソッドの挙動

デバッグ時、mutatingメソッドが呼び出されるときの挙動を確認することで、mutatingの挙動をさらに理解する手助けとなります。

struct DebuggableCounter {
    var count: Int = 0

    mutating func debugIncrement() {
        print("現在のcount値: \(self.count)")
        self.count += 1
        print("増加後のcount値: \(self.count)")
    }
}

var debugCounter = DebuggableCounter()
debugCounter.debugIncrement()

このコードでは、DebuggableCounter構造体に、デバッグ情報を表示するdebugIncrementメソッドが追加されています。

このメソッドを呼び出すと、現在のcount値と増加後のcount値が表示されます。実行すると、次のような結果が得られます。

現在のcount値: 0
増加後のcount値: 1

これにより、mutatingメソッドが呼び出された際に、プロパティの値がどのように変更されるのかを具体的に確認することができます。

●よくある質問とその回答

Swiftのmutatingメソッドに関して、多くの方が抱える疑問や誤解を解消しましょう。

具体的なサンプルコードとともに、実際の挙動や注意点を詳しく解説します。

○非mutatingメソッドは存在するのか?

はい、存在します。

Swiftのmutatingメソッドは、主に値型である構造体や列挙型において、そのインスタンス自身を変更するためのメソッドです。

一方、非mutatingメソッドとは、インスタンス自身を変更せず、何らかの操作を行った結果を返すメソッドを指します。

例として、次のコードを考えます。

struct IntegerWrapper {
    var value: Int

    func doubledValue() -> Int {
        return value * 2
    }
}

let wrapper = IntegerWrapper(value: 3)
let result = wrapper.doubledValue()
print(result)  // 6が出力されます。

このコードではdoubledValueメソッドは、valueプロパティを変更せずにその2倍の値を返します。

したがって、このメソッドは非mutatingです。

○mutatingメソッドはクラスでも使用できるのか?

いいえ、クラスではmutatingキーワードは使用できません。

これは、クラスが参照型であり、そのインスタンス自体の変更が常に可能であるためです。

クラスのメソッドはデフォルトでインスタンスのプロパティを変更できます。

class IntegerClassWrapper {
    var value: Int = 0

    func increment() {
        value += 1
    }
}

let classWrapper = IntegerClassWrapper()
classWrapper.increment()
print(classWrapper.value)  // 1が出力されます。

このコードでは、IntegerClassWrapperクラスのincrementメソッドは、プロパティvalueを1増やします。

この操作は、クラスが参照型であるため、mutatingキーワードなしに実行できます。

○サンプルコード14:一般的なエラーケースとその対処法

mutatingメソッドに関しては、特定のエラーが発生することがあります。

struct ImmutableStruct {
    let constantValue: Int = 10

    mutating func changeValue() {
        // constantValue = 20  // エラーが発生します。
    }
}

このコードでは、ImmutableStructconstantValueは定数として宣言されているため、値の変更はできません。

そのため、changeValueメソッド内での代入はエラーとなります。

このような場合、プロパティをvarキーワードを使用して変数として宣言する必要があります。

○サンプルコード15:mutatingメソッドの最適化テクニック

mutatingメソッドのパフォーマンスを向上させるための一つのテクニックを紹介します。

struct LargeData {
    var data: [Int] = Array(repeating: 0, count: 1_000_000)

    mutating func optimizeData() {
        data = data.filter { $0 != 0 }
    }
}

var largeDataInstance = LargeData()
largeDataInstance.optimizeData()

このコードでは、大量のデータを持つLargeData構造体が定義されています。

optimizeDataメソッドは、データ内の0でない要素のみを保持するようにデータをフィルタリングします。

このように、不要なデータの除去やデータの再構築を行うことで、mutatingメソッドの処理速度を最適化することができます。

まとめ

Swiftのmutatingメソッドは、値型である構造体や列挙型でインスタンス自体を変更する際に非常に役立ちます。

この記事を通して、mutatingメソッドの基本的な使い方から応用例、さらには内部動作や一般的なエラーケースまでを詳細に解説しました。

Swiftプログラミングにおいて、このメソッドの役割や特性を理解することは、効率的で堅牢なコードを書くために欠かせないスキルとなります。

特に、構造体や列挙型を活用する際、mutatingメソッドの挙動を正確に把握しておくことで、安全かつ効率的なコードの実装が可能となります。

また、参照型であるクラスとの違いを明確に理解することで、Swiftの特性を最大限に活かした開発が進められます。

この記事が、Swiftのmutatingメソッドに関する疑問や誤解を解消し、より深い理解へと導く手助けとなれば幸いです。