今までの非同期処理
Swiftで簡単に最新の同期処理/非同期処理を学ぼうでまとめました。
Async/awaitとは何か
WWDC2021の0296-async-await.mdをまとめてみました。
Async/awaitは、非同期を関数毎に実行して、関数の呼び出し元は同期を維持する仕組みです。
APIの呼び出しなど非同期が完了後に、別の非同期の呼び出しを行う場合、非同期ブロックをネストさせることになりますよね。
冒頭では、いかにソースが冗長になるかの説明から始まりますが割愛します。
非同期関数(Asynchronous functions)
新しい予約語にasyncとawaitが追加されます。
非同期関数(async形式の関数)を、処理待ち(await)で呼び出します。
非同期関数の定義と呼び出し
// ① 非同期関数の定義は引数とthrowsの間にasyncを付けることで定義する。
// Tips1 throwは必須ではない。
func loadWebResource(_ path: String) async throws -> Resource
func decodeImage(_ r1: Resource, _ r2: Resource) async throws -> Image
func dewarpAndCleanupImage(_ i : Image) async throws -> Image
// ③ 非同期関数の呼び出しは、そのスレッドを止める必要があるので、
// 止まっても良いスレッドか、呼び出し元の関数も非同期メソッドである、
// 必要があると推測する。
func processImageData() async throws -> Image {
// ② 非同期関数の呼び出しはtryと関数名の間にawaitを付ける。
// Tips2 関数が非同期で呼び出され、呼び出しが終わるまで、
// 呼び出し元の関数はその行で処理を待つ。アウェイト式という。
// Tips3 関数が終了すると呼び出し行で、関数を処理したはスレッド放棄される。
// 放棄されるポイントをサスペンションポイント(Suspension points)という。
let dataResource = try await loadWebResource("dataprofile.txt")
let imageResource = try await loadWebResource("imagedata.dat")
let imageTmp = try await decodeImage(dataResource, imageResource)
let imageResult = try await dewarpAndCleanupImage(imageTmp)
return imageResult
}
非同期関数型(Asynchronous function types)
関数型は既存のthrowsと同様に、asyncを内部的に持ちます。
内部的な型変換が起こるのでthrowsと同様に注意が必要です。
非同期関数型の暗黙的な型変換
struct FunctionTypes {
// ① 同期型かつ例外を投げない
var syncNonThrowing: () -> Void
// ② 同期型かつ例外を投げる
var syncThrowing: () throws -> Void
// ③ 非同期型かつ例外を投げない
var asyncNonThrowing: () async -> Void
// ④ 非同期型かつ例外を投げる
var asyncThrowing: () async throws -> Void
mutating func demonstrateConversions() {
// Tips1 暗黙変換でasync、throwsを付与できる。
// ③ << ① 暗黙変換でasyncを付与
asyncNonThrowing = syncNonThrowing
// ④ << ② 暗黙変換でasyncを付与
asyncThrowing = syncThrowing
// ② << ① 暗黙変換でthrowsを付与
syncThrowing = syncNonThrowing
// ④ << ③ 暗黙変換でthrowsを付与
asyncThrowing = asyncNonThrowing
// Tips2 暗黙変換でasync、throwsは削除できない。
syncNonThrowing = asyncNonThrowing // error
syncThrowing = asyncThrowing // error
syncNonThrowing = syncThrowing // error
asyncNonThrowing = syncThrowing // error
}
}
アウェイト式(Await expressions)
非同期処理の関数結果を次の非同期に渡すことができます。
アウェイト式のメリットと注意点
func redirectURL(for url: URL) async -> URL { ... }
func dataTask(with: URL) async throws -> (Data, URLResponse) { ... }
// Tips1 非同期が完了後に次の行へ
let newURL = await server.redirectURL(for: url)
let (data, response) = try await session.dataTask(with: newURL)
// Tips2 サスペンションポイントが上のように、
// 複数あると呼び出し元スレッドが止まることが多くなる。
// 1行で書いて潜在的なサスペンションポイントを減らすことができる。
let (data, response) = try await session.dataTask(with: server.redirectURL(for: url))
オートクロージャ(Autoclosures)
既存のオートクロージャについての呼び出し注意点です。
オートクロージャの呼び出し
func computeArgumentLater<T>(_ fn: @escaping @autoclosure () async -> T) { }
// Tips サスペンションポイントがオートクロージャの呼び出し時なので、
// この呼び出しはできない。
computeArgumentLater(await getIntSlowly())
// オートクロージャの呼び出しも非同期にする必要がある。
await computeArgumentLater(getIntSlowly())
プロトコルの適合性(Protocol conformance)
プロトコルではasync型の関数に通常の関数も含みます。
プロトコルの使用例
protocol Asynchronous {
func f() async
}
protocol Synchronous {
func g()
}
struct S1: Asynchronous {
func f() async { } // 型が一致する場合OK
}
struct S2: Asynchronous {
func f() { } // Tips1 非同期関数の定義に通常の関数を含むのでOK
}
struct S3: Synchronous {
func g() { }
}
struct S4: Synchronous {
func g() async { } // Tips2 同期関数の定義に非同期関数を含まないのでError
}
Actor型
Actor型はAsync/awaitを使った排他制御のための型です。