4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Engineers and CreatorsAdvent Calendar 2023

Day 5

[Swift]世界一わかりやすい講座シリーズ~クロージャとは~

Last updated at Posted at 2023-11-26

はじめに

学生でiOSエンジニアをしてます、あつあつです。🔥

今日はSwift学習者が詰まりがちなSwiftのクロージャについて、わかりやすく解説しようと思います!
基本的に難しい言葉は使わず、噛み砕いて説明するので、この記事がクロージャの理解の促進に繋がれば幸いです。

そもそもクロージャってなに?

クロージャは、{}で囲んだブロックの中にある処理を実行するもの
いわば処理のかたまりです。

ここで重要なことですが、実は関数はクロージャの一種です。そのため関数とクロージャは仕様が似てる部分が多くあり、また関数よりも気軽に宣言できます(ここら辺はあとで詳しく説明するので今は一旦そういうものか!と思っておいて欲しいです!)

クロージャの書き方は以下の通りです。

クロージャの書き方
{(引数) -> 戻り値の型 in
    行う処理
    必要に応じてreturn文
}

//ex
let double = {(x: Int) -> Int in
    return x * 2
}
double(2) //4

クロージャの宣言方法について

クロージャの宣言方法
//変数の宣言
var count: Int
//クロージャの宣言
var closure: (Int) -> Int

//引数が変数の場合
func someCount(x: Int){
    print("x")
}
//引数がクロージャの場合の関数の宣言
func someFunction(x: (Int) -> Int) {
    //ここで引数で受け取ったクロージャを使って処理をかく。
}

クロージャは変数と同じように宣言することができ、クロージャの型は通常の型と同じように扱えるので変数や定数の型や関数の引数の型として利用することができます。そのため宣言する際には変数と同じように宣言することができます。

イメージとしては、整数型や文字列型と同じ系統にクロージャ型があると考えるとここは掴みやすいと思います。

上記のプログラムでは、closureという引数Int型、返り値Int型のクロージャと
someFunction関数の引数として用いられているクロージャ(引数Int型、返り値Int型)を表しています。

クロージャの実行方法について

クロージャの実行方法
let 定数名 = クロージャ名(引数1, 引数2, ...)

//ex1
//クロージャを宣言
var double:(Int) -> Int
//クロージャ内部で行う処理を記載
let double = {(x: Int) -> Int in
    return x * 2
}
//クロージャを実行
double(2) //4

//ex2
//クロージャを宣言
var lenghOfString:(String , String) -> Int
//クロージャ内部で行う処理を記載
lenghOfString = {(x: String,y: String) -> Int in
    return x.count + y.count
}
//クロージャを実行
lenghOfString("banana", "apple")//11

呼び出し方は基本的に関数と全く同じで、クロージャが代入されている変数名や定数名の末尾に()をつけ、()の内部に引数をカンマで区切ることで実行することができます。以下の例を見ていきましょう。

クロージャの型推論について

クロージャの引数と戻り値の宣言は、クロージャの代入先の型から推論することこによって省略できることがあります。

変数の型からクロージャの型を決定するとき
//クロージャを変数として宣言
var closure: (String) -> Int

//引数と戻り値の型を明示した場合
closure = { (text: String) -> Int in
    return text.count
}
closure("abc")//3

//引数と戻り値の型を省略した場合
closure = { text -> in
    return text.count
}
closure("abc")//3

上記のプログラムでは、1行目の宣言の際に変数としてクロージャの引数と返り値の型を宣言しているため、クロージャの処理内容を書くときに引数と戻り値の方を省略して書くこともできるようになります。
これをよく、変数の型からクロージャの型を推論すると言います。

反対に、クロージャの型から変数の型を決定するプログラムは以下の通りです。

クロージャの型から変数の型を推論する場合
var lenghofString = {(string: String) -> Int in
    return string.count
}
lenghofString("banana")//6

上記のプログラムでは、(String)->Int型のクロージャを変数lenghofStringに代入しています。この時、変数lenghofStringの型は(String)->Int型として推論されます。
最後に、変数、クロージャともに型が決定されていない時は以下のプログラムのようにコンパイルエラーが起きます。

どちらも型が定まっていない時
let closure = { text: in 
    //変数closureからもクロージャからも型が決定しないためコンパイルエラー
    return text.count
}

このように、クロージャで型推論を行う場合は、(代入先である)変数またはクロージャ自体が型を決定していないといけないということがわかります。

戻り値がないクロージャの書き方

戻り値がない場合のクロージャ
//処理1
let cl = { () -> () in
    print("banana")
}
cl()
//処理2
let cl_2 = { () -> () in
    print("apple")
}
cl_2()
//処理3
let cl_3 = {
    print("banana")
}
cl_3()

戻り値がない関数を定義できるとの同様に、戻り値がないクロージャを定義できる。
戻り値がない時、上記どれでも書くことはできるは基本は処理2と処理3のように、() -> voidの形で書くかそもそも省略して書く。

クロージャと関数の違い

クロージャ式の引数は、関数の引数の仕様と概ね同じですが使える機能が少し異なっています。
それをまとめたのが以下の表です。

使える機能 関数 クロージャ式
外部引数名 ⚪︎ ×
デフォルト引数 ⚪︎ ×
インアウト引数
可長変引数 ⚪︎
簡略引数名 × ⚪︎

1つづつ以下で見ていきます。

1.外部引数名

クロージャと関数の違い1
//クロージャ
let add = {(x: Int , y: Int) -> Int in
    return x + y
}
add(1, 2)//3

//外部引数名を指定しない関数
func add(n: Int, m: Int) -> Int {
    return n + m
}
add(n: 10, m: 20)//30

//外部引数名を指定した関数
func add(number1 n: Int, number2 m: Int) -> Int {
    return n + m
}
add(number1: 10, number2: 20)//30

//外部引数名を_に指定した関数(呼び出し方がクロージャと一緒のやつ)
func add(_n: Int, _m: Int) -> Int {
    return n + m
}
//クロージャと一致している
add(10,20)//30

関数は外部引数を使用できるのに対し、クロージャは関数内部で用いる内部引数のみを指定でき、呼び出すときに使用される外部引数を指定することはできません
つまり常に関数で外部引数名を_にしている状態と同じになります。

2.デフォルト引数

デフォルト引数のサンプルコード
//デフォルト引数を使用した関数
func search(fruit: String, food: String = "hoge") -> Int {
    return 1
}
search(fruit: "apple")//1

//デフォルト引数を使用したクロージャ
let greet = { (user: String = "Taro") -> Void in
    //デフォルト引数を使用しているのでコンパイルエラー
    print("hello")
}

関数ではデフォルト引数を使用できるのに対して、クロージャではデフォルト引数を使用することができません。上記のプログラムのように、実行するとコンパイルエラーが起こります。

3・4.インアウト引数、可長変引数

これらは関数と同じようにクロージャでも使用できます。

5. 簡略引数名

定義するクロージャの型が推論できる時、さらに引数名の定義を省略することができ代わりに簡略引数名を用いる。
簡略引数名はにインデックスをつけた$0$1のようになる。

簡略引数名について
let isEqual: (Int, Int) -> Bool = {
    return $0 == $1
}

isEqual(1,1) //true

関数の引数としてのクロージャ

クロージャは関数の引数として使用できます。

サンプルコード1
//関数を宣言する。
func someFunction(culculate: (Int) -> Int) {
    //ここで受け取ったクロージャを使って処理をかく。
    var resultNumber = culculate(3)
    print(resultNumber)//6
}
//関数を呼び出し
someFunction(culculate: { (num :Int)  -> Int in
    //ここでクロージャが行う処理を書きます。
    return num * 3 //9
})
//関数を呼び出し(戻り値と引数の型を省略)
someFunction(culculate: { num in
    //関数を宣言した際に、クロージャculculateの引数と戻り値の型は定まっているためどちらも省略して書いてます。
    //ここでクロージャが行う処理を書きます。
    return num * 2 //6
})

上記のプログラムのように、クロージャは関数の引数として使用することができます。
クロージャを引数として関数の引数として使用する場合は関数を呼び出す時に、クロージャが行う処理についても記述する必要があります。

上記のプログラムではまだ比較的プログラムが読みやすいですが、クロージャが複数行に渡る場合などは()がクロージャの外まで広がり、コードの可読性が下がります。そこで使用できるのがトレイリングクロージャという記法です。

トレイリングクロージャは、関数の最後の引数がクロージャの場合にクロージャを()の外に書くことができる記法です。以下のプログラムを見てみましょう。

トレイリングクロージャ
func make(number: Int, touch:(String) -> Void) {
    touch("Your number is \(number)")
}

//トレイリングクロージャを使用しない場合
make(number: 3, touch: {string in
    print(string) //your number is 3
})
//トレイリングクロージャを使用する場合
make(number: 5){ string in
    print(string) // your number is 5
}

上記の2つを比較すると、トレイリングクロージャを使用した方が可読性が上がっていることがわかります。

実際に書く際には正直どちらでも良いですが、ここで大事なのが
多くのプログラムではトレイリングクロージャを使って書かれてる
ということです。
そのためこのような書き方があることを知っておくことが重要です!

##最後に
以上でクロージャの基本的な説明は終わりです!
次の記事でクロージャの属性(@escaping)について解説したいと思います。

参考文献

Swift 実践入門

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?