Swift @escaping
What is @escaping
クロージャが関数の引数として渡され、関数が返った後に呼び出されるとき、クロージャは関数をエスケープすると言われます。クロージャを引数のひとつとする関数を宣言するとき、クロージャがエスケープできることを示すために、引数の型の前に@escapingと書くことができます。
クロージャがエスケープされる1つの方法は、関数の外部で定義された変数に格納されることです。例えば、非同期処理を開始する関数の多くは、完了ハンドラとしてクロージャ引数を取ります。関数は処理を開始すると戻りますが、処理が完了するまでクロージャは呼び出されません。例えば
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
someFunctionWithEscapingClosure(_:)関数はクロージャを引数にとり、関数の外で宣言された配列に追加します。この関数のパラメータを@escapingでマークしなければ、コンパイル時にエラーが発生する。
selfを参照するエスケーピング・クロージャは、selfがクラスのインスタンスを参照している場合、特別な配慮が必要です。selfをエスケーピング・クロージャーに取り込むと、誤って強い参照サイクルを作りやすくなります。参照サイクルについては、自動参照カウントを参照してください。
通常、クロージャは変数をクロージャ本体で使うことで暗黙的に変数をキャプチャしますが、この場合は明示的に行う必要があります。selfをキャプチャしたい場合は、selfを使うときに明示的に書くか、クロージャのキャプチャリストにselfを含める。selfを明示的に書くことで、自分の意図を伝えることができ、参照サイクルがないことを確認することができます。例えば、以下のコードでは、someFunctionWithEscapingClosure(:)に渡されたクロージャは明示的にselfを参照している。対照的に、someFunctionWithNonescapingClosure(:)に渡されたクロージャはノンエスケープ・クロージャであり、暗黙的にselfを参照することができる。
func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure()
}
class SomeClass {
var x = 10
func doSomething() {
someFunctionWithEscapingClosure { self.x = 100 }
someFunctionWithNonescapingClosure { x = 200 }
}
}
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// Prints "200"
completionHandlers.first?()
print(instance.x)
// Prints "100"
SwiftUIでどう使うのか?
ボタンのコンポーネントを作ったときに、処理を書く部分のクロージャーのプロパティを定義する部分で使いました。
import SwiftUI
struct CustomButton: View {
var iconName: String
var title: String
var action: () -> Void
var backgroundColor: Color
var foregroundColor: Color
var width: CGFloat
var height: CGFloat
init(iconName: String,
title: String,
backgroundColor: Color = .white,
foregroundColor: Color = .black,
width: CGFloat = 300,
height: CGFloat = 52,
action: @escaping () -> Void) {
self.iconName = iconName
self.title = title
self.action = action
self.backgroundColor = backgroundColor
self.foregroundColor = foregroundColor
self.width = width
self.height = height
}
var body: some View {
Button(action: action) {
HStack {
Image(systemName: iconName)
.frame(width: 24, alignment: .leading)
Text(title)
.frame(maxWidth: .infinity, alignment: .leading)
Spacer()
}
.padding(.leading, 10)
.foregroundColor(foregroundColor)
.font(.system(size: 16, weight: .semibold))
.frame(width: width, height: height, alignment: .leading)
.background(backgroundColor)
.cornerRadius(15)
}
}
}
使用例
Bookマークボタンを作るのに使いました。左にアイコン、右にタイトルがあります。{}
の中に、処理を書くことができます。
CustomButton(
iconName: "bookmark.fill",
title: "お気に入り",
backgroundColor: .blue,
foregroundColor: .white,
width: 157,
height: 64
) {
print("お気に入りに追加されました")
}
まとめ
Flutterで似たようなものをボタンのコンポーネントで作ったことがありました。そのときは、VoidCallbackというデータ型を使っていましたね。voidだから戻り値がない。コンポーネントの時だと、何も処理書かないので、使うまでは、エスケープ「逃亡・脱出」してくださいとのことみたいです。
使うまでは、何もないってことですね。
Discussion