見出し画像

Swiftでプログラミング。-Methods

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

構造体や列挙体がSwiftでメソッドを定義できるのは、CやObjective-Cとの大きな違いです。Objective-Cでは、クラスはメソッドを定義できる唯一の型です。Swiftでは、クラス、構造体、列挙体のいずれを定義するかを選択でき、なおかつ作成した型にメソッドを定義する柔軟性があります。

Instance Methods

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

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

ここでは、あるアクションの発生回数をカウントするための単純なCounterクラスを定義する例を示します。

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

Counterクラスには3つのインスタンスメソッドが定義されています。

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

また、Counterクラスでは、現在のカウンタの値を記録するためのプロパティ、変数countも宣言されています。

インスタンスメソッドの呼び出しには、プロパティと同じドット構文を使用します。

   let counter = Counter()
   // the initial counter value is 0
   counter.increment()
   // the counter's value is now 1
   counter.increment(by: 5)
   // the counter's value is now 6
   counter.reset()
   // the counter's value is now 0

関数のパラメータは、「関数の引数ラベルとパラメータ名」で説明したように、名前(関数本体で使用)と引数ラベル(関数を呼び出すときに使用)の両方を持つことができます。メソッドは単なる型に関連付けられた関数なので、メソッドのパラメータも同様です。

The self Property

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

上の例の increment() メソッドは、次のように書くことができます。

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

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

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

ここでは、"x"という名前のメソッドパラメータと、同じく"x"という名前のインスタンスプロパティを、selfが区別しています。

   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")
   }
   // Prints "This point is to the right of the line where x == 1.0"

selfの接頭語がなければ、Swiftは"x"の両方の使用が"x"というメソッドパラメータを参照していると判断します。

Modifying Value Types from Within Instance Methods   インスタンスメソッド内での値の型変更

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

ただし、特定のメソッド内で構造体や列挙体のプロパティを変更する必要がある場合は、そのメソッドのミューティング動作を選択することができます。メソッドは、メソッド内からそのプロパティをミューテート(変更)することができ、メソッドの終了時には、その変更内容が元の構造体に書き戻されます。メソッドは、その暗黙の 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))")
   // Prints "The point is now at (3.0, 4.0)"

上記の Point 構造体では、Point インスタンスを一定量だけ移動させる mutating moveBy(x:y:) メソッドが定義されています。このメソッドは新しい Point を返すのではなく、呼び出された Point を実際に修正します。このメソッドの定義には mutating キーワードが追加されており、プロパティの変更が可能になっています。

構造体タイプの定数では、「構造体定数インスタンスのストアド・プロパティ」で説明したように、そのプロパティが可変プロパティであっても変更できないため、ミューティング・メソッドを呼び出すことはできないことに注意してください。

    let fixedPoint = Point(x: 3.0, y: 3.0)
   fixedPoint.moveBy(x: 2.0, y: 3.0)
   // this will report an error

Assigning to self Within a Mutating Method  

Mutating メソッドは、暗黙の 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の値がターゲットの位置に設定された新しい構造体が作成されます。このバージョンのメソッドを呼び出した場合の最終結果は、以前のバージョンを呼び出した場合とまったく同じになります。

列挙体のためのミューティング・メソッドは、暗黙の自己パラメータを同じ列挙体の別のケースに設定することができます。

    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 is now equal to .high
   ovenLight.next()
   // ovenLight is now equal to .off

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

Type Methods

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

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

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

   class SomeClass {
       class func someTypeMethod() {
           // type method implementation goes here
       }
   }
   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の設定ができたかどうかを示すBoolean値を返します。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)")
   // Prints "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 hasn't yet been unlocked")
   }
   // Prints "level 6 hasn't yet been unlocked"

この記事が気に入ったらサポートをしてみませんか?