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

Combine・sink(RxSwift・subscribe)では Task を作らないようにして async なメソッドを呼ぶ / Do not create a Task inside a Combine/sink (RxSwift/subscribe) closure when calling an async method there

Combine・sink(RxSwift・subscribe)では Task を作らないようにして async なメソッドを呼ぶ / Do not create a Task inside a Combine/sink (RxSwift/subscribe) closure when calling an async method there

Combine・sink(RxSwift・subscribe)では Task を作らないようにして async なメソッドを呼ぶ / Do not create a Task inside a Combine/sink (RxSwift/subscribe) closure when calling an async method there

公開 SwiftWednesday【iOSDC Japan 2023 直前】
2023/08/24 19:30〜
https://dena.connpass.com/event/291447/

登壇ノートはこちら: 準備中

treastrain / Tanaka Ryoga

August 24, 2023
Tweet

More Decks by treastrain / Tanaka Ryoga

Other Decks in Technology

Transcript

  1. Copyright © 2023 treastrain / Tanaka RyogaɹAll rights reserved.
    USFBTUSBJO5BOBLB3ZPHB%F/"$P -UE
    $PNCJOFɾsinkʢ3Y4XJGUɾsubscribeʣͰ͸

    TaskΛ࡞Βͳ͍Α͏ʹͯ͠

    asyncͳϝιουΛݺͿ
    4XJGU8FEOFTEBZ
    1
    ެ։4XJGU8FEOFTEBZʲJ04%$+BQBO௚લʳ"VHVTU

    View full-size slide

  2. Copyright © 2023 treastrain / Tanaka RyogaɹAll rights reserved.
    ඇಉظॲཧͷҠߦ
    $PNCJOFɾ3Y4XJGUˠ4XJGU$PODVSSFODZ
    w ͭͷΠϕϯτ
    w $PNCJOFFutureʢDeferredFutureʣ
    w 3Y4XJGUSingleɾMaybeɾCompletable
    w ෳ਺ͷΠϕϯτ
    w $PNCJOFPublisher
    w 3Y4XJGURxSwift.Observable
    2

    View full-size slide

  3. Copyright © 2023 treastrain / Tanaka RyogaɹAll rights reserved.
    ݺΜͰ͍ͨϝιου͕asyncʹͳͬͨ
    4XJGU$PODVSSFODZ΁ͷҠߦ
    3
    import Combine


    import RxSwift


    import UIKit


    protocol PresenterProtocol {


    func handleEventA()


    func handleEventB()


    }


    final class ViewController: UIViewController {


    let presenter: some PresenterProtocol = ...




    // Combine


    let publisher: some Publisher = ...


    var cancellables: [AnyCancellable] = []




    // RxSwift


    let observable: RxSwift.Observable = ...


    let disposeBag = DisposeBag()




    override func viewDidLoad() {


    super.viewDidLoad()




    connect()


    }


    }


    extension ViewController {


    func connect() {


    publisher


    .sink(


    receiveValue: { [weak self] output in


    self?.presenter.handleEventA()


    }


    )


    .store(in: &cancellables)




    observable


    .subscribe(


    onNext: { [weak self] output in


    self?.presenter.handleEventB()


    }


    )


    .disposed(by: disposeBag)


    }


    }
    async


    async

    View full-size slide

  4. Copyright © 2023 treastrain / Tanaka RyogaɹAll rights reserved.
    ݺΜͰ͍ͨϝιου͕asyncʹͳͬͨ
    4XJGU$PODVSSFODZ΁ͷҠߦ
    4
    import Combine


    import RxSwift


    import UIKit


    protocol PresenterProtocol {


    func handleEventA() async


    func handleEventB() async


    }


    final class ViewController: UIViewController {


    let presenter: some PresenterProtocol = ...




    // Combine


    let publisher: some Publisher = ...


    var cancellables: [AnyCancellable] = []




    // RxSwift


    let observable: RxSwift.Observable = ...


    let disposeBag = DisposeBag()




    override func viewDidLoad() {


    super.viewDidLoad()




    connect()


    }


    }


    extension ViewController {


    func connect() {


    publisher


    .sink(


    receiveValue: { [weak self] output in


    self?.presenter.handleEventA()


    }


    )


    .store(in: &cancellables)




    observable


    .subscribe(


    onNext: { [weak self] output in


    self?.presenter.handleEventB()


    }


    )


    .disposed(by: disposeBag)


    }


    }
    'async' call in a function that does not support concurrency

    View full-size slide

  5. Copyright © 2023 treastrain / Tanaka RyogaɹAll rights reserved.
    ҟͳΔΞΫλʔͷϝιουΛݺͿ͜ͱʹͳͬͨ
    4XJGU$PODVSSFODZ΁ͷҠߦ
    5
    import Combine


    import RxSwift


    import UIKit


    protocol ViewProtocol: AnyObject {


    func handleEventA()


    func handleEventB()


    }


    final class ViewController: UIViewController {


    @ViewLoading var object: ViewObject




    override func viewDidLoad() {


    super.viewDidLoad()




    object = .init(view: self)


    }


    }


    extension ViewController: ViewProtocol { ... }
    final class ViewObject {


    unowned let view: View


    ...




    func connect() {


    publisher


    .sink(


    receiveValue: { [weak self] output in


    self?.view.handleEventA()


    }


    )


    .store(in: &cancellables)




    observable


    .subscribe(


    onNext: { [weak self] output in


    self?.view.handleEventB()


    }


    )


    .disposed(by: disposeBag)


    }


    }
    @MainActor

    View full-size slide

  6. Copyright © 2023 treastrain / Tanaka RyogaɹAll rights reserved.
    ҟͳΔΞΫλʔͷϝιουΛݺͿ͜ͱʹͳͬͨ
    4XJGU$PODVSSFODZ΁ͷҠߦ
    6
    import Combine


    import RxSwift


    import UIKit


    @MainActor


    protocol ViewProtocol: AnyObject {


    func handleEventA()


    func handleEventB()


    }


    final class ViewController: UIViewController {


    @ViewLoading var object: ViewObject




    override func viewDidLoad() {


    super.viewDidLoad()




    object = .init(view: self)


    }


    }


    extension ViewController: ViewProtocol { ... }
    final class ViewObject {


    unowned let view: View


    ...




    func connect() {


    publisher


    .sink(


    receiveValue: { [weak self] output in


    self?.view.handleEventA()


    }


    )


    .store(in: &cancellables)




    observable


    .subscribe(


    onNext: { [weak self] output in


    self?.view.handleEventB()


    }


    )


    .disposed(by: disposeBag)


    }


    }
    Call to main actor-isolated instance method * in a synchronous
    nonisolated context

    View full-size slide

  7. Copyright © 2023 treastrain / Tanaka RyogaɹAll rights reserved.
    4XJGU$PODVSSFODZ΁ͷҠߦ
    ίϯύΠϧ࣌ͷΤϥʔΛղফ͢Δʹ͸
    w Πϕϯτड৴࣌ʹasyncͳ

    ϝιουΛ࣮ߦͨ͘͠ͳͬͨ
    㾎TaskΛ࡞ͬͯݺͿ
    w Πϕϯτड৴࣌ʹ

    ҟͳΔΞΫλʔʹ෼཭͞Ε͍ͯΔ

    ϝιουΛ࣮ߦͨ͘͠ͳͬͨ
    㾎TaskΛ࡞ͬͯݺͿ
    㾎ಉ͡ΞΫλʔͰ࣮ߦ͢ΔΑ͏ʹ
    ݺͿ
    7

    View full-size slide

  8. Copyright © 2023 treastrain / Tanaka RyogaɹAll rights reserved.
    Πϕϯτड৴৔ॴͰTaskΛ࡞ͬͯawait
    asyncͳϝιουΛݺͿͱ͖
    8
    extension ViewController {


    func connect() {


    publisher


    .sink(


    receiveValue: { [weak self] output in




    self?.presenter.handleEventA()




    }


    )


    .store(in: &cancellables)




    observable


    .subscribe(


    onNext: { [weak self] output in




    self?.presenter.handleEventB()




    }


    )


    .disposed(by: disposeBag)


    }


    }


    extension ViewController {


    func connect() {


    publisher


    .sink(


    receiveValue: { [weak self] output in


    Task {


    await self?.presenter.handleEventA()


    }


    }


    )


    .store(in: &cancellables)




    observable


    .subscribe(


    onNext: { [weak self] output in


    Task {


    await self?.presenter.handleEventB()


    }


    }


    )


    .disposed(by: disposeBag)


    }


    }


    View full-size slide

  9. Copyright © 2023 treastrain / Tanaka RyogaɹAll rights reserved.
    Πϕϯτड৴৔ॴͰTaskΛ࡞ͬͯawait
    asyncͳϝιουΛݺͿͱ͖
    w self͸͜ͷ··Ͱ͍͍ͷ͔ʜʜʁ
    w ࣮ߦ͞ΕͨTaskͷΩϟϯηϧΛ
    Ͳ͏͠Α͏ʜʜ
    w $PNCJOFɾ3Y4XJGUͷ

    4DIFEVMFSͷࢦఆ͸ʜʜʁ
    w ωετ͕ਂ͍ʜʜ
    9
    extension ViewController {


    func connect() {


    publisher


    .sink(


    receiveValue: { [weak self] output in


    Task {


    await self?.presenter.handleEventA()


    }


    }


    )


    .store(in: &cancellables)




    observable


    .subscribe(


    onNext: { [weak self] output in


    Task {


    await self?.presenter.handleEventB()


    }


    }


    )


    .disposed(by: disposeBag)


    }


    }


    View full-size slide

  10. Copyright © 2023 treastrain / Tanaka RyogaɹAll rights reserved.
    ΠϕϯτΛ

    AsyncSequenceͰड৴͢Δ
    10

    View full-size slide

  11. Copyright © 2023 treastrain / Tanaka RyogaɹAll rights reserved.
    AsyncSequence
    ͓͞Β͍
    w Sequenceͷඇಉظ൛
    w for-inϧʔϓΛॻ͚Δ
    11
    let numbers: some Sequence = [0, 1, 2, 3]


    for number in numbers {


    print(number, terminator: " ")


    }


    // "0 1 2 3 "


    var iterator = [0, 1, 2, 3].makeIterator()


    let counter: some AsyncSequence = AsyncStream {


    try? await Task.sleep(for: .seconds(1))


    return iterator.next()


    }


    for try await count in counter {


    print(count, terminator: " ")


    }


    // "0 1 2 3 "


    View full-size slide

  12. Copyright © 2023 treastrain / Tanaka RyogaɹAll rights reserved.
    AsyncSequence
    ͓͞Β͍
    w Sequenceͷඇಉظ൛
    w for-inϧʔϓΛॻ͚Δ
    w for-await-inϧʔϓΛॻ͚Δ
    w ࣦഊ͢ΔՄೳੑ͕͋ΔͳΒ

    for-try-await-inϧʔϓ
    12
    let numbers: some Sequence = [0, 1, 2, 3]


    for number in numbers {


    print(number, terminator: " ")


    }


    // "0 1 2 3 "


    var iterator = [0, 1, 2, 3].makeIterator()


    let counter: some AsyncSequence = AsyncStream {


    try? await Task.sleep(for: .seconds(1))


    return iterator.next()


    }


    for try await count in counter {


    print(count, terminator: " ")


    }


    // "0 1 2 3 "


    View full-size slide

  13. Copyright © 2023 treastrain / Tanaka RyogaɹAll rights reserved.
    ༻ҙ͞Ε͍ͯΔΠϯελϯεϓϩύςΟ
    ͍ͣΕ΋AsyncSequenceʹద߹͍ͯ͠Δ
    w $PNCJOF w 3Y4XJGUʢ3Y4XJGUʣ
    13
    extension Publisher where Self.Failure == Never {


    @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)


    public var values: AsyncPublisher { get }


    }


    extension Publisher {


    @available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)


    public var values: AsyncThrowingPublisher { get }


    }


    #if swift(>=5.5.2) && canImport(_Concurrency)


    import Foundation


    @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)


    public extension ObservableConvertibleType {


    var values: AsyncThrowingStream { get }


    }


    #endif


    #if swift(>=5.5.2) && canImport(_Concurrency) && !os(Linux)


    @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)


    public extension InfallibleType {


    var values: AsyncStream { get }


    }


    #endif


    View full-size slide

  14. Copyright © 2023 treastrain / Tanaka RyogaɹAll rights reserved.
    valuesΛ࢖ͬͯfor-await-inϧʔϓͰड৴
    await͕࢖͑ͯίʔυ͕୹͘ͳΓωετ΋ઙ͘ͳͬͨ
    14
    extension ViewController {


    func connect() {


    publisher


    .sink(


    receiveValue: { [weak self] output in


    Task {


    await self?.presenter.handleEventA()


    }


    }


    )


    .store(in: &cancellables)




    observable


    .subscribe(


    onNext: { [weak self] output in


    Task {


    await self?.presenter.handleEventB()


    }


    }


    )


    .disposed(by: disposeBag)


    }


    }


    extension ViewController {


    func connect() {


    Task {


    for await output in publisher.values {


    await presenter.handleEventA()


    }


    }




    Task {


    for try await output in observable.values {


    await presenter.handleEventB()


    }


    }


    }


    }

    View full-size slide

  15. Copyright © 2023 treastrain / Tanaka RyogaɹAll rights reserved.
    valuesΛ࢖ͬͯfor-await-inϧʔϓͰड৴
    await͕࢖͑ͯίʔυ͕୹͘ͳΓωετ΋ઙ͘ͳͬͨ
    15
    extension ViewController {


    func connect() {


    publisher


    .sink(


    receiveValue: { [weak self] output in


    Task {


    await self?.presenter.handleEventA()


    }


    }


    )


    .store(in: &cancellables)




    observable


    .subscribe(


    onNext: { [weak self] output in


    Task {


    await self?.presenter.handleEventB()


    }


    }


    )


    .disposed(by: disposeBag)


    }


    }


    extension ViewController {


    func connect() {


    Task {


    for await output in publisher.values {


    await presenter.handleEventA()


    }


    }




    Task {


    for try await output in observable.values {


    await presenter.handleEventB()


    }


    }


    }


    }
    🎉

    View full-size slide

  16. Copyright © 2023 treastrain / Tanaka RyogaɹAll rights reserved.
    ࣮ࡍʹಈ͔ͯ͠ΈΔ
    for-await-inϧʔϓͰड৴
    16
    import Combine


    import UIKit


    final class ViewController: UIViewController {


    deinit {


    print(self, "is deinited 🎉")


    }




    override func viewDidLoad() {


    // ...


    connect()


    }




    let publisher: some Publisher = ...




    func connect() {


    Task {


    for await output in publisher.values {


    // ...


    }


    }


    }


    }


    EFJOJU͕ݺ͹Εͳ͍ʜʜ
    GPSBXBJUJOϧʔϓΛ

    ऴྃͤ͞Δज़͕ͳ͍

    View full-size slide

  17. Copyright © 2023 treastrain / Tanaka RyogaɹAll rights reserved.
    handleEventsɾreceiveCancel͕ݺ͹Εͳ͍
    3Y4XJGUͳΒdoɾonDispose
    17
    import Combine


    import UIKit


    final class ViewController: UIViewController {


    // ...


    func connect() {


    Task {


    let values = publisher


    .handleEvents(


    receiveCancel: {


    print("receiveCancel")


    }


    )


    .values




    for await output in values {


    // ...


    }


    }


    }


    // ...


    }


    ݺ͹Εͳ͍ʜʜ
    Ωϟϯηϧ͞Εͣʹ଴͍ͬͯΔ

    View full-size slide

  18. Copyright © 2023 treastrain / Tanaka RyogaɹAll rights reserved.
    handleEventsɾreceiveCancel͕ݺ͹Εͳ͍
    3Y4XJGUͳΒdoɾonDispose
    18
    import Combine


    import UIKit


    final class ViewController: UIViewController {


    // ...


    func connect() {


    Task {


    let values = publisher


    .handleEvents(


    receiveCancel: {


    print("receiveCancel")


    }


    )


    .values




    for await output in values {


    // ...


    }


    }


    }


    // ...


    }


    import Combine


    import UIKit


    final class ViewController: UIViewController {


    // ...


    private var cancellables: [AnyCancellable] = []




    func connect() {


    publisher


    .handleEvents(


    receiveCancel: {


    print("receiveCancel")


    }


    )


    .sink(


    receiveValue: { [weak self] output in


    // ...


    }


    )


    .store(in: &cancellables)


    }


    // ...


    }


    ݺ͹Εͳ͍ʜʜ
    Ωϟϯηϧ͞Εͣʹ଴͍ͬͯΔ
    ݺ͹ΕΔʂ

    View full-size slide

  19. Copyright © 2023 treastrain / Tanaka RyogaɹAll rights reserved.
    for-await-inͷ͋ΔTaskʹ

    ΩϟϯηϧΛ఻͑Δ
    19

    View full-size slide

  20. Copyright © 2023 treastrain / Tanaka RyogaɹAll rights reserved.
    for-await-inͷ͋ΔTaskʹΩϟϯηϧΛ఻͑Δ
    ͞·͟·ͳํ๏͕͋Δ
    w TaskΛ$PNCJOFͷ
    Cancellableʹద߹ͤ͞ɺ
    $PNCJOFͷPublisherͱ

    ಉ͡Α͏ʹ

    store(in: &cancellables)
    w ΦϦδφϧͰCancelBagΛ࡞Δ

    ʢDisposeBagͷΑ͏ͳʣ
    w TaskΛอ͓͖࣋ͯ͠ɺ

    deinitͳͲͷλΠϛϯάͰ
    Task.cancel()ΛࣗΒݺͿ
    w ෳ਺͋Δ৔߹͸TaskGroupͰ
    ·ͱΊͯ΋Α͍



    ɹɹɹɹɹɹɹɹɹFUD
    20

    View full-size slide

  21. Copyright © 2023 treastrain / Tanaka RyogaɹAll rights reserved.
    TaskΛ$PNCJOFͷCancellableʹ͢Δ
    ݁ہ$PNCJOF͕ཁΔ͕͓खܰ
    21
    extension ViewController {


    func connect() {


    Task {


    for await output in publisher.values {


    await presenter.handleEventA()


    }


    }


    Task {


    for try await output in observable.values {


    await presenter.handleEventB()


    }


    }


    }


    }


    import Combine


    import Foundation


    extension Task: Cancellable {}


    import Combine


    import UIKit


    final class ViewController: UIViewController {


    private let publisher: some Publisher = ...




    private var cancellables: [AnyCancellable] = []


    // ...


    func connect() {


    Task { [publisher] in


    for await output in publisher.values {


    // ...


    }


    }


    .store(in: &cancellables)


    }


    // …


    }

    View full-size slide

  22. Copyright © 2023 treastrain / Tanaka RyogaɹAll rights reserved.
    deinitͳͲͷλΠϛϯάͰTask.cancel()ΛݺͿ
    deinit͕ݺ͹ΕΔΑ͏ʹ஫ҙ͢ΔʢselfΛΩϟϓνϟ͠ͳ͍ʣ
    22
    extension ViewController {


    func connect() {


    Task {


    for await output in publisher.values {


    await presenter.handleEventA()


    }


    }


    Task {


    for try await output in observable.values {


    await presenter.handleEventB()


    }


    }


    }


    }


    final class ViewController: UIViewController {


    var task1: Task<(), Never>?


    var task2: Task<(), any Error>?




    deinit {


    task1?.cancel()


    task2?.cancel()


    }




    ...


    }


    extension ViewController {


    func connect() {


    task1 = Task { [publisher, presenter] in


    for await output in publisher.values {


    await presenter.handleEventA()


    }


    }




    task2 = Task { [publisher, presenter] in ... }


    }


    }


    View full-size slide

  23. Copyright © 2023 treastrain / Tanaka RyogaɹAll rights reserved.
    ·ͱΊ
    $PNCJOFɾ3Y4XJGUˠ4XJGU$PODVSSFODZ
    w $PNCJOFɾ3Y4XJGUͷΠϕϯτΛ

    4XJGU$PODVSSFODZʢAsyncSequenceʣͰ

    ड͚औΕΔ࢓૊Έ͕༻ҙ͞Ε͍ͯΔ
    w Ҡߦظʹศརʹ࢖͑ͦ͏
    w ΩϟϯηϧΛࣗ෼ͰߦΘͳ͍ͱղ์͞Εͣʹ࢒ͬͯ͠·͏ͷͰ஫ҙ͢Δ
    23

    View full-size slide

  24. Copyright © 2023 treastrain / Tanaka RyogaɹAll rights reserved.
    ͋Γ͕ͱ͏͍͟͝·ͨ͠ʂ
    $PNCJOFɾsinkʢ3Y4XJGUɾsubscribeʣͰ͸

    TaskΛ࡞Βͳ͍Α͏ʹͯ͠

    asyncͳϝιουΛݺͿ
    4XJGU8FEOFTEBZ
    24
    ެ։4XJGU8FEOFTEBZʲJ04%$+BQBO௚લʳ"VHVTU

    View full-size slide