はじめに
UdemyでSwiftUIのチュートリアルを数十時間勉強したのですが、プロジェクトのSwiftのコードを読んでみて、さっぱり理解できない状態だったので、理解できるように勉強中です。
Swiftのコードが読みにくい理由の一つとして、Closureの書き方が多すぎて、よくわからん状態だったので、整理してみました。
基本形
// 基本形
let normalClosure1 = {(s: String, i: Int) -> String in return "\(s)\(i)"}
// 一行の場合、inの後のreturnは省略可能
let normalClosure2 = {(s: String, i: Int) -> String in "\(s)\(i)"}
// 複数行の場合、inの後のreturnは必須
let normalClosure3 = {(s: String, i: Int) -> String in
let iStr = String(i)
return "\(s)\(iStr)"
}
print(normalClosure1("normalClosure", 1))
print(normalClosure2("normalClosure", 2))
print(normalClosure3("normalClosure", 3))
引数なし 戻り値なし
// 引数なし 戻り値なし
// 引数はVoid、戻り値もVoidになるが、引数には()、戻り値にはVoidを指定する
let voidClosure1 = {() -> Void in print("voidClosure1")}
// 戻り値のみの省略は可能(引数の()だけ省略は不可)
let voidClosure2 = {() in print("voidClosure2")}
// 引数、戻り値の両方省略は可能
let voidClosure3 = {print("voidClosure3")}
voidClosure1()
voidClosure2()
voidClosure3()
引数あり 戻り値なし
// 引数あり 戻り値なし
let useArgClosure1 = {(s: String) -> Void in print(s)}
// 戻り値がVoidなので省略可能
let useArgClosure2 = {(s: String) in print(s)}
useArgClosure1("useArgClosure1")
useArgClosure2("useArgClosure2")
引数なし 戻り値あり
// 引数なし 戻り値ありの場合、returnを省略するかどうかの違いのみ
let returnClosure1 = {() -> String in "returnClosure1"}
let returnClosure2 = {() -> String in return "returnClosure2"}
print(returnClosure1())
print(returnClosure2())
型を明示した場合
// 型を明示した場合
// 通常
let typeClosure1: (String, Int) -> String = {
(s: String, i: Int) -> String in
return "\(s)\(i)"
}
// 型推論により、引数の方を省略
let typeClosure2: (String, Int) -> String = {(s, i) in "\(s)\(i)"}
// 引数名の定義を省略し、$0で引数を使用する(簡略引数名というらしい)
let typeClosure3: (String, Int) -> String = {"\($0)\($1)"}
print(typeClosure1("typeClosure", 1))
print(typeClosure2("typeClosure", 2))
print(typeClosure3("typeClosure", 3))
トレイリングクロージャ
// トレイリングクロージャ
// 関数の引数の最後がクロージャーの場合、トレイリングクロージャーという書き方が可能
func printStr1(num: Int, getStr: () -> String) {
let str = getStr()
print("\(str)\(num)")
}
// 普通に書くとこう.getStrという引数にクロージャーを指定
printStr1(num: 1, getStr: {() -> String in return "trailingClosure"})
// ()の外にクロージャーをかける
printStr1(num: 2){() -> String in "trailingClosure"}
// 省略するとこうなる。printStr側で、型が決まっているため
printStr1(num: 3){"trailingClosure"}
// 関数の引数がクロージャーで一つだけの場合
func printStr2(getStr: () -> String) {
print(getStr())
}
// 普通に書くとこう
printStr2(getStr: {() -> String in return "trailingClosure"})
// printStr2()の()も省略できる。知らないと何これってなりそう。
printStr2{"trailingClosure"}
autoClosure
// autoclosure
// 遅延実行が可能
// 遅延実行とは必要になったタイミングで実行されること
func getNum() -> Int {
print("call getNum()")
return 0
}
func getStr() -> String {
print("call getStr()")
return "実行する必要なし"
}
func execute(num: Int, str: String) {
if(num == 0){
print(num)
return
}
print(str)
}
// このように実行すると、getNumは0を返すので、
// execute関数のロジック内でstrは必要ないが、getStrも無駄に実行されてしまう
execute(num: getNum(), str: getStr())
// @autoclosureを指定すると、引数がclosureで包まれる
func autoClosure(num: Int, str: @autoclosure () -> String) {
if(num == 0){
print(num)
return
}
print(str())
}
// 引数が自動的にclosureで包まれるので、strが実行されるまで、 getStrは実行されない
autoClosure(num: getNum(), str: getStr())
// @autoclosureを指定しないと、以下のような形。
func noAutoClosure(num: Int, str: () -> String) {
if(num == 0){
print(num)
return
}
print(str())
}
noAutoClosure(num: getNum(), str: { getStr() })
escaping
※よく理解できていないので、理解できたら追記したいと思います
// クロージャーでは、@escapingを指定できる
// @escapingの使い方は、まだよく理解できてないので追記する
func sampleExecute(esc: @escaping () -> Void) {
esc()
}
キャプチャリスト
※わかりやすい例が思いつかなかったので、また追記します。
おわりに
まとめて、個人的にはスッキリしました。
Swiftの文法もそうですが、UIKitやSwiftUIのフレームワーク固有の知識と、RxSwiftなどライブラリの知識などまだまだ理解しないといけない部分があるので、引き続き、調べていきます。