├── README.md ├── api ├── articles └── user ├── data └── 123.png └── src ├── 01a-answer.swift ├── 01a.swift ├── 01b-answer.swift ├── 01b.swift ├── 02a-answer.swift ├── 02a.swift ├── 02b-answer.swift ├── 02b.swift ├── 03a-answer.swift ├── 03a.swift ├── 03b-answer.swift ├── 03b.swift ├── 04-answer.swift ├── 04.swift ├── 05-answer.swift ├── 05.swift ├── 06-answer.swift ├── 06.swift ├── 07-answer.swift └── 07.swift /README.md: -------------------------------------------------------------------------------- 1 | # 先取り! Swift 6 の async/await 練習問題 2 | 3 | `async/await` の Proposal が Accept され、 Swift に `async/await` が導入されることが決定しました。本リポジトリには、そんな Swift の `async/await` を使ったコードを実務でどのように使うのか、一足早く練習できる問題を作成しました。 4 | 5 | 本練習問題は Web API から `User` の JSON を取得し、それをデコードして返す非同期処理を題材にしています。 6 | 7 | ```swift 8 | struct User: Identifiable, Codable { 9 | typealias ID = Int 10 | let id: ID 11 | var name: String 12 | var thumbnailURL: URL 13 | } 14 | ``` 15 | 16 | 問題は 01 - 07 まであり、それぞれ非同期処理のエラーハンドリングや並行処理など、実務で直面しそうなケースを取り上げています(ただし、 **どの問題の解答も数行程度のコードで書けます** )。 01 - 03 については A と B があり、同じ処理を A ではコールバックで、 B では `async/await` を使ってコードを書く問題になっています。そのため、これまでコールバックで書いていたコードがどのように `async/await` と対応するのか、どのように変化するのかを対比して理解することができます。 17 | 18 | なお、本リポジトリは `async/await` 自体の説明を含みませんが、 **本練習問題を用いて開催されたハンズオンの録画動画( `async/await` 自体の説明や問題の解説などを含みます)** およびスライドを下記 URL からご覧いただけます。スライドだけで説明がわかるようにはできていないので、動画の視聴をオススメします。 19 | 20 | - **ハンズオンの動画: https://www.youtube.com/watch?v=OZN_YFZy770&t=1467** 21 | - スライド: https://speakerdeck.com/koher/await 22 | 23 | `async/await` を使ったコードは Swift 5.3 環境で実行することはできません。しかし、すでに [apple/swift](https://github.com/apple/swift) の `main` ブランチにマージされているため、最新ビルドの Swift であれば利用することができます。 **[Swift Fiddle](https://swiftfiddle.com/pkyef4aymbf3fnmodkiym2rxhi) を使えば、ブラウザ上で手軽に最新ビルドの Swift を用いて `async/await` を含むコードをコンパイル・実行することができます** (ドロップダウンリストから `nightly-main` を選択する必要があります。上記ハンズオンも Swift Fiddle を用いて行われました)。 24 | 25 | | 問題 | 解答例 | テーマ | 26 | |:--|:--|:--| 27 | | [01A](src/01a.swift), [01B](src/01b.swift) | [01A](src/01a-answer.swift), [01B](src/01b-answer.swift) | `async/await` (エラーハンドリングなし) | 28 | | [02A](src/02a.swift), [02B](src/02b.swift) | [02A](src/02a-answer.swift), [02B](src/02b-answer.swift) | `async/await` (エラーハンドリングあり) | 29 | | [03A](src/03a.swift), [03B](src/03b.swift) | [03A](src/03a-answer.swift), [03B](src/03b-answer.swift) | `async/await` (連続した複数の処理) | 30 | | [04](src/04.swift) | [04](src/04-answer.swift) | `async let` (並行処理) | 31 | | [05](src/05.swift) | [05](src/05-answer.swift) | `@asyncHandler` ( `async` の起点) | 32 | | [06](src/06.swift) | [06](src/06-answer.swift) | `Task.Handle` とキャンセル | 33 | | [07](src/07.swift) | [07](src/07-answer.swift) | Continuation (コールバックからの変換) | 34 | 35 | -------------------------------------------------------------------------------- /api/articles: -------------------------------------------------------------------------------- 1 | [{"id":1010,"title":"Heart of SwiftUI"},{"id":1009,"title":"Purely Value-typed Swift"},{"id":1008,"title":"Best Practices of Swift's Optional"},{"id":1007,"title":"Mastering Swift's Optional"},{"id":1006,"title":"How Swift Improved Checked Exceptions in Java"},{"id":1005,"title":"Why Swift's Protocols Do Not Support Generics"},{"id":1004,"title":"What are Opaque Result Types in Swift 5.1"},{"id":1003,"title":"Preparing for Result in Swift 5"},{"id":1002,"title":"The Impact of Conditional Conformance in Swift 4.1"},{"id":1001,"title":"Heart of Swift"}] 2 | -------------------------------------------------------------------------------- /api/user: -------------------------------------------------------------------------------- 1 | {"id":123,"name":"koher","thumbnailURL":"https://koherent.org/async-await-challenge/data/123.png"} 2 | -------------------------------------------------------------------------------- /data/123.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koher/async-await-challenge/96e05583400dc0697b9e7562dc51ec0201646e50/data/123.png -------------------------------------------------------------------------------- /src/01a-answer.swift: -------------------------------------------------------------------------------- 1 | // サーバーから JSON を取得し、 2 | // User インスタンスをデコードする 3 | // 非同期関数 fetchUser を完成させて下さい。 4 | // 5 | // サーバーから JSON を取得するには 6 | // download 関数を用います。 7 | // download の実装は擬似的なものですが 8 | // 変更せずにそのまま利用して下さい。 9 | // 10 | // なお、通信やデコードに起因するエラーは 11 | // 起こらないものとします。 12 | 13 | import Foundation 14 | import FoundationNetworking 15 | 16 | struct User: Identifiable, Codable { 17 | typealias ID = Int 18 | let id: ID 19 | var name: String 20 | var thumbnailURL: URL 21 | } 22 | func download(from url: URL, completion: @escaping (Data) -> Void) { 23 | let data: Data = try! Data(contentsOf: url) 24 | completion(data) 25 | } 26 | 27 | func fetchUser(for id: User.ID, completion: @escaping (User) -> Void) { 28 | let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=\(id.description)")! 29 | download(from: url) { data in 30 | let user: User = try! JSONDecoder().decode(User.self, from: data) 31 | completion(user) 32 | } 33 | } 34 | 35 | fetchUser(for: 123) { user in 36 | print(user.name) 37 | } 38 | -------------------------------------------------------------------------------- /src/01a.swift: -------------------------------------------------------------------------------- 1 | // サーバーから JSON を取得し、 2 | // User インスタンスをデコードする 3 | // 非同期関数 fetchUser を完成させて下さい。 4 | // 5 | // サーバーから JSON を取得するには 6 | // download 関数を用います。 7 | // download の実装は擬似的なものですが 8 | // 変更せずにそのまま利用して下さい。 9 | // 10 | // なお、通信やデコードに起因するエラーは 11 | // 起こらないものとします。 12 | 13 | import Foundation 14 | import FoundationNetworking 15 | 16 | struct User: Identifiable, Codable { 17 | typealias ID = Int 18 | let id: ID 19 | var name: String 20 | var thumbnailURL: URL 21 | } 22 | func download(from url: URL, completion: @escaping (Data) -> Void) { 23 | let data: Data = try! Data(contentsOf: url) 24 | completion(data) 25 | } 26 | 27 | func fetchUser(for id: User.ID, completion: @escaping (User) -> Void) { 28 | let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=\(id.description)")! 29 | // 🚧 ここを実装する 30 | 31 | 32 | 33 | } 34 | 35 | fetchUser(for: 123) { user in 36 | print(user.name) 37 | } 38 | -------------------------------------------------------------------------------- /src/01b-answer.swift: -------------------------------------------------------------------------------- 1 | // サーバーから JSON を取得し、 2 | // User インスタンスをデコードする 3 | // 非同期関数 fetchUser を完成させて下さい。 4 | // 5 | // サーバーから JSON を取得するには 6 | // download 関数を用います。 7 | // download の実装は擬似的なものですが 8 | // 変更せずにそのまま利用して下さい。 9 | // 10 | // なお、通信やデコードに起因するエラーは 11 | // 起こらないものとします。 12 | 13 | import Foundation 14 | import FoundationNetworking 15 | 16 | struct User: Identifiable, Codable { 17 | typealias ID = Int 18 | let id: ID 19 | var name: String 20 | var thumbnailURL: URL 21 | } 22 | func download(from url: URL) async -> Data { 23 | let data: Data = try! Data(contentsOf: url) 24 | return data 25 | } 26 | 27 | func fetchUser(for id: User.ID) async -> User { 28 | let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=\(id.description)")! 29 | let data = await download(from: url) 30 | let user: User = try! JSONDecoder().decode(User.self, from: data) 31 | return user 32 | } 33 | 34 | runAsyncAndBlock { 35 | let user = await fetchUser(for: 123) 36 | print(user.name) 37 | } 38 | -------------------------------------------------------------------------------- /src/01b.swift: -------------------------------------------------------------------------------- 1 | // サーバーから JSON を取得し、 2 | // User インスタンスをデコードする 3 | // 非同期関数 fetchUser を完成させて下さい。 4 | // 5 | // サーバーから JSON を取得するには 6 | // download 関数を用います。 7 | // download の実装は擬似的なものですが 8 | // 変更せずにそのまま利用して下さい。 9 | // 10 | // なお、通信やデコードに起因するエラーは 11 | // 起こらないものとします。 12 | 13 | import Foundation 14 | import FoundationNetworking 15 | 16 | struct User: Identifiable, Codable { 17 | typealias ID = Int 18 | let id: ID 19 | var name: String 20 | var thumbnailURL: URL 21 | } 22 | func download(from url: URL) async -> Data { 23 | let data: Data = try! Data(contentsOf: url) 24 | return data 25 | } 26 | 27 | func fetchUser(for id: User.ID) async -> User { 28 | let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=\(id.description)")! 29 | // 🚧 ここを実装する 30 | 31 | 32 | 33 | } 34 | 35 | runAsyncAndBlock { 36 | let user = await fetchUser(for: 123) 37 | print(user.name) 38 | } 39 | -------------------------------------------------------------------------------- /src/02a-answer.swift: -------------------------------------------------------------------------------- 1 | // サーバーから JSON を取得し、 2 | // User インスタンスをデコードする 3 | // 非同期関数 fetchUser を完成させて下さい。 4 | 5 | import Foundation 6 | import FoundationNetworking 7 | 8 | struct User: Identifiable, Codable { 9 | typealias ID = Int 10 | let id: ID 11 | var name: String 12 | var thumbnailURL: URL 13 | } 14 | func download(from url: URL, completion: @escaping (Result) -> Void) { 15 | do { 16 | let data: Data = try Data(contentsOf: url) 17 | completion(.success(data)) 18 | } catch { 19 | completion(.failure(error)) 20 | } 21 | } 22 | 23 | func fetchUser(for id: User.ID, completion: @escaping (Result) -> Void) { 24 | let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=\(id.description)")! 25 | download(from: url) { data in 26 | do { 27 | let user: User = try JSONDecoder().decode(User.self, from: data.get()) 28 | completion(.success(user)) 29 | } catch { 30 | completion(.failure(error)) 31 | } 32 | } 33 | } 34 | 35 | fetchUser(for: 123) { user in 36 | do { 37 | print(try user.get().name) 38 | } catch { 39 | print(error) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/02a.swift: -------------------------------------------------------------------------------- 1 | // サーバーから JSON を取得し、 2 | // User インスタンスをデコードする 3 | // 非同期関数 fetchUser を完成させて下さい。 4 | 5 | import Foundation 6 | import FoundationNetworking 7 | 8 | struct User: Identifiable, Codable { 9 | typealias ID = Int 10 | let id: ID 11 | var name: String 12 | var thumbnailURL: URL 13 | } 14 | func download(from url: URL, completion: @escaping (Result) -> Void) { 15 | do { 16 | let data: Data = try Data(contentsOf: url) 17 | completion(.success(data)) 18 | } catch { 19 | completion(.failure(error)) 20 | } 21 | } 22 | 23 | func fetchUser(for id: User.ID, completion: @escaping (Result) -> Void) { 24 | let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=\(id.description)")! 25 | // 🚧 ここを実装する 26 | 27 | 28 | 29 | } 30 | 31 | fetchUser(for: 123) { user in 32 | do { 33 | print(try user.get().name) 34 | } catch { 35 | print(error) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/02b-answer.swift: -------------------------------------------------------------------------------- 1 | // サーバーから JSON を取得し、 2 | // User インスタンスをデコードする 3 | // 非同期関数 fetchUser を完成させて下さい。 4 | 5 | import Foundation 6 | import FoundationNetworking 7 | 8 | struct User: Identifiable, Codable { 9 | typealias ID = Int 10 | let id: ID 11 | var name: String 12 | var thumbnailURL: URL 13 | } 14 | func download(from url: URL) async throws -> Data { 15 | let data: Data = try Data(contentsOf: url) 16 | return data 17 | } 18 | 19 | func fetchUser(for id: User.ID) async throws -> User { 20 | let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=\(id.description)")! 21 | let data: Data = try await download(from: url) 22 | let user: User = try JSONDecoder().decode(User.self, from: data) 23 | return user 24 | } 25 | 26 | runAsyncAndBlock { 27 | do { 28 | let user: User = try await fetchUser(for: 123) 29 | print(user.name) 30 | } catch { 31 | print(error) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/02b.swift: -------------------------------------------------------------------------------- 1 | // サーバーから JSON を取得し、 2 | // User インスタンスをデコードする 3 | // 非同期関数 fetchUser を完成させて下さい。 4 | 5 | import Foundation 6 | import FoundationNetworking 7 | 8 | struct User: Identifiable, Codable { 9 | typealias ID = Int 10 | let id: ID 11 | var name: String 12 | var thumbnailURL: URL 13 | } 14 | func download(from url: URL) async throws -> Data { 15 | let data: Data = try Data(contentsOf: url) 16 | return data 17 | } 18 | 19 | func fetchUser(for id: User.ID) async throws -> User { 20 | let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=\(id.description)")! 21 | // 🚧 ここを実装する 22 | 23 | 24 | 25 | } 26 | 27 | runAsyncAndBlock { 28 | do { 29 | let user: User = try await fetchUser(for: 123) 30 | print(user.name) 31 | } catch { 32 | print(error) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/03a-answer.swift: -------------------------------------------------------------------------------- 1 | // サーバーから JSON を取得し、 2 | // User インスタンスをデコードし、 3 | // それに加えて User が保持する thumbnailURL から 4 | // サムネイル画像をダウンロードする非同期関数 5 | // fetchUserWithThumbnail を完成させて下さい。 6 | 7 | import Foundation 8 | import FoundationNetworking 9 | 10 | struct User: Identifiable, Codable { 11 | typealias ID = Int 12 | let id: ID 13 | var name: String 14 | var thumbnailURL: URL 15 | } 16 | func download(from url: URL, completion: @escaping (Result) -> Void) { 17 | do { 18 | let data: Data = try Data(contentsOf: url) 19 | completion(.success(data)) 20 | } catch { 21 | completion(.failure(error)) 22 | } 23 | } 24 | 25 | func fetchUserWithThumbnail(for id: User.ID, completion: @escaping (Result<(user: User, thumbnail: Data), Error>) -> Void) { 26 | let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=\(id.description)")! 27 | download(from: url) { data in 28 | do { 29 | let user: User = try JSONDecoder().decode(User.self, from: data.get()) 30 | download(from: user.thumbnailURL) { thumbnail in 31 | do { 32 | completion(.success((user: user, thumbnail: try thumbnail.get()))) 33 | } catch { 34 | completion(.failure(error)) 35 | } 36 | } 37 | } catch { 38 | completion(.failure(error)) 39 | } 40 | } 41 | } 42 | 43 | fetchUserWithThumbnail(for: 123) { result in 44 | do { 45 | let (user, thumbnail) = try result.get() 46 | print(user.name, thumbnail.count) 47 | } catch { 48 | print(error) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/03a.swift: -------------------------------------------------------------------------------- 1 | // サーバーから JSON を取得し、 2 | // User インスタンスをデコードし、 3 | // それに加えて User が保持する thumbnailURL から 4 | // サムネイル画像をダウンロードする非同期関数 5 | // fetchUserWithThumbnail を完成させて下さい。 6 | 7 | import Foundation 8 | import FoundationNetworking 9 | 10 | struct User: Identifiable, Codable { 11 | typealias ID = Int 12 | let id: ID 13 | var name: String 14 | var thumbnailURL: URL 15 | } 16 | func download(from url: URL, completion: @escaping (Result) -> Void) { 17 | do { 18 | let data: Data = try Data(contentsOf: url) 19 | completion(.success(data)) 20 | } catch { 21 | completion(.failure(error)) 22 | } 23 | } 24 | 25 | func fetchUserWithThumbnail(for id: User.ID, completion: @escaping (Result<(user: User, thumbnail: Data), Error>) -> Void) { 26 | let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=\(id.description)")! 27 | // 🚧 ここを実装する 28 | 29 | 30 | 31 | } 32 | 33 | fetchUserWithThumbnail(for: 123) { result in 34 | do { 35 | let (user, thumbnail) = try result.get() 36 | print(user.name, thumbnail.count) 37 | } catch { 38 | print(error) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/03b-answer.swift: -------------------------------------------------------------------------------- 1 | // サーバーから JSON を取得し、 2 | // User インスタンスをデコードし、 3 | // それに加えて User が保持する thumbnailURL から 4 | // サムネイル画像をダウンロードする非同期関数 5 | // fetchUserWithThumbnail を完成させて下さい。 6 | 7 | import Foundation 8 | import FoundationNetworking 9 | 10 | struct User: Identifiable, Codable { 11 | typealias ID = Int 12 | let id: ID 13 | var name: String 14 | var thumbnailURL: URL 15 | } 16 | func download(from url: URL) async throws -> Data { 17 | let data: Data = try Data(contentsOf: url) 18 | return data 19 | } 20 | 21 | func fetchUserWithThumbnail(for id: User.ID) async throws -> (user: User, thumbnail: Data) { 22 | let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=\(id.description)")! 23 | let data = try await download(from: url) 24 | let user: User = try JSONDecoder().decode(User.self, from: data) 25 | let thumbnail: Data = try await download(from: user.thumbnailURL) 26 | return (user: user, thumbnail: thumbnail) 27 | } 28 | 29 | runAsyncAndBlock { 30 | do { 31 | let (user, thumbnail) = try await fetchUserWithThumbnail(for: 123) 32 | print(user.name, thumbnail.count) 33 | } catch { 34 | print(error) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/03b.swift: -------------------------------------------------------------------------------- 1 | // サーバーから JSON を取得し、 2 | // User インスタンスをデコードし、 3 | // それに加えて User が保持する thumbnailURL から 4 | // サムネイル画像をダウンロードする非同期関数 5 | // fetchUserWithThumbnail を完成させて下さい。 6 | 7 | import Foundation 8 | import FoundationNetworking 9 | 10 | struct User: Identifiable, Codable { 11 | typealias ID = Int 12 | let id: ID 13 | var name: String 14 | var thumbnailURL: URL 15 | } 16 | func download(from url: URL) async throws -> Data { 17 | let data: Data = try Data(contentsOf: url) 18 | return data 19 | } 20 | 21 | func fetchUserWithThumbnail(for id: User.ID) async throws -> (user: User, thumbnail: Data) { 22 | let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=\(id.description)")! 23 | // 🚧 ここを実装する 24 | 25 | 26 | 27 | } 28 | 29 | runAsyncAndBlock { 30 | do { 31 | let (user, thumbnail) = try await fetchUserWithThumbnail(for: 123) 32 | print(user.name, thumbnail.count) 33 | } catch { 34 | print(error) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/04-answer.swift: -------------------------------------------------------------------------------- 1 | // サーバーから User とその Article 最新 10 件の JSON を取得し、 2 | // それらを返す非同期関数 fetchUserWithArticles を実装して下さい。 3 | // ただし、 User と Article の JSON は並行して取得するものとし、 4 | // User と Artcile の取得には fetchUser および 5 | // fetchArticles を用いるものとします。 6 | 7 | import Foundation 8 | import FoundationNetworking 9 | 10 | struct User: Identifiable, Codable { 11 | typealias ID = Int 12 | let id: ID 13 | var name: String 14 | var thumbnailURL: URL 15 | } 16 | struct Article: Identifiable, Codable { 17 | typealias ID = Int 18 | let id: ID 19 | var title: String 20 | } 21 | func fetchUser(for id: User.ID) async throws -> User { 22 | let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=\(id.description)")! 23 | let data: Data = try Data(contentsOf: url) 24 | let user: User = try JSONDecoder().decode(User.self, from: data) 25 | return user 26 | } 27 | func fetchArticles(for userID: User.ID, limit: Int) async throws -> [Article] { 28 | let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/articles?userID=\(userID.description)")! 29 | let data: Data = try Data(contentsOf: url) 30 | let articles: [Article] = try JSONDecoder().decode([Article].self, from: data) 31 | return articles 32 | } 33 | 34 | func fetchUserWithArticles(for id: User.ID, limit: Int) async throws -> (user: User, articles: [Article]) { 35 | async let user: User = fetchUser(for: id) 36 | async let articles: [Article] = fetchArticles(for: id, limit: limit) 37 | return try await (user: user, articles: articles) 38 | } 39 | 40 | runAsyncAndBlock { 41 | do { 42 | let (user, articles) = try await fetchUserWithArticles(for: 123, limit: 10) 43 | print(user, articles) 44 | } catch { 45 | print(error) 46 | } 47 | } -------------------------------------------------------------------------------- /src/04.swift: -------------------------------------------------------------------------------- 1 | // サーバーから User とその Article 最新 10 件の JSON を取得し、 2 | // それらを返す非同期関数 fetchUserWithArticles を実装して下さい。 3 | // ただし、 User と Article の JSON は並行して取得するものとし、 4 | // User と Artcile の取得には fetchUser および 5 | // fetchArticles を用いるものとします。 6 | 7 | import Foundation 8 | import FoundationNetworking 9 | 10 | struct User: Identifiable, Codable { 11 | typealias ID = Int 12 | let id: ID 13 | var name: String 14 | var thumbnailURL: URL 15 | } 16 | struct Article: Identifiable, Codable { 17 | typealias ID = Int 18 | let id: ID 19 | var title: String 20 | } 21 | func fetchUser(for id: User.ID) async throws -> User { 22 | let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=\(id.description)")! 23 | let data: Data = try Data(contentsOf: url) 24 | let user: User = try JSONDecoder().decode(User.self, from: data) 25 | return user 26 | } 27 | func fetchArticles(for userID: User.ID, limit: Int) async throws -> [Article] { 28 | let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/articles?userID=\(userID.description)")! 29 | let data: Data = try Data(contentsOf: url) 30 | let articles: [Article] = try JSONDecoder().decode([Article].self, from: data) 31 | return articles 32 | } 33 | 34 | func fetchUserWithArticles(for id: User.ID, limit: Int) async throws -> (user: User, articles: [Article]) { 35 | // 🚧 ここを実装する 36 | 37 | 38 | 39 | } 40 | 41 | runAsyncAndBlock { 42 | do { 43 | let (user, articles) = try await fetchUserWithArticles(for: 123, limit: 10) 44 | print(user, articles) 45 | } catch { 46 | print(error) 47 | } 48 | } -------------------------------------------------------------------------------- /src/05-answer.swift: -------------------------------------------------------------------------------- 1 | // ViewController の reloadUserButton が押されたときに 2 | // fetchUser 関数を使ってサーバーから User を取得し、 3 | // userNameLabel.text に取得したユーザーの name を設定するように 4 | // onReloadUserButtonPressed メソッドを完成させて下さい。 5 | 6 | import Foundation 7 | import FoundationNetworking 8 | 9 | class UIViewController {} 10 | final class UIButton {} 11 | final class UILabel { 12 | var text: String? 13 | } 14 | 15 | struct User: Identifiable, Codable { 16 | typealias ID = Int 17 | let id: ID 18 | var name: String 19 | var thumbnailURL: URL 20 | } 21 | func fetchUser(for id: User.ID) async throws -> User { 22 | let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=\(id.description)")! 23 | let data: Data = try Data(contentsOf: url) 24 | let user: User = try JSONDecoder().decode(User.self, from: data) 25 | return user 26 | } 27 | 28 | final class ViewController: UIViewController { 29 | let reloadUserButton: UIButton = .init() 30 | let userNameLabel: UILabel = .init() 31 | let userID: User.ID 32 | init(userID: User.ID) { self.userID = userID } 33 | 34 | @asyncHandler 35 | func onReloadUserButtonPressed(_ sender: UIButton) { 36 | if let user = try? await fetchUser(for: userID) { 37 | userNameLabel.text = user.name 38 | } 39 | } 40 | } 41 | 42 | let viewController: ViewController = .init(userID: 123) 43 | viewController.onReloadUserButtonPressed(viewController.reloadUserButton) 44 | -------------------------------------------------------------------------------- /src/05.swift: -------------------------------------------------------------------------------- 1 | // ViewController の reloadUserButton が押されたときに 2 | // fetchUser 関数を使ってサーバーから User を取得し、 3 | // userNameLabel.text に取得したユーザーの name を設定するように 4 | // onReloadUserButtonPressed メソッドを完成させて下さい。 5 | 6 | import Foundation 7 | import FoundationNetworking 8 | 9 | class UIViewController {} 10 | final class UIButton {} 11 | final class UILabel { 12 | var text: String? 13 | } 14 | 15 | struct User: Identifiable, Codable { 16 | typealias ID = Int 17 | let id: ID 18 | var name: String 19 | var thumbnailURL: URL 20 | } 21 | func fetchUser(for id: User.ID) async throws -> User { 22 | let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=\(id.description)")! 23 | let data: Data = try Data(contentsOf: url) 24 | let user: User = try JSONDecoder().decode(User.self, from: data) 25 | return user 26 | } 27 | 28 | final class ViewController: UIViewController { 29 | let reloadUserButton: UIButton = .init() 30 | let userNameLabel: UILabel = .init() 31 | let userID: User.ID 32 | init(userID: User.ID) { self.userID = userID } 33 | 34 | // 🚧 このメソッドを完成させる 35 | func onReloadUserButtonPressed(_ sender: UIButton) { 36 | 37 | } 38 | } 39 | 40 | let viewController: ViewController = .init(userID: 123) 41 | viewController.onReloadUserButtonPressed(viewController.reloadUserButton) 42 | -------------------------------------------------------------------------------- /src/06-answer.swift: -------------------------------------------------------------------------------- 1 | // ViewController の cancelReloadingUser ボタンを押すと 2 | // reloadUser ボタンで実行されている User のリロードを 3 | // キャンセルするように ViewController の実装を完成させて下さい。 4 | 5 | import Foundation 6 | import FoundationNetworking 7 | 8 | class UIViewController {} 9 | final class UIButton {} 10 | final class UILabel { 11 | var text: String? 12 | } 13 | 14 | struct User: Identifiable, Codable { 15 | typealias ID = Int 16 | let id: ID 17 | var name: String 18 | var thumbnailURL: URL 19 | } 20 | func fetchUser(for id: User.ID) async throws -> User { 21 | let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=\(id.description)")! 22 | let data: Data = try Data(contentsOf: url) 23 | let user: User = try JSONDecoder().decode(User.self, from: data) 24 | return user 25 | } 26 | 27 | final class ViewController: UIViewController { 28 | let reloadUserButton: UIButton = .init() 29 | let cancelReloadingUserButton: UIButton = .init() 30 | let userNameLabel: UILabel = .init() 31 | let userID: User.ID 32 | init(userID: User.ID) { self.userID = userID } 33 | 34 | private var userReloadingHandle: Task.Handle? 35 | 36 | func onReloadUserButtonPressed(_ sender: UIButton) { 37 | guard userReloadingHandle == nil else { return } 38 | userReloadingHandle = Task.runDetached { [self] in 39 | if let user = try? await fetchUser(for: userID) { 40 | userNameLabel.text = user.name 41 | } 42 | } 43 | } 44 | 45 | func onCancelReloadingUserButtonPressed(_ sender: UIButton) { 46 | userReloadingHandle?.cancel() 47 | userReloadingHandle = nil 48 | } 49 | } 50 | 51 | let viewController: ViewController = .init(userID: 123) 52 | viewController.onReloadUserButtonPressed(viewController.reloadUserButton) 53 | viewController.onCancelReloadingUserButtonPressed(viewController.cancelReloadingUserButton) 54 | -------------------------------------------------------------------------------- /src/06.swift: -------------------------------------------------------------------------------- 1 | // ViewController の cancelReloadingUser ボタンを押すと 2 | // reloadUser ボタンで実行されている User のリロードを 3 | // キャンセルするように ViewController の実装を完成させて下さい。 4 | 5 | import Foundation 6 | import FoundationNetworking 7 | 8 | class UIViewController {} 9 | final class UIButton {} 10 | final class UILabel { 11 | var text: String? 12 | } 13 | 14 | struct User: Identifiable, Codable { 15 | typealias ID = Int 16 | let id: ID 17 | var name: String 18 | var thumbnailURL: URL 19 | } 20 | func fetchUser(for id: User.ID) async throws -> User { 21 | let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=\(id.description)")! 22 | let data: Data = try Data(contentsOf: url) 23 | let user: User = try JSONDecoder().decode(User.self, from: data) 24 | return user 25 | } 26 | 27 | // 🚧 このクラスを完成させる 28 | final class ViewController: UIViewController { 29 | let reloadUserButton: UIButton = .init() 30 | let cancelReloadingUserButton: UIButton = .init() 31 | let userNameLabel: UILabel = .init() 32 | let userID: User.ID 33 | init(userID: User.ID) { self.userID = userID } 34 | 35 | private var userReloadingHandle: Task.Handle? 36 | 37 | @asyncHandler 38 | func onReloadUserButtonPressed(_ sender: UIButton) { 39 | if let user = try? await fetchUser(for: userID) { 40 | userNameLabel.text = user.name 41 | } 42 | } 43 | 44 | func onCancelReloadingUserButtonPressed(_ sender: UIButton) { 45 | 46 | } 47 | } 48 | 49 | let viewController: ViewController = .init(userID: 123) 50 | viewController.onReloadUserButtonPressed(viewController.reloadUserButton) 51 | viewController.onCancelReloadingUserButtonPressed(viewController.cancelReloadingUserButton) 52 | -------------------------------------------------------------------------------- /src/07-answer.swift: -------------------------------------------------------------------------------- 1 | // コールバックで結果を受け取る非同期関数 download を使って、 2 | // async で結果を返す非同期関数 download を実装して下さい。 3 | 4 | import Foundation 5 | import FoundationNetworking 6 | 7 | func download(from url: URL, completion: @escaping (Result) -> Void) { 8 | do { 9 | let data: Data = try Data(contentsOf: url) 10 | completion(.success(data)) 11 | } catch { 12 | completion(.failure(error)) 13 | } 14 | } 15 | 16 | func download(from url: URL) async throws -> Data { 17 | try await withUnsafeThrowingContinuation { continuation in 18 | download(from: url) { data in 19 | continuation.resume(with: data) 20 | } 21 | } 22 | } 23 | 24 | runAsyncAndBlock { 25 | do { 26 | let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=123")! 27 | let data: Data = try await download(from: url) 28 | print(String(data: data, encoding: .utf8)!) 29 | } catch { 30 | print(error) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/07.swift: -------------------------------------------------------------------------------- 1 | // コールバックで結果を受け取る非同期関数 download を使って、 2 | // async で結果を返す非同期関数 download を実装して下さい。 3 | 4 | import Foundation 5 | import FoundationNetworking 6 | 7 | func download(from url: URL, completion: @escaping (Result) -> Void) { 8 | do { 9 | let data: Data = try Data(contentsOf: url) 10 | completion(.success(data)) 11 | } catch { 12 | completion(.failure(error)) 13 | } 14 | } 15 | 16 | // 🚧 ここを実装する 17 | 18 | 19 | 20 | 21 | runAsyncAndBlock { 22 | do { 23 | let url: URL = URL(string: "https://koherent.org/async-await-challenge/api/user?id=123")! 24 | let data: Data = try await download(from: url) 25 | print(String(data: data, encoding: .utf8)!) 26 | } catch { 27 | print(error) 28 | } 29 | } 30 | --------------------------------------------------------------------------------