Edited at

[iOS]Closureと非同期処理の超基本(初学者向け)

超基本部分を平易に書きます

難しいことは混乱するので書きません

ハマりどころも書きます


対象

初学者向けの基礎部分です

1冊参考書読んだくらいのレベルを想定しています(関数、引数くらいはわかる人)


なぜアプリで非同期処理が重要か

スマホアプリのプログラムというのは大抵の場合一瞬で処理が終わってしまいます

でも実際に利用する実環境では、様々な理由で待ちが発生します

・アニメーション

・サーバーとの通信

・重たい処理、生成

アプリを作っているとこれらを避けて通るのが難しいです

すると「終わってから何かをしたい」というケースが頻発します


分かりやすい非同期処理とClosure

以下は0.3秒でアニメーションをする関数です

animateという関数には、duration,animations,completionという3つの引数があります


UIView.animate(withDuration: 0.3,
animations: {
// アニメーション処理
}, completion: { (finished:Bool) in
// 終了後の処理
})

このうち、animationsとcompletionがClosureです


Closureとは何か

SwiftにおけるClosureとは何かですが

関数オブジェクトの一種(by wikipedia)です

ざっくり言ってしまえば、関数を変数にしたものです

何が嬉しいかと言えば、非同期処理をしたいときに便利になります

「処理Aが終わったときに処理Bをしたい」

この処理BをClosureで定義します

先程のアニメーションの例なら、処理BはCompletionです

処理Bは関数でもいいじゃないか、と思うかもしれません

もちろん実際にそういう作り方もできますし、昔はClosureがなかったためそちらが主流でした

しかしClosureの方が便利なので、Swiftの時代からはほとんどがClosureで定義されています


Closureが混ざると、処理の順番で混乱する

Closureがないと、大抵は処理が上から順に実行されるのでイメージしやすいと思います

しかしClosureが入ってくるとそれが前後するので注意しなければなりません

func myAnimation() -> Int {

// ①

UIView.animate(withDuration: 0.3, animations: {
// アニメーション
}, completion: { (finished:Bool) -> Void in
// 終了後の処理 ③
// return 1 <-- これは間違い
})

// ②

return 0

}

上記において、処理順は①②③の順です(アニメーションの中はややこしくなるので無視)

今、②がcompletionより前に実行されるので、myAnimationのreturnを③に書くのは間違いです

Closureは関数と同じなので、Closureに対してreturnができます

completion: { (finished:Bool) -> Void in

このVoidが戻り値の型です(普段は省略されます)


Closureから値を返す

誰しも通る道だと思いますが、Closureを覚えるとこういう処理を書いてしまいます


func hoge() {
// myAnimationの結果をprintしたい
let result = myAnimation()
print(result)
}

completionで処理をした後に、その値を用いて次の処理をしたいことがあります

しかし先程書いたように、completionの値をreturnで返すことはできません

次に考えることは、hoge() → myAnimation() → completionと来たのだから、そこから逆に返していく方法です

しかしそれもできません

completionはあらかじめ定義されたものなので変更できません

そしてなにより、closureが値を返した時には既にprintが終わってしまっています

なので、completionから結果を得るにはこうします

typealias CompletionClosure = ((_ result:Int) -> Void)

func hogeC() {

// ①
hogeD(completionClosure: { (result:Int) in
// ⑥ 10が返ってくる
print(result)
})

// ④
}
func hogeD(completionClosure:@escaping CompletionClosure) {
// ②
UIView.animate(withDuration: 0.3,
animations: {
}, completion: { (finished) in
// ⑤

// 終了した時、10という値を返す
completionClosure(10)
})

// ③
}

自分でClosureを定義して、それを「終わったらこれを呼び出して」と渡してやります

その中で、Viewの更新などを行います

tableViewを使っているなら、tableView.reladData()などを実行します

一見するとトンデモなくややこしく見えます

私も最初はそうでしたが、使っていくととても便利なものだと分かります

ただ処理順序はこのように混乱しがちなので、処理を複雑にしないよう最新の注意を払わなければなりません


備考

Q. XcodeでClosureを書いたら書式が違う

それは省略形です

今回は省略しない形を自分で打ち込んでいます(省略されるとパット見で何か分からなくなるので)

Closureは色んな省略形があります

複雑なので大変ですが調べてください

Q. どうしても非同期で得た値をその関数でreturnしたい

これたまにあります

(私はiPhone内のAlbumを読み込むときにそうなりました。と言っても今では取り方が違うんですが)

その場合、非同期を同期化することも可能です

ですが複雑なのでここでは書きません

Q. delegateと存在意義がかぶっている?

歴史的な経緯です

両方使いますが、自分で定義するならClosureの方が最近主流な気がします(2018年)


あとがき

教科書やちゃんとしたドキュメント読むとここらへん混乱しますよね

書式とかではなく、何に使うか、何で必要か、挙動を確認したほうが早い気がします


メモ

これQ&Aサイトの回答用に書いたんですが、細部の用語について少し自信がありません

問題があれば微修正していくつもりです

あわせて読みたい(自分が)

非同期処理ってどういうこと?JavaScriptで一から学ぶ

JavaScriptの非同期処理を並列処理と勘違いしていませんか?