LoginSignup
0
1

クロージャを理解したいということで、、、

Last updated at Posted at 2024-01-14

流れ

  • キーワード
  • クロージャとは?
  • コードの簡略化
  • キャプチャ
  • まとめ
  • 参考文献

キーワード

『in』 (引数を使用する時は省略不可)
『return』  (文が一つの場合は省略可能)
『インラインクロージャ』 (()の中に記述されたクロージャ)
『トレーリングクロージャ』
『省略引数』

クロージャとは?

僕の現段階の理解で、クロージャの大きな特徴をあげるとすると、

  1. 引数や返り値の型を省略することで、コードを簡略化することができる。
  2. 周りの値を取り込んで、インスタンスを作ることができる。

この『 2つ 』がクロージャの大きな特徴だと思っています。(もしも、これ便利だよ!と言う意見がありましたら、是非教えていただきたいです。)

ちなみに、関数はクロージャの一部です。機能的にも似ている箇所があります。
概要の型が以下です。

関数

func f1(仮引数: ) ->  {  ... (return) }

クロージャ

{ (仮引数: ) ->  in  ... (return) }

形として大きく違うところは以下2つ。
名前があるかどうか
inキーワードがあるかどうか

コードの簡略化

クロージャは省略せずに書くとかなり長くなりますが、文脈から引数や返り値の型がわかっている場合は、省略して記述することが可能です。

省略なし

let c1: () -> Void = { () -> Void in return print("出力") } //省略なし
c1()//実行

上の例を省略した形

var c1 = { print($0) }
closure("出力")//実行

クロージャでは引数と返り値を省略することが可能です。しかし、関数のように返り値に何も記述しない場合Voidを返してくれるというものではありません。文脈からクロージャの型を推論できることが鍵となります。
上の例では、{ }の中に文が一つしかありません。よって、文を評価した結果の型が、クロージャ自体の型(この場合はVoid型)であることが推論できます。返り値を省略できるということです。
そして、引数は使用されていないので、引数も省略することができます。

捕捉ですがprintはドキュメントを見たらわる通り、Void型を返すメソッドです。

public func print(_ items: Any..., separator: String = " ", terminator: String = "\n")

print()の公式ドキュメント

以下は公式ドキュメントの抜粋部分を翻訳したものになります。

戻り値の型が定義されていない関数は、Void型の特別な値を返します。これは単に空のタプルで、()と書かれます。

引数なし

//省略なし
var closure1: () -> Void = { () -> Void in return print("出力") } 
//省略あり
var closure2 = { () -> Void in return print("出力") } //①型アノテーション省略
var closure3 = { () -> Void in print("出力") } //②returnの省略
var closure4 = { () in print("出力") } //③返り値の省略
var closure5 = { print("出力") } //④引数の省略。注意:inも消す
closure()//closure1~5

引数あり (closure1~7でエラーが起こるのはどれでしょう?)

注意点
{ }の中で、仮引数の型だけを定義することはできません。型を定義する場合は仮引数も必須です。
{ (Int) -> String in return "(Int)はだめ" }
→ { (num: Int) -> String in return "(num)はよし" }

var closure0 = { print($0) } //⑤出力を$0に変更。 渡されてきた型を$0に渡す。
closure6("出力")

//省略なし
var closure1: (Int, Int) -> Void = { (num1: Int, num2: Int) -> Void in return print("num1:(\(num1)) / num2:(\(num2))") } //省略なし
//省略あり
var closure2 =  { (num1: Int, num2: Int) -> Void in return print("num1:(\(num1)) / num2:(\(num2))") } 
var closure3 =  { (num1: Int, num2: Int) -> Void in print("num1:(\(num1)) / num2:(\(num2))") } 
var closure4:(Int,Int) -> Void =  { num1, num2 in print("num1:(\(num1)) / num2:(\(num2))") } 
var closure5 =  { num1, num2 in print("num1:(\(num1)) / num2:(\(num2))") } 
var closure6: (Int, Int) -> Void =  { print("num1:(\($0)) / num2:(\($1))") } 
var closure7 =  { print("num1:(\($0)) / num2:(\($1))") }
closure(1, 2)//後ろに1~7をつけて実行

swiftが標準で持っている、map()は引数にクロージャを持っています。

func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]

使い方は下のような形です。Int配列を持っているプロパティnumに対してmapメソッドを使用するので、引数として渡されてくるのはInt型の配列です。そして、順次渡されてきた数字を使って『*2』します。この場合は、引数の型はInt型で{ }の中ではInt型が返されていることがわかります。よって、省略することが可能です。

let numbers = [1,2,3]

let doubleNumber = numbers.map({$0 * 2})

print(doubleNumber)

キャプチャ

クロージャが宣言されたスコープ内では、クロージャは周りの定数や変数の値を取り込み、インスタンスの一部とすることができます。以下例です。

var c1:(String) -> Void

do {
    let sako = "sako"
    c1 = { name in
        print("\(sako),\(name)")//sako定数をキャプチャしている。
    }
}
print(sako)// これはスコープ外となりエラーになる。
c1("hiro")

キャプチャした値の変更を検知して欲しくないとき。。。

キャプチャリストを使用します。記述箇所は仮引数の前の[]の中に再代入された値を反映して欲しくない変数を書きます。

{ [キャプチャリスト] (仮引数: ) -> 返り値の型 in  }
var c1: (String) -> Void

var color = "赤"

c1 = { [color] (fruitName: String) -> Void in
    print("\(fruitName)\(color)色です")
}

color = "黒"

c1("りんご")

まとめ

省略はなんとなく使っていたので、これを機に理解を深めることができてよかったです。
キャプチャリストは実務でも使われるということを勉強会で知ったので、個人開発でも使う場面があれば使っていきたいと考えています。
まだまだ、理解が浅いので理解できたことがあれば再びアウトプットしていこうと思います。

参考文献

0
1
2

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
0
1