Udemy Study for Swift Concurrency
- Udemy ๊ฐ์๋ฅผ ๊ตฌ๋งค ํ ๊ฐ๋ณ ์ธ์ฆ์ ํด์ผ ์คํฐ๋ ์ฐธ์ฌ ๊ฐ๋ฅ
- ๊ฐ์ ํ์ตํ ๋ด์ฉ์ ๊ณต์ฉ study git repository์ ๊ณต์ ํ๊ณ ํ ์ํ๋ฉด์ ๋ด์ฉ ๋ณด์
- ๊ฐ์ธ์ ์ผ๋ก ๊ณต๋ถํ ๋ด์ฉ์ ๊ฐ์ธํด๋์ ๊ธฐ๋ก (fork ํ, ๊ฐ์ธ ์คํฐ๋ ๋ด์ฉ ๊ธฐ๋ก ํ๋ค๊ฐ ๊ณต์ฉ repository์ PR)
- ๊ฐ์ ์๋ฌด๊ฐ์ ๊ฐ๊ณ ์คํฐ๋ํ๊ธฐ ์ํจ
- ๋งค์ฃผ ๋ค์๊ฒฐ๋ก ์คํฐ๋ ๋ ์ง ์ง์ ํ, 2์๊ฐ์ฉ ์คํฐ๋ ์งํ
- ๊ฐ์ธ์ ์ผ๋ก ๊ณต๋ถํ ๋ด์ฉ์ ๊ฐ์ธํด๋์ ๊ธฐ๋ก (fork ํ, ๊ฐ์ธ ์คํฐ๋ ๋ด์ฉ ๊ธฐ๋ก ํ๋ค๊ฐ ๊ณต์ฉ repository์ PR)
- ์คํฐ๋ ๊ธฐ๋ก ์ ๋ฆฌ ๋ฐฉ์์ ๋ํ ์ข์ ์๊ฒฌ ์์ ๋กญ๊ฒ ๊ณต์
- ์คํฐ๋ ์ค์ ์์ ๋กญ๊ฒ ์ดํดํ ๋ด์ฉ์ ์๊ธฐํ ์ ์์ผ๋ฉฐ, ์ดํด๊ฐ ๋์ง ์์ ๋ด์ฉ์ด ์๋ค๋ฉด ์ธ์ ๋ ์ง ๊ณต์
- 3/26(์ผ), ์คํ 1์ ~ 3์
- Section 1: Introduction ~ Async/Await Using Continuation
- ๐ฉ๐ปโ๐ป applebuddy | AppleCEO | ChoiYS | Jae-eun | JongHoooon | Lim-YongKwan
- 4/1(์ผ), ์คํ 1์ ~ 3์
- Section 6: Async/Await Using Continuation ~ Section 9: Download RandomImages and Quotes
- ๐ฉ๐ปโ๐ป applebuddy | ChoiYS | Jae-eun | JongHoooon | Lim-YongKwan
- 4/9(์ผ), ์คํ 1์ ~ 3์
- Section 9: Download RandomImages and Quotes ~ Section 12: What are Actors?
- ๐ฉ๐ปโ๐ป applebuddy | AppleCEO | ChoiYS | Jae-eun | JongHoooon | Lim-YongKwan
- 4/15(์ผ), ์คํ 8์ ~ 10์
- Section 12: What are Actors? ~ End ๐ค
- ๐ฉ๐ปโ๐ป applebuddy | AppleCEO | ChoiYS | Jae-eun | JongHoooon
Concurrency๋, ๋์์ ๋ค์์ ์์ ์ ์งํํ๋ ๊ฒ.
-
serialํ๊ฒ ๋์ํ๋ Main thread์์ UI Event, Downloading Images task๋ฑ์ ๋ชจ๋ ๋์์ํจ๋ค๋ฉด?โฆ ๋ฉ์ถคํ์์ด ๋ฐ์ํ ์ ์์ => ๋งค์ฐ ๋์ user experience๋ฅผ ๋ง๋ค ์ ์์
-
์์ ํน์ฑ์ ๋ง๊ฒ ๊ฐ๊ธฐ thread์์ ๋์ํ๋๋ก ํ ์ ์์ (ex) downloading images๋ฅผ main thread ๋์ background thread์์ ๋์)
- DispatchQueue.global().async { let _ = try? Data(contentOf: imageURL }
-
์ดํ main thread์์ UI๋ฅผ ์ ๋ฐ์ดํธ ์ํฌ ์ ์์
- DispatchQueue.main.async { // update the ui }
-
์ด์ฒ๋ผ GCD๋ฅผ ์ด์ฉํด ์ํฉ์ ๋ฐ๋ผ main, background thread์์ ๋น๋๊ธฐ๋ก ์์ ์ ์ํํ ์ ์์
DispatchQueue(Grand Central DispatchQueue)๋ ๋ง์ถคํ ์์ ์คํ์ ์ํ C-๊ธฐ๋ฐ ๋ฉ์ปค๋์ฆ์ ๋๋ค. DispatchQueue๋ ์์ฐจ์ ์ด๊ฑฐ๋ ๋์์ ์ผ๋ก ์์ ์ ์คํํ์ง๋ง ์ด๋ ํญ์ FIFO(First In First Out) ์์๋ก ์คํ๋ฉ๋๋ค. SerialQueue๋ ์์ฐจ์ ์ผ๋ก ์์ ์ด ์งํ๋๊ณ , ConcurrentQueue๋ ๋์์ ์ผ๋ก ์์ ์ด ์งํ๋ฉ๋๋ค. ๊ทธ ์ธ GlobalQueue(QoS), CustomQueue(Serial, Concurrent), MainQueue(Serial)๋ฑ์ ํตํด ๋ค์ํ ์์ ์ ์ํํ ์ ์์ต๋๋ค.
- ์ฝ๊ณ ๊ฐ๊ฒฐํ ํ๋ก๊ทธ๋๋ฐ ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํฉ๋๋ค.
- ์๋์ ์ด๊ณ ์ ์ฒด์ ์ธ ์ค๋ ๋ ํ ๊ด๋ฆฌ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค.
- ์ ์ ํ ์กฐ์จ๋ ์ด์ ๋ธ๋ฆฌ์ด์ ์คํผ๋๋ฅผ ์ ๊ณตํฉ๋๋ค.
- ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ์ ํจ์จ์ ์ ๋๋ค. (์ค๋ ๋ ์คํ์ด ์ดํ๋ฆฌ์ผ์ด์ ๋ฉ๋ชจ๋ฆฌ์ ๋จ์ง ์๊ธฐ ๋๋ฌธ์ ๋๋ค.)
- ์ปค๋์ ๋ถํ๋ฅผ ์ฃผ์ง ์์ต๋๋ค.
- DispatchQueue๋ฅผ ํตํ ์์ ๋น๋๊ธฐ ์ ์ก์ ๋๊ธฐ์ด์ ๊ต์ฐฉ์ํ๋ฅผ ์ผ๊ธฐํ์ง ์์ต๋๋ค.
- Main thread๋ serial queue๋ก ๋์ํ๋ค. ํ๋์ฉ ์์ฐจ์ ์ผ๋ก ์์ ์ด ์งํ ๋๋ค. ํ๋์ ์์ ์ด ์งํ๋๋๋์ ๋ค๋ฅธ ์ด๋ฒคํธ๋ ์ฒ๋ฆฌํ ์๊ฐ ์๋ค.
- Global Queue๋ QoS(Quality of Service)๋ฅผ ์ค์ ํ ์ ์๋ค.
- User Interactive
- animation, event handling, updating user interface ๋ฑ ์ฌ์ฉ์์ ์ง์ ์ํธ์์ฉํ๋ ์์
- ๋ฉ์ธ ์ค๋ ๋์์ ์ฒ๋ฆฌํ๋ฉด ๋ง์ ๋ก๋๊ฐ ๊ฑธ๋ฆด ์ ์๋ ์์ ๋ค์ userInteractive์์ ์ฒ๋ฆฌํด์ ๋ฐ๋ก ๋์ํ๋ ๊ฒ์ฒ๋ผ ๋ณด์ด๊ฒ ํ ์ ์์
- User Initiated
- ์ ์ฅ๋ ๋ฌธ์ ์ด๊ธฐ ๋ฑ ํด๋ฆญ ์ ์์ ์ ์ํํ ๋ ์ฒ๋ผ ์ฆ๊ฐ์ ์ธ ๊ฒฐ๊ณผ๊ฐ ํ์ํ ์์ ,
- userInteractive๋ณด๋ค๋ ์กฐ๊ธ ์ค๋๊ฑธ๋ฆด ์ ์์ง๋ง ์ ์ ๊ฐ ์ด๋ฅผ ์ธ์งํ๊ณ ์์
- Utility
- ๋ฐ์ดํฐ ๋ค์ด๋ก๋ ์ฒ๋ผ ๋ณดํต progress bar์ ํจ๊ป ๊ธธ๊ฒ ์คํ๋๋ ์์
- Background
- ๋๊ธฐํ ๋ฐ ๋ฐฑ์ ์ฒ๋ผ ์ ์ ๊ฐ ์ง์ ์ ์ผ๋ก ์ธ์งํ ํ์์ฑ์ด ์ ์ ์์
- Default
- ์ผ๋ฐ์ ์ธ ์์
- Unspecified
- ๋ช ํํ ์ง์ ๋ QoS๊ฐ ์์
- User Interactive
DispatchQueue.global().async {
// download the image
// refresh the UI (background queue ์์ UI๋ฅผ ์
๋ฐ์ดํธ ํ๋ฉด ์๋จ)
}
DispatchQueue.global().async {
// download the image
DispatchQueue.main.async {
// refresh the UI (UI ๊ด๋ จ ์์
์ Main thread์์ ๋์์์ผ์ผ ํจ)
}
}// Serial Queue๋ Concurrent Queue์ ๋ฌ๋ฆฌ ์์ฐจ์ ์ผ๋ก ์์
์ด ์งํ๋๋ฏ๋ก ์์
์์๊ฐ ๋ณด์ฅ๋๋ค.
let queue = DispatchQueue(label: "SerialQueue")
queue.async {
// this task is executed first
}
queue.async {
// this task is executed second
}// Concurrent Queue๋ Serial Queue์ ๋ฌ๋ฆฌ ์์
์์๊ฐ ๋ณด์ฅ๋์ง ์๋๋ค.
let queue = DispatchQueue(label: "ConcurrentQueue", attributes: .concurrent)
queue.async {
// ...
}
queue.async {
// ...
}
// Tasks will start in the order they are added but they can finish in any order (์์์ ์์๋๋ก ์งํ๋์ง๋ง, ์์
์ด ์ข
๋ฃ๋๋ ์์๋ ๋ณด์ฅ๋์ง ์์)-
Best practices, ์ค์ฉ์ ์ธ ๊ตฌ์กฐ๋ฅผ ๋ง๋ค๊ธฐ ์ํ ๋ ธ๋ ฅ
-
Relationships between classes and objects, ํด๋์ค ๋ฑ์ ๊ฐ์ฒด ๊ฐ์ ๊ด๊ณ๋ฅผ ์ ์
-
Speed up development, ๊ฐ๋ฐ ์๋ ํฅ์
-
Programming independent, ํ๋ก๊ทธ๋๋ฐ ๋ ๋ฆฝ์ฑ
-
Flexible, reusable and maintainable, ์ตํต์ ์ผ๋ก, ์ฌ์ฌ์ฉ๊ฐ๋ฅํ๊ฒ, ์ ์ง๋ณด์๊ฐ ๋์ฑ ์ฝ๊ฒ ๋ง๋ค๊ธฐ ์ํด
- Model, View, ViewModel๋ก ๊ตฌ์ฑ๋๋ ๋์์ธํจํด ๊ธฐ๋ฒ
- ViewModel์ด ๋น์ฆ๋์ค๋ก์ง์ ๊ฐ์ ธ๊ฐ๊ฒ ๋๋ฉฐ MVC์ Massive ViewController๋ฅผ ํด๊ฒฐํ๊ณ Testability์ ์ด๋ ค์์ ํด์ํ ์ ์๋ค.
- ViewModel์ด ์ฃผ์ ๋น์ฆ๋์ค๋ก์ง์ ๊ฐ๊ณ ์๋ค. ViewModel์ ๋ณํ๋ฅผ View๋ ๊ฐ์งํ๊ณ ๊ทธ์ ๋ง๊ฒ ๋ณํํ๋ค.
- View๋ ์ด๋ฒคํธ๋ฅผ ViewModel์ ์ ๋ฌํ๊ณ , ViewModel์ ์ด๋ฒคํธ์ ๋ง๋ ๋น์ฆ๋์ค ๋ก์ง์ ์ํํ๋ค.
- ViewModel์์๋ Constant๊ฐ์ด๋ ๋ณต์กํด์ง๋ ๋น์ฆ๋์ค ๋ชจ๋ธ ๋ฑ์ Model๋ก ๋ถ๋ฆฌํ์ฌ ๊ด๋ฆฌ๋๋ค. (View, Model์ ์๋ก ์ง์ ์ ์ผ๋ก ์ํตํ ์ผ์ด ์๋ค.)
- why MVVM? : view๋ก๋ถํฐ ๋ค์ด์ค๋ value์ ๋ํ validation์ ViewModel์์ ํ ์ ์๋ค. ViewModel์ View ๋ถ๋ฆฌ๋์ด์๊ธฐ ๋๋ฌธ์ View์ ์ํฅ์ ๋ฏธ์น์ง ์๊ณ ViewModel์ ๋ ๋ฆฝ์ ์ธ ํ ์คํธ์ฝ๋๋ฅผ ์์ฑํด์ ํ ์คํธํ๊ธฐ ์ฉ์ดํ๋ค.
- View -> Web service -> API ์์ฒญ์ ํ๋ ๊ฒ์ ๊ฐ๋ฅ์ ํ์ง๋ง ๊ฒฐ์ฝ ์ข์ ๋ก์ง์ด ์๋๋ค.
- MVVM ํจํด์์๋ View - ์ด๋ฒคํธ -> ViewModel -> Web service / Client -> API ์์ฒญ์ ํ ์ ์๋ค.
-
continuation์ ์ฌ์ฉํ๋ฉด ๊ธฐ์กด์ callback closure๊ฐ ์๋ legacy ๋ฉ์๋๋ฅผ ๊ทธ๋๋ก ์ ์งํ๊ณ wrappingํด์ ์ธ๋ถ์์ ์ฝ๋ฒก ๊ฒฐ๊ณผ์ ๋ฐ๋ฅธ async await ์ฒ๋ฆฌ๋ฅผ ํ ์ ์๋๋ก ๋์์ค๋ค.
-
callback closure๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ ์ฌ๋ฌ ์ฌ์ ๋ก ๋ณํํ๊ธฐ ํ๋ third-party, legacy ๋ฉ์๋๋ฅผ wrappingํด์ ์ธ๋ถ์์ async await ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌํ๊ณ ์ ํ ๋ ์ ์ฉํ๊ฒ ์ฌ์ฉํ ์ ์๋ค.
-
withCheckedContinuation ์ฌ์ฉ ์์
func getPosts() async throws -> [Post] {
// error๋ฅผ throwํ ์ผ์ด ์์ผ๋ฉด withCheckedContinuation์ ์ฌ์ฉ
return await withCheckedContinuation { continuation in
// continuation์ ํ์ฉํ๋ฉด callback closure๊ฐ ์๋ getPosts ๋ฉ์๋๋ฅผ ์ธ๋ถ์์๋ async await ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌํ ์ ์๋๋ก ํ ์ ์๋ค.
getPosts { posts in
continuation.resume(returning: posts)
}
}
}Global actor-qualified wrappedValue๋ฅผ ๊ฐ๊ณ ์๋ propertyWrapper๋ฅผ ์ฑํํ ํ๋ฌํผํฐ๋ฅผ ๊ฐ๊ณ ์๋ ๊ตฌ์กฐ์ฒด ๋๋ ํด๋์ค๋ @MainActor๋ก ์ถ๋ก ๋ฉ๋๋ค.
์ด๋ฐ๋ถ ์์
์์ @StateObject์ ์ฌ์ฉ๊ณผ @MainActor์ ์ถ๋ก ์ ๋ํ ์๋ฌธ์ด ์์๋๋ฐ, ์์๊ฐ์ ์ด์ ๋ก ์ดํดํ ์ ์์ต๋๋ค.
News App ์ด๊ธฐ์ํ๋ async await, continiuation ๋ฑ์ Concurrency๋ฅผ ์ฌ์ฉํ์ง ์์ ๋ฒ์ ์ ๋๋ค. @escaping closure ๋ฑ์ผ๋ก ์ฝ๋ฐฑ ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ ์๋ ์์ง๋ง, ์ฝ๋ฐฑ ์ง์ฅ์ ์ผ๊ธฐํ๊ฑฐ๋, ์ฝ๋ฐฑ ํด๋ก์ ธ ์คํ ํ ํน์ ๋ถ๊ธฐ return์ ๋์น๋ฉด ๋น์ ์ ๋์์ ํ ์ ์๋ ๋จ์ ์ด ์์ต๋๋ค.
์ด์ ์ด ์ฑ์ async/await, continuation, mainActor ๋ฑ์ ๊ฐ๋ ์ ์ ์ฉํด ๋ด ์๋ค!
async/await, continuation, @MainActor ๋ฑ์ ๊ฐ๋ ๋ค์ URLSession, Notification, HealthKit, CoreData ๋ฑ ๋ค์ํ ๊ณณ์์ ํ์ฉ ๊ฐ๋ฅํ๋ค
๐ฉ๐ปโ๐ป learning point : Structured Concurrency, Async Let, Task Group, Unstructured Tasks, Detached Tasks, Task Cancellation
// try await์ ์ฌ์ฉํ์๊ธฐ์ equifaxUrl๋ก๋ถํฐ ๊ฒฐ๊ณผ ๊ฐ์ ์์ ๋ฐ์๋๊น์ง suspend ๋๋ค. ใ
ใ
equifaxUrl ์์ฒญ์ด ๋๋์ผ experianUrl๋ก๋ถํฐ ์์ฒญ์ ์ํํ๋ค..
// => Concurrentlyํ๊ฒ ๋๊ฐ ๋ค ์์ฒญํ๋ ๋ฐฉ๋ฒ?
// "Let's work on these two tasks(equifax, experian) concurrently!!"
// => then, how do we do that?? => async let!
// MARK: Async-let
// - async let์ ์ฌ์ฉํ๋ฉด, async ์์
์ ๋ํ reference๋ฅผ ์ก๊ณ ์๋๋ค. ์ฆ์ ๋ฐํ๋๋ฉฐ, concurrent task๋ก ๋์ํ๊ฒ ๋๋ค.
// - async let์ ๋ถ์๋ค๋ฉด ๋ค์ ๋ถ์ฌ ์ฌ์ฉํ๋ try await์ ๋ช
์ํ์ง ์์๋ ๋๋ค.(ex) ์๋ ์ฝ๋์ URLSession ์์ try await๋ฅผ ๋ช
์ํ ์๋ฌด๊ฐ ์์
// * ์๋ equifaxData, experianData๋ ๋ชจ๋ async let์ผ๋ก ์ ์๋๋ค.
async let (equifaxData, _) = URLSession.shared.data(from: equifaxUrl)
async let (experianData, _) = URLSession.shared.data(from: experianUrl)
// custom code
// async throws ๋ฉ์๋๋ก๋ถํฐ async let ์์๋ฅผ ๋ฐ์ ๊ฒ์ด๋ฏ๋ก, ์ด๋ฅผ ์ฌ์ฉํ ๋๋ try await์ ์ฌ์ฉํด์ผ ํ๋ค.
// ์๋์ ๊ฐ์ด async let ๊ฐ์ ๋ํ await(try await)์ ํ ๋ ๋น๋ก์ suspend ๋๋ค! ๋ฐ๋ผ์ async task๋ ๋์์ ๋์์ํค๊ณ , ์ดํ์ ์ค์ ๊ฐ์ ๋ฐ๋ ๋ถ๋ถ์์ ๊ธฐ๋ค๋ฆฌ๋ ๊ฒ => API ์์ฒญ์ concurrentlyํ๊ฒ ํ๊ณ , ๋ฐ์ ๊ฐ์ feedingํ ๋๋ง ์์ฐจ์ ์ผ๋ก ๋๋ ์ค.
let equifaxCreditScore = try? JSONDecoder().decode(CreditScore.self, from: try await equifaxData)
let experianCreditScore = try? JSONDecoder().decode(CreditScore.self, from: try await experianData)let ids = [1, 2, 3, 4, 5]
Task {
for id in ids {
// * ์๋์ ๊ฐ์ด loop๋ฌธ์์ async/await์ ์ฌ์ฉํ ์ ์๋๋ฐ ์์๋์ด์ผ ํ ์
// 1) loop ๋ฌธ์ด ํ๋ฒ ๋ ๋, getAPR ๋ด์ async let task๋ค์ด concurrent ํ๊ฒ ์ํ๋๋ค.
// 2) task๋ concurrent ํ๊ฒ ๋์ํ์ง๋ง, ๊ฒฐ๊ตญ feeding ๋จ๊ณ์์ suspending์ด ๋๋ค.
// 3) ๋๊ฐ์ task๊ฐ ์ ๋ถ ๋๋๊ณ , feeding๊น์ง ๋๋๋ฉด, ๋น๋ก์ loop์ ๋ค์ getAPR๋ฅผ ์ํํ๋ค. (๊ฒฐ๊ตญ ๊ฐ getAPR ๋ฉ์๋ ๋ด์์ awaitํ๋ ๋ผ์ธ์ด ์๊ธฐ ๋๋ฌธ์ suspendํ๊ธด ํจ. API ์์ฒญ์ด concurrent ํ ๋ฟ.)
// => loop๋ฅผ ์ฌ์ฉํ๋ค๊ณ , ๋ชจ๋ getAPR ๋์๋ค์ด concurrentํ๊ฒ ๋์ํ๋๊ฒ์ด ์๋๋ผ๋ ์ ์ ์์์ผ ํ๋ค. (task group์ ํ์ฉํ๋ฉด ์ด ๋ํ concurrent ํ๊ฒ ๋์์ ๊ฐ๋ฅ ํจ.)
// task group์ ์ดํด ๋ณด๊ธฐ ์ ์ ๋จผ์ ์ค์ํ ์์ ์ค ํ๋์ธ cancelling a task ๋ฅผ ์์๋ณด์.
let apr = try await getAPR(userId: id)
print(apr)
}
}let ids = [1, 2, 3, 4, 5]
var invalidIds: [Int] = []
Task {
for id in ids {
do {
// Task.checkCancellation()์ ์ฌ์ฉํ๋ฉด, ์๋ฌ๊ฐ throwing๋์ด๋ ์ดํ์ loop task๋ฅผ ๋ฉ์ถ์ง ์๊ณ ์ง์ ์ํํ ์ ์๋ค.
try Task.checkCancellation()
let apr = try await getAPR(userId: id)
print(apr)
} catch {
print(error)
invalidIds.append(id)
}
}
// error๊ฐ ๋ฐ์ํ id๋ฅผ ์ถ๋ ฅ => invalidIdList : 2 4
print("invalidIdList : \(invalidIds.map { String($0) }.joined(separator: " "))")
}// MARK: 41. Group Tasks
// async let ์ loop๋ฌธ์์ ์ฌ์ฉํ๋ฉด lopp ๋ด ๊ฐ๊ฐ์ task ๋ด์์ API ์์ฒญ์ concurrent ํ๊ฒ ๋์ํ์ง๋ง ๊ฒฐ๊ตญ feeding ๊ณผ์ ์์ suspend ๋๊ณ , ์ด๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ ๊ฒ์ ์ ์ ์์๋ค.
// => ๋ฃจํ ๋ด ๊ฐ๊ฐ์ task๋ฅผ ๋ชจ๋ concurrentํ๊ฒ ๋์ํ๊ณ ์ถ๋ค๋ฉด? => task groups๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค.
// getAPR์ ๊ฐ๊ฐ 2๊ฐ์ API ์์ฒญ์ concurrentํ๊ฒ ์งํํจ
// [Main Task] -> first Group (getAPR) -> two tasks concurrently
// -> second Group (getAPR) -> two tasks concurrently
// -> ..... (getAPR) -> two tasks concurrently
let ids = [1, 2, 3, 4, 5]
var invalidIds: [Int] = []
func getAPRForAllUsers(ids: [Int]) async throws -> [Int: Double] {
var userAPR: [Int: Double] = [:]
// 1) loop ๋ด ์์
๋ค์ concurrentํ๊ฒ ๋์ํ๊ธฐ ์ํด for loop ๋ฐ๊นฅ์ try await withThrowingTaskGroup์ ์ฌ์ฉํ ์ ์๋ค.
// - of: group์ ์ถ๊ฐํ task ๊ฒฐ๊ณผ ํ์
// - body: group task๊ฐ ์ํ๋ ํด๋ก์ ธ๋ฅผ ์ ์
try await withThrowingTaskGroup(of: (Int, Double).self, body: { group in
for id in ids {
// 2) group.addTask { ... } ๋ด์ concurrentlyํ๊ฒ ๋์์ํฌ ์์
์ ์ ์, ๊ฒฐ๊ณผ๋ ์์์ ์ ์ํ (Int, Double) ํํํ์
์ผ๋ก ๋ฐํ
group.addTask {
// ํด๋น ๋ธ๋ญ์์๋ task ๋ธ๋ญ ๋ฐ์ ๊ฐ์ ๋ณ๊ฒฝํ ์ ์๋ค getAPR์ ๊ฒฐ๊ณผ๋ฅผ ํํ๋ฐฉ์์ผ๋ก group task๋ก ์ถ๊ฐํ๋ค.
// loop๊ฐ one by one์ผ๋ก ๋์์ด ๋๊ธฐ ๋๋ฌธ์ dataRacing์ ๋ฐ์ํ ๊ฑฑ์ ๋ ์๋ค.
// ์ฌ๊ธฐ์ ์์
์ loop ๋ด ๊ฐ๊ฐ์ task ์ค ์ด๋ค๊ฒ ๊ฐ์ฅ ๋จผ์ ์๋ฃ๋ ์ง ์ ์ ์์ด์. concurrentํ๊ฒ ๋์ํ๊ธฐ ๋๋ฌธ์!
return (id, try await getAPR(userId: id))
}
}
// 3) group์ ์ถ๊ฐ๋ task๋ค์ asyncํ๊ฒ ์ฐจ๋ก๋๋ก ์์
ํ๋ค. ์ฌ๊ธฐ์์ loop ๋ด๋ถ ๊ฐ task๋ค์ ์์ฐจ์ ์ผ๋ก ๋์ํ์ฌ data racing ๊ฑฑ์ ์๋ค.
for try await (id, apr) in group {
// loop๋ฌธ์์ ๊ฐ task ๊ฒฐ๊ณผ์ ๋ํ addTask๋ฅผ ์ํํใ
for try await loop์์ ๋น๋ก์ ๋์
๋๋ฆฌ์ ์
ํ
์ด ๊ฐ๋ฅํ๋ค. (์ฌ๊ธฐ๋ addTask ๋ธ๋ญ ๋ด๋ถ๊ฐ ์๋๋ฏ๋ก, ์ธ๋ถ ๊ฐ ๋ณ๊ฒฝ์ด ๊ฐ๋ฅ
userAPR[id] = apr
}
})
return userAPR
}
Task {
let userAPRs = try await getAPRForAllUsers(ids: ids)
print(userAPRs)
}// task group์ ์ฌ์ฉํด์ loop ๋ด์ ๊ฐ image ์์ฒญ์ ๋ชจ๋ concurrentํ๊ฒ ์ํํ๋๋ก ํด๋ณด์.
func getRandomImages(ids: [Int]) async throws -> [RandomImage] {
try await withThrowingTaskGroup(of: (Int, RandomImage).self, body: { group in
for id in ids {
// ๋ฃจํ๋ด ๊ฐ task ๊ฐ๊ฐ concurret task group์ ๋ง๋ ๋ค.
// task group ๋ด์ addTask ํด๋ก์ ธ ๋ด๋ถ์ concurrently ๋์์ํฌ ์์
์ ์์
ํ๊ณ , of: ๋ ์ด๋ธ์ ์ค์ ํ ํ์
์ ๋ง๊ฒ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๋ค.
group.addTask {
let randomImage = try await self.getRandomImage(id: id)
return (id, randomImage)
}
}
// group์ ์ถ๊ฐํ๋ task๋ค์ ์์ฐจ์ ์ผ๋ก suspendingํ์ฌ ๊ฒฐ๊ณผ๊ฐ์ randomImages์ appendingํ๋ค.
// => ๋ชจ๋ getRandomImage ์์ฒญ๋ค์ concurrently ๋์์ ํ๋ค. ์ดํ ์๋ for try await loop์์ suspending์ ํ๋ฉฐ, ์์ ํ ๊ฐ์ ์์ฐจ์ ์ผ๋ก randomImages์ ์ถ๊ฐํ๋ค.
// => task group์ ์ฌ์ฉํ์ง ์์์๋ : loop์ ๊ฐ task ๋ด๋ถ ๋์์ asyncํ์ง๋ง, ๊ฐ getRandomImage ๊ฒฐ๊ณผ๊ฐ์ ์ป๊ธฐ ์ ๊น์ง suspending๋์ด ๋ฃจํ ๋ค์ task(getRandomImage)๋ฅผ ๋์์ ์ํํ์ง ๋ชปํ์.
// => task group์ ์ฌ์ฉํ์๋ : loop์ ๊ฐ task๋ concurrently, asynchronous ํ๊ฒ ๋์ํ๋ค. suspending์ for try await loop์์ ๋ฐ์ํ๋ค.
for try await (_, randomImage) in group {
self.randomImages.append(randomImage)
}
})
return randomImages
}์๋์ ๊ฐ์ ์์๋ฅผ ํ์ฉํ์ฌ randomImage API ์์ฒญ์ concurrentํ๊ฒ ์์ฒญํ ์ ์๋ค.
-
- ๋ค์์ API ์์ฒญ์ concurrentํ๊ฒ ์ํํ๊ณ , feeding ๋จ๊ณ(await ์ฌ์ฉ ์์น)์์ suspendํ์ฌ ์์ฐจ์ ์ผ๋ก feeding์ ํ ์ ์๋ค.
-
- ๊ฐ์ฅ ์ ๋จ์์ await ๋์์ ์ํํ๊ธฐ ์ํด์๋ Task ๋ธ๋ญ ๋ด๋ถ์์ ์์ฒญํ๋ค. ํน์, View์ onAppear ์ด๋ฒคํธ ์ await ๋์์ ์ํํ๊ณ ์ ํ๋ค๋ฉด, .task { ... } viewModifier๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
-
- ๋ชจ๋ loop์ Task(๊ฐ๊ฐ random, quote API๋ฅผ ํธ์ถํ๋ task)๋ค์ concurrentํ๊ฒ ๋์์ํฌ๋ ์ฌ์ฉํ ์ ์๋ค.
- withThrowingTaskGroup, withTaskGroup ๋ด์์ concurrentํ๊ฒ ๋์ํด์ผํ ์ฝ๋๋ฅผ ์์ฑ ํ(addTask), ๊ทธ ๋ค์ for await - in loop / for try await - in loop ๋ฌธ ๋ด์์ ์์ฒญ ๊ฒฐ๊ณผ๋ฅผ ์์ฐจ์ ์ผ๋ก feeding ํ ์ ์๋ค.
-
- @MainActor๋ฅผ ์ง์ ํ ์์ญ์ ํญ์ ๋ฉ์ธ์ค๋ ๋์์์ ๋์์ด ๋ณด์ฅ๋๋ฏ๋ก ๋ด๋ถ์ ์ถ๊ฐ์ ์ผ๋ก DispatchQueue.main.async { ... }, MainActor.run { ... } ์ ๊ฐ์ thread ๋ช ์๋ฅผ ํ ํ์๊ฐ ์๋ค.
-
AsyncSequence Availability
- iOS 13.0+
-
Sequence protocol์ Async ์ฑ๊ฒฉ์ด ์ถ๊ฐ๋ ๊ฒ
-
Sequence์ ๊ฑฐ์ ๋์ผํ๋ค. (for loop์ ์ฌ์ฉํ๊ณ makeIterator, next๋ฅผ ๊ตฌํํ๋ค๊ฑฐ๋, operator ์ฌ์ฉ ๋ฑ... ์ ์ฌ)
- map, allSatisfy, max, prefix, compactMap, zip, flatMap, dropFirst, contains, filter, reduce ๋ฑ ๋ชจ๋ ์ฌ์ฉ ๊ฐ๋ฅ!
-
related posting link : https://0urtrees.tistory.com/361
// MARK: - Section 10: AsyncSequence
// MARK: Loop Over Sequence Without AsyncSequence
// - AsyncSequence๋ฅผ ํ์ฉํด๋ณด๊ธฐ์ ์์์ ๋จผ์ ์ผ๋ฐ Sequence๋ฅผ ์ฌ์ฉํด๋ณด์.
import SwiftUI
import PlaygroundSupport
extension URL {
func allLines() async -> Lines {
Lines(url: self)
}
}
struct Lines: Sequence {
let url: URL
func makeIterator() -> some IteratorProtocol {
let lines = (try? String(contentsOf: url))?.split(separator: "\n") ?? []
return LinesIterator(lines: lines)
}
}
// IteratorProtocol์ conformํ๊ธฐ ์ํด์๋ next() ๋ฉ์๋๋ฅผ ๊ตฌํํด์ผ ํ๋ค.
struct LinesIterator: IteratorProtocol {
typealias Element = String
var lines: [String.SubSequence]
// struct ๋ด๋ถ ๋ฉ์๋์ ๋ฉค๋ฒ ๋ณ๊ฒฝ์ด ์์ผ๋ฏ๋ก mutating์ ๋ช
์ํ๋ค.
mutating func next() -> Element? {
if lines.isEmpty {
return nil
}
return String(lines.removeFirst())
}
}
let endPointURL = URL(string: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_month.csv")!
Task {
// Sequene์ ๋ํ await ๋์์ ํ๊ณ ์๋ค. -> allLines()์์ ๋ชจ๋ line๋ค์ ์ฒ๋ฆฌํ๊ณ ๋ ์ดํ์์ผ loop์ ๊ฐ line์ด ์ถ๋ ฅ๋๋ค.
// ์๋ ๋ผ์ธ์ endPointURL.allLines()๊ฐ ๋จผ์ ์คํ๋์ด ๋ชจ๋ ๋์์ด ์๋ฃ๋๋ฉด -> ๊ทธ๋์์ผ iterate ํ๊ฒ ๋๋ค.
for line in await endPointURL.allLines() {
// endPointURL.allLines() ์์
์ด ๋๋๊ธฐ ์ ๊น์ง ์๋ ๋ผ์ธ์ ์์๋ ๋ชปํ๋ค... allLines() ์์
์ด ๋งค์ฐ ํฌ๋ค๋ฉด ๋งค์ฐ ๋นํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌ ๋ ์ ์๋ค.
print("line : \(line)")
}
}- ์์ Sequence๋ฅผ ์ฌ์ฉํ์๋๋ ๋ชจ๋ lines์ ์์ ์ ์ฒ๋ฆฌํ ๋ค์ ๋ค๋ฆ๊ฒ iterator๊ฐ ๋์๊ฐ๋ค. ์ด๋ big pause๋ฅผ ๋ฐ์์ํจ๋ค.
- AsyncSequence + for try await์ ์ฌ์ฉํ๋ฉด ๊ฐ๊ฐ์ line task๋ฅผ ์ํํ๋ฉด์ ์์ ์ด ๋๊ธฐ ๋๋ฌธ์ big pause๋ฅผ ํด๊ฒฐํ ์ ์๋ค. (๊ฐ๊ฐ์ iterator์ ๋ํ ์์ ์ด asyncํ๊ฒ ๋์, ํ๋ฉด ์๋ฃ๋๋๋๋ก ๊ทธ ๋ค์ iterator์ ๋ํ ์์ ์ ์ํ)
let endPointURL = URL(string: "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_month.csv")!
// Task { ... } -> unstructured concurrency
Task {
// endPointURL.lines๋ AsyncLineSequence<URL.AsyncBytes> ํ์
// AsyncSequence๋ฅผ ์ฌ์ฉํ๋ฉด for await - in / for try await - in loop๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
// Sequence๋ฅผ ์ฌ์ฉํ์๋์๋ endPointURL.allLines()์ ๋ํ ๋ชจ๋ ์์
์ด ๋๋๊ณ ๋์์ผ ์ํ๋ฅผ ํ์ง๋ง....
// -> AsyncSequence์ for try await๋ฅผ ์ฌ์ฉํ๋ฉด big pauceํ ํ์์์ด ์ํ๋ฅผ ํ๋ฉฐ ๊ฐ line์ ๋ํ try await ์์
์ ์งํํ๋ค.
for try await line in endPointURL.lines {
print(line)
}
}
/*
Task {
// Sequene์ ๋ํ await ๋์์ ํ๊ณ ์๋ค. -> allLines()์์ ๋ชจ๋ line๋ค์ ์ฒ๋ฆฌํ๊ณ ๋ ์ดํ์์ผ loop์ ๊ฐ line์ด ์ถ๋ ฅ๋๋ค.
// ์๋ ๋ผ์ธ์ endPointURL.allLines()๊ฐ ๋จผ์ ์คํ๋์ด ๋ชจ๋ ๋์์ด ์๋ฃ๋๋ฉด -> ๊ทธ๋์์ผ iterate ํ๊ฒ ๋๋ค.
for line in await endPointURL.allLines() {
// endPointURL.allLines() ์์
์ด ๋๋๊ธฐ ์ ๊น์ง ์๋ ๋ผ์ธ์ ์์๋ ๋ชปํ๋ค... allLines() ์์
์ด ๋งค์ฐ ํฌ๋ค๋ฉด ๋งค์ฐ ๋นํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌ ๋ ์ ์๋ค.
print("line : \(line)")
}
}
*/- There're a lot of built-in functions using AsyncSequence
// MARK: 55. Built-In AsyncSequences in iOS Framework
// - AsyncSequences๋ฅผ ์ฌ์ฉํ๋ ๋ค์ํ framework ๋ด์ฅ ๊ธฐ๋ฅ(Built-In-Functions)๋ค์ด ์๋ค.
// ex) ๋ก์ปฌํ์ผ, URL์ byte ์ฝ์๋, NotificationCenter๋ก๋ถํฐ ํน์ ์ด๋ฒคํธ ์ฒ๋ฆฌํ ๋ ๋ฑ...
import Foundation
import UIKit
import _Concurrency
// txtํ์ผ์ ๋ถ๋ฌ์์ line์ ์ถ๋ ฅ
let paths = Bundle.main.paths(forResourcesOfType: "txt", inDirectory: nil)
let fileHandle = FileHandle(forReadingAtPath: paths[0])
/*
Task {
for try await line in fileHandle!.bytes {
// print async bytes
print(line)
}
}
*/
// ์ด์ฒ๋ผ URL, local data์ ๋ํ byte๋ฅผ ์ฝ์๋๋ AsyncSequence๋ฅผ ํ์ฉํ ์ ์๋ค.
let url = URL(string: "https://www.google.com")!
Task {
let (bytes, _) = try await URLSession.shared.bytes(from: url)
for try await byte in bytes {
print(byte)
}
}
Task {
let center = NotificationCenter.default
await center.notifications(named: UIApplication.didEnterBackgroundNotification).first {
guard let key = $0.userInfo?["key"] as? String else { return false }
return key == "SomeValue"
}
}-
Concurrency์์๋ AsyncStream, AsyncSequence๋ฅผ ์ค์ํ์ฌ ๋น๋๊ธฐ Iterator๋ฅผ ์ง์ ๊ตฌํํ์ง ์๊ณ ๋ AsyncSequence๋ฅผ ์ฝ๊ฒ ์์ฑํ ์ ์์ต๋๋ค.
-
AsyncStream์ Continuation์์ yield๋ฅผ ์ฌ์ฉํด์ ๋ฐ์ดํฐ๋ฅผ stream์ ์ ๊ณตํ๊ฑฐ๋, ๋์ด์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ง ๋ชปํ๋ ๊ฒฝ์ฐ, finish๋ฅผ ํธ์ถํฉ๋๋ค. ํน์ ๋ฐ์ดํฐ ์ฑ๊ณต ์ฌ๋ถ๋ฅผ yield.(with: .success()), yield.(with: .failure))๋ก ์ ๋ฌํ ์ ์์ต๋๋ค. failure๋ก ์ ๋ฌํ ๋๋ AsyncThrowingStream์ ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค.
-
withCheckedThrowingContinuation, withCheckedContinuation์ ๋จ์, async await method์ผ๋ก ๋ฐ๊พธ๊ธฐ ์ด๋ ค์ด ์ ๋ค์ ๋ํํด์ async await ๋ฐฉ์์ผ๋ก ์ฌ์ฉํ๊ธฐ ์ํ ๊ธฐ๋ฅ์ด์๋ค๋ฉด, AsyncStream์ ์ฐ์์ ์ธ ๋น๋๊ธฐ ๋์์ผ๋ก for await, for try await ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌํ๊ธฐ ์ฝ๊ฒ AsyncStream์ผ๋ก ๋ณํ ์์ผ์ฃผ๋ ๊ธฐ๋ฅ
-
AsyncStream, for await (with AsyncStream)์ฌ์ฉ ์์
import SwiftUI
func countdown() async {
let counter = AsyncStream<String> { continuation in
var countdown = 3
Timer.scheduledTimer(
withTimeInterval: 1.0,
repeats: true
) { timer in
guard countdown > 0 else {
timer.invalidate()
// .failure ์ด๋ฒคํธ๋ ์ ๋ฌํ๊ณ ์ถ๋ค๋ฉด, AsyncStream ๋์ , AsyncThrowingStream์ ์ฌ์ฉํด์ผํจ
continuation.yield(with: .success("\(Date()) bye bye!"))
// countdown์ด ๋ชจ๋ ๋๋๋ฉด, continuation ์ข
๋ฃ
return
}
// countdown ํ ๋๋ง๋ค yield๋ก AsyncStream์ ์ ๊ณตํ ๊ฐ์ ์ ๋ฌ
continuation.yield("\(Date()) countdown : \(countdown)")
// Timer์ ์ํด 1์ด๋ง๋ค countdown์ด 1์ฉ ๊ฐ์, 0์ด๋๋ฉด Timer ์ค์ง
countdown -= 1
}
}
// AsyncStream์ธ counter๋ฅผ ์ํํ๋ฉฐ await ์์
์ ์งํํ๊ณ ์๋ค.
// ๋ง์ฝ AsyncThrowingStream์ด์๋ค๋ฉด, for try await ๋ก ์์
์ด ๋์์ ๊ฒ์ด๋ค.
for await count in counter {
print(count)
}
}
func runAsyncStreamTask() {
Task {
await countdown()
}
}- AsyncThrowingStream for try await (with AsyncThrowingStream<String, Error>) ์ฌ์ฉ ์์
import SwiftUI
enum MyError: Error {
case invalidCount
}
func countdown() async throws {
let counter = AsyncThrowingStream<String, Error> { continuation in
var countdown = 3
Timer.scheduledTimer(
withTimeInterval: 1.0,
repeats: true
) { timer in
guard countdown > 0 else {
timer.invalidate()
// .failure ์ด๋ฒคํธ๋ ์ ๋ฌํ๊ณ ์ถ๋ค๋ฉด, AsyncStream ๋์ , AsyncThrowingStream์ ์ฌ์ฉํด์ผํจ
continuation.yield(with: .success("\(Date()) bye bye!"))
// countdown์ด ๋ชจ๋ ๋๋๋ฉด, continuation ์ข
๋ฃ
return
}
// ํน์ ์ํฉ์ ์๋ฌ๋ฅผ ๋์ง๊ณ ์ถ์๋ .failure ์ด๋ฒคํธ๋ฅผ ๋ณด๋ด๋ฉด AsyncSequence์ ์๋ฌ์ด๋ฒคํธ๊ฐ ์ ๊ณต๋๋ค.
if countdown == 1 {
continuation.yield(with: .failure(MyError.invalidCount))
}
// countdown ํ ๋๋ง๋ค yield๋ก AsyncStream์ ์ ๊ณตํ ๊ฐ์ ์ ๋ฌ
continuation.yield("\(Date()) countdown : \(countdown)")
// Timer์ ์ํด 1์ด๋ง๋ค countdown์ด 1์ฉ ๊ฐ์, 0์ด๋๋ฉด Timer ์ค์ง
countdown -= 1
}
}
// AsyncThrowingStream์ธ counter๋ฅผ ์ํํ๋ฉฐ try await ์์
์ ์งํํ๊ณ ์๋ค.
for try await count in counter {
print(count)
}
}
func runAsyncThrowingStreamTask() {
// ๋ง์ง๋ง Task { ... } ๋ธ๋ญ ์ฌ์ฉ ๋ถ์์๋ ๋ฉ์๋ ๋ฐํ๋ถ ์์ async, async throws๋ฅผ ๋ถํ์ง ์๋๋ค.
Task {
do {
try await countdown()
} catch {
// error๋ฅผ throwํ์ง ์๋ ๊ฒฝ์ฐ, ๋ฉ์๋์ throws ํค์๋ ์ค์ ์ํด๋ ๋จ
print(error.localizedDescription)
}
}
}
runAsyncThrowingStreamTask()- BitcoinPriceMonitor callback๋ค์ AsyncStream์ผ๋ก AsyncSeqenceํํด์ ์ฒ๋ฆฌํ๊ธฐ
- TaskGroup, AsyncStream ๋ชจ๋ ๋ด๋ถ์ ์ผ๋ก AsyncSequence์. ๊ทธ๋์ ๋ชจ๋ for try await, for await loop์
- callback stream -> AsyncSequence๋ก ๋ฐ๊พธ์ด ์ฌ์ฉํ ์ ์๋ค.
- ๊ธฐ์กด Sequence๋ฅผ ์ฑํํ ์ ๋ค์ด ์ฌ์ฉ๊ฐ๋ฅํ ๋ค์ํ ์ฐ์ฐ์๋ฅผ ํจ๊ป ํ์ฉ ๊ฐ๋ฅํ๋ค.
- Async/Awaitํ๊ฒ ๋์์ฑ ํ๋ก๊ทธ๋๋ฐ์ ํ ์ ์๋ค.
// MARK: 56. Adapting Existing Callbacks or Handlers to AsyncSequence Using AsyncStream
import UIKit
class BitcoinPriceMonitor {
var price: Double = 0.0
var timer: Timer?
var priceHandler: (Double) -> Void = { _ in }
// Timer ์ค์ ์ #selector๋ก ์ง์ ๋๊ธฐ ์ํด์๋ @objc๋ฅผ ๋ถ์ฌ์ Objective-C runtime์์ ์ํต๊ฐ๋ฅํ๋๋ก ํด์ฃผ์ด์ผ ํฉ๋๋ค.
@objc func getPrice() {
priceHandler(Double.random(in: 20000...40000))
}
func startUpdating() {
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(getPrice), userInfo: nil, repeats: true)
}
func stopUpdating() {
timer?.invalidate()
}
}
/*
let bitcoinPriceMonitor = BitcoinPriceMonitor()
// ์๋์ ๊ฐ์ ํ์ด๋จธ ๊ฐ์ ์์ ํ๋ ํด๋ก์ ธ๋ฅผ AsyncStreamํํด์ Async/Await ํ๊ฒ ์ฌ์ฉํ ์ ์์๊น? -> ๊ฐ๋ฅํจ.
bitcoinPriceMonitor.priceHandler = {
print($0)
}
bitcoinPriceMonitor.startUpdating()
*/
let bitcoinPriceStream = AsyncStream(Double.self) { continuation in
let bitcoinPriceMonitor = BitcoinPriceMonitor()
bitcoinPriceMonitor.priceHandler = {
// AsyncSequence์ ์ ๊ณต๋ ๊ฐ์ ์ ๋ฌํ ๋ yield๋ฅผ ์ฌ์ฉ
continuation.yield($0)
}
// continuation์ ํตํด onTermination callback ํด๋ก์ ธ๋ฅผ ์ค์ ๊ฐ๋ฅ
// continuation.onTermination = { _ in }
bitcoinPriceMonitor.startUpdating()
}
Task {
// AsyncStream์ ์ฌ์ฉํ ๋ ์ฅ์
// 1) ์ฝ๋ฐฑ ์คํธ๋ฆผ๋ค์ AsyncSequene๋ก ๋ณํํด์ ์ฌ์ฉํ ์ ์๋๋ฐ ์ด๋ ๊ธฐ์กด Sequence๋ฅผ ์ฑํํ ์ ๋ค์ด ์ฌ์ฉ๊ฐ๋ฅํ ๋ค์ํ ์ฐ์ฐ์๋ฅผ ํจ๊ป ํ์ฉ ๊ฐ๋ฅํ๋ค.
// 2) Async/Awaitํ๊ฒ ๋์์ฑ ํ๋ก๊ทธ๋๋ฐ์ ํ ์ ์๋ค.
for await bitcoinPrice in bitcoinPriceStream {
print(bitcoinPrice)
}
}- AsyncThrowingStream ๋ด์ Task.sleep()์ ํ์ฉํ ํ์ด๋จธ ๊ธฐ๋ฅ ๊ตฌํ๋ฐฉ๋ฒ
- ์๋ ์ฝ๋์ฒ๋ผ, continuation์ ๊ตณ์ด ์ฌ์ฉํ์ง ์๋ ๋ฐฉ์์ผ๋ก๋ ์ฌ์ฉ ๊ฐ๋ฅ, ๊ณ์ ํน์ ์์ ์ ํ๋ค๊ฐ nil์ ๋ฐํํ๋ฉด stream์ด ์ข ๋ฃ๋๋๋ก ํ ์ ์์.
func countdown() async throws {
var countdown = 3
let counter = AsyncThrowingStream<String, Error> {
do {
// ์๋ ์ฒ๋ผ 1๋ก ์ง์ฐ ์์
์ ์ฃผ๋ฉด์ Timerํด๋์ค ์์ด Timer ๊ธฐ๋ฅ์ ๊ตฌํ ๊ฐ๋ฅ
try await Task.sleep(nanoseconds: 1_000_000_000)
} catch {
return nil
}
// ํ๋ฒ์ stream์์
๋ธ๋ญ์ด ๋๋ ๋๋ง๋ค countdown์ 1์ฉ ์ค์
defer { countdown -= 1 }
if countdown == 1 {
// AsyncThrowingStream์ด๋ฏ๋ก, throw๋ก Error ๋์ง ์๋ ์์
throw NSError(domain: "error", code: 1)
}
switch countdown {
case (1...): return "\(Date()) \(countdown)..."
case 0: return "\(Date()) ๐ Hello"
default: return nil
}
}
for try await count in counter {
print(count)
}
}
func run() {
Task {
do {
try await countdown()
} catch {
print(error)
}
}
}
/** Output
2022-06-22 15:58:23 +0000 3...
2022-06-22 15:58:24 +0000 2...
Error Domain=error Code=1 "(null)"
*/- ๋์์ ์ผ๋ก(using concurrent queue) ์ถ๊ธ์์ฒญ์์
์ด ์งํ๋๋ฉด ์๊ณ ๊ฐ -(minus)๊ฐ ๋ ์๋ ์์
- serial queue, actor ๋ฑ์ ์ฌ์ฉํ์ฌ ํด๊ฒฐ ๊ฐ๋ฅ
// MARK: - Section 11. Concurrent Programming: Problem and Solutions
// MARK: 58. Problem: Bank Account Withdraw (์ํ ๊ณ์ข ์ถ๊ธ ๋ฌธ์ )
// MARK: 59. Solutiion 1: Bank Account Withdraw Using Serial Queue
import UIKit
class BankAccount {
var balance: Double
init(balance: Double) {
self.balance = balance
}
func withdraw(_ amount: Double) {
if balance >= amount {
let processingTime = UInt32.random(in: 0...3) // ์ฒ๋ฆฌ ์๊ฐ์ 1 ~ 3์ด๋ก ๋์
print("[withdraw] Processing for \(amount) \(processingTime) seconds")
sleep(processingTime) // 3์ด ํ amount๋งํผ ์ถ๊ธ์ ์๋
print("withdrawing \(amount) from account")
balance -= amount
print("Balance is \(balance)")
}
}
}
// Q. ๋ง์ฝ ๋์์ ๋ง์ ์์ฒญ์ด ์ค๊ฒ ๋๋ค๋ฉด??
let bankAccount = BankAccount(balance: 500)
let queue = DispatchQueue(label: "ConcurrentQueue", attributes: .concurrent)
queue.async {
bankAccount.withdraw(300)
}
queue.async {
bankAccount.withdraw(500)
}
// concurrent ํ๊ฒ ๋ค์์ ์ถ๊ธ ์์ฒญ์ ์คํ ์ ์๊ณ ๊ฐ -๊ฐ ๋๋ ์ํฉ์ด ๋ฐ์!!
/*
[withdraw] Processing for 300.0 2 seconds
[withdraw] Processing for 500.0 3 seconds
withdrawing 300.0 from account
Balance is 200.0
withdrawing 500.0 from account
Balance is -300.0
*/- Actor (section 12์์ ์์๋ณด์)
- NSLock ์ธ์คํด์ค์ lock(), unlock()์ ํตํ locking
- concurrent queue ๋์ serial queue๋ฅผ ์ฌ์ฉํ๊ธฐ
// MARK: - Section 11. Concurrent Programming: Problem and Solutions
// MARK: 58. Problem: Bank Account Withdraw (์ํ ๊ณ์ข ์ถ๊ธ ๋ฌธ์ )
// MARK: 59. Solution 1: Bank Account Withdraw Using Serial Queue
// MARK: 60. Solution 2: Bank Account Withdraw Using Locks(NSLock)
import UIKit
class BankAccount {
var balance: Double
let lock = NSLock()
init(balance: Double) {
self.balance = balance
}
func withdraw(_ amount: Double) {
// NSLock์ ํตํ locking์ ํ๋ฉด, ๋์์ ์์
์ผ๋ก ์ธํด ์๊ณ ๊ฐ minus๊ฐ ๋๋๊ฒ์ ๋ฐฉ์งํด์ค๋ค. (ํ๋์ ์ค๋ ๋์ ํ๋์ ๋์์ฉ๋ง ์คํ๋๋๋ก ๋ณด์ฅ)
// lock() ์ฌ์ฉ ์, ๋ฐ๋์ unlock()์ ์ง์ ์ง์ ํด์ฃผ์ด์ผ ํ๋ ๋จ์ ์ด ์๋ค. ๋ง์ฝ ๊น๋จน๊ณ unlock()์ ์ฒ๋ฆฌํ์ง ์์ผ๋ฉด, ํด๋น ์ค๋ ๋์ ๋ค์ ์์
์ด ๋งค์ฐ ์ง์ฐ๋ ์ ์๋ค.
// -> ์ฐจ์ ์ฑ
์ผ๋ก actor๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
lock.lock()
if balance >= amount {
let processingTime = UInt32.random(in: 0...3) // ์ฒ๋ฆฌ ์๊ฐ์ 1 ~ 3์ด๋ก ๋์
print("[withdraw] Processing for \(amount) \(processingTime) seconds")
sleep(processingTime) // 3์ด ํ amount๋งํผ ์ถ๊ธ์ ์๋
print("withdrawing \(amount) from account")
balance -= amount
print("Balance is \(balance)")
}
lock.unlock()
}
}
// Q. ๋ง์ฝ ๋์์ ๋ง์ ์์ฒญ์ด ์ค๊ฒ ๋๋ค๋ฉด??
let bankAccount = BankAccount(balance: 500)
// ์์
์ concurrentํ๊ฒ ํ์ง์๊ณ , serialํ๊ฒ ๋์ํ๋ฉด, ๋์์ ์์
์ด ์ํ๋๋ ์ผ์ด ์๊ธฐ์ ์๊ณ ๊ฐ -๊ฐ ๋ฐ์ํ์ง๋ ์๋๋ค.
let queue = DispatchQueue(label: "Serial Queue")
queue.async {
bankAccount.withdraw(300)
}
queue.async {
bankAccount.withdraw(500)
}
// concurrent ํ๊ฒ ๋ค์์ ์ถ๊ธ ์์ฒญ์ ์คํ ์ ์๊ณ ๊ฐ -๊ฐ ๋๋ ์ํฉ์ด ๋ฐ์!!
/*
[withdraw] Processing for 300.0 2 seconds
[withdraw] Processing for 500.0 3 seconds
withdrawing 300.0 from account
Balance is 200.0
withdrawing 500.0 from account
Balance is -300.0
*/
// serial ํ๊ฒ ์คํํ๊ฑฐ๋, NSLock()์ lock(), unlock()์ ์ฌ์ฉ ์, ์๊ณ ๊ฐ -๊ฐ ๋๋ ๋ฌธ์ ๋ ํด๊ฒฐ์ด ๋จ.
// -> ๋๋ค๋ฅธ ํด๊ฒฐ๋ฐฉ๋ฒ => Actor๋ฅผ Section 12์์ ์์๋ณด์!!
/*
[withdraw] Processing for 300.0 2 seconds
withdrawing 300.0 from account
Balance is 200.0
// ๋๋ฒ์งธ ์์
์ ์คํ๋์ง ์์. ์ถ๊ธ์ด ๋ถ๊ฐ๋ฅ(์ถ๊ธํ amount๊ฐ ์๊ณ ๋ณด๋ค ํผ)ํ๊ธฐ์
*/protect mutable state, accessing Actor isolated states, MainActor, Nonisolated instances
- actor๋ class์ ์ ์ฌํ๋, ์์์ด ๋ถ๊ฐ๋ฅํ๋ค.
- ํ๋์ ์ค๋ ๋์์๋ง ๋์ํ์ฌ data racing ๋ฌธ์ ๋ฅผ ๋ฐฉ์งํ ์ ์๋ค.
- ๋ด๋ถ์ ์ ์๋ ๋ฉ์๋๋ await ํค์๋๋ก ํธ์ถ์ด ๊ฐ๋ฅํ๋ฉฐ, ๋จ๊ธฐ๊ฐ์ ๋ค์ฐจ๋ก ๋ฐ๋ณต ํธ์ถ์ ํด๋, ํ๋ฒ์ ๋์์ด ๋๋์ผ ๊ทธ ๋ค์ ๋์์ ์ํํ๋ค.
// MARK: - Section 12: What are Actors?
// MARK: 63. Understanding Actors
import SwiftUI
// 1) class๋ก ์ฌ์ฉํ๋ค๋ฉด
/*
class Counter {
var value: Int = 0
func increment() -> Int {
value += 1
return value
}
}
// => concurrently ๋์ ์, ์ถ๋ ฅ ์์๊ฐ ๋ณด์ฅ๋์ง ์์
*/
/*
// 2) struct๋ก ์ฌ์ฉํ๋ค๋ฉด
struct Counter {
var value: Int = 0
mutating func increment() -> Int {
value += 1
return value
}
}
// => concurrently ๋์ ์, ์ถ๋ ฅ ์์๊ฐ ๋ณด์ฅ๋์ง ์์
// ๊ฐ ๋ณต์ฌํด์ ํธ์ถํ ๊ฒฝ์ฐ, 1์ด ๋ฌด์ํ ์ถ๋ ฅ ๋จ..
*/
// 3) class, struct ๋์ actor๋ฅผ ์ฌ์ฉํด๋ณด๊ธฐ
// actor๋ ๋จ ํ๋์ ์ค๋ ๋์์๋ง ๋์ํ๋๋ก ๋ณด์ฅํด์ค๋ค. ๋ฐ๋ผ์ data racing ๋ฌธ์ ๊ฐ ํด๊ฒฐ๋๋ค.
actor Counter {
var value: Int = 0
// ํ๋์ ์ค๋ ๋์์ ํ๋ฒ์ ํ๋์ ๋์๋ง, ๋์์ด ์๋ฃ๋๋ฉด suspended ๋ค์ ๋์์ด ์ํ๋๋ฏ๋ก ์ถ๋ ฅ ์์๊ฐ ๋ณด์ฅ
// actor ๋ด์ methods๋ await๋ฅผ ๋ถํ์ ํธ์ถ, ๋๊ฐ ์ด์์ ์ค๋ ๋์์ ํ๋ฒ์ ๋์ํ์ง ์์
func increment() -> Int {
value += 1
return value
}
}
struct ContentView: View {
var body: some View {
Button {
let counter = Counter()
// 1) ๋ง์ฝ concurrent ํ๊ฒ ๋์์ increment๊ฐ ๋ฐ์ํ๋ค๋ฉด?
// 100๊น์ง ์ฆ๊ฐํ๋ฉด์ ์ถ๋ ฅ๋๋ ๊ฒ์ ๊ธฐ๋ํ๊ณ ์๋์ฝ๋๋ฅผ ์คํํ๋ค๋ฉด? => ์นด์ดํ
๋ค์ฃฝ๋ฐ์ฃฝ ์์๋ก ์ถ๋ ฅ์ด ๋จ... => concurrently ํ๊ฒ ๋์ํ๋ฏ๋ก, ๊ฐ๋ณ ์์
๋ค์ ๋ํ ์์์ ์์๋๋ก ๋๋ผ๋, ์๋ฃ๋๋ ์์๊ฐ ๋ณด์ฅ๋์ง ์๋๋ค.
DispatchQueue.concurrentPerform(iterations: 100) { _ in
// 2) ์๋์ฒ๋ผ struct์ํ counter์ copy๋ฅผ ์์ฑํ๊ณ , increment()๋ฅผ ํธ์ถํ๋ฉด? -> ์ ๋ถ ๊ฐ๋ณต์ฌ๋ก zero์์ ์์ํ๋ฏ๋ก 1์ด ๋ฌด์ํ๊ฒ ์ถ๋ ฅ๋จ.
// var counter = counter // struct๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ
// print(counter.increment())
// 3) actor๋ฅผ ์ฌ์ฉํด๋ณด์.
Task {
// await, try await ๋ฑ์ Task ๋ธ๋ญ ๋ด๋ถ, .task viewModifier ๋ด๋ถ ๋ฑ(unstructured concurrency)์์ ์ฌ์ฉํด์ผํ๋ค.
// => increment() ์ถ๋ ฅ ๊ฒฐ๊ณผ, ์์๊ฐ ๋ณด์ฅ๋๋ค!
print(await counter.increment())
}
}
} label: {
Text("Increment")
}
}
}// MARK: 65. Actors Example: Bank Account Transfer Funds
// MARK: 66. Understanding nonisolated Keyword in Swift
// actor ๋ด์์ nonisolated keyword๊ฐ ๋ถ์ ๋ฉ์๋๋
// - ๋ด๋ถ์ ๋ณ๊ฒฝ์ฝ๋๋ฅผ ์์ฑํ ์ ์๋ค. (๋ณ๊ฒฝํ๋ ค๊ณ ํ๋ฉด ์ปดํ์ผ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.)
// - ์ธ๋ถ์์ ์ฌ์ฉํ ๋ Task ๋ธ๋ญ ๋ด์ async/await ๋ฐฉ์์ผ๋ก ์ฌ์ฉํ ํ์๊ฐ ์๋ค. data racing ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ฌ์ง๊ฐ ์๊ธฐ ๋๋ฌธ์ด๋ค.
import SwiftUI
enum BankError: Error {
case insufficientFunds(Double)
}
// ์ด๋ฒ์๋ BankAccount๋ฅผ actor๋ก ์ ์ธํ๋ค. ํ๋ฒ์ ํ๋ฒ์ฉ๋ง ์ ๊ทผ์ด ๊ฐ๋ฅํ๋ค.
// concurrent task๋ก ๊ณตํต์ ์์์ ๋ณํ์ ์ผ๋ก ์ฝ๊ฑฐ๋ ์ฐ๋ ๋ฌธ์ ์ธ data racing(race condition)์ ๋ฐฉ์งํด์ฃผ๋ฉฐ ๋ด๋ถ์ ๋ฉ์๋๋ async/await ํ๊ฒ ๋์ํด์ผ ํ๋ค.
actor BankAccount {
let accountNumber: Int
var balance: Double
init(accountNumber: Int, balance: Double) {
self.accountNumber = accountNumber
self.balance = balance
}
// getCurrentAPR์ ๊ณ ์ ๋ ๊ฐ๋ง ๋ฐํํ์ง ๋ด๋ถ์์ ๋ณ๊ฒฝ์ด ์ผ์ด๋๋ ๋ฉ์๋๋ ์๋๋ค.
// ๋ฐ๋ผ์ Data racing์ด ๋ฐ์ํ ์ผ์ด ์๋ค. ์ด๋ฐ ๊ฒฝ์ฐ์๋ ์์ nonisolated๋ฅผ ๋ถํ์ actor๊ฐ ์๋ struct, class ๋ฉ์๋์ฒ๋ผ ํธ์ถํด์ ์ฌ์ฉํ ์ ์๋ค.
// => nonisolated func : "์ผ ์ด๊ฑฐ race condition ๋ฐ์ํ ์ผ ์๋ ๋์ด์ผ async/await call ๋ฐฉ์์ ์ทจํ ํ์๊ฐ ์์ด!"
nonisolated func getCurrentAPR() -> Double {
// nonisolated func์ ๋ด๋ถ์ ๋ณ๊ฒฝ ์ฝ๋๋ฅผ ํ์ฉํ์ง ์๋๋ค.
// * ๊ฒฝ๊ณ ๋ด์ฉ : Actor-isolated property 'balance' can not be mutated from a non-isolated context
// balance += 10
return 0.2
}
// ๋ฐํ๋ถ ์์ async๋ฅผ ๋ถํ๋ ์๋ถํ๋ ์ธ๋ถ์์๋ await์ ๋ถํ์ ์ฌ์ฉํด์ผํ๋ค. actor ๋ฉ์๋๋๊น.
func deposit(_ amount: Double) {
balance += amount
}
func transfer(amount: Double, to other: BankAccount) async throws {
if amount > balance {
throw BankError.insufficientFunds(amount)
}
balance -= amount
// other๋ actor(BankAccount)์ด๋ค. ๋ฐ๋ผ์ deposit ๋ฉ์๋ ๋์์ ์ํด await๋ฅผ ๋ถ์ธ๋ค.
await other.deposit(amount)
// other์ ๋ชจ๋ ๋ฉค๋ฒ๊ฐ await์ผ๋ก ์ฌ์ฉ๋๋๊ฑด ์๋๋ค. accountNumber๋ ์์์ด๋ฏ๋ก await ์์ด๋ ๋์์ด ๊ฐ๋ฅํ๋ค.
print(other.accountNumber)
print("Current Account: \(balance), Other Account: \(await other.balance)")
}
}
struct ContentView: View {
var body: some View {
Button {
let bankAccount = BankAccount(accountNumber: 123, balance: 500)
let otherAccount = BankAccount(accountNumber: 456, balance: 100)
// getCurrentAPR()์ actor method์์๋ nonisolated func์ด๋ฏ๋ก, async/awaitํ๊ฒ ์ฌ์ฉํ์ง ์์๋ ๋๋ค.
let _ = bankAccount.getCurrentAPR() // await, try await ๋ฑ์ ์์ฝ์ด๊ฐ ๋ถ์ง ์๋ ๋ชจ์ต. nonisolated property์ด๊ธฐ ๋.
// ์ํ ์๊ณ ์ถ๊ธ์ concurrentํ๊ฒ ์งํํ๋๋ฐ, ์๋ฃ ์์ ์ด ๋ค์ฃฝ๋ฐ์ฃฝ์ด ๋๋ค๋ฉด? ์๋์น ์์ ์ฌ๊ณ ๊ฐ ๋ฐ์ํ ์ ์๋ค!
DispatchQueue.concurrentPerform(iterations: 100) { _ in
Task {
try? await bankAccount.transfer(amount: 300, to: otherAccount)
}
}
} label: {
Text("Transfer")
}
}
}import SwiftUI
protocol Human {
associatedtype Food
var food: Food { get set }
func eat(food: Food)
}
class Baby: Human {
// ์๋์ฒ๋ผ Food associatedtype์ ํ์
์ ์ ์ํ ์๋ ์์ง๋ง ๊ด๋ จ ๋ฉค๋ฒ๋ค์ ํ์
์ ํตํด ํ์
์ ์ถ๋ก ํ๋ ๋ฐฉ์์ผ๋ก ์ฌ์ฉ๋ ๋จ
// Human protocol์ ํ์ ๊ตฌํ ๋ฉ์๋, eat์์ food ํ์
์ธ Food๋ฅผ Stringํ์
์ผ๋ก ์ฌ์ฉํ๊ธฐ์ ์ด๋ฅผ ํ์
์ถ๋ก ํ์ฌ Food ํ์
์ String์ผ๋ก ์ธ์ํจ
// typealias Food = String
var food: String
init(food: String) {
self.food = food
}
func eat(food: String) {
print(food)
}
}
protocol ActorMan {
associatedtype Food
var food: Food { get }
}
actor ActMan: ActorMan {
typealias Food = String
// food๋ data racing, race condition์ด ๋ฐ์ํ ๊ฐ๋ฅ์ฑ์ด ์ ํ ์๋ ๋์ด๋ผ nonisolated ๋ก ์ ์ธํด์ ์ธ๋ถ์์๋ async/await ๋ฐฉ์์ผ๋ก ์ฌ์ฉ ์ํด๋ ๋๋ค.
// constant๋ ์์์ด๋ฏ๋ก, actor ๋ฉค๋ฒ์ด์ง๋ง nonisolated ๋ช
์ ์ํด๋ ์ธ๋ถ์์ await์ผ๋ก ์ฌ์ฉํ ํ์๊ฐ ์์
let constant = "hahhaa"
// ๋ณ๊ฒฝ ๊ฐ๋ฅ์ฑ์ด ์๋ ์๋์ ๊ฐ์ getter ํ๋กํผํฐ๋ nonisolated ๋ช
์๋ฅผ ํตํด ์ธ๋ถ์์ await ํ๊ฒ ์ฌ์ฉํ ํ์ ์๋๋ก ํ ์ ์์
nonisolated var food: String {
return "apple"
}
init() {}
}
let baby = Baby(food: "apple")
baby.eat(food: "human")
let actorMan = ActMan()
print(actorMan.constant)
print(actorMan.food)
