๐ WWDC21: Meet async/await in Swift
- ๋น๋๊ธฐ ํจ์์ ๊ณตํต์
- ํธ์ถํ๋ฉด ์์
์์ ํ ์ค๋ ๋์ ์ฐจ๋จ์ ๋น ๋ฅด๊ฒ ํด์
- ์ฅ๊ธฐ ์คํ ์์ ์ด ์๋ฃ๋๋ ๋์ ์ค๋ ๋๊ฐ ๋ค๋ฅธ ์์ ์ํ ๊ฐ๋ฅ
- ํธ์ถํ๋ฉด ์์
์์ ํ ์ค๋ ๋์ ์ฐจ๋จ์ ๋น ๋ฅด๊ฒ ํด์
Completion Handle Example
- ์ผ๋ถ ์์
์ ์๊ฐ์ด ๊ฑธ๋ฆผ
- prepareThumbnail
- dataTask
- ์๋ฃ ์, completionHandler๋ฅผ ํตํด ๊ฒฐ๊ณผ ๋ฐํ
- ํ์ง๋ง ์คํจ ์์๋ ์ด๋ ํ ์๋ฆผ๋ ๋ฐ์ ์ ์์
- ์ด๋ฏธ์ง๊ฐ ๋ณด์ฌ์ง์ง ์๊ณ ๋ฌดํ ๋ก๋ฉ
- ํ์ง๋ง ์คํจ ์์๋ ์ด๋ ํ ์๋ฆผ๋ ๋ฐ์ ์ ์์
- ๋ค ๊ฐ์ง ์์
์ ๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ ๊ทธ ๋ด๋ถ์ ๋ ์์
์ ๋น๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌ
- ์ฝ๋์ ์๋๋ฅผ ๋ช
ํํ๊ฒ ํ์
ํ๊ธฐ ์ด๋ ต๋ค๋ ๋ฌธ์ ๊ฐ ์์์
- ์ด๋ฅผ ์ํด Result Type์ ํ์ฉํจ
- ์ฝ๋์ ์๋๋ฅผ ๋ช
ํํ๊ฒ ํ์
ํ๊ธฐ ์ด๋ ต๋ค๋ ๋ฌธ์ ๊ฐ ์์์
- Result Type์ ํตํด ์์์ ์ผ๋ก ์๋ฌ๋ฅผ ๋ณด๋ด์ค ์ ์๊ฒ๋จ
- ํ์ง๋ง ์ฌ์ ํ ์ฝ๋๋ฅผ ์ง๊ด์ ์ผ๋ก ์ดํดํ๊ธฐ ์ด๋ ต๋ค๋ ๋ฌธ์ ๊ฐ ์์
- ์ด๋ฅผ ํด๊ฒฐํ๊ณ ์ async / await์ด ๋ฑ์ฅํจ
- ํ์ง๋ง ์ฌ์ ํ ์ฝ๋๋ฅผ ์ง๊ด์ ์ผ๋ก ์ดํดํ๊ธฐ ์ด๋ ต๋ค๋ ๋ฌธ์ ๊ฐ ์์
Async/await Example
- ๊ธฐ๋ณธ ๊ฐ๋
- async
- ํจ์๊ฐ ๋น๋๊ธฐ์์ ๋ช ์
- await
- async๋ก ๋ช
์๋ ํจ์๋ฅผ ํธ์ถ
- ๊ทธ๋ ๋ค๊ณ ๊ผญ ๋น๋๊ธฐ ํจ์๋ฅผ ํธ์ถํ ๋ ์ฌ์ฉํ๋ ๊ฒ์ ์๋
- maybeImage?.thumbnail ์ฒ๋ผ ๋น๋๊ธฐ์ ์์ฑ์ ๋ฐ์์ฌ ๋๋ ์ฌ์ฉ ๊ฐ๋ฅ
- ๊ทธ๋ ๋ค๊ณ ๊ผญ ๋น๋๊ธฐ ํจ์๋ฅผ ํธ์ถํ ๋ ์ฌ์ฉํ๋ ๊ฒ์ ์๋
- ๋น๋๊ธฐ ์์ ์ด ์ผ์์ค๋จ๋ ์ ์์์ ์๋ฏธ
- async๋ก ๋ช
์๋ ํจ์๋ฅผ ํธ์ถ
- async
- ํจ์ ์ ์
- async ํค์๋๋ฅผ ํตํด ํจ์ ์์ฒด๊ฐ ๋น๋๊ธฐ์์ ๋ช
์
- ํจ์์ ํํ๊ฐ ์ข ๋ ๊ฐ๋จํด์ง
- async ํค์๋๋ ํญ์ throws ๋ฐ๋ก ์
- ์๋ฌ๊ฐ ์๋ค๋ฉด → ์์ ์กด์ฌ
- ๋น๋๊ธฐ ํจ์๊ฐ ์ฑ๊ณตํ๋ค๋ฉด ๋ช ์ํด์ค ๋ฐ์ดํฐ ํ์ ์ ๊ฒฐ๊ณผ๊ฐ ๋ฐํ
- ์คํจํ๋ค๋ฉด ๊ทธ์ ์๋ฌ๋ฅผ ๋์ง (throws)
- ex) func fetchTumbnail(for id: String) async throws → UIImage
- async ํค์๋๋ฅผ ํตํด ํจ์ ์์ฒด๊ฐ ๋น๋๊ธฐ์์ ๋ช
์
- Completion Handler์์ ๋น๊ต
- Completion Handler์ ๋นํด ๋น๊ต์ ๋จ์ํ, ์ง๊ด์ ์ธ ์ฝ๋ ์ฌ์ฉ ๊ฐ๋ฅ
- ์์๋๋ก ์ํ๋์ด์ผ ํ๋ ๋ค ์์
์ด ์์๋๋ก ๋์ด๋จ
- ์ฝ๋์ ์๋๋ฅผ ๋ณด๋ค ๋ช ํํ๊ฒ ํ์ ํ ์ ์์
func fetchThumbnail(for id: String) async throws -> UIImage {
let request = thumbnailURLRequest(for: id)
// ๋น๋๊ธฐ ์ฒ๋ฆฌ -> ์ค๋ ๋ ๋ฐ๋ก ํด์
// try : throws์ Error๋ฅผ ๋ฐ์์ค๊ธฐ ์ํด ์ฌ์ฉ
// await : async๋ก ๋ช
์๋ ํจ์๋ฅผ ํธ์ถํ๊ธฐ ์ํด ์ฌ์ฉ
// ํํ์์ ์ฌ๋ฌ ๋น๋๊ธฐ ํจ์ ํธ์ถ์ด ์๋ ๊ฒฝ์ฐ, await์ ํ ๋ฒ๋ง ์ฌ์ฉํด์ค๋ ๋จ
// == ์ฌ๋ฌ ๊ฐ์ ์๋ฌ๋ฅผ ๋์ง๋ ํจ์์ ๋ํด ํ ๋ฒ์ try๋ง ์ฌ์ฉํ๋ ๊ฒ๊ณผ ๊ฐ์ ๋๋
let (data, response) = try await URLSession.shared.data(for: request)
// ๋ฐ์ดํฐ ์๋ฃ ์ ํ์ฌ ํจ์๋ก ๋์์ ๋ค์ ์์
์ ์์ํจ
// ๋ง์ฝ ์ try ๊ฒฐ๊ณผ๋ก ์๋ฌ๊ฐ ๋์ ธ์ง๋ค๋ฉด ์๋ฌ๊ฐ ๋ฐ์ํจ
// ๊ฒฐ๊ณผ๋ฅผ ์ฑ๊ณต์ ์ผ๋ก ๋ฐ์์๋ค๋ฉด (data, response)์ ๊ฐ์ด ๋ค์ด๊ฐ
// URLSession์ dataTask ๋ฉ์๋์์ ์ ๋ฌ๋ completionHandler๊ฐ ํธ์ถ๋์์ ๋์ ์ ์ฌ
// ํ์ง๋ง await๋ ์ข ๋ ๊ฐ๋จํ๋ฉด์ ์ง๊ด์ ์ (๋ฐํ๋ ๊ฐ์ ๋ณ์์ ํ ๋นํด! / ๋ฌธ์ ์๊น ์๋ฌ ๋์)
guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.badID }
// ๋ค์ด๋ก๋ํ ๋ฐ์ดํฐ๋ก UIImage ์์ฑ
let maybeImage = UIImage(data: data)
// ์ฑ๊ณต ์, ํด๋น ์ด๋ฏธ์ง์ ์ธ๋ค์ผ ๋ ๋๋ง ์ํ
// ์ด ๋์๋ await์ ํตํด ์ค๋ ๋ ๋ฐ๋ก ํด์ -> ๋ค๋ฅธ ์์
๊ฐ๋ฅ
// ์ฑ๊ณต ์ ์ธ๋ค์ผ ๋ฐํ / ์คํจ ์ ์๋ฌ ๋ฐ์
// .thumbnail์ด ๋น๋๊ธฐ์ด๊ธฐ ๋๋ฌธ์ await ์ฌ์ฉ
guard let thumbnail = await maybeImage?.thumbnail else { throw FetchError.badImage }
return thumbnail
}
Async Properties
- Swift 5.5๋ถํฐ ํ์ ํ๋กํผํฐ์ getter์๋ throw ์ฌ์ฉ ๊ฐ๋ฅ
- ํ์
ํ๋กํผํฐ์ setter์๋ async ์ฌ์ฉ ๋ถ๊ฐ
- ์ค๋ก์ง getter์๋ง ์ฌ์ฉ ๊ฐ๋ฅ
await works in for loops
- for๋ฌธ์ ๋น๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌ ๊ฐ๋ฅ
- ์์๋ฅผ ๋น๋๊ธฐ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ค๋ ์ ์ ์ ์ธํ๊ณ ๋ ์ผ๋ฐ์ ์ธ for๋ฌธ๊ณผ ๋์ผ
- ์ผ๋ฐ์ ์ธ ํจ์ ํธ์ถ ์, ์ค๋ ๋์ ์ ์ด๊ถ์ด ํจ์๋ก ๋์ด์ด. ์ด ๋, ์ค๋ ๋๋ ํด๋น ํจ์๋ฅผ ์ํํ๋๋ฐ ์ฌ์ฉ๋จ
- ๊ฒฐ๊ณผ์ ์ผ๋ก ๊ฐ์ ๋ฐํํ๊ฑฐ๋ ์๋ฌ ๋ฐ์ ์, ์ ์ด๊ถ์ด ํจ์๋ก ๋ค์ ๋์ด์ด
- finishing์ ํตํด ์ ์ด๊ถ์ด ํฌ๊ธฐ๋๋ฉฐ ๋ชจ๋ ์์ ์ด ์ข ๋ฃ๋จ
- ๋น๋๊ธฐ ํจ์ ํธ์ถ ์ ํด๋น ํจ์์ ์ค๋ ๋ ์ ์ด๊ถ ๋ถ์ฌ
- ๋น๋๊ธฐ ์์ ์ํ๋์ ์ค๋ ๋ ์ ์ด ํฌ๊ธฐ
- ํจ์์ ์ ์ด๊ถ์ ๋ค์ ๋ถ์ฌํ๋ ๋์ , ์์คํ ์ ์ค๋ ๋์ ๋ํ ์ ์ด๊ถ์ ๋ถ์ฌ
- ํจ์๋ ์ผ์์ ์ง ⇒ ์์คํ ์ด ์ค๋ ๋๋ฅผ ์์ ๋กญ๊ฒ ์ฌ์ฉํด ๋ค๋ฅธ ์์ ์ํ ๊ฐ๋ฅ : await
- ํ์ฌ ์ํ๋์ด์ผ ํ ๊ฐ์ฅ ์ค์ํ ์์ ๋น๋๊ธฐ ์์ ์ด๋ผ๊ณ ํ๋จ๋๋ฉด ์์คํ ์ ์ฌ๊ฐ : resume
- ํด๋น ๋น๋๊ธฐ ํจ์๊ฐ ๋ค์ ์ค๋ ๋๋ฅผ ์ ์ดํ๊ณ ํด๋น ์์
์ ๊ณ์ํจ
- ์ดํ์๋ ์ฌ๋ฌ ๋ฒ ์ผ์์ ์ง ๊ฐ๋ฅ
- ๋ฐ๋ฉด, ์ผ์์ ์ง ์์ฒด๊ฐ ํ์์์ ์ ์์
- async๋ก ํ์๋๋ค๊ณ ํญ์ ์ผ์์ ์ง๋๋ ๊ฒ์ ์๋
- await์ด ์๋ค๊ณ ํจ์๊ฐ ๋ฐ๋์ ์ผ์์ ์ง๋๋๊ฒ๋ ์๋
- ๋น๋๊ธฐ ์์ ์ดํ, ์ค๋ ๋์ ๋ํ ์ ์ด๊ถ์ ํจ์์๊ฒ ๋ค์ ๋ถ์ฌํด์ค
func fetchThumbnail(for id: String) async throws -> UIImage {
let request = thumbnailURLRequest(for: id)
// ๋น๋๊ธฐ ๋ฉ์๋ ํธ์ถ -> ์ค๋ ๋ ์คํ ์ค์ง
// ์์คํ
์ ์ค๋ ๋ ์ ์ด๊ถ ๋ถ์ฌ ๋ฐ ์์คํ
์ URLSessiond์ ์์
์ผ์ ์์ฝ
// ์ค๋ ๋์ ๋ํ ์ ์ด๊ถ์ ์์คํ
์ด ๊ฐ์ง๊ธฐ ๋๋ฌธ์ ๋ฐ๋ก ์์๋์ง ์์ ์ ์์
// ๋์ ์ค๋ ๋๋ฅผ ๋ค๋ฅธ ์ฉ๋๋ก ์ฌ์ฉํ ์ ์์
// ํด๋น ๋น๋๊ธฐ ์์
์๋ฃ ํ, fetchThumbnail๋ก ๋์์ด
// - ํจ์๊ฐ ์ผ์์ ์ง์ธ ์ํ์์ ๋ค๋ฅธ ์์
์ ์ํํ ์ ์์ผ๋ฏ๋ก ๋น๋๊ธฐ ์์
์ await๋ก ํ์ํจ
// - ๋ํ, await ํค์๋๋ ์ฝ๋ ๋ธ๋ก์ด ํ๋์ ํธ๋์ญ์
์ผ๋ก ์คํ๋์ง ์์์ ์๋ฏธ
let (data, response) = try await URLSession.shared.data(for: request)
guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw FetchError.badID }
let maybeImage = UIImage(data: data)
guard let thumbnail = await maybeImage?.thumbnail else { throw FetchError.badImage }
return thumbnail
}
await ํค์๋๋ฅผ ํตํ ํ๋์ ์ฝ๋ ๋ธ๋ญ ๋ด ํธ๋์ญ์ ์ด ๋ถ๋ฆฌ๋จ
async/await facts
- async๋ ํจ์๊ฐ ๋น๋๊ธฐ์ ์ผ๋ก ์คํ๋ ์ ์์์ ์๋ฏธํ๋ ํค์๋
- = ํด๋น ํจ์๊ฐ ์ผ์์ ์ง ๋ ์ ์์
- ํธ์ถ์๋ ์ผ์์ ์ง๊ฐ ๋๋ฏ๋ก ํธ์ถ์๋ ๋น๋๊ธฐ์ฌ์ผ ํจ
- ๋น๋๊ธฐ ์์ ๋ด์์ ์ผ์์ ์ง๋ฅผ ํ ์์น ์ง์ ์ ์ํด await ํค์๋ ์ฌ์ฉ
- ๋น๋๊ธฐ ์์
์ด ์ผ์์ ์ง๋๋ ๋์ ์ค๋ ๋๋ ์ฐจ๋จ๋์ง ์์
- ์์คํ
์ด ๋ค๋ฅธ ์์
์ ํ ์ ์์
- ์ฑ ์ํ๊ฐ ๋ณํ ์ ์์์ ์๋ฏธ
- ์์คํ
์ด ๋ค๋ฅธ ์์
์ ํ ์ ์์
- ๋น๋๊ธฐ ํจ์ ์ฌ์์ ์, ๋น๋๊ธฐ ํจ์ ๊ฒฐ๊ณผ๋ก ๋ฐํ๋ ๋ฐ์ดํฐ๊ฐ ์๋ ํจ์๋ก ์ ์ ๋๊ณ ์ค๋จ๋ ๋ถ๋ถ๋ถํฐ ์ด์ด์ ์คํ๋จ
Testing async code
- XCTest๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋น๋๊ธฐ ์ง์
- ๋น๋๊ธฐ ์ฝ๋๊ฐ ๋๊ธฐ์ฒ๋ผ ์ฝ๊ฒ ์์ฑ๋๊ธธ ์ํ๊ธฐ ๋๋ฌธ
- ๋น๋๊ธฐ๊ฐ ์๋ Context ๋ด์์๋ ๋น๋๊ธฐ ํจ์ ํธ์ถ ๋ถ๊ฐ
- onAppear๋ ์ผ๋ฐ ํด๋ก์ ๋ฅผ ์ฌ์ฉ
- ๋๊ธฐ์ ๋น๋๊ธฐ๋ฅผ ์ฐ๊ฒฐํด์ฃผ๋ ๋งค๊ฐ์ฒด๊ฐ ํ์ํ๊ฒ ๋จ
- ์ด ๋, Task๋ก ๋น๋๊ธฐ ์์ ์ ๊ฐ์ธ์ค
- ๋๊ธฐ์ ๋น๋๊ธฐ๋ฅผ ์ฐ๊ฒฐํด์ฃผ๋ ๋งค๊ฐ์ฒด๊ฐ ํ์ํ๊ฒ ๋จ
์ฌ๋น๋ ์ ์๋ฌ ๋ฉ์์ง ์ฌ๋ผ์ง
- Task๋ฅผ ํตํด ๋๊ธฐ ์์ ๋ด์์ ๋น๋๊ธฐ ์์ ์ ํธ์ถํ ์ ์๊ฒ๋จ
CompletionHandler ⇒ Async
Swift 5.5 ์ด์ ๊น์ง์ ๋ฐฉ์
Swift 5.5๋ถํฐ ๊ฐ๋ฅํ ๋ฐฉ์
- ๋น๋๊ธฐ ์์ ์ ํธ์ถ๊ณผ ๋์์ ๊ฒฐ๊ณผ๊ฐ ๋ฐํ๋์ง ์์ผ๋ฏ๋ก ํจ์๋ช ์์ get์ ์ฐ์ง์๋ ๊ฒ์ด ์ข์
Async alternatives and continuations
- ํ์ฉ ํจ์
func getPersistentPosts(completion: @escaping ([Post], Error?) -> Void) {
do {
let req = Post.fetchRequest()
req.sortDescriptors = [NSSortDescriptor(key: "date", ascending: true)]
let asyncRequest = NSAsynchronousFetchRequest<Post>(fetchRequest: req) { result in
// ์๋ฃ ํ completionHandler๋ก ๊ฒฐ๊ณผ ๋ฐํ
completion(result.finalResult ?? [], nil)
}
try self.managedObjectContext.execute(asyncRequest)
} catch {
completion([], error)
}
}
Async/await์ ๋ฐ์ํด ์ฌ์ฉํด๋ณด์
- ๋ฌธ์ ์
- getpersistentPosts์ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ ธ์ ๋ญ ํ ์๊ฐ ์์ด ํจ์๊ฐ ๋ ์งํ๋์ง ์์
- ํ์ํ ํด๊ฒฐ๋ฐฉ๋ฒ
- getPersistentPosts์ ๊ฒฐ๊ณผ๋ฅผ persistentPosts์ ํจ์๊ฐ ์ผ์์ ์ง๋ ์์น๋ก ๋ฐํํด์ฃผ์ด์ผํจ
- ๋ํ, ํธ์ถ์๋ ์ผ์์ ์ง๋ ์ํ๋ก ์ ์ ํ ์์ ์ ์ฌ๋ฐ๋ฅธ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํด ์์ ์ ์ฌ๊ฐํ ์ ์๊ฒ ํด์ฃผ์ด์ผํจ
- CompletionHandler๋ฅผ ๊ฐ์ ธ์ ๊ฒฐ๊ณผ๋ฅผ ํตํด ๋ค์ ์์ํ๋ resume์ด ์์
- ๊ฒฐ๊ตญ ์ฐ๋ฆฌ๊ฐ ์ํ๋ ๊ฒฐ๊ณผ๋ฅผ ์ํด์๋ ์ด๋ฌํ ํํ๋ก ๋ง๋ค์ด์ฃผ์ด์ผํจ
- ์ด๋ฅผ ์ํด withCheckedThrowingContinuation์ ์ฌ์ฉ
- ์๋ฌ๋ฅผ ๋ฐํํ๋ Completion Handler๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์
- ์ค์ง๋ ๋น๋๊ธฐ ํจ์๋ฅผ ์ฌ๊ฐํ๋๋ฐ ์ฌ์ฉํ ์ ์์
- ์๋ฌ๊ฐ ์๋ ์ํฉ์ด๋ผ๋ฉด withCheckedContinuations ์ฌ์ฉ
func persistentPosts() async throws -> [Post] {
typealias PostContinuation = CheckedContinuation<[Post], Error>
// getPersistentposts์ ๋ํ ํธ์ถ์ ๊ธฐ๋ค๋ฆฌ๋๋ก ํจ
return try await withCheckedThrowingContinuation { (continuation: PostContinuation) in
self.getPersistentPosts { posts, error in
// ๊ฒฐ๊ณผ๊ฐ ์จ๋ค๋ฉด ํ์ฌ ์ผ์์ ์ง๋ ํจ์๋ฅผ resume ์ํด
if let error = error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: posts)
}
}
}
}
- โ ๏ธ ์ฃผ์์ฌํญ
- resume์ ๋ชจ๋ ๊ฒฝ๋ก์์ ์ค๋ก์ง ํ ๋ฒ๋ง ํธ์ถ
- resume์ด ์๋ ๊ฒฝ์ฐ, warning
- resume์ด ์ฌ๋ฌ ๊ฐ์ผ ๊ฒฝ์ฐ, ๋ ๋ฒ์งธ ํธ์ถ์์ ์๋ฌ๊ฐ ๋ฐ์
- resume์ ๋ชจ๋ ๊ฒฝ๋ก์์ ์ค๋ก์ง ํ ๋ฒ๋ง ํธ์ถ
'Study' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
๐ Concurrency Docs ๋ด ์ผ๋ถ (0) | 2024.09.11 |
---|---|
๐ WWDC21 : Explore structured concurrency in Swift (0) | 2024.09.07 |
๐ Continuation ์์๋ณด๊ธฐ (0) | 2024.08.24 |
๐ ARC Docs ์ ๋ฆฌ (0) | 2024.08.06 |
๐ย WWDC : ARC in Swift (0) | 2024.08.02 |