Swiftの関数の基本的な取り扱い
はじめに
Swift言語において関数はもっとも重要な要素の一つです。クラスや構造体のメソッドでもあり、クロージャ(関数閉包)、さらにはファーストクラスオブジェクトでもあります。今回はSwiftにおける関数の基礎について説明します。
ユーザ定義関数の書式
Swiftではユーザ定義関数をfuncキーワードで宣言します。次にSwiftにおける基本的な関数定義の書式を示します。
func 関数名(引数ラベル1 引数名1: 型, 引数ラベル2 引数名2: 型, ...) -> 戻り値の型 { ~処理~ return 戻り値 }
「引数ラベル(Function Argument Label)」とは、関数呼び出し時に、実引数を指定する際に個々の引数に対して設定するラベルです。PythonやJavaScript(ES6)のキーワード引数(名前付き引数)と同じような機能です。関数を呼び出して、戻り値を変数に格納する際には次のように記述します。
変数 = 関数名(引数ラベル1: 値1, 引数ラベル2: 値2, ...)
上記のように引数ラベルを指定することにより、呼び出し時にどの引数に値を指定しているかが明確になります。
次の例は、身長(cm)とBMIの値を引数に、「身長(m)×身長(m)×BMI」を計算し、標準体重(kg)を求めて戻すstdWeight()関数の定義例です。引数heightのラベルをmyHeightに、引数bmiのラベルをmyBMIにしています。
func stdWeight(myHeight height: Double, myBMI bmi: Double) -> Double { return pow(height / 100, 2) * bmi }
上記のように定義しておくと、Xcodeのエディタを使用して関数呼び出しのコードを入力するときに、関数名の最初の数文字をタイプした時点で関数のヒントがラベル付きで表示されます。
関数を呼び出す際には、次のようにそれぞれの引数にラベルを記述して呼び出します。
let myStdWeight = stdWeight(myHeight: 180.0, myBMI: 22.0)
ただし、ラベルを指定した場合でも、実引数の順番は仮引数の順番と同じである必要があります。Pythonのキーワード引数のように、引数の順番を変えて呼び出すことはできません。
let myStdWeight = stdWeight(myBMI: 22.0, myHeight: 180.0) //<- 引数の順番を変えるとエラーに
デフォルトでは呼び出し時にラベルを省略できない
また、Pythonのキーワード引数と異なり、Swiftの場合、関数定義のラベルを省略した際には引数名が呼び出し時のラベルとなります。前述のstdWeight()関数を、次のようにラベルを省略して定義したとします。
func stdWeight(height: Double, bmi: Double) -> Double { return pow(height / 100, 2) * bmi }
この場合、呼び出し時にラベルを省略することはできません。引数名をそのままラベルとして使用して、次のようにして呼び出します。
// 引数名をラベルとして設定 let myStdWeight = stdWeight(height: 180.0 , bmi: 22.0)
引数のデフォルト値
関数定義時に引数を「引数名: 型名 = 値」のように記述すると、引数を省略して呼び出した場合のデフォルト値を設定できます。myStdWeight()関数で、引数bmiを省略した場合のデフォルト値を「22.0」とするには、次のようにします。
// 引数bmiのデフォルト値を「22.0」に設定 func stdWeight(height: Double, bmi: Double = 22.0) -> Double { return pow(height / 100, 2) * bmi } // 引数bmiを指定して呼び出す let myStdWeight1 = stdWeight(height: 180.0 , bmi: 21.0) // 引数bmiを省略して呼び出す let myStdWeight2 = stdWeight(height: 180.0)
ラベルを省略して呼び出す
関数によっては引数ラベルが不要なものもあるでしょう。その場合、関数定義のラベル名に「_」(アンダースコア)を指定します。次のaverage()関数は、引数として渡された3つの数値の平均を求めて戻します。
// 3つの引数(Double型の数値)の平均を求めて戻す func average(_ n1: Double, _ n2: Double, _ n3: Double) -> Double { return (n1 + n2 + n3) / 3 }
上記の例では、3つの引数のラベル名に「_」を指定しているため、全てラベルなしで呼び出します。
var av = average(9.5, 4.5, 5.5) // 変数avの値は6.5
タプルを返す関数
関数から複数の値を戻したい場合には、戻り値をタプルとし、戻り値の型を「(型名1, 型名2, ...)」のように記述します。次に、2つの整数を引数として、最初の引数を2番目の引数で割った商と余りをタプルとして戻すdivmod()関数の定義例を示します。
// 割り算の商と余りをタプルにして戻す関数 func divmod(_ n1:Int, _ n2:Int) -> (Int, Int){ return(n1 / n2, n1 % n2 ) }
タプルを返す関数は次のように呼び出し、返されたタプルに含まれる値は、「タプル名.番号」(番号は0から数える)で参照できます。
var result = divmod(15, 4) print("商: \(result.0), 余り: \(result.1)") // -> 商: 3, 余り: 3
タプルの要素を個別の変数に代入することもできます。
var (show, amari) = divmod(15, 4) print("商: \(show), 余り: \(amari)") // -> 商: 3, 余り: 3
なお、戻されたタプルのどちらかの値が不要な場合には、「_」を指定します。次に、余りのみを変数amariに格納する例を示します。
var (_, amari) = divmod(100, 13) // amariの値は9
オプショナル型を戻す関数
関数定義の戻り値の後ろに「?」を記述することにより、連載第5回で説明したオプショナル型の値を戻す関数を定義できます。次の例は、最初に紹介したstdWeight()関数の戻り値をオプショナル型にし、引数heightにマイナスの値を指定して呼び出した場合にnilを戻すようにした例です。
// オプショナル型を戻す関数(heightがマイナスの場合にはnilを戻す) func stdWeight(height: Double, bmi: Double = 22.0) -> Double? { if height < 0 { return nil } else { return pow(height / 100, 2) * bmi } }
戻り値はnilとなる可能性があるため、次のようにオプショナルバインディングを使用してアンラップします。
if let myStdWeight = stdWeight(height: 170) { print(myStdWeight) // -> 63.58 }
可変長引数を処理する
Swiftは任意の個数の引数を取ることが可能な、いわゆる可変長引数を設定することが可能です。例えば、print()関数は可変長引数が可能で、任意の数の引数を順に画面に表示します。
print("Swift", "Objective-C", "Python") // -> Swift Objective-C Python
ユーザ定義関数で、可変長引数を設定するには、引数定義の後ろに「...」を記述します。渡された値は配列となります。次の例は、任意の数の数値を受け取り、その中で正の数値の和を戻すsumPlus()関数の定義例です。
// 正の数値の和を戻す func sumPlus(_ nums: Double ...) -> Double { var sum:Double = 0 for num in nums { if num > 0 { sum += num } } return sum } print(sumPlus(-1, 6, -5, 3)) // 引数4つ -> 9.0 print(sumPlus(4, 6, 9.5, -1, -3)) // 引数5つ -> 19.5
引数の値を関数の内部で変更するには
Swift 2以前は関数の引数定義の前に「var」を指定すると、関数の内部で引数の値を変更できました。例えば、最初の引数に2番目の引数の値を足して戻すaddTwo()関数の例は、次のようになります。
// Swift 2で引数の値を変更する例(Swift 3以降ではエラー) func addTwo(var num1: Int, num2: Int) -> Int{ num1 += num2 // 引数num1の値を変更(呼び出し元には反映されない) return num1 }
この場合、呼び出し元には値の変更は反映されません(上記の例ではnum1)。
Swift 3以降では、引数のvar宣言は廃止されました。引数の値を呼び出し、元に返す「inout」キーワードと混同しやすいというのがその理由です。Swift 2のvarキーワードと異なり、inoutを指定した場合は引数の変更が呼び出し側に反映されます。Swift 3以降ではinoutは、データ型の前に記述します(Swift 2以前は引数名の前に記述していました) 。
例えば、前述のaddTwo()関数を、最初の引数に2番目の引数を足して、呼び出し側に最初の引数の変更を反映させるには次のようにします。
func addTwo(num1: inout Int, num2: Int) { num1 += num2 // 引数num1の値を変更(呼び出し元には反映される) }
引数にinoutを指定した場合、関数を呼び出す際に引数の前に「&」を記述します。
var num1 = 10 // 関数で変更される値はvar宣言された変数 var num2 = 20 addTwo(num1: &num1, num2: num2) print(num1) // -> 20
SwiftプログラミングのAPI設計ガイドラインは、Swift3で大きく変更されました。ここでは、UIKitに含まれるベジェ曲線を描くUIBezierPathクラスのメソッドの例で説明しましょう。UIBezierPathクラスには、既存の線にポイントを追加するaddLine()メソッド(Swift 2以前はaddLineTo_Point()メソッド)があります。引数には追加するポイント(CGPointのインスタンス)を指定します。
Objective-Cでは、メソッド名は「addLineToPoint」で、メソッド名に、最初の引数との関係を含ませていました。
// Objective-C [path addLineToPoint: cp];
Swiftの場合、Swit 2以前は、名前は「addLineTo_Point」となり、途中に「_」は入りますが、Objective-Cと同じようになガイドラインでメソッド名が設定されていました。また、Objective-Cと同じく最初の引数にはラベルがありません。
// Swift 2以前 path.addLineTo_Point(cp)
本編で述べたようにSwift 3以降では、デフォルトで全ての引数にラベルが必要です。また、関数名/メソッド名には最初の引数との関係を含めず、ラベルで指定します。したがって、名前は「addLineTo_Point」から「addLine」に変更になり、ラベルとして「to」が必要になります。
// Swift 3以降 path.addLine(to: cp)