Swift 言語ガイド – 11. メソッド

swift

はじめに

公式ページのSwift 言語ガイド「The Swift Programming Language Swift 5.3」に基づき記載いたします。本ページでは、「メソッド」について記載いたします。
記載内容に誤り等ございましたら、ご連絡をいただければ幸いです。

11. メソッド

メソッドは、特定の型に関連付けられている関数です。クラス、構造体、および列挙型はすべて、特定の型のインスタンスを操作するための特定のタスクと機能をカプセル化するインスタンスメソッドを定義できます。クラス、構造体、および列挙型は、型自体に関連付けられている型メソッドを定義することもできます。型メソッドは、Objective-Cのクラスメソッドに似ています。

構造体と列挙型がSwiftのメソッドを定義できるという事実は、CやObjective-Cとの大きな違いです。Objective-Cでは、メソッドを定義できるのはクラスだけです。Swiftでは、クラス、構造体、または列挙型を定義するかどうかを選択できますが、作成する型のメソッドを柔軟に定義できます。

11.1. インスタンスメソッド

インスタンスメソッドは、特定のクラス、構造体、または列挙型のインスタンスに属する関数です。これらは、インスタンスプロパティにアクセスして変更する方法を提供するか、インスタンスの目的に関連する機能を提供することにより、これらのインスタンスの機能をサポートします。インスタンスメソッドは、関数で説明されているように、関数とまったく同じ構文を持っています。

インスタンスメソッドは、それが属する型の開始中括弧と終了中括弧内に記述します。インスタンスメソッドは、その型の他のすべてのインスタンスメソッドとプロパティに暗黙的にアクセスできます。インスタンスメソッドは、それが属する型の特定のインスタンスでのみ呼び出すことができます。既存のインスタンスがないと、単独で呼び出すことはできません。

これは、アクションが発生した回数をカウントするために使用できる単純なCounterクラスを定義する例です。

class Counter {
    var count = 0
    func increment() {
        count += 1
    }
    func increment(by amount: Int) {
        count += amount
    }
    func reset() {
        count = 0
    }
}

Counterクラスは、次の3つのインスタンスメソッドを定義します。

  • incremental()は、カウンターを1つインクリメントします。
  • incremental(by:Int)は、指定された整数量だけカウンターをインクリメントします。
  • reset()は、カウンターをゼロにリセットします。

Counterクラスは、現在のカウンター値を追跡するための変数プロパティcountも宣言します。

プロパティと同じドット構文でインスタンスメソッドを呼び出します。

let counter = Counter()
// 初期カウンター値は0です
counter.increment()
// カウンターの値は1になりました
counter.increment(by: 5)
// カウンターの値は6になりました
counter.reset()
// カウンターの値は0になりました

関数の引数ラベルとパラメータ名で説明されているように、関数のパラメータには、名前(関数の本体内で使用するため)と引数ラベル(関数を呼び出すときに使用するため)の両方を含めることができます。メソッドは型に関連付けられた単なる関数であるため、メソッドパラメータについても同じことが言えます。

11.2. 自己プロパティ

型のすべてのインスタンスには、selfと呼ばれる暗黙のプロパティがあります。これは、インスタンス自体とまったく同じです。selfプロパティを使用して、独自のインスタンスメソッド内で現在のインスタンスを参照します。

上記の例のincrement()メソッドは、次のように記述できます。

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

実際には、コードにselfを頻繁に記述する必要はありません。明示的にselfを記述しない場合、Swiftは、メソッド内で既知のプロパティまたはメソッド名を使用するときは常に、現在のインスタンスのプロパティまたはメソッドを参照していると見なします。この仮定は、Counterの3つのインスタンスメソッド内で(self.countではなく)countを使用することで示されます。

このルールの主な例外は、インスタンスメソッドのパラメータ名がそのインスタンスのプロパティと同じ名前である場合に発生します。この状況では、パラメータ名が優先され、より適切な方法でプロパティを参照する必要があります。selfプロパティを使用して、パラメータ名とプロパティ名を区別します。

ここで、xと呼ばれるメソッドパラメータとxとも呼ばれるインスタンスプロパティの間で自己明確になります。

struct Point {
    var x = 0.0, y = 0.0
    func isToTheRightOf(x: Double) -> Bool {
        return self.x > x
    }
}
let somePoint = Point(x: 4.0, y: 5.0)
if somePoint.isToTheRightOf(x: 1.0) {
    print("This point is to the right of the line where x == 1.0")
}
// 「この点は、x == 1.0の行の右側にあります」と出力します。

self接頭辞がない場合、Swiftはxの両方の使用がxと呼ばれるメソッドパラメータを参照していると想定します。

11.3. インスタンスメソッド内からの値型の変更

構造体と列挙型は値型です。デフォルトでは、値型のプロパティは、そのインスタンスメソッド内から変更することはできません。

ただし、特定のメソッド内で構造体または列挙型のプロパティを変更する必要がある場合は、そのメソッドの動作を変更することを選択できます。その後、メソッドは、メソッド内からそのプロパティを変更でき、メソッドが行った変更は、メソッドの終了時に元の構造に書き戻されます。このメソッドは、完全に新しいインスタンスをその暗黙のselfプロパティに割り当てることもできます。この新しいインスタンスは、メソッドが終了すると既存のインスタンスを置き換えます。

そのメソッドのfuncキーワードの前にmutatingキーワードを配置することで、この動作をオプトインできます。

struct Point {
    var x = 0.0, y = 0.0
    mutating func moveBy(x deltaX: Double, y deltaY: Double) {
        x += deltaX
        y += deltaY
    }
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0)
print("The point is now at (\(somePoint.x), \(somePoint.y))")
// "The point is now at (3.0, 4.0)"を出力します

上記のPoint構造体は、変化するmoveBy(x:y:)メソッドを定義します。このメソッドは、Pointインスタンスを特定の量だけ移動します。このメソッドは、新しいポイントを返す代わりに、呼び出されるポイントを実際に変更します。mutatingキーワードが定義に追加され、プロパティを変更できるようになります。

定数構造体インスタンスの格納型プロパティで説明されているように、変数プロパティであってもプロパティを変更できないため、構造体の定数型でmutatingメソッドを呼び出すことはできないことに注意してください。

let fixedPoint = Point(x: 3.0, y: 3.0)
fixedPoint.moveBy(x: 2.0, y: 3.0)
// これはエラーを報告します

11.5. mutatingメソッド内でのselfへの割り当て

メソッドを変更すると、まったく新しいインスタンスを暗黙のselfプロパティに割り当てることができます。上記のPointの例は、代わりに次のように記述できます。

struct Point {
    var x = 0.0, y = 0.0
    mutating func moveBy(x deltaX: Double, y deltaY: Double) {
        self = Point(x: x + deltaX, y: y + deltaY)
    }
}

このバージョンのmutating moveBy(x:y:)メソッドは、x値とy値がターゲットの場所に設定されている新しい構造体を作成します。この代替バージョンのメソッドを呼び出した結果は、以前のバージョンを呼び出した場合とまったく同じになります。

列挙型のメソッドを変更すると、暗黙のselfパラメータを同じ列挙型とは異なるケースに設定できます。

enum TriStateSwitch {
    case off, low, high
    mutating func next() {
        switch self {
        case .off:
            self = .low
        case .low:
            self = .high
        case .high:
            self = .off
        }
    }
}
var ovenLight = TriStateSwitch.low
ovenLight.next()
// ovenLightは.highと等しくなります
ovenLight.next()
// ovenLightは.offと等しくなります

この例では、スリーステートスイッチの列挙型を定義します。スイッチは、next()メソッドが呼び出されるたびに、3つの異なる電源状態(off、low、high)の間を循環します。

11.6. 型メソッド

上記のように、インスタンスメソッドは、特定の型のインスタンスで呼び出すメソッドです。型自体で呼び出されるメソッドを定義することもできます。これらの種類のメソッドは、型メソッドと呼ばれます。メソッドのfuncキーワードの前にstaticキーワードを記述することにより、型メソッドを示します。クラスは代わりにclassキーワードを使用して、サブクラスがそのメソッドのスーパークラスの実装をオーバーライドできるようにすることができます。

注意
Objective-Cでは、Objective-Cクラスに対してのみ型レベルのメソッドを定義できます。Swiftでは、すべてのクラス、構造体、および列挙型に対して型レベルのメソッドを定義できます。各型メソッドは、サポートする型に明示的にスコープされます。

型メソッドは、インスタンスメソッドと同様にドット構文で呼び出されます。ただし、その型のインスタンスではなく、型に対して型メソッドを呼び出します。SomeClassというクラスで型メソッドを呼び出す方法は次のとおりです。

class SomeClass {
    class func someTypeMethod() {
        // ここに型メソッドを実装
    }
}
SomeClass.someTypeMethod()

型メソッドの本体内では、暗黙のselfプロパティは、その型のインスタンスではなく、型自体を参照します。これは、インスタンスプロパティとインスタンスメソッドパラメータの場合と同じように、selfを使用して型プロパティと型メソッドパラメータを明確にすることができることを意味します。

より一般的には、型メソッドの本体内で使用する非修飾メソッドおよびプロパティ名は、他の型レベルのメソッドおよびプロパティを参照します。型メソッドは、型名をプレフィックスとして付けることなく、他のメソッドの名前で別の型メソッドを呼び出すことができます。同様に、構造体と列挙型の型メソッドは、型名の接頭辞なしで型プロパティの名前を使用して型プロパティにアクセスできます。

以下の例では、LevelTrackerと呼ばれる構造体を定義しています。この構造体は、ゲームのさまざまなレベルまたはステージでのプレーヤーの進行状況を追跡します。これはシングルプレイヤーゲームですが、1つのデバイスに複数のプレイヤーの情報を保存できます。

ゲームが最初にプレイされると、ゲームのすべてのレベル(レベル1を除く)がロックされます。プレーヤーがレベルを終了するたびに、そのレベルはデバイス上のすべてのプレーヤーに対してロック解除されます。LevelTracker構造体は、型プロパティとメソッドを使用して、ゲームのどのレベルがロック解除されたかを追跡します。また、個々のプレーヤーの現在のレベルを追跡します。

struct LevelTracker {
    static var highestUnlockedLevel = 1
    var currentLevel = 1

    static func unlock(_ level: Int) {
        if level > highestUnlockedLevel { highestUnlockedLevel = level }
    }

    static func isUnlocked(_ level: Int) -> Bool {
        return level <= highestUnlockedLevel
    }

    @discardableResult
    mutating func advance(to level: Int) -> Bool {
        if LevelTracker.isUnlocked(level) {
            currentLevel = level
            return true
        } else {
            return false
        }
    }
}

LevelTracker構造体は、プレーヤーがロックを解除した最高レベルを追跡します。 この値は、highestUnlockedLevelという型プロパティに格納されます。

LevelTrackerは、highestUnlockedLevelプロパティを操作する2つのタイプ関数も定義します。1つ目は、unlock(_:)と呼ばれる型関数で、新しいレベルがロック解除されるたびに、highestUnlockedLevelの値を更新します。2つ目は、isUnlocked(_:)と呼ばれる便利な型関数で、特定のレベル番号がすでにロック解除されている場合にtrueを返します。(これらの型メソッドは、LevelTracker.highestUnlockedLevelとして記述する必要なしにhighestUnlockedLevel型プロパティにアクセスできることに注意してください。)

LevelTrackerは、型プロパティと型メソッドに加えて、ゲーム全体の個々のプレーヤーの進行状況を追跡します。 currentLevelというインスタンスプロパティを使用して、プレーヤーが現在プレイしているレベルを追跡します。

currentLevelプロパティの管理を支援するために、LevelTrackerはadvance(to:)と呼ばれるインスタンスメソッドを定義します。currentLevelを更新する前に、このメソッドは、要求された新しいレベルがすでにロック解除されているかどうかを確認します。advance(to:)メソッドは、実際にcurrentLevelを設定できたかどうかを示すブール値を返します。advance(to:)メソッドを呼び出して戻り値を無視するのは必ずしも間違いではないため、この関数は@discardableResult属性でマークされています。この属性の詳細については、「属性」を参照してください。

LevelTracker構造体は、以下に示すPlayerクラスで使用され、個々のプレーヤーの進行状況を追跡および更新します。

class Player {
    var tracker = LevelTracker()
    let playerName: String
    func complete(level: Int) {
        LevelTracker.unlock(level + 1)
        tracker.advance(to: level + 1)
    }
    init(name: String) {
        playerName = name
    }
}

Playerクラスは、LevelTrackerの新しいインスタンスを作成して、そのプレーヤーの進行状況を追跡します。また、プレーヤーが特定のレベルを完了するたびに呼び出されるcomplete(level:)というメソッドも提供します。このメソッドは、すべてのプレーヤーの次のレベルのロックを解除し、プレーヤーの進行状況を更新して、プレーヤーを次のレベルに移動します。(前の行のLevelTracker.unlock(_:)の呼び出しによってレベルがロック解除されていることがわかっているため、advance(to:)のブール値の戻り値は無視されます。)

新しいプレーヤーのPlayerクラスのインスタンスを作成し、プレーヤーがレベル1を完了したときに何が起こるかを確認できます。

var player = Player(name: "Argyrios")
player.complete(level: 1)
print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)")
// "highest unlocked level is now 2"を出力します

ゲーム内のどのプレーヤーもまだロックを解除していないレベルに移動しようとする2番目のプレーヤーを作成すると、プレーヤーの現在のレベルを設定しようとして失敗します。

player = Player(name: "Beto")
if player.tracker.advance(to: 6) {
    print("player is now on level 6")
} else {
    print("level 6 has not yet been unlocked")
}
// "level 6 has not yet been unlocked"と出力します
タイトルとURLをコピーしました