Swiftの関数の基本的な取り扱い

2018年6月13日(水)
大津 真
連載の6回目となる今回は、Swiftにおける関数の基本的な扱い方について説明する。

はじめに

Swift言語において関数はもっとも重要な要素の一つです。クラスや構造体のメソッドでもあり、クロージャ(関数閉包)、さらにはファーストクラスオブジェクトでもあります。今回はSwiftにおける関数の基礎について説明します。

ユーザ定義関数の書式

Swiftではユーザ定義関数をfuncキーワードで宣言します。次にSwiftにおける基本的な関数定義の書式を示します。

リスト1:関数定義の書式

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にしています。

リスト2:身長とBMIから体重を求める関数stdWeight

func stdWeight(myHeight height: Double, myBMI bmi: Double) -> Double {
        return pow(height / 100, 2) * bmi
}

上記のように定義しておくと、Xcodeのエディタを使用して関数呼び出しのコードを入力するときに、関数名の最初の数文字をタイプした時点で関数のヒントがラベル付きで表示されます。

Xcodeのエディタが関数のヒントを表示する

Xcodeのエディタが関数のヒントを表示する

関数を呼び出す際には、次のようにそれぞれの引数にラベルを記述して呼び出します。

リスト3:引数にラベルを記述して、stdWeight()関数を呼び出す

let myStdWeight = stdWeight(myHeight: 180.0, myBMI: 22.0)

ただし、ラベルを指定した場合でも、実引数の順番は仮引数の順番と同じである必要があります。Pythonのキーワード引数のように、引数の順番を変えて呼び出すことはできません。

リスト4:引数の順番を変えるとエラーになる

let myStdWeight = stdWeight(myBMI: 22.0, myHeight: 180.0) //<- 引数の順番を変えるとエラーに

デフォルトでは呼び出し時にラベルを省略できない

また、Pythonのキーワード引数と異なり、Swiftの場合、関数定義のラベルを省略した際には引数名が呼び出し時のラベルとなります。前述のstdWeight()関数を、次のようにラベルを省略して定義したとします。

リスト5:引数ラベルを省略してstdWeight()関数を定義する

func stdWeight(height: Double, bmi: Double) -> Double {
        return pow(height / 100, 2) * bmi
}

この場合、呼び出し時にラベルを省略することはできません。引数名をそのままラベルとして使用して、次のようにして呼び出します。

リスト6:引数名がラベルとなる

// 引数名をラベルとして設定
let myStdWeight = stdWeight(height: 180.0 , bmi: 22.0)

引数のデフォルト値

関数定義時に引数を「引数名: 型名 = 値」のように記述すると、引数を省略して呼び出した場合のデフォルト値を設定できます。myStdWeight()関数で、引数bmiを省略した場合のデフォルト値を「22.0」とするには、次のようにします。

リスト7:引数にデフォルト値を設定したstdWeight()関数

// 引数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つの数値の平均を求めて戻します。

リスト8:3つの引数の平均を求めるaverage()関数の定義

// 3つの引数(Double型の数値)の平均を求めて戻す
func average(_ n1: Double, _ n2: Double, _ n3: Double) -> Double {
        return (n1 + n2 + n3) / 3
}

上記の例では、3つの引数のラベル名に「_」を指定しているため、全てラベルなしで呼び出します。

リスト9:average()関数は引数のラベルなしで呼び出せる

var av = average(9.5, 4.5, 5.5) // 変数avの値は6.5

タプルを返す関数

関数から複数の値を戻したい場合には、戻り値をタプルとし、戻り値の型を「(型名1, 型名2, ...)」のように記述します。次に、2つの整数を引数として、最初の引数を2番目の引数で割った商と余りをタプルとして戻すdivmod()関数の定義例を示します。

リスト10:タプルを返すdivmod()関数の定義

// 割り算の商と余りをタプルにして戻す関数
func divmod(_ n1:Int, _ n2:Int) -> (Int, Int){
        return(n1 / n2, n1 % n2 )
}

タプルを返す関数は次のように呼び出し、返されたタプルに含まれる値は、「タプル名.番号」(番号は0から数える)で参照できます。

リスト11:タプルを返す関数の呼び出し方

var result = divmod(15, 4)
print("商: \(result.0), 余り: \(result.1)") // -> 商: 3, 余り: 3

タプルの要素を個別の変数に代入することもできます。

リスト12:タプルの要素を個別の変数に代入

var (show, amari) = divmod(15, 4)
print("商: \(show), 余り: \(amari)") // -> 商: 3, 余り: 3

なお、戻されたタプルのどちらかの値が不要な場合には、「_」を指定します。次に、余りのみを変数amariに格納する例を示します。

リスト13:不要なタプルの要素には「_」を指定

var (_, amari) = divmod(100, 13) // amariの値は9

オプショナル型を戻す関数

関数定義の戻り値の後ろに「?」を記述することにより、連載第5回で説明したオプショナル型の値を戻す関数を定義できます。次の例は、最初に紹介したstdWeight()関数の戻り値をオプショナル型にし、引数heightにマイナスの値を指定して呼び出した場合にnilを戻すようにした例です。

リスト14:オプショナル型を戻すstdWeight()関数

// オプショナル型を戻す関数(heightがマイナスの場合にはnilを戻す)
func stdWeight(height: Double, bmi: Double = 22.0) -> Double? {
        if height < 0 {
                return nil
        } else {
                return pow(height / 100, 2) * bmi
        }
}

戻り値はnilとなる可能性があるため、次のようにオプショナルバインディングを使用してアンラップします。

リスト15:戻り値がnilをとる可能性を考慮したコード

if let myStdWeight = stdWeight(height: 170) {
        print(myStdWeight) // -> 63.58
}

可変長引数を処理する

Swiftは任意の個数の引数を取ることが可能な、いわゆる可変長引数を設定することが可能です。例えば、print()関数は可変長引数が可能で、任意の数の引数を順に画面に表示します。

リスト16:print()関数は可変長引数をとれる

print("Swift", "Objective-C", "Python") // -> Swift Objective-C Python

ユーザ定義関数で、可変長引数を設定するには、引数定義の後ろに「...」を記述します。渡された値は配列となります。次の例は、任意の数の数値を受け取り、その中で正の数値の和を戻すsumPlus()関数の定義例です。

リスト17:可変長引数のユーザ定義関数

// 正の数値の和を戻す
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()関数の例は、次のようになります。

リスト18:関数内部で引数の値を変更

// 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番目の引数を足して、呼び出し側に最初の引数の変更を反映させるには次のようにします。

リスト19:inoutを用いたaddTwo()関数の例

func addTwo(num1: inout Int, num2: Int) {
        num1 += num2 // 引数num1の値を変更(呼び出し元には反映される)
}

引数にinoutを指定した場合、関数を呼び出す際に引数の前に「&」を記述します。

リスト20:引数にinoutを指定した関数の呼び出し方

var num1 = 10 // 関数で変更される値はvar宣言された変数
var num2 = 20
addTwo(num1: &num1, num2: num2)
print(num1) // -> 20
API設計ガイドラインにおける関数名と引数の変遷

SwiftプログラミングのAPI設計ガイドラインは、Swift3で大きく変更されました。ここでは、UIKitに含まれるベジェ曲線を描くUIBezierPathクラスのメソッドの例で説明しましょう。UIBezierPathクラスには、既存の線にポイントを追加するaddLine()メソッド(Swift 2以前はaddLineTo_Point()メソッド)があります。引数には追加するポイント(CGPointのインスタンス)を指定します。

Objective-Cでは、メソッド名は「addLineToPoint」で、メソッド名に、最初の引数との関係を含ませていました。

リスト21:Objective-C

// Objective-C
[path addLineToPoint: cp];

Swiftの場合、Swit 2以前は、名前は「addLineTo_Point」となり、途中に「_」は入りますが、Objective-Cと同じようになガイドラインでメソッド名が設定されていました。また、Objective-Cと同じく最初の引数にはラベルがありません。

リスト22:Swift 2以前

// Swift 2以前
path.addLineTo_Point(cp)

本編で述べたようにSwift 3以降では、デフォルトで全ての引数にラベルが必要です。また、関数名/メソッド名には最初の引数との関係を含めず、ラベルで指定します。したがって、名前は「addLineTo_Point」から「addLine」に変更になり、ラベルとして「to」が必要になります。

リスト23:Swift 3以降

// Swift 3以降
path.addLine(to: cp)

東京都生まれ。早稲田大学理工学部卒業後、外資系コンピューターメーカーにSEとして8年間勤務。現在はフリーランスのテクニカルライター、プログラマー。

<主な著書>
きちんとわかる! JavaScript とことん入門(技術評論社)
基礎Python(インプレス)
他多数

連載バックナンバー

開発言語技術解説
第9回

Swiftでの文字列操作の基礎(その1)

2019/9/27
連載9回目となる今回は、Swfitによる文字列操作の基本を学びます。あわせてSwift 5で導入された新機能にもついても紹介します。
開発言語
第8回

Swiftでクロージャを理解する

2018/12/21
連載8回目となる今回は、モダンな言語に欠かせないクロージャについて解説します。
開発言語技術解説
第7回

Swiftのユーザ定義関数の活用

2018/9/13
連載7回目となる今回は、Swiftのユーザ定義関数のさまざまな活用方法を紹介していく。

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています