Slide 1

Slide 1 text

©2023 RAKUS Co., Ltd. 既存プロジェクトへの Swift Concurrency 導入戦略 Mobile勉強会 Wantedly × チームラボ #10 @akkiee76

Slide 2

Slide 2 text

Akihiko Sato 株式会社ラクス / 楽楽精算 / Lead Engineer @akkiee76 自己紹介

Slide 3

Slide 3 text

Swift Concurrency 導入しましたか?

Slide 4

Slide 4 text

弊社でも現在対応中😁

Slide 5

Slide 5 text

導入すると幸せになれる?

Slide 6

Slide 6 text

Swift Concurrency の2つのメリット✨

Slide 7

Slide 7 text

Concurrency のメリット 1. 並行処理を簡潔・安全に記述できる

Slide 8

Slide 8 text

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() }

Slide 9

Slide 9 text

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 } 簡潔に記述できる😁

Slide 10

Slide 10 text

Concurrency のメリット 2. データの競合、デッドロックを回避 品質担保に期待できる😁

Slide 11

Slide 11 text

Swift Concurrency のおさらい ● async / await ● Sendable ● Actor ● Task

Slide 12

Slide 12 text

Swift Concurrency のおさらい ● async / await ● Sendable ● Actor ● Task

Slide 13

Slide 13 text

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 で呼び出し 実行完了まで待機

Slide 14

Slide 14 text

Swift Concurrency のおさらい ● async / await ● Sendable ● Actor ● Task

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

Swift Concurrency のおさらい ● async / await ● Sendable ● Actor ● Task

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

Swift Concurrency のおさらい ● async / await ● Sendable ● Actor ● Task

Slide 19

Slide 19 text

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) } 並行処理を実行する単位 複数タスクの構造化も可能

Slide 20

Slide 20 text

既存プロジェクトに導入してみよう😀

Slide 21

Slide 21 text

クラスイメージ ● ViewController ● ViewModel ● Model(ApiClient)

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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() }

Slide 24

Slide 24 text

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 } }

Slide 25

Slide 25 text

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 から実行

Slide 26

Slide 26 text

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 { // エラー処理 } } } }

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

まとめ Swift Concurrency の主な機能を中心に、 既存プロジェクトへの導入戦略を紹介させて頂きました。 あくまでも一つの戦略なので、他にも良い方法があれば教えてください!

Slide 29

Slide 29 text

Thank you !