超基本部分を平易に書きます
難しいことは混乱するので書きません
ハマりどころも書きます
対象
初学者向けの基礎部分です
1冊参考書読んだくらいのレベルを想定しています(関数、引数くらいはわかる人)
なぜアプリで非同期処理が重要か
スマホアプリのプログラムというのは大抵の場合一瞬で処理が終わってしまいます
でも実際に利用する実環境では、様々な理由で待ちが発生します
・アニメーション
・サーバーとの通信
・重たい処理、生成
アプリを作っているとこれらを避けて通るのが難しいです
すると「終わってから何かをしたい」というケースが頻発します
分かりやすい非同期処理とClosure
以下は0.3秒でアニメーションをする関数です
animateという関数には、duration,animations,completionという3つの引数があります
アニメーション処理が終わったらcompletionが呼ばれます
UIView.animate(withDuration: 0.3, animations: {
// ここでアニメーション処理
}, completion: { (finished:Bool) in
// アニメーション終了後の処理
})
このうち、animationsとcompletionがClosureです
animationsを0.3秒で実行して、その後completionを呼びます
Closureとは何か
SwiftにおけるClosureとは関数オブジェクトの一種です(by wikipedia)
平たく言えば関数を変数にしたものです
何が嬉しいかと言えば、非同期処理をしたいときに便利になります
「処理Aが終わったときに処理Bをしたい」
この処理BをClosureで定義します
先程のアニメーションの例なら、処理BはCompletionです
処理Bは関数でもいいじゃないか、と思うかもしれません
もちろん実際にそういう作り方もできますし昔はClosureが無かったため、そちらが主流でした
しかしClosureの方が何かと便利なことが多く、Swiftの時代からはClosureが多用されています
Closureが混ざると、処理の順番で混乱する
Closureがないと、大抵は処理が上から順に実行されるのでイメージしやすいと思います
しかしClosureが入ってくるとそれが前後するので注意しなければなりません
func myAnimation() -> Int {
// 1番目
UIView.animate(withDuration: 0.3, animations: {
// アニメーション
}, completion: { (finished:Bool) -> Void in
// 終了後の処理 3番目
// return 1 <-- これは間違い
})
// 2番目
return 0
}
上記において、処理順は①②③の順です(animationsの中はややこしくなるので無視してください)
今、②はcompletionより前に実行されます。myAnimationのreturnを③に書くのは間違いです
Closureは関数と同じなので、Closureに対してreturnができます
completion: { (finished:Bool) -> Void in
このVoidが戻り値の型です(普段は省略されます)
Closureから値を返す
誰しも通る道だと思いますが、Closureを覚えるとこういう処理を書いてしまいます
func myFunc() {
// myAnimationの結果をprintしたい
let result = myAnimation()
print(result)
}
completionで処理をした後に、その値を用いて次の処理をしたいことがあります
しかし先程書いたように、completionの値をreturnで返すことはできません
次に考えることは、myFunc() → myAnimation() → completionと来たのだから、そこから逆に返していく方法です。completionから値を得て、それをmyAnimationで返せるように工夫すればと頭を捻ります
しかしそれもできません
ここでのcompletionはあらかじめ定義されたものなのでIntを返すように変更はできません
そしてなにより、closureが値を返した時には既にprintが終わってしまっています
なので、completionから結果を得るには例えばこうします
/// CompletionClosureというClosureの型を定義
typealias CompletionClosure = ((_ result:Int) -> Void)
func myFuncC() {
// 1番目
myFuncD(completionClosure: { (result:Int) in
// 6番目 10が返ってくる
print(result)
})
// 4番目
}
func myFuncD(completionClosure:@escaping CompletionClosure) {
// 2番目
UIView.animate(withDuration: 0.3, animations: {
// アニメーション
}, completion: { (finished) in
// 5番目
// 終了した時、10という値を返す
completionClosure(10)
})
// 3番目
}
何をやっているかというと、自分で新しくClosureの型を定義して、それを「終わったらこれを呼び出して」と渡してやります
一見するとトンデモなくややこしく見えます
私も最初はそうでしたが、使っていくととても便利なものだと分かります
ただ処理順序はこのように混乱しがちなので、処理を複雑にしないよう注意を払わなければなりません。
別解1.終わるまで待つ
詳しくは書きませんが、アニメーションが終わるまで処理を待つ方法ももちろんあります
しかし主流ではありません
理由は幾つかあります
- 結局処理が複雑になる
- 不具合があると完全に処理が止まってしまう
- 処理を止めると不都合が生じる
本当にどうしても値を直接化したい時や、複数の非同期処理が混ざる時に使うことが多いと思います
備考その他
Q. XcodeでClosureを書いたら書式が違う
それは省略形です
今回は省略しない形を自分で打ち込んでいます(省略されるとパット見で何か分からなくなるので)
Closureは色んな省略形があります
複雑なので大変ですが調べてください
fucking closure syntaxとググるとたくさん見つかります
Q. delegateと存在意義がかぶっている?
歴史的な経緯です
両方使いますが、自分で定義するならClosureの方が最近主流な気がします(2018年)
あとがき
教科書やちゃんとしたドキュメント読むとここらへん混乱しますよね
書式とかではなく、何に使うか、何で必要か、挙動を確認したほうが早い気がします
メモ
これQ&Aサイトの回答用に書いたんですが、細部の用語について少し自信がありません
問題があれば微修正していくつもりです
あわせて読みたい(自分が)
非同期処理ってどういうこと?JavaScriptで一から学ぶ
JavaScriptの非同期処理を並列処理と勘違いしていませんか?