メソッド
Objective-Cのクラスと同様に、Swiftのクラスとストラクチャにはメソッドを定義できます(もちろん前回述べたとおり、列挙型に対しても定義可能です)。
外部引数名に対する挙動
本連載第2回で述べたように、Objective-Cでは、以下のような2つ目の引数以降にラベルを付けたメソッドが多く見られます。
NSObject *obj = [NSObject new];
[obj performSelector: @selector(description)
withObject: nil
afterDeley: 1.0f]; //各引数をラベルで説明する
Swiftのメソッド宣言でもこの記法を踏襲しています。そのため、関数の引数とは宣言方法と動作が少し異なります。
- 第1引数 …… 内部引数のみを示す(関数と同じ)
- 第2引数以降 …… 内部引数が外部引数名も示す(関数の「#」による外部引数名の略記法と同じ)
次の例では、printString()メソッドを呼び出す際、第2引数に外部引数名withTimesも書いています。printString()メソッドでは引数を(string: String, withTimes: Int)と宣言していますから、関数であれば外部引数名は使用できません(内部引数に「#」を付ける必要がある)。
class CustomPrinter {
func printString(string: String, withTimes: Int) {
for _ in 1...withTimes {
print(string)
}
}
}
let printer = CustomPrinter()
printer.printString("Wow!!", withTimes: 3) // Wow!!Wow!!Wow!!
第2引数以降で外部引数名を使いたくない場合には、内部引数の前に「_」(アンダースコア)を付けます。また、第1引数に外部引数名を持たせたい場合には、関数と同じように明示的に外部引数名を宣言します。
class CustomPrinter {
// 第1引数に外部引数名を持たせ、第2引数を_で省略
func mprint(string str: String, _ withTimes: Int) {
for _ in 1...withTimes {
print(str)
}
}
}
let printer = CustomPrinter()
printer.mprint(string: "Yea!!", 4) // Yea!!Yea!!Yea!!Yea!!
mutatingキーワード(副作用の明示)
まず、次の例を見てください。CustomRangeストラクチャの宣言後、そのインスタンスからincrementStart()メソッドを2回呼び出していますが、incrementStart()メソッドによりstartプロパティの値が更新されるため、両者で実行結果が異なっています。
struct CustomRange {
var start: Int = 0
var length: UInt = 0
mutating func incrementStart() {
start++
}
}
var range = CustomRange()
range.start = 2
var beforeincrement = range.start // 2
range.incrementStart()
var afterincrement = range.start // 3
ストラクチャでは、呼び出したときにこのような副作用(値や状態などの更新)を伴うメソッドにはmutatingキーワードを付ける必要があります。この例では、incrementStart()メソッドにmutatingキーワードが付けられています。クラスに対しては同様の操作にmutatingキーワードは必要ありません。
メソッド呼び出しの前後で内部状態が変わるかもしれないことを他のプログラマが知るには、ストラクチャ(あるいは列挙型)内部の実装を見るか、実際に動作させるか、メソッド名から想像するくらいしか方法がありません。本連載第1回でも少し触れましたが、副作用を伴うメソッドは、意図しない結果を生む可能性を高くします。使用対象は値型に限られていますが、このように副作用があることを明示すれば、プログラマがバグを埋め込みにくくなります。
なお、mutating付きメソッドを利用する予定のあるインスタンスは常に変数に代入します。値型の定数についた値型プロパティは変更できず、mutating付きメソッドを呼び出すと値型プロパティに対して変更が加わる可能性があるためです。
let range = CustomRange() range.incrementStart() // エラー
なお、ストラクチャと同じ値型である列挙型でも、計算プロパティで内部状態を変更するメソッドにmutatingキーワードを付ける必要があります。
また、値型のmutatingキーワードを付けたメソッドでは、次のようにして、呼び出しインスタンスそのものを入れ替えることが可能です。
struct CustomRange {
let start: Int = 0
var length: UInt = 0
mutating func moveStart(start: Int) {
self = CustomRange(start:start, length:self.length)
}
}
var range = CustomRange // start: 0, length: 0
range.moveStart(2)
range // start: 2, length: 0
Objective-Cでは、自分のインスタンスを参照するためにクラス内からselfを用いてプロパティやメソッドにアクセスしていましたが、mutatingキーワードを付けたメソッド内であれば、このselfをごっそり入れ替えれることが分かります。
上記の例では、startのプロパティは後から変更できないためにmutatingメソッド内でインスタンスそのものを入れ替えています。
