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

async/awaitやactorでiOSアプリ開発がどう変わるか Before & Afterの具体例で学ぶ

Yuta Koshizawa
September 19, 2021

async/awaitやactorでiOSアプリ開発がどう変わるか Before & Afterの具体例で学ぶ

今秋リリース予定のSwift 5.5には、async/awaitやactorなどの並行処理関連の新機能が多く含まれます。これによって、iOSアプリ開発における非同期処理や並行処理のコードは劇的に変化します。本トークでは、iOSアプリ開発でよく見かけるようなケースを取り上げ、Before & Afterのコードを示し、何がどのように変わるのかを説明します。

async/awaitやactorは使いこなせばとても便利ですが、新しい概念を初めから抽象的に学ぶのは大変です。しかし、それらが解決しようとしている問題自体はiOSアプリ開発者にとって馴染み深いものです。async/awaitやactorで書くコードが既存コードのどのような処理に対応するのか、既存コードにはどんな問題があり、async/awaitやactorはそれをどう解決してくれるのか、具体的なケースで学べば入口のハードルはぐっと下がります。

Swiftの並行処理関連の新機能は、async/await、async let、Task、CheckedContinuation、actor、Sendable、MainActorなど多岐に渡ります。それらを一つ一つ取り上げ、具体例を題材に、iOSアプリ開発のコードがどのように改善されるのかを説明します。

https://fortee.jp/iosdc-japan-2021/speaker/proposal/view/19c076c3-18cb-4d04-9e9d-59cc02220538

Yuta Koshizawa

September 19, 2021
Tweet

More Decks by Yuta Koshizawa

Other Decks in Programming

Transcript

  1. async/await΍actorͰ
    iOSΞϓϦ։ൃ͕Ͳ͏มΘΔ͔
    Before & A)erͷ۩ମྫͰֶͿ
    Yuta Koshizawa @koher

    View full-size slide

  2. Concurrency
    ʢฒߦॲཧʣ

    View full-size slide

  3. iOS ΞϓϦ։ൃΛܶతʹม͑Δ

    View full-size slide

  4. Proposal: 12 ຊ
    WWDC: 9 ຊ

    View full-size slide

  5. ۩ମతͳࣄྫ

    ந৅తͳ֓೦

    View full-size slide

  6. iOS ΞϓϦ։ൃͱ͍͏۩ମྫ

    View full-size slide

  7. Before & A)er ͷ۩ମྫͰֶͿ

    View full-size slide

  8. 21 ݸͷέʔε

    View full-size slide

  9. Concurrency
    !
    async / await
    !
    Structured Concurrency
    !
    actor

    View full-size slide

  10. !
    async / await

    View full-size slide

  11. Case 1: ඇಉظؔ਺ͷར༻
    ΤϥʔϋϯυϦϯά͕ͳ͍৔߹

    View full-size slide

  12. Before
    func downloadData(from url: URL,
    completion: @escaping (Data) -> Void)

    View full-size slide

  13. Before
    func downloadData(from url: URL,
    completion: @escaping (Data) -> Void)
    downloadData(from: url) { data in
    // data Λ࢖͏ॲཧ
    }

    View full-size slide

  14. async / await0296
    0296 h'ps:/
    /github.com/apple/swi9-evolu

    View full-size slide

  15. Before
    func downloadData(from url: URL,
    completion: @escaping (Data) -> Void)
    downloadData(from: url) { data in
    // data Λ࢖͏ॲཧ
    }

    View full-size slide

  16. A"er
    func downloadData(from url: URL) async -> Data
    let data = await downloadData(from: url)
    // data Λ࢖͏ॲཧ

    View full-size slide

  17. A ͱ B ͕ಉ͡εϨουͰ࣮ߦ͞ΕΔͱ͸ݶΒͳ͍
    func downloadData(from url: URL) async -> Data
    print("A")
    let data = await downloadData(from: url)
    print("B")

    View full-size slide

  18. A ͱ B ͕ಉ͡εϨουͰ࣮ߦ͞ΕΔͱ͸ݶΒͳ͍
    func downloadData(from url: URL,
    completion: @escaping (Data) -> Void)
    print("A")
    downloadData(from: url) { data in
    print("B")
    }

    View full-size slide

  19. Case 3: ඇಉظؔ਺ͷར༻
    ΤϥʔϋϯυϦϯά͕͋Δ৔߹

    View full-size slide

  20. Case 1 (Before)
    func downloadData(from url: URL, completion:
    @escaping (Data) -> Void)
    downloadData(from: url) { data in
    // data Λ࢖͏ॲཧ
    }

    View full-size slide

  21. Before
    func downloadData(from url: URL, completion:
    @escaping (Result) -> Void)
    downloadData(from: url) { result in
    do {
    let data = try result.get()
    // data Λ࢖͏ॲཧ
    } catch {
    // ΤϥʔϋϯυϦϯά
    }
    }

    View full-size slide

  22. A"er
    func downloadData(from url: URL) async throws -> Data
    do {
    let data = try await downloadData(from: url)
    // data Λ࢖͏ॲཧ
    } catch {
    // ΤϥʔϋϯυϦϯά
    }

    View full-size slide

  23. Case 4: ඇಉظؔ਺ͷ࣮૷
    ΤϥʔϋϯυϦϯά͕͋Δ৔߹

    View full-size slide

  24. ྫ: User Λද͢ JSON Λऔಘͯ͠σίʔυ

    View full-size slide

  25. Before
    func fetchUser(for id: User.ID, completion: @escaping (Result) -> Void) {
    let url: URL = .init(string: "https://koherent.org/fake-service/api/user?id=\(id)")!
    }

    View full-size slide

  26. Before
    func fetchUser(for id: User.ID, completion: @escaping (Result) -> Void) {
    let url: URL = .init(string: "https://koherent.org/fake-service/api/user?id=\(id)")!
    downloadData(from: url) { result in
    }
    }

    View full-size slide

  27. Before
    func fetchUser(for id: User.ID, completion: @escaping (Result) -> Void) {
    let url: URL = .init(string: "https://koherent.org/fake-service/api/user?id=\(id)")!
    downloadData(from: url) { result in
    let data = try result.get()
    let user = try JSONDecoder().decode(User.self, from: data)
    completion(.success(user))
    }
    }

    View full-size slide

  28. Before
    func fetchUser(for id: User.ID, completion: @escaping (Result) -> Void) {
    let url: URL = .init(string: "https://koherent.org/fake-service/api/user?id=\(id)")!
    downloadData(from: url) { result in
    do {
    let data = try result.get()
    let user = try JSONDecoder().decode(User.self, from: data)
    completion(.success(user))
    } catch {
    completion(.failure(error))
    }
    }
    }

    View full-size slide

  29. Before
    func fetchUser(for id: User.ID, completion: @escaping (Result) -> Void) {
    let url: URL = .init(string: "https://koherent.org/fake-service/api/user?id=\(id)")!
    downloadData(from: url) { result in
    do {
    let data = try result.get()
    let user = try JSONDecoder().decode(User.self, from: data)
    completion(.success(user))
    } catch {
    completion(.failure(error))
    }
    }
    }

    View full-size slide

  30. A"er
    func fetchUser(for id: User.ID) async throws -> User {
    let url: URL = .init(string: "https://koherent.org/fake-service/api/user?id=\(id)")!
    let data = try await downloadData(from: url)
    let user = try JSONDecoder().decode(User.self, from: data)
    return user
    }

    View full-size slide

  31. Case 5: ඇಉظؔ਺ͷ࿈݁

    View full-size slide

  32. ྫ: ϢʔβʔΞΠίϯͷμ΢ϯϩʔυ
    ʢͨͩ͠ɺΞΠίϯͷ URL ͸Ϣʔβʔͷ JSON ͷதʹهࡌʣ

    View full-size slide

  33. Ϣʔβʔͷ JSON ͷμ΢ϯϩʔυ

    ΞΠίϯͷμ΢ϯϩʔυ

    View full-size slide

  34. Before
    func fetchUserIcon(for id: User.ID,
    completion: @escaping (Result) -> Void) {
    let url: URL = .init(string: "https://koherent.org/fake-service/api/user?id=\(id)")!
    downloadData(from: url) { data in
    do {
    let data = try data.get()
    let user = try JSONDecoder().decode(User.self, from: data)
    downloadData(from: user.iconURL) { icon in
    do {
    let icon = try icon.get()
    completion(.success(icon))
    } catch { completion(.failure(error)) }
    }
    } catch {
    completion(.failure(error))
    }
    }
    }

    View full-size slide

  35. A"er
    func fetchUserIcon(for id: User.ID) async throws -> Data {
    let url: URL = .init(string: "https://koherent.org/fake-service/api/user?id=\(id)")!
    let data = try await downloadData(from: url)
    let user = try JSONDecoder().decode(User.self, from: data)
    let icon = try await downloadData(from: user.iconURL)
    return icon
    }

    View full-size slide

  36. Case 6: ίʔϧόοΫ͔Β
    async ΁ͷม׵

    View full-size slide

  37. // ίʔϧόοΫ
    func downloadData(from url: URL,
    completion: @escaping (Result) -> Void)
    // async
    func downloadData(from url: URL) async throws -> Data {
    }

    View full-size slide

  38. // ίʔϧόοΫ
    func downloadData(from url: URL,
    completion: @escaping (Result) -> Void)
    // async
    func downloadData(from url: URL) async throws -> Data {
    downloadData(from: url) { result in
    do {
    let data = try result.get()
    } catch {
    }
    }
    }

    View full-size slide

  39. Con$nua$onSE-0300
    SE-0300 h(ps:/
    /github.com/apple/swi:-evolu

    View full-size slide

  40. // ίʔϧόοΫ
    func downloadData(from url: URL,
    completion: @escaping (Result) -> Void)
    // async
    func downloadData(from url: URL) async throws -> Data {
    downloadData(from: url) { result in
    do {
    let data = try result.get()
    } catch {
    }
    }
    }

    View full-size slide

  41. // ίʔϧόοΫ
    func downloadData(from url: URL,
    completion: @escaping (Result) -> Void)
    // async
    func downloadData(from url: URL) async throws -> Data {
    try await withCheckedThrowingContinuation { continuation in
    downloadData(from: url) { result in
    do {
    let data = try result.get()
    continuation.resume(returning: data)
    } catch {
    continuation.resume(throwing: error)
    }
    }
    }
    }

    View full-size slide

  42. // ίʔϧόοΫ
    func downloadData(from url: URL,
    completion: @escaping (Result) -> Void)
    // async
    func downloadData(from url: URL) async throws -> Data {
    try await withCheckedThrowingContinuation { continuation in
    downloadData(from: url) { result in
    do {
    let data = try result.get()
    continuation.resume(returning: data)
    } catch {
    continuation.resume(throwing: error)
    }
    }
    }
    }

    View full-size slide

  43. // ίʔϧόοΫ
    func downloadData(from url: URL,
    completion: @escaping (Result) -> Void)
    // async
    func downloadData(from url: URL) async throws -> Data {
    try await withCheckedThrowingContinuation { continuation in
    downloadData(from: url) { result in
    continuation.resume(with: result)
    }
    }
    }

    View full-size slide

  44. !
    Structured Concurrency

    View full-size slide

  45. Case 7: ඇಉظॲཧͷ։࢝

    View full-size slide

  46. async ؔ਺͸ async ؔ਺ͷதͰ͔͠ݺ΂ͳ͍
    func foo() async { ... }
    func bar() async {
    await foo() //

    }

    View full-size slide

  47. async ؔ਺͸ async ؔ਺ͷதͰ͔͠ݺ΂ͳ͍
    func foo() async { ... }
    func bar() {
    await foo() //

    }

    View full-size slide

  48. async ؔ਺͸ async ؔ਺ͷதͰ͔͠ݺ΂ͳ͍
    func foo() async { ... }
    func bar() async {
    await foo()
    }
    func baz() async {
    await bar()
    }
    ...

    View full-size slide

  49. ඇಉظॲཧͷ։࢝
    func main() {
    // ???
    }

    View full-size slide

  50. ඇಉظॲཧͷ։࢝
    func main() {
    Task {
    await foo()
    }
    }

    View full-size slide

  51. ඇಉظॲཧͷ։࢝
    func main() {
    Task(operation: {
    await foo()
    })
    }

    View full-size slide

  52. ඇಉظॲཧͷ։࢝
    func main() {
    Task {
    await foo()
    }
    }
    ͢΂ͯͷ async ؔ਺͸ Task ্Ͱ࣮ߦ͞ΕΔɻ

    View full-size slide

  53. iOS ΞϓϦ։ൃͰͲͷΑ͏ʹ࢖ΘΕΔ͔
    • viewDidAppear
    • View Controller ͕දࣔ͞ΕͨΒ fetchUser Ͱ User Λऔಘ
    ͯ͠දࣔ
    • @IBAction
    • μ΢ϯϩʔυϘλϯ͕ԡ͞ΕͨΒ downloadData Ͱμ΢ϯ
    ϩʔυ
    • ...

    View full-size slide

  54. A"er
    extension UserViewController {
    override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    ɹ
    }
    }

    View full-size slide

  55. A"er
    extension UserViewController {
    override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    do {
    let user = try await fetchUser(for: userID)
    self.user = user
    } catch {
    // ΤϥʔϋϯυϦϯά
    }
    }
    }

    View full-size slide

  56. A"er
    extension UserViewController {
    override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    Task {
    do {
    let user = try await fetchUser(for: userID)
    self.user = user
    } catch {
    // ΤϥʔϋϯυϦϯά
    }
    }
    }
    }

    View full-size slide

  57. Case 9: ฒߦॲཧ
    ݻఆݸ਺ͷ৔߹

    View full-size slide

  58. ྫ: େখͷΞΠίϯΛಉ࣌ʹμ΢ϯϩʔυ

    View full-size slide

  59. A"er
    func fetchUserIcons(for id: User.ID) async throws -> (small: Data, large: Data) {
    let smallURL: URL = ...
    let largeURL: URL = ...
    return icons
    }

    View full-size slide

  60. ͜ΕͰ͸ฒߦʹͳΒͳ͍
    func fetchUserIcons(for id: User.ID) async throws -> (small: Data, large: Data) {
    let smallURL: URL = ...
    let largeURL: URL = ...
    let smallIcon = try await downloadData(from: smallURL)
    let largeIcon = try await downloadData(from: largeURL)
    let icons = (small: smallIcon, large: largeIcon)
    return icons
    }

    View full-size slide

  61. async let BindingSE-0317
    SE-0317 h*ps:/
    /github.com/apple/swi<-evolu>on/blob/main/proposals/0317-async-let.md

    View full-size slide

  62. ͜ΕͰ͸ฒߦʹͳΒͳ͍
    func fetchUserIcons(for id: User.ID) async throws -> (small: Data, large: Data) {
    let smallURL: URL = ...
    let largeURL: URL = ...
    let smallIcon = try await downloadData(from: smallURL)
    let largeIcon = try await downloadData(from: largeURL)
    let icons = (small: smallIcon, large: largeIcon)
    return icons
    }

    View full-size slide

  63. A"er
    func fetchUserIcons(for id: User.ID) async throws -> (small: Data, large: Data) {
    let smallURL: URL = ...
    let largeURL: URL = ...
    async let smallIcon = downloadData(from: smallURL)
    async let largeIcon = downloadData(from: largeURL)
    let icons = try await (small: smallIcon, large: largeIcon)
    return icons
    }

    View full-size slide

  64. A"er
    func fetchUserIcons(for id: User.ID) async throws -> (small: Data, large: Data) {
    let smallURL: URL = ...
    let largeURL: URL = ...
    async let smallIcon = downloadData(from: smallURL)
    async let largeIcon = downloadData(from: largeURL)
    let icons = try await (small: smallIcon, large: largeIcon)
    return icons
    }
    async let ͸ඞͣείʔϓ಺Ͱ await ͞Εͳ͚Ε͹ͳΒͳ͍ɻ

    View full-size slide

  65. Structured ConcurrencySE-0304
    SE-0304 h)ps:/
    /github.com/apple/swi;-evolu=on/blob/main/proposals/0304-structured-concurrency.md

    View full-size slide

  66. Structured Programming
    ʢߏ଄Խϓϩάϥϛϯάʣ

    View full-size slide

  67. ߏ଄Խϓϩάϥϛϯά
    for value in values {
    if value.isValid {
    ...
    } else {
    ...
    }
    }

    View full-size slide

  68. A"er
    func fetchUserIcons(for id: User.ID) async throws -> (small: Data, large: Data) {
    let smallURL: URL = ...
    let largeURL: URL = ...
    async let smallIcon = downloadData(from: smallURL)
    async let largeIcon = downloadData(from: largeURL)
    let icons = try await (small: smallIcon, large: largeIcon)
    return icons
    }
    async let ͸ඞͣείʔϓ಺Ͱ await ͞Εͳ͚Ε͹ͳΒͳ͍ɻ

    View full-size slide

  69. async let ͱ Task
    Task {
    async let a = foo()
    async let b = bar()
    print(await a + b)
    }

    View full-size slide

  70. Case 11: ඇಉظॲཧͷΩϟϯηϧ
    ඇಉظ API ͷར༻ଆ

    View full-size slide

  71. Before
    func downloadData(
    from url: URL,
    cancellation: @escaping () -> Void,
    completion: @escaping (Result) -> Void
    ) -> DownloadCanceller

    View full-size slide

  72. Before
    extension ViewController {
    @IBAction func downloadButtonPressed(_ sender: UIButton) {
    ɹ
    ɹ
    ɹ
    }
    }

    View full-size slide

  73. Before
    extension ViewController {
    @IBAction func downloadButtonPressed(_ sender: UIButton) {
    canceller = downloadData(from: url, cancellation: {
    // Ωϟϯηϧ࣌ͷॲཧ
    }, completion: { data in
    do {
    let data = try data.get()
    // data Λ࢖͏ॲཧ
    } catch {
    // ΤϥʔϋϯυϦϯά
    }
    })
    }
    }

    View full-size slide

  74. Before
    extension ViewController {
    @IBAction func cancelButtonPressed(_ sender: UIButton) {
    canceller?.cancel()
    canceller = nil
    }
    }

    View full-size slide

  75. A"er
    func downloadData(from url: URL) async throws -> Data

    View full-size slide

  76. A"er
    extension ViewController {
    @IBAction func downloadButtonPressed(_ sender: UIButton) {
    Task {
    }
    }
    }

    View full-size slide

  77. A"er
    extension ViewController {
    @IBAction func downloadButtonPressed(_ sender: UIButton) {
    task = Task {
    }
    }
    }

    View full-size slide

  78. A"er
    extension ViewController {
    @IBAction func cancelButtonPressed(_ sender: UIButton) {
    task?.cancel()
    task = nil
    }
    }

    View full-size slide

  79. A"er
    extension ViewController {
    @IBAction func downloadButtonPressed(_ sender: UIButton) {
    task = Task {
    do {
    let data = try await downloadData(from: url)
    // data Λ࢖͏ॲཧ
    } catch {
    if Task.isCancelled {
    // Ωϟϯηϧ࣌ͷॲཧ
    } else {
    // ΤϥʔϋϯυϦϯά
    }
    }
    }
    }
    }

    View full-size slide

  80. Case 14: ڞ༗͞Εͨঢ়ଶͷมߋ

    View full-size slide

  81. ڞ༗͞Εͨঢ়ଶΛಉ࣌ʹಡΈॻ͖

    σʔλڝ߹

    View full-size slide

  82. σʔλڝ߹ͷྫ
    final class Counter {
    private var count: Int = 0
    func increment() -> Int {
    count += 1
    return count
    }
    }

    View full-size slide

  83. σʔλڝ߹ͷྫ
    let counter: Counter = .init()
    DispatchQueue.global().async {
    print(counter.increment()) // ?
    }
    DispatchQueue.global().async {
    print(counter.increment()) // ?
    }

    View full-size slide

  84. σʔλڝ߹ͷྫ
    let counter: Counter = .init()
    DispatchQueue.global().async {
    print(counter.increment()) // 1
    }
    DispatchQueue.global().async {
    print(counter.increment()) // 2
    }

    View full-size slide

  85. σʔλڝ߹ͷྫ
    let counter: Counter = .init()
    DispatchQueue.global().async {
    print(counter.increment()) // 2
    }
    DispatchQueue.global().async {
    print(counter.increment()) // 1
    }

    View full-size slide

  86. σʔλڝ߹ͷྫ
    let counter: Counter = .init()
    DispatchQueue.global().async {
    print(counter.increment()) // 2
    }
    DispatchQueue.global().async {
    print(counter.increment()) // 2
    }

    View full-size slide

  87. σʔλڝ߹ͷྫ
    final class Counter {
    private var count: Int = 0
    func increment() -> Int {
    count += 1
    return count
    }
    }

    View full-size slide

  88. σʔλڝ߹Λ๷͙खஈ
    • ϩοΫ
    • γϦΞϧΩϡʔ

    View full-size slide

  89. Before
    final class Counter {
    private let queue: DispatchQueue
    = .init(label: UUID().uuidString)
    private var count: Int = 0
    func increment(completion: @escaping (Int) -> Void) {
    queue.async { [self] in
    count += 1
    completion(count)
    }
    }
    }

    View full-size slide

  90. Before
    let counter: Counter = .init()
    counter.increment { count in
    print(count) // 1 or 2
    }
    counter.increment { count in
    print(count) // 2 or 1
    }

    View full-size slide

  91. actorSE-0306 Ͱղܾ
    SE-0306 h)ps:/
    /github.com/apple/swi;-evolu=on/blob/main/proposals/0306-actors.md

    View full-size slide

  92. A"er
    actor Counter {
    private var count: Int = 0
    func increment() -> Int {
    count += 1
    return count
    }
    }

    View full-size slide

  93. σʔλڝ߹Λى͜͢ Counter
    final class Counter {
    private var count: Int = 0
    func increment() -> Int {
    count += 1
    return count
    }
    }

    View full-size slide

  94. A"er
    actor Counter {
    private var count: Int = 0
    func increment() -> Int {
    count += 1
    return count
    }
    }

    View full-size slide

  95. A"er
    actor Counter {
    private var count: Int = 0
    func increment() async -> Int
    }

    View full-size slide

  96. A"er
    let counter: Counter = .init()
    Task.detached {
    print(await counter.increment()) // 1 or 2
    }
    Task.detached {
    print(await counter.increment()) // 2 or 1
    }

    View full-size slide

  97. Case 15: ڞ༗͞Εͨঢ়ଶͷมߋ
    Πϯελϯε಺Ͱͷϝιουݺͼग़͠

    View full-size slide

  98. Counter ͷத͔Β Counter ͷϝιουΛ࢖͏
    final class Counter {
    ...
    func increment(completion: @escaping (Int) -> Void)
    func incrementTwice(completion: @escaping (Int) -> Void) {
    }
    }

    View full-size slide

  99. Counter ͷத͔Β Counter ͷϝιουΛ࢖͏
    final class Counter {
    ...
    func increment(completion: @escaping (Int) -> Void)
    func incrementTwice(completion: @escaping (Int) -> Void) {
    increment { [self] _ in
    increment { count in
    completion(count)
    }
    }
    }
    }

    View full-size slide

  100. ڝ߹ঢ়ଶͷྫ
    let counter: Counter = .init()
    counter.incrementTwice { count in
    print(count) // ?
    }
    counter.incrementTwice { count in
    print(count) // ?
    }

    View full-size slide

  101. ڝ߹ঢ়ଶͷྫ
    let counter: Counter = .init()
    counter.incrementTwice { count in
    print(count) // 2
    }
    counter.incrementTwice { count in
    print(count) // 4
    }

    View full-size slide

  102. ڝ߹ঢ়ଶͷྫ
    let counter: Counter = .init()
    counter.incrementTwice { count in
    print(count) // 3
    }
    counter.incrementTwice { count in
    print(count) // 4
    }

    View full-size slide

  103. ڝ߹ঢ়ଶͷྫ
    final class Counter {
    ...
    func increment(completion: @escaping (Int) -> Void)
    func incrementTwice(completion: @escaping (Int) -> Void) {
    increment { [self] _ in
    increment { count in
    completion(count)
    }
    }
    }
    }

    View full-size slide

  104. Before
    final class Counter {
    ...
    private func _increment() -> Int {
    count += 1
    return count
    }
    func increment(completion: @escaping (Int) -> Void) {
    queue.async { [self] in
    _increment()
    completion(count)
    }
    }
    ...
    }

    View full-size slide

  105. Before
    final class Counter {
    ...
    func incrementTwice(completion: @escaping (Int) -> Void) {
    queue.async { [self] in
    _increment()
    _increment()
    completion(count)
    }
    }
    }

    View full-size slide

  106. A"er
    actor Counter {
    ...
    @discardableResult
    func increment() -> Int {
    count += 1
    return count
    }
    func incrementTwice() -> Int {
    increment()
    increment()
    return count
    }
    }

    View full-size slide

  107. Case 16: ڞ༗͞Εͨঢ়ଶͷมߋ
    ge#er

    View full-size slide

  108. Case 14 (A*er)
    actor Counter {
    private var count: Int = 0
    @discardableResult
    func increment() -> Int {
    count += 1
    return count
    }
    }

    View full-size slide

  109. A"er
    actor Counter {
    var count: Int = 0
    @discardableResult
    func increment() -> Int {
    count += 1
    return count
    }
    }

    View full-size slide

  110. Effec%ul Read-only ϓϩύςΟSE-0310
    class Foo {
    var value : Int {
    get {
    ...
    }
    }
    }
    SE-0310 h)ps:/
    /github.com/apple/swi;-evolu=on/blob/main/proposals/0310-effecAul-readonly-proper=es.md

    View full-size slide

  111. Effec%ul Read-only ϓϩύςΟSE-0310
    class Foo {
    var value : Int {
    get async throws {
    ...
    }
    }
    }
    SE-0310 h)ps:/
    /github.com/apple/swi;-evolu=on/blob/main/proposals/0310-effecAul-readonly-proper=es.md

    View full-size slide

  112. A"er
    actor Counter {
    var count: Int {
    get async { ... }
    }
    ...
    }

    View full-size slide

  113. A"er
    print(await counter.count)

    View full-size slide

  114. Case 18: ڞ༗͞Εͨঢ়ଶͷมߋ
    ඇಉظॲཧ݁Ռͷ൓ө

    View full-size slide

  115. ObservableObject ͷಋೖ
    final class UserViewController: UIViewController {
    private let state: UserViewState
    ...
    override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    state.loadUser()
    }
    }

    View full-size slide

  116. ObservableObject ͷಋೖ
    final class UserViewController: UIViewController {
    ...
    override func viewDidLoad() {
    ...
    state
    .objectWillChange
    .receive(on: DispatchQueue.main)
    .sink { [self] _ in
    // state Λ View ʹ൓ө͢Δॲཧ
    }
    .store(in: &cancellables)
    }
    ...
    }

    View full-size slide

  117. A"er
    actor UserViewState: ObservableObject {
    let userID: User.ID
    @Published var user: User?
    ...
    func loadUser() async {
    do {
    user = try await fetchUser(for: userID)
    } catch {
    // ΤϥʔϋϯυϦϯά
    }
    }
    }

    View full-size slide

  118. A"er
    final class UserViewController: UIViewController {
    ...
    override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    state.loadUser() //
    }
    }

    View full-size slide

  119. A"er
    final class UserViewController: UIViewController {
    ...
    override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    Task {
    await state.loadUser() //
    }
    }
    }

    View full-size slide

  120. A"er
    state
    .objectWillChange
    .receive(on: DispatchQueue.main)
    .sink { [self] _ in
    // state Λ View ʹ൓ө͢Δॲཧ
    }
    .store(in: &cancellables)

    View full-size slide

  121. A"er
    state
    .objectWillChange
    .receive(on: DispatchQueue.main)
    .sink { [self] _ in
    // state Λ View ʹ൓ө͢Δॲཧ
    let user = await state.user
    nameLabel.text = user?.name
    }
    .store(in: &cancellables)

    View full-size slide

  122. A"er
    state
    .objectWillChange
    .receive(on: DispatchQueue.main)
    .sink { [self] _ in
    // state Λ View ʹ൓ө͢Δॲཧ
    Task {
    let user = await state.user
    nameLabel.text = user?.name
    }
    }
    .store(in: &cancellables)

    View full-size slide

  123. Case 19: ΞΫλʔڥքΛӽ͑Δ

    View full-size slide

  124. let user = await state.user

    View full-size slide

  125. struct User {
    let id: ID
    var name: String
    var age: Int
    ...
    }

    View full-size slide

  126. final class User {
    let id: ID
    var name: String
    var age: Int
    ...
    }

    View full-size slide

  127. let user = await state.user //
    user?.age += 1 // actor ͷ֎͔Βมߋ

    View full-size slide

  128. final class User {
    let id: ID
    let name: String
    let age: Int
    ...
    }

    View full-size slide

  129. let user = await state.user //
    user?.age += 1 //

    View full-size slide

  130. Sendable ϓϩτίϧSE-0302
    SE-0302 h)ps:/
    /github.com/apple/swi;-evolu=on/blob/main/proposals/0302-concurrent-value-and-concurrent-
    closures.md

    View full-size slide

  131. struct User {
    let id: ID
    var name: String
    var age: Int
    ...
    }

    View full-size slide

  132. struct User: Sendable {
    let id: ID
    var name: String
    var age: Int
    ...
    }

    View full-size slide

  133. final class User: Sendable {
    let id: ID
    var name: String
    var age: Int
    ...
    }

    var ϓϩύςΟΛ࣋ͭͷͰίϯύΠϧΤϥʔ

    View full-size slide

  134. final class User: Sendable {
    let id: ID
    let name: String
    let age: Int
    ...
    }

    ΠϛϡʔλϒϧͳͷͰ OK

    View full-size slide

  135. final class User: Sendable {
    let id: ID
    let name: NSString
    let age: Int
    ...
    }

    Sendable Ͱͳ͍ܕͷϓϩύςΟΛ࣋ͭͷͰίϯύΠϧΤϥʔ

    View full-size slide

  136. final class User: Sendable {
    let id: ID
    let firstName: String
    let familyName: String
    let age: Int
    var name: String {
    "\(firstName) \(familyName)"
    }
    ...
    }

    Computed Property Λ͍࣋ͬͯͯ΋ΠϛϡʔλϒϧͳͷͰ OK

    View full-size slide

  137. final class User: Sendable {
    let id: ID
    let firstName: String
    let familyName: String
    let age: Int
    private var _name: String? //
    var name: String {
    if _name == nil {
    _name = "\(firstName) \(familyName)"
    }
    return _name!
    }
    ...
    }

    View full-size slide

  138. final class User: @unchecked Sendable {
    let id: ID
    let firstName: String
    let familyName: String
    let age: Int
    private var _name: String? //
    var name: String {
    if _name == nil {
    _name = "\(firstName) \(familyName)"
    }
    return _name!
    }
    ...
    }

    View full-size slide

  139. ϓϩτίϧʹద߹ͤ͞ΒΕͳ͍৔߹͸ʁ

    View full-size slide

  140. actor Foo {
    func bar(_ f: (String) -> Int) { //
    // ...
    }
    }

    View full-size slide

  141. actor Foo {
    func bar(_ f: @Sendable (String) -> Int) { //
    // ...
    }
    }
    Ϋϩʔδϟ͸ϓϩτίϧʹద߹Ͱ͖ͳ͍ͷͰ @Sendable Λ࢖͏

    View full-size slide

  142. Case 20: ڞ༗͞Εͨঢ়ଶͷมߋ
    ϝΠϯεϨου্Ͱͷॲཧ

    View full-size slide

  143. UserViewState ͷΩϡʔ͕
    DispatchQueue.main ͩͱ͏Ε͍͠

    View full-size slide

  144. UserViewState ͷΩϡʔ͕ DispatchQueue.main
    final class UserViewState: ObservableObject {
    private let queue: DispatchQueue = .main
    ...
    }

    View full-size slide

  145. Case 18 (A*er)
    // queue ͸Πϯελϯε͝ͱʹࣗಈͰ࡞ΒΕΔ
    actor UserViewState: ObservableObject {
    ...
    }

    View full-size slide

  146. Global ActorSE-0316 Ͱܕ΍
    ΠϯελϯεΛӽ͑ͨ Actor Λ࡞Δ
    SE-0316 h*ps:/
    /github.com/apple/swi<-evolu>on/blob/main/proposals/0316-global-actors.md

    View full-size slide

  147. A"er
    @MainActorɹ
    final class UserViewState: ObservableObject {
    ...
    }

    View full-size slide

  148. UIViewController h/ps:/
    /developer.apple.com/documenta;on/uikit/uiviewcontroller

    View full-size slide

  149. Case 18 (A*er)
    override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    Task {
    await state.loadUser()
    }
    }

    View full-size slide

  150. A"er
    override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    state.loadUser()
    }

    View full-size slide

  151. Case 18 (A*er)
    state
    .objectWillChange
    .receive(on: DispatchQueue.main)
    .sink { [self] _ in
    // state Λ View ʹ൓ө͢Δॲཧ
    Task {
    let user = await state.user
    nameLabel.text = user?.name
    }
    }
    .store(in: &cancellables)

    View full-size slide

  152. A"er
    state
    .objectWillChange
    .receive(on: DispatchQueue.main)
    .sink { [self] _ in
    // state Λ View ʹ൓ө͢Δॲཧ
    nameLabel.text = state.user?.name
    }
    .store(in: &cancellables)

    View full-size slide

  153. @MainActor ͸ @StateObject ʹ΋ඞਢ
    struct UserView: View {
    @StateObject private var state: UserViewState
    ...
    var body: some View {
    VStack {
    if let user = state.user {
    Text(user.name)
    }
    ...
    }
    .onAppear { state.loadUser() }
    }
    }

    View full-size slide

  154. ʲ࿕ใʳSwi$ Concurrency ͸ iOS 13 Ҏ߱Ͱ
    ͋Ε͹ར༻Մೳʂ#39051
    #39051 h)ps:/
    /github.com/apple/swi;/pull/39051

    View full-size slide

  155. ·ͱΊ
    • Concurrency ͰίʔυΛॻ͘ͷָ͕ʹͳΔ
    • ͔͠΋ɺΑΓ҆શʹʂ
    • ࠓ݄͔Β࢖͑Δʂʂʂ

    View full-size slide

  156. h"ps:/
    /zenn.dev/koher/ar1cles/swi6-
    concurrency-cheatsheet

    View full-size slide