$30 off During Our Annual Pro Sale. View Details »

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

akkie76
August 01, 2023

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

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

akkie76

August 01, 2023
Tweet

More Decks by akkie76

Other Decks in Technology

Transcript

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

    View Slide

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

    View Slide

  3. Swift Concurrency 導入しましたか?

    View Slide

  4. 弊社でも現在対応中😁

    View Slide

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

    View Slide

  6. Swift Concurrency の2つのメリット✨

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  15. 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

    View Slide

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

    View Slide

  17. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  29. Thank you !

    View Slide