Upgrade to Pro — share decks privately, control downloads, hide ads and more …

既存プロジェクトへの Swift Concurrency 導入戦略

akkiee76
August 01, 2023

既存プロジェクトへの Swift Concurrency 導入戦略

「Mobile勉強会 Wantedly × チームラボ #10」で発表した資料になります。
https://teamlab.connpass.com/event/289576/

akkiee76

August 01, 2023
Tweet

More Decks by akkiee76

Other Decks in Technology

Transcript

  1. Concurrency のメリット 従来の Block 構文 func fetchImageData(request: URLRequest, completionHandler: @escaping

    (UIImage?) -> ()) { self.session.dataTask(with: request) { data, response, error in // Imageをロードする self.loadImage(data: data) { image in // Imageサイズが適切かチェックする self.checkImage(image: image) { completionHandler(image) } } }.resume() }
  2. Concurrency のメリット async / await 記法 func request(url: URL) async

    throws -> UIImage? { let (data, response) = try await URLSession .shared.data(from: url, delegate: nil) let image = try await loadImage(data: data) let result = try await checkImageSize(image: image) return result } 簡潔に記述できる😁
  3. async / await func execute() { Task.detached { do {

    let url = URL(string: "https://api.example.com")! let response = try await self.request(url: url) // 後続処理 } catch { print(error.localizedDescription) } } } func request(url: URL) async throws -> HTTPURLResponse { // 通信処理 return result } async で非同期関数を定義 await で呼び出し 実行完了まで待機
  4. Sendable final class Valid: Sendable { let name: String init(name:

    String) { self.name = name } } final class Invalid: Sendable { var name: String init(name: String) { self.name = name } } データ競合を コンパイル時に警告してくれる Stored property 'name' of 'Sendable'-conforming class 'Invalid' is mutable
  5. Actor actor MainActor { var number: Int = 1 }

    func increment() { let act = MainActor() Task.detached { await act.number = 100 } } データ競合を回避する新しい型 Actor-isolated property 'number' can not be mutated from a Sendable closure
  6. Task func getUser() async -> UserInfo { // 親タスク await

    withTaskGroup(of: DataType.self) { group in // 子タスクA group.addTask { let friends = await self.getFriends() return DataType.friends(friends) } // 子タスクB group.addTask { let notes = await self.getNotes() return DataType.notes(notes) } for await getResult in group { switch getResult { case .friends(let f): friends = f case .notes(let n): notes = n } } } return UserInfo(friends: friends, notes: notes) } 並行処理を実行する単位 複数タスクの構造化も可能
  7. 導入の流れ 1. PoC 実装(対応方針 FIX) 2. スコープ分割 3. 横展開 4.

    テスト 1. PoC 実装(対応方針 FIX) 2. スコープ分割 3. 横展開 4. テスト
  8. 1. PoC実装 既存実装(Model) func request(request: URLRequest, completionHandler: @escaping (Data?) ->

    ()) { self.session.dataTask(with: request) { data, response, error in // 色々な処理 completionHandler(data) }.resume() } withCheckedThrowingContinuation で Cocurrency 関数を作成 func requestAsync(request: URLRequest) async throws -> Data? { return try await withCheckedThrowingContinuation { continuation in self.session.dataTask(with: request) { data, response, error in if let error = error { continuation.resume(throwing: self.handleNSURLError(error: error as NSError)) return } continuation.resume(returning: data) }.resume() } } @available(*, deprecated, message: "This function is deprecated. Use the requestAsync() instead.") func request(request: URLRequest, completionHandler: @escaping (Data?) -> ()) { self.session.dataTask(with: request) { data, response, error in completionHandler(data) }.resume() }
  9. 1. PoC実装 既存実装(Model) @available(*, deprecated, message: "This function is deprecated.

    Use the requestAsync() instead.") func request(request: URLRequest, completionHandler: @escaping (Data?) -> ()) { self.session.dataTask(with: request) { data, response, error in completionHandler(data) }.resume() } async / throws で Concurrency 関数を作成 func requestAsync(request: URLRequest) async throws -> Data? { let request = self.createRequest(request) do { let (data, response) = try await URLSession.shared.data(for: request) // 色々な処理 return data } catch { throw error } }
  10. 1. PoC実装 ViewModel の関数を async に修正 class ViewModel { private

    let apiService = ApiService() func fetchUserData() async throws -> Data? { do { let urlRequest = await self.createUrlRequest() return try await apiService.requestAsync(request: urlRequest) } catch { throw await self.handleError(error) } } } @MainActor class ViewModel { private let apiService = ApiService() nonisolated func fetchUserData() async throws -> Data? { do { let urlRequest = await self.createUrlRequest() return try await apiService.requestAsync(request: urlRequest) } catch { throw await self.handleError(error) } } } Main Thread から実行
  11. 1. PoC実装 非同期関数を呼び出しを修正 class MainViewController: UIViewController { private let viewModel

    = ViewModel() override func viewDidLoad() { super.viewDidLoad() self.viewModel.requestUserData { data in guard let data = data else { // エラー処理 return } updateUser(data) } } } class MainViewController: UIViewController { private let viewModel = ViewModel() override func viewDidLoad() { super.viewDidLoad() Task { do { let userData = try await viewModel.requestUserData() self.updateUser(userData) } catch { // エラー処理 } } } }
  12. 導入の流れ 1. PoC 実装(対応方針 FIX) 2. スコープ分割 3. 横展開 4.

    テスト 1. PoC 実装(対応方針 FIX) 2. スコープ分割 3. 横展開 4. テスト 1. PoC 実装(対応方針 FIX) 2. スコープ分割 3. 横展開 4. テスト