Swiftの型メソッド(Type Methods)

SwiftのType Methods

型メソッド(Type Methods)です。これまで紹介したメソッドは、ある特定の型(クラスなど)のインスタンスが保有するメソッドでした。インスタンスではなくて、型自身が保有するメソッドを型メソッドと呼びます。これは、型プロパティ(type properties)との類推で考えれば理解しやすいです。

ここでは、型メソッドとは何か?という点を簡単に説明した後、型メソッドの定義構文、型メソッドの具体的な使用例を詳しく紹介していきます。

型メソッド(Type Methods)って何?

インスタンスメソッド(Instance Methods)というのは、ある特定の型のインスタンスから呼び出されるメソッドです。一方で、

型メソッド(Type Methods)というのは、型自身が保有しているメソッド

になります。型に何か共通の機能を持たせたい場合には、型メソッドが使えます。プロパティでも、インスタンスプロパティ(instance properties)と型プロパティ(type properties)がありましたが、それとの類推で理解できます。

型メソッドの定義構文 | staticとclassキーワード

型メソッドの定義構文では、funcキーワードの前にstaticキーワードを置きます。また、クラス限定でclassキーワード指定することができます。そうすると、サブクラスでのメソッドのオーバーライドが可能になります(これも、プロパティの場合と同様です)。

構造体を使って、定義構文を実際に書いてみると

// Type methodsの定義構文
struct SomeStructure {
    static var name = "some structure"
    static func typeMethod(name: String) {
        print("Type property:\(self.name), パラメータ:\(name)")
    }

    static func anotherTypeMethod() {
        print("anotherTypeMethod | ", terminator:"")
        typeMethod("Type method呼び出し")
    }
}
SomeStructure.typeMethod("hello")
//"Type property:some structure, パラメータ:hello"と表示
SomeStructure.anotherTypeMethod()
//"anotherTypeMethod | Type property:some structure, パラメータ:Type method呼び出し"と表示

という具合になります。型メソッド呼び出しの際には、インスタンスではなくて、型自身からdot syntaxを使って呼び出します。そんなに難しくはないと思いますが、少し詳しく説明していきます。

selfプロパティは型自身

型メソッド内部では、selfは型そのものを指します。インスタンスメソッドの場合と同様に、型プロパティと型メソッドのパラメータ名を区別するのに必要です。

上記の例ですと、typeMethod(name:)内部

static func typeMethod(name: String) {
    print("Type property:\(self.name), パラメータ:\(name)")
}

で、selfプロパティを使用しています。selfなしだと、パラメータが優先されます。

型メソッド内部では、インスタンスプロパティ & メソッドを使えない

型メソッド内部では、型プロパティや型メソッドを使用できますが、その場合型の名前を明示的に付ける必要がありません。

上記の例ですと、anotherTypeMethod()で、

static func anotherTypeMethod() {
    print("anotherTypeMethod | ", terminator:"")
    typeMethod("Type method呼び出し")
}

のように、別の型メソッドであるtypeMethod(name:)を呼び出しています。普通はSomeStructure.typeMethod()と書きますが、型メソッド内部での呼び出しでは型の名前は必要ありません(もちろん明示的に書いても構いません)。

逆に、型メソッド内部ではインスタンスプロパティとインスタンスメソッドを呼び出すことができません。試しにやってみると、

// 型メソッド内部からインスタンスプロパティ・メソッドを呼び出す(コンパイルエラー)
struct SomeStructure {
    ....
    var someInstanceVariable = "property"
    func instanceMethod() -> Int {
        return 1
    }
    static func typeMethod(name: String) {
        print("Type property:\(self.name), パラメータ:\(name)")

        // 型メソッドからインスタンスプロパティとインスタンスメソッドを呼び出すことは出来ない
        print(someInstanceVariable)
        print(instanceMethod())
    }
    ....
}
//コンパイルエラー
//error: instance member 'someInstanceVariable' cannot be used on type 'SomeStructure'
//error: missing argument for parameter #1 in call

と怒られます。インスタンスプロパティ・メソッドは、インスタンスが保有するものですから、型のみで使える型メソッドから呼び出すことはできません。

型メソッドの具体例

公式マニュアルにある、レベルトラッカー(ゲーム難易度のコントロールをする構造体)も分かりやすいですが、ここでは超簡易版道具屋で説明します。

// 道具屋
struct Vender {
    static var elxeir = 1
    static func sellElxeir() {
        if elxeir == 0 {
            print("エリクサーは売り切れです")
        } else {
            print("エリクサーを購入")
            elxeir = 0
        }
    }
}

// プレイヤー
class Player {
    func buy(name: String) {
        switch (name) {
            case "elxeir","エリクサー":
                Vender.sellElxeir()
            default:
                print("エリクサーしか置いてません")
                break
        }
    }
}

let 勇者 = Player()

勇者.buy(name: "薬草")
//"エリクサーしか置いてません"と表示

勇者.buy(name: "エリクサー")
//"エリクサーを購入"と表示

let 魔法使い = Player()
魔法使い.buy(name: "エリクサー")
//"エリクサーは売り切れです"と表示

イメージとしては、一個しか置いていない激レアアイテム(例えばエリクサーみたいなもの)を売っている道具屋です。ただし、そのエリクサーは、どこの道具屋でも手に入るものとします。今は、道具としてエリクサーしか実装していませんし、道具本体の実装がないので片手落ちですが、なんとなく感じはつかめると思います。

構造体Venderは、elxeirという型プロパティと、sellElxeir()という型メソッドを持っています。

// 道具屋
struct Vender {
    static var elxeir = 1
    static func sellElxeir() {
        if elxeir == 0 {
            print("エリクサーは売り切れです")
        } else {
            print("エリクサーを購入")
            elxeir = 0
        }
    }
}

一つしかないエリクサーを、型プロパティで表現しています。型プロパティになっていることで、「どこの道具屋でも手に入る」ということを実装しています。sellElxeir()内部では、if文で条件分岐しています。一度メソッドが呼び出されるとelxeirの数を0個にセットして、2回目以降の「購入」ができないようになっています。メソッド名がsell(売る)なのは、Venderが売り手だからです。

プレイヤー(Player)は、このメソッドを通して道具を買えます。

let 勇者 = Player()
勇者.buy(name: "薬草")
//"エリクサーしか置いてません"と表示

勇者.buy(name: "エリクサー")
//"エリクサーを購入"と表示

let 魔法使い = Player()
魔法使い.buy(name: "エリクサー")
//"エリクサーは売り切れです"と表示

この道具屋はエリクサーしか置いていないので、薬草を買おうとすると、switch分のdefault制御がコールされます。

一人目のプレイヤー(勇者)は、当然エリクサーを購入することができます。しかしながら、この時点でエリクサーは売り切れになります(elxeir=0)。したがって、別のプレイヤー(魔法使い)がエリクサーを買おうとすると、「売り切れです」と言われて購入できません。

今は、プレイヤーが異なる場合を考えましたが、異なる道具屋を実装することもできます。そうすると「異なる道具屋で異なるプレイヤーが色々な物を購入」という感じで、実際のゲームに近いアイテムの売買(プレイヤーが物を売る方は実装してませんが)が可能になります。

まとめ

  • 型メソッドは型自身が保有しているメソッド
  • 型メソッドの定義構文では、staticキーワードとclassキーワードが使える
  • 型メソッド内部のselfは、(インスタンスではなく)型自身を指す
  • 型メソッド内部では、インスタンスプロパティとインスタンスメソッドを使用できない