Swiftでクロージャを理解する
はじめに
前回は、Swiftにおけるファーストクラスオブジェクトとしての関数の活用法について説明しました。Swiftの関数はクロージャでもあります。クロージャとは耳慣れない用語かもしれませんが、モダンなプログラミング言語に欠かせない、関数を利用したデータ構造です。今回は、クロージャと、クロージャをシンプルに記述できるクロージャ式の取り扱いについて説明します。
クロージャとは
クロージャ(Closure:関数閉包)とは、大雑把に言えば、関数のスコープにある変数を自分が定義された環境に閉じ込めるためのデータ構造です。Swiftでは、funcで定義した関数はクロージャです。またクロージャ式というクロージャをシンプルに生成するための書式も用意されています。
クロージャの働きについて段階的に説明していきましょう。
グローバルな関数でのクロージャ
まず、グローバルに定義された関数がどのようにしてクロージャとして働くのかを見てみましょう。次の例は、func文を使用してカウントアップを行うカウンタをcountUp()関数として定義した例です。
var count = 1 // (1) func countUp()->Int { // (2) count += 1 // (3) return count // (4) } print("\(countUp())") // -> 2 (5) print("\(countUp())") // -> 3 print("\(countUp())") // -> 4 print("count: \(count)") // -> 4 (6)
(1)でグローバル変数countを宣言して、1に初期化しています。(2)で定義したcountUp()関数は、内部の(3)でグローバル変数countの値に1を加えて、(4)のreturn文でその値を戻しています
これを(5)で3回呼び出すと、変数countの値が1ずつカウントアップされます。(6)で変数countを表示すると、確かに3回カウントアップされ、値が4になっています。
上記の例は、グローバルに定義された関数の通常の働きとして直感的に理解できると思います。これをクロージャ的に考えると、「countup()関数の外部で宣言されたグローバル変数countは、countUp()関数が定義された環境に保持されている」と考えることができます。
ネストした関数でのクロージャ
続いてネストした関数を使用して、クロージャの働きを見てみましょう。前回説明したように、Swiftの関数はファーストクラスオブジェクトですので、関数を戻す関数を定義できます。次のcounterFactor()関数は、カウンタの初期値を引数として渡し、カウントアップを行う関数を戻す関数です。
func counterFactory(initValue:Int) -> () -> Int { // (1) var num = initValue // (2) func counter() -> Int { // (3) num += 1 return num } return counter // (4) } var counter1 = counterFactory(initValue:1) // (5) print("counter1: \(counter1())") // (6) print("counter1: \(counter1())") print("counter1: \(counter1())")
(1)でcounterFactory()関数を定義しています。
(2)で引数として渡された初期値を、ローカル変数numに代入しています。
(3)が、ネストされたcounter()関数の定義です。内部ではその外側の関数のローカル変数numをカウントアップしています。
(4)のreturn文でcounter関数を戻しています。
(5)で初期値を1にしてcounterFactory()関数を呼び出してカウンタ用の関数を生成し、変数counterに代入しています。
(6)で生成された関数を3回呼び出して、結果を表示しています。
実行結果は次のようになります。
counter1: 2 counter1: 3 counter1: 4
(6)でcounter1()を呼び出すたびに、(2)の変数numがカウントアップされていることがわかります。この動作は、普通に考えると不思議に思うかもしません。なぜなら、変数numはcounterFactory()関数のローカル変数だからです。そのため、(5)のcounterFactory()関数の呼び出しが終わると消滅しそうな気がします。ところが、結果を見ると(6)でcounter1()を呼び出すたびにカウントアップされています。
実は(5)で生成されたカウンタ関数が、その外側の関数のローカル変数numの状態を保持してるのです。
これがクロージャの働きの肝の部分です。関数が生成された時点でそのスコープ(有効範囲)にある変数は、クロージャの中に保持されるのです。
複数のカウンタを用意してみよう
クロージャは、生成された時点での環境を個別に保持します。
前述の例では、counterFactory()関数が呼び出されたタイミングでクロージャが作成され、変数numの値が保持されるわけです。プログラム内で、複数のカウンタを使用したいといった場合も、クロージャで簡単に実現できます。それぞれのクロージャは個別に変数を保持するからです。
次の例を見てみましょう。
var counter1 = counterFactory(initValue:1) // (1) var counter2 = counterFactory(initValue:10) // (2) print("counter1: \(counter1())") // (3) print("counter1: \(counter1())") print("counter2: \(counter2())") print("counter2: \(counter2())")
(1)で初期値を1に設定したカウンタを生成してそれを変数counter1に、(2)で初期値を10にしたカウンタを生成してそれを変数counter2に、それぞれ代入しています。そして(3)以降では、それぞれを2回ずつカウントアップしています。
結果を確認すると、カウンタ用の変数を個別にカウントアップしていることがわかります。
counter1: 2 counter1: 3 counter2: 11 counter2: 12
クロージャ式
クロージャ式という書式を使用すると、関数、つまりクロージャをシンプルに生成できます。次に、もっとも基本的なクロージャ式の書式を示します。
{ (引数宣言) -> 戻り値の型 in 処理 ... return 戻り値 }
上記のように、クロージャ式では「関数名」がありません。そのため「無名関数」とも呼ばれます。「{ }」内には、まず「(引数宣言) -> 戻り値の型 in」を記述し、その後ろに処理の中身を記述します。通常の関数と同様に、戻り値がある場合にはreturn文で戻します。
クロージャ式を変数に代入して関数として使用する
まずは、シンプルな例をみてみましょう。クロージャ式を変数に代入することで、通常の関数として呼び出すことができます。次に2つの整数を引数として渡し、その和を戻す単純な関数をクロージャ式で定義する例を示します。
let sum = {(num1:Int, num2:Int) -> Int in // (1) return num1 + num2 } var num = sum(4, -5) // -> -1 (2)
(1)でクロージャ式を記述し、変数sumに代入しています。(2)でそれを呼び出して、結果を変数numに代入しています。
連載バックナンバー
Think ITメルマガ会員登録受付中
全文検索エンジンによるおすすめ記事
- Microsoft、「Windows 10 19H1」でWindowsからのWSL・Linuxファイルシステムへのアクセスをサポート
- Microsoft、Windows Subsystem for Linux(WSL)で動作するLinuxディストリビューション「WLinux」をリリース
- Microsoft、Windows Subsystem for Linux(WSL)で動作するLinuxディストリビューション「WLinux」をリリース
- Microsoft、本物のLinuxカーネルを搭載した「Windows Subsystem for Linux 2」を発表
- WSL2登場でWindowsは有力なWeb開発環境に
- 「Windows 10」プレビュー版(Build 21364)の「WSL2」がLinuxのGUIアプリケーション実行に対応
- Microsoft、Windows上で「Debian GNU/Linux」を導入可能に
- Windows 11でLinuxを使う:Windows Subsystem for Linux 2の設定
- 「WSL2」をインストールしよう
- Windows Subsystem for Linux 2 でDocker を使用する(その2)