使ってみようクロージャ
今回は『クロージャ』です。
iOSプログラミングではあちこちに登場します。使う機会も増える一方です。
『クロージャ』はSwiftUIでも不可欠です。
言語の説明が取っ付きにくい印象ですがもう避けては通れません、関数との共通点から理解を深めましょう。
【2020年12月26日内容を見直し内容やリンクなどを確認し一部追記しました。(最初の公開2019年9月)】
毎月札幌でiOSアプリ作りをアシストするセミナーをやっています。1時間にわたるセミナーの全内容を、物理的に参加できない方のためにnote上で公開します。
お知らせ
電子書籍『Swift5初級ガイド』をAppleのブックストアから出しています。
文字サイズを好みに変更でき、本文を検索可能な電子書籍です。
無料サンプルでご確認ください。
MacでもiPadでもiPhoneでも読めます。
Xcode 12に対応した第7版がダウンロード可能です。(ご購入済みの場合は無料アップデートです)
(2020年10月12日に第7版にアップデートしました)
Swift 5.3は第6版で対応済みです。
ブックストアから一度購入すると今後のアップデートは無料で読めます。
iOSアプリ作りをアシストするセミナーは2020年3月以降COVID-19感染拡大防止のため休止しています。
詳細は connpass.com の 札幌Swiftでご確認ください。そして機会があればぜひ参加してください。
アプリ作りやプログラミング教育に関連する話題は 札幌Swift のfacebookページで発信しています。
・・・
・ソースコード部分は横にスクロール表示できます
用語はできるだけ英語スペルを併記しています。
Appleのドキュメントとの対応を取りやすくするためです。
1 とっつきにくい印象
クロージャ(Closure)は解説だけ読むと難解・あるいはとっつきにくい印象を持ちがちと思います。私自身もそうでした。
関数の復習からステップバイステップで説明します。
クロージャはSwiftでは頻繁に登場します。クロージャは避けて通ることができません。
まずはサンプルなどでクロージャに出会った時に、読むことができるようになることを目標にしてください。
2 関数とクロージャ
Swiftでは関数(function)とクロージャはかなり似ています。
すでに関数には十分なじみがあると思いますが、復習をかねて確認しましょう。
ひとまとまりの処理に名前を付けて利用するのが『関数』です。
関数の定義は func 関数名(引数) -> 戻り値の型 { 処理 } が基本的な形です。
// 関数定義の例
func greet(name: String, day: String) -> String {
return "Hello \(name), today is \(day)."
}
引数がない場合は括弧だけ書きます。
カッコは関数の目印です、省略はできません。
// 引数なし関数の例
func seyHelloWorld() -> String {
return "Hello, world"
}
戻り値のない場合、関数定義に -> 戻り値の型 を書きません。
// 戻り値のない関数の例
func moveForward() {
// 定義省略
// 型なしなので return も不要
}
戻り値がない場合、関数の処理を途中でぬける場合は return だけを書きます。
2-1 関数の引数
Swiftの関数の引数は『引数ラベル argument label』と『パラメータ名 parameter name』を持ちます。
2-1-1 関数定義で引数ラベルの定義を省略した場合
パラメータ名は関数定義で使うための変数名(定数)です。
それぞれのパラメータ名続けてコロンを書き、次に型名を書きます。
引数が複数ある場合はカンマで区切ります。
// 引数ラベルの省略
func turnLock(up: Bool, numberOfTimes: Int) {
// 定義省略
}
パラメータ名だけで定義した場合その関数を呼び出すときのパラメータ名と引数ラベルは同じになります。(up と numberOfTimes はそれぞれ『引数ラベル』と『パラメータ名』を兼ねています。)
func turnLock(up up: Bool, numberOfTimes numberOfTimes: Int) { と定義したのと同じになります。
引数ラベルは関数呼び出しで使います。
関数呼び出しでは引数ラベルの次にコロンを書き、次に引き渡す値や変数などを書きます。
引数ラベルは(英語の)文章のように 読みやすい/誤解を招かない 短い文のような方法で関数を呼び出す役割があります。
// 使用例
turnLock(up: true, numberOfTimes: 3)
関数定義で『引数ラベル』と『パラメータ名』をスペースで区切りそれぞれ書くこともできます。 フレームワークではこの書き方が多いようです。
わかりやすい引数ラベルと、関数内で使いやすいパラメータ名は一致するとは限らないためです。
// 引数ラベルを明示した例
func turnLock(up upFlag: Bool, numberOfTimes num: Int)
これでもこの関数の使い方はかわりません。
2-1-1 引数ラベルを書かない定義
関数の呼び出し時に引数ラベルを書かないよう定義もできます。
引数ラベルのかわりにアンダースコア _ を使い、関数呼び出しで引数ラベルを不要にすることを明確にします。
// 先頭の引数ラベルを書かない定義例
func turnLock(_ up: Bool, numberOfTimes num: Int) {
// 定義省略
}
このように定義すると関数の使い方が変わります。
turnLock(true, numberOfTimes: 3) // up:を書かずに直接引き渡す値や変数を書く
2-1-2 デフォルト値を設定する
関数定義で各引数のデフォルトの値を設定できます。
型名の次にイコールを書き次にデフォルト値を書きます。
デフォルトの値を設定すると、関数呼び出しではその引数を引数ラベルと値の両方省略できます。(引数の数が少ないのと同じ書き方ができる)
func someFunction (parameterA: Int, parameterB: Int = 12) {
// 二つ目の引数を省略したて関数を呼び出す場合は
// parameterBの値は自動的に12になる
}
// 関数の呼び出し例
someFunction (parameterA: 3, parameterB: 6) // parameterB は 6
someFunction (parameterA: 4) // parameterB は 12
2-1-3 In-Out引数
関数の引数はデフォルトでは定数です。
関数本体で引数の値を変更するコードはコンパイルエラーとなります。
関数から引数の値を変更する場合は、各引数の型の前に inout キーワードを使います。
// 二つの引数がinoutの場合 二つの引数の値を入れ替える関数
func swapTwoInts(_ a:inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
inout キーワード付き引数の関数を呼び出す場合には、inout 引数には変数のみ渡すことができます。
変更のできない定数やリテラルはinout引数には渡すことはできず、エラーになります。
inout引数に引数を渡す場合は、変数名の前にアンパサンド(&)を置き関数の実行により値が変わることを明示します。
// inout引数がある関数の使用例
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
2-2 関数と関数の型
すべての関数には関数の型(Function Type)があります。
関数の型は引数の型と関数の戻り値の型を取り出したものです。
関数名や引数ラベルは関数の型には関係しません。
// 関数の例
func addTwoInts(_ a: Int, _ b: Int) -> Int {
return a + b
}
ここでは『関数の型』と『関数型』の両方を使っていますが、どちらも Function Type のことです。
戻り値の型とまぎらわしいので注意してください。
この addTwoInts(_ a : Int, _ b : Int) -> Int 関数の関数型は (Int, Int) -> Int です。
// 関数型が同じ別の関数例
func multiplyTwoInts(_ a: Int, _ b: Int) -> Int {
return a * b
}
multiplyTwoInts は関数名も処理も違いますが関数型は同じ (Int, Int) -> Int です。
関数の型も通常の型と同じく変数の型として利用できます。
// 関数型の変数
var mathFunction: (Int, Int) -> Int
この変数に代入する場合は関数名を書きます。
// 代入は関数名のみ
mathFunction = addTwoInts
mathFunction = addTwoInts() とは書けません。カッコは付けません。
mathFunction = addTwoInts(Int, Int) とも書けません。
mathFunction = addTwoInts(_:_:) は正しい書き方です。
カッコを付ける場合はこのように引数ラベルも含めて書きます。
この書き方の場合コロンの後ろにカンマは不要です。 (この例では引数ラベルはアンダーバーで省略されていることを明示します)
カッコを付けると引数ラベルまで含めて関数名となります。
関数型の変数は、関数として実行できます。
// 関数型変数は実行できる 代入したaddTwoIntsを実行し結果を返す
mathFunction(2, 3)
この例では足し算を実行し結果を返します。
次にmultiplyTwoIntsを代入してから、同じ引数で実行すると...
//
mathFunction = multiplyTwoInts
mathFunction(2, 3)
同じ「mathFunction(2, 3)」でも処理は変わり、かけ算を実行し結果を返します。
このように関数型変数は処理内容を切り替える、あるいは外部から処理を指定することができます。
2-3 クロージャ式
ここからはクロージャの説明です。
クロージャ式(Closure Expression)は基本的には次のような構造です。
{ ( 引数 ) -> 戻り値の型 in
コード // 複数行でも良い
}
全体を波カッコでかこみます。
in キーワードの前は関数型の書き方が使えます。少なくとも引数の数とそれぞれの型を明示します。(引数と型も書けます)
in キーワードの後にコードを書きます。
引数は複数あってもよく、ひとつもない場合カッコだけ書きます。
型の前にコロンで区切って引数名を書くことはできますが、クロージャ式では引数ラベルは書けません。
関数名に相当する部分はありません。(常に何も書かない)
引数が複数ある場合はカンマで区切ります。
結果を返さない場合 ->型 は省略できます。
引数が一つもなく結果も返さない場合は ( ) も省略できます。
値を返す場合は関数と同じく return 文を使います。
クロージャ式は柔軟な書き方がゆるされています。
はじめのうちは、いろいろな書き方があるので戸惑うとおもいますが、頻繁に使われるために省略記法が求められた結果と理解してください。
2-4 クロージャ式の引数
クロージャ式の引数部分は型だけでなく、関数のように引数名も書くことができます。
(Int, Int) -> Int は (a: Int, b: Int) -> Int と書くこともできます。
関数型の (Int, Int) -> Int のように引数の数ごとに型だけ書くことができるのは、クロージャ式の中で省略時の引数名を先頭から順に $0、$1 などと決まっているためです。
クロージャ式ではこの省略した引数名で書かれる場合が多いようです。
省略は引数の数が少ないことが前提ですね。
2-5 関数型変数にクロージャを代入し実行できる
クロージャは実行できます。
関数型の変数にクロージャを代入すると、関数として実行できるのところもまったく同じです。
実は 2-1 で説明した関数型変数 mathFunction にはクロージャも代入できます。
// 関数型の変数
var mathFunction: (Int, Int) -> Int
この変数にクロージャ式を次の例のように、代入でき実行もできます。
// 関数型変数にクロージャを代入でき 実行もできる
mathFunction = {(a: Int, b: Int)-> Int in return b - a}
mathFunction(2, 3)
関数型変数に代入でき実行もできるので、関数とクロージャは同じと言えます。
クロージャ式の引数と戻り値の型を正式に書くと、次の例のように変数の型指定は省略できます。次の mathClosure は新しい変数です。
// 正式なクロージャ式の書き方なら変数の型は省略できる
var mathClosure = {(a: Int, b: Int)-> Int in return b - a}
これも次で説明する型推論のひとつです。
3 Swift の型推論
Swift言語には『型推論(Type Inference)』と呼ばれるしくみがあります。
推論とは堅苦しくむずかしく感じますが、「型を自動で決めるしくみ」と考えてください。
今後も記事を増やすつもりです。 サポートしていただけると大変はげみになります。