PR

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

2018年12月21日(金)
大津 真
連載8回目となる今回は、モダンな言語に欠かせないクロージャについて解説します。

はじめに

前回は、Swiftにおけるファーストクラスオブジェクトとしての関数の活用法について説明しました。Swiftの関数はクロージャでもあります。クロージャとは耳慣れない用語かもしれませんが、モダンなプログラミング言語に欠かせない、関数を利用したデータ構造です。今回は、クロージャと、クロージャをシンプルに記述できるクロージャ式の取り扱いについて説明します。

クロージャとは

クロージャ(Closure:関数閉包)とは、大雑把に言えば、関数のスコープにある変数を自分が定義された環境に閉じ込めるためのデータ構造です。Swiftでは、funcで定義した関数はクロージャです。またクロージャ式というクロージャをシンプルに生成するための書式も用意されています。

クロージャの働きについて段階的に説明していきましょう。

グローバルな関数でのクロージャ

まず、グローバルに定義された関数がどのようにしてクロージャとして働くのかを見てみましょう。次の例は、func文を使用してカウントアップを行うカウンタをcountUp()関数として定義した例です。

リスト1:グローバルな関数でのクロージャの例

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()関数は、カウンタの初期値を引数として渡し、カウントアップを行う関数を戻す関数です。

リスト2:ネストした関数でのクロージャの例

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回呼び出して、結果を表示しています。

実行結果は次のようになります。

リスト3:実行結果

counter1: 2
counter1: 3
counter1: 4

(6)でcounter1()を呼び出すたびに、(2)の変数numがカウントアップされていることがわかります。この動作は、普通に考えると不思議に思うかもしません。なぜなら、変数numはcounterFactory()関数のローカル変数だからです。そのため、(5)のcounterFactory()関数の呼び出しが終わると消滅しそうな気がします。ところが、結果を見ると(6)でcounter1()を呼び出すたびにカウントアップされています。

実は(5)で生成されたカウンタ関数が、その外側の関数のローカル変数numの状態を保持してるのです。

これがクロージャの働きの肝の部分です。関数が生成された時点でそのスコープ(有効範囲)にある変数は、クロージャの中に保持されるのです。

複数のカウンタを用意してみよう

クロージャは、生成された時点での環境を個別に保持します。

前述の例では、counterFactory()関数が呼び出されたタイミングでクロージャが作成され、変数numの値が保持されるわけです。プログラム内で、複数のカウンタを使用したいといった場合も、クロージャで簡単に実現できます。それぞれのクロージャは個別に変数を保持するからです。

次の例を見てみましょう。

リスト4:複数のカウンタを持つ例

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回ずつカウントアップしています。

結果を確認すると、カウンタ用の変数を個別にカウントアップしていることがわかります。

リスト5:実行結果

counter1: 2
counter1: 3
counter2: 11
counter2: 12

クロージャ式

クロージャ式という書式を使用すると、関数、つまりクロージャをシンプルに生成できます。次に、もっとも基本的なクロージャ式の書式を示します。

{ (引数宣言) -> 戻り値の型 in
    処理
    ...
    return 戻り値
}

上記のように、クロージャ式では「関数名」がありません。そのため「無名関数」とも呼ばれます。「{ }」内には、まず「(引数宣言) -> 戻り値の型 in」を記述し、その後ろに処理の中身を記述します。通常の関数と同様に、戻り値がある場合にはreturn文で戻します。

クロージャ式を変数に代入して関数として使用する

まずは、シンプルな例をみてみましょう。クロージャ式を変数に代入することで、通常の関数として呼び出すことができます。次に2つの整数を引数として渡し、その和を戻す単純な関数をクロージャ式で定義する例を示します。

リスト6:シンプルなクロージャ式の例

let sum = {(num1:Int, num2:Int) -> Int in   // (1)
    return num1 + num2
}

var num = sum(4, -5) // -> -1 (2)

(1)でクロージャ式を記述し、変数sumに代入しています。(2)でそれを呼び出して、結果を変数numに代入しています。

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

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

連載バックナンバー

開発言語
第8回

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

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

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

2018/9/13
連載7回目となる今回は、Swiftのユーザ定義関数のさまざまな活用方法を紹介していく。
開発言語技術解説
第6回

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

2018/6/13
連載の6回目となる今回は、Swiftにおける関数の基本的な扱い方について説明する。

Think IT会員サービス無料登録受付中

Think ITでは、より付加価値の高いコンテンツを会員サービスとして提供しています。会員登録を済ませてThink ITのWebサイトにログインすることでさまざまな限定特典を入手できるようになります。

Think IT会員サービスの概要とメリットをチェック

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