概要
iOSエンジニアの採用の基準にAPIの呼び出しができるかという基準をよく聞きます。
APIの呼び出しができれば、非同期処理なのでクロージャの呼び出しや定義、非同期処理の動きを把握てきていることになります。
意図的に別スレッドを作成して、どのような動きになるか説明します。
同期処理の例(通常の関数呼び出しなどは同期処理になる)
countUp変数に関数型の値(1〜3のカウントアップ処理)を格納します。
呼び出し元が同期されているので、countUp(1, count)が終わった後に、countUp(2, count)が呼ばれるので順番が狂いません。これが同期処理です。
let countUp = {
(method: Int, count: Int) -> Int in
for i in 1...3 {
print("method\(method) count=\(count + i)")
}
return count + 3
}
var count = 0
// クロージャーの呼び出し
count = countUp(1, count) // count == 0
count = countUp(2, count) // count == 3
count = countUp(3, count) // count == 6
/*
処理結果:
method1 1
method1 2
method1 3 // method1が終了後にmethod2
method2 4
method2 5
method2 6 // method2が終了後にmethod3
method3 7
method3 8
method3 9
*/
非同期処理の例(OperationQueue)
プログラムで重たい処理を対処する時、メインスレッドを使うと、ボタン押下などUI対処で常に監視すべき処理も止まってしまいます。
そのような場合、スレッドを複数作成し平行処理させる必要が出てきます。
APIのコールバックも同様の理由で、API呼び出し中ネットワークや、サーバーの応答を待ち続けるためにメインスレッドを使うことはできません。
応答専用のスレッド使用する必要が出てきます。
平行処理(非同期処理)の課題1
平行処理のためcountUp(1, count)完了後に、countUp(2, count)が呼ばれることが、保証されなくなります。
let countUp = {
(thread: Int, count: Int) -> Int in
for i in 1...3 {
print("thread\(thread) \(count + i)")
}
return count + 3
}
let queue = OperationQueue()
// クロージャーをOperationQueueに渡す
// 3つのスレッドで平行に処理を行う
var count = 0
queue.addOperation({
count = countUp(1, count) // count == 0
})
queue.addOperation({
count = countUp(2, count) // count == 0
})
queue.addOperation({
count = countUp(3, count) // count == 0
})
/*
処理結果:
thread1 1 // thread1が途中なのにthread2、thread3が呼ばれる。
thread3 1
thread2 1
thread1 2
thread1 3 // 求める演算に到達できない。
thread3 2
thread3 3
thread2 2
thread2 3
*/
平行処理(非同期処理)の同期と新たな課題
処理ブロック完了後に処理を行う必要が出てきます。
let countUp = {
(thread: Int, count: Int) -> Int in
for i in 0...2 {
print("thread\(thread) \(count + i)")
}
return count + 3
}
let queue = OperationQueue()
var count = 1
queue.addOperation({
count = countUp(1, count) // count == 0
// 非同期完了時に次の非同期処理を行う。
queue.addOperation({
count = countUp(2, count) // count == 3
// 非同期完了時に次の非同期処理を行う。
queue.addOperation({
count = countUp(3, count) // count == 6
})
})
})
/*
処理結果:
thread1 1
thread1 2
thread1 3 // thread1が終了後にthread2
thread2 4
thread2 5
thread2 6 // thread2が終了後にthread3
thread3 7
thread3 8
thread3 9
*/
平行処理(非同期処理)の課題2
別スレッドのため重たい処理でも対処できるようになりましたが、今度は可読性が下がってしまいます。スレッドの繋がり全体の終端処理など、入れるとさらに可読性が下がります。この問題を対処するために、Swift5.5からJavascriptと同じAsync/awaitが導入されます。
Swift5.5からの平行処理(非同期処理)と同期
Async/awaitで記事を書きました。
let countUp = {
(thread: Int, count: Int) async -> Int in
for i in 0...2 {
print("thread\(thread) \(count + i)")
}
return count + 3
}
let queue = OperationQueue()
var count = 1
queue.addOperation {
count = await countUp(1, count) // count == 0
count = await countUp(2, count) // count == 3
count = await countUp(3, count) // count == 6
}
/*
処理結果:
thread1 1
thread1 2
thread1 3 // thread1が終了後にthread2
thread2 4
thread2 5
thread2 6 // thread2が終了後にthread3
thread3 7
thread3 8
thread3 9
*/