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

suspend-view-controller-sample

to4iki
March 15, 2022

 suspend-view-controller-sample

iOS Tech Talk【みてね x Mirrativ】
Swift Concurrencyを利用したUIViewController表示の排他制御の実装
https://mirrativ.connpass.com/event/240385/

sample code: https://github.com/to4iki/suspend-view-controller-sample

to4iki

March 15, 2022
Tweet

More Decks by to4iki

Other Decks in Programming

Transcript

  1. 4XJGU$PODVSSFODZΛར༻
    ͨ͠6*7JFX$POUSPMMFSදࣔ
    ͷഉଞ੍ޚͷ࣮૷
    J045FDI5BMLʲΈͯͶY.JSSBUJWʳ
    !UPJLJ

    View Slide

  2. ࣗݾ঺հ
    !UPJLJ
    UPJLJ
    5PTIJLJ5BLF[BXB
    w .JSSBUJW *OD
    w α΢φʔ🧖

    View Slide

  3. ໨࣍
    w BTZODBXBJUͱ͸
    w ը໘දࣔͷ࣮૷Ͱར༻͢Δ
    w ฒߦॲཧͷΩϟϯηϧ

    View Slide

  4. 4XJGU$PODVSSFODZ
    w ݴޠߏจͱͯ͠ͷBTZODBXBJU
    w ֤ฒߦॲཧؒͷؔ܎ੑΛ੔ཧ͢ΔͨΊͷ֓೦Ͱ͋Δ
    4USVDUVSFE$PODVSSFODZ
    w ҆શͳฒߦॲཧΛهड़͢ΔͨΊͷ৽ͨͳܕͰ͋Δ"DUPS

    View Slide

  5. BTZODBXBJU
    ඇಉظॲཧɺฒߦॲཧΛಉظॲཧͱಉ͡Α͏ͳॻ͖ํͰɺ
    ؆͔ܿͭਖ਼֬ʹॻ͚ΔΑ͏ʹͨ͠ߏจ

    View Slide

  6. BTZODBXBJU
    BTZODͰඇಉظͷ݁ՌΛ໭Γ஋ͱͯ͠ఆٛ
    BXBJUͰதஅɾ࠶։͢Δ
    func fetchImage(url: URL) async throws -> UIImage? {


    let request = imageRequest(url: url)


    let (data, _) = try await URLSession.shared.data(for: request)


    guard let image = UIImage(data: data) else {


    return nil


    }


    return image


    }

    View Slide

  7. ը໘දࣔͷ࣮૷Ͱར༻͢Δ

    View Slide

  8. ͭͷτϦΨʔͰෳ਺ͷը໘ΛϞʔμϧදࣔ͢Δ৔߹
    lBMSFBEZQSFTFOUJOHXBSOJOHz͕ൃੜ
    ޙଓͷը໘දࣔ͸࣮ߦ͞Εͳ͍
    func tapButton() {


    let coverVC1 = CoverViewController(rootView: .init(item: .red))


    present(coverVC1, animated: true)


    let coverVC2 = CoverViewController(rootView: .init(item: .yellow))


    present(coverVC2, animated: true)


    }

    View Slide

  9. BTZODBXBJUରԠ
    දࣔதͷ଴ػΛࣔ͢ܕͷఆٛ
    ฒߦॲཧͷ࣮૷
    ฒߦॲཧͷݺͼग़͠

    View Slide

  10. BTZODBXBJUରԠ
    දࣔதͷ଴ػΛࣔ͢ܕͷఆٛ
    ฒߦॲཧͷ࣮૷
    ฒߦॲཧͷݺͼग़͠

    View Slide

  11. දࣔதͷ଴ػΛࣔ͢ܕͷఆٛ
    TVTQFOE͢Δը໘Λࣔ͢QSPUPDPM
    @MainActor


    protocol SuspendableViewControllerProtocol: AnyObject {


    var didDisappearHandler: (() -> Void)? { get set }


    }


    typealias SuspendableViewController


    = UIViewController & SuspendableViewControllerProtocol

    View Slide

  12. දࣔதͷ଴ػΛࣔ͢ܕͷఆٛ
    TVTQFOE͢Δը໘ͷ࣮૷
    class CoverViewController: UIHostingController,


    SuspendableViewControllerProtocol {


    var didDisappearHandler: (() -> Void)?


    override func viewDidDisappear(_ animated: Bool) {


    super.viewDidDisappear(animated)


    didDisappearHandler?()


    }


    }

    View Slide

  13. BTZODBXBJUରԠ
    දࣔதͷ଴ػΛࣔ͢ܕΛ༻ҙ
    ฒߦॲཧͷ࣮૷
    ฒߦॲཧͷݺͼग़͠

    View Slide

  14. XJUI$IFDLFE$POUJOVBUJPO
    ը໘දࣔ TVTQFOE
    dը໘ඇදࣔ SFTVNF
    ͷؒͷz଴ػzΛ
    ܧଓ DPOUJOVBUJPO
    Λར༻͠ఆٛ͢Δ
    extension UIViewController {


    func presentAsync(_ viewController: SuspendableViewController,


    animated: Bool,


    completion: (() -> Void)? = nil) async {


    present(viewController, animated: animated, completion: completion)


    await withCheckedContinuation { continuation in


    viewController.didDisappearHandler = {


    continuation.resume()


    }


    }


    }


    }

    View Slide

  15. BTZODBXBJUରԠ
    දࣔதͷ଴ػΛࣔ͢ܕΛ༻ҙ
    ฒߦॲཧͷ࣮૷
    ฒߦॲཧͷݺͼग़͠

    View Slide

  16. 5BTL
    TZODίʔυ͔ΒBTZODίʔυΛݺͼग़͢
    func tapButton() {


    Task {


    let coverVC1 = CoverViewController(rootView: .init(item: .red))


    await presentAsync(coverVC1, animated: true)


    let coverVC2 = CoverViewController(rootView: .init(item: .yellow))


    await presentAsync(coverVC2, animated: true)


    }


    }

    View Slide

  17. ฒߦॲཧͷΩϟϯηϧ

    View Slide

  18. ը໘දࣔΛΩϟϯηϧ͍ͨ͠৔߹
    ޙଓͷը໘දࣔΛΩϟϯηϧ͢Δ
    FYάϩʔόϧͳΤϥʔ͕ൃੜ࣌ʹը໘Λด͍ͨ͡

    View Slide

  19. 5BTLDBODFM

    5BTLΠϯελϯεΛอ࣋͠ɺΩϟϯηϧΛݺͼग़͢
    var task: Task?


    func tapButton() {


    DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in


    self?.task?.cancel()


    }


    self.task = Task {


    let coverVC1 = CoverViewController(rootView: .init(item: .red))


    await presentAsync(coverVC1, animated: true)


    let coverVC2 = CoverViewController(rootView: .init(item: .yellow))


    await presentAsync(coverVC2, animated: true)


    }


    }

    View Slide

  20. ڠௐతͳΩϟϯηϧ
    5BTLDBODFM
    ͸ɺ
    λεΫʹΩϟϯηϧ͞Εͨͱ͍͏ϑϥάΛཱͯΔ͚ͩͰɺ
    Ωϟϯηϧ͞Ε͔ͨͲ͏͔͸֤λεΫଆͰ֬ೝ͢Δඞཁ͕͋Δ
    IUUQTEPDTTXJGUPSHTXJGUCPPL-BOHVBHF(VJEF$PODVSSFODZIUNM

    View Slide

  21. ߏ଄Խ͞Ε͍ͯͳ͍ฒߦੑ
    w 5BTLܕΛ௚઀ѻ͏Α͏ͳ৔߹
    w ߏ଄Խ͞Ε͍ͯͳ͍ฒߦੑͰ͸ɺฒߦॲཧͷϥΠϑαΠΫϧ
    ΍ΩϟϯηϧΛ࣮૷ऀ͕؅ཧ͢Δ
    IUUQTEFWFMPQFSBQQMFDPNWJEFPTQMBZXXED

    View Slide

  22. ΩϟϯηϧͷϋϯυϦϯά
    w 5BTLJT$BODFMMFE
    w 5BTLDIFDL$BODFMMBUJPO
    w XJUI5BTL$BODFMMBUJPO)BOEMFS

    View Slide

  23. 5BTLJT$BODFMMFE
    ݱࡏͷλεΫ͕Ωϟϯηϧ͞Ε͍ͯͨͱ͖ɺ೚ҙͷॲཧΛߦ͏
    func presentAsync(_ viewController: SuspendableViewController,


    animated: Bool,


    completion: (() -> Void)? = nil) async {


    if Task.isCancelled {


    return


    }


    present(viewController, animated: animated, completion: completion)


    await withCheckedContinuation { continuation in


    viewController.didDisappearHandler = {


    continuation.resume()


    }


    }


    }

    View Slide

  24. 5BTLJT$BODFMMFE
    ݺͼग़͠ଆ
    func tapButton() {


    DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in


    self?.task?.cancel()


    }


    self.task = Task {


    let coverVC1 = CoverViewController(rootView: .init(item: .red))


    await presentAsync(coverVC1, animated: true)


    let coverVC2 = CoverViewController(rootView: .init(item: .yellow))


    await presentAsync(coverVC2, animated: true)


    }


    }

    View Slide

  25. 5BTLJT$BODFMMFE
    ޙଓͷը໘දࣔ͸࣮ߦ͞Εͳ͍

    View Slide

  26. 5BTLDIFDL$BODFMMBUJPO
    ݱࡏͷλεΫ͕Ωϟϯηϧ͞Ε͍ͯͨͱ͖ɺ
    $BODFMMBUJPO&SSPSͷྫ֎Λฦ͢
    func presentAsync(_ viewController: SuspendableViewController,


    animated: Bool,


    completion: (() -> Void)? = nil) async throws {


    try Task.checkCancellation()


    present(viewController, animated: animated, completion: completion)


    await withCheckedContinuation { continuation in


    viewController.didDisappearHandler = {


    continuation.resume()


    }


    }


    }

    View Slide

  27. 5BTLDIFDL$BODFMMBUJPO
    ݺͼग़͠ଆͰͷΩϟϯηϧ࣌ͷϋϯυϦϯά
    func tapButton() {


    DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in


    self?.task?.cancel()


    }


    self.task = Task {


    do {


    let coverVC1 = CoverViewController(rootView: .init(item: .red))


    try await presentAsync(coverVC1, animated: true)


    let coverVC2 = CoverViewController(rootView: .init(item: .yellow))


    try await presentAsync(coverVC2, animated: true)


    } catch { // CancellationError


    dismiss(animated: true)


    }


    }


    }

    View Slide

  28. 5BTLDIFDL$BODFMMBUJPO
    BTZODؔ਺ͷ࣮ߦλΠϛϯάͰ
    ྫ֎Λݕ஌ɺը໘Λด͡Δ

    View Slide

  29. Ωϟϯηϧ࣌ʹBTZODؔ਺ଆͰଈ࣌ʹߦ͏ϋϯυϦϯά
    func presentAsync(_ viewController: SuspendableViewController, …) async {


    if Task.isCancelled {


    return


    }


    present(viewController, animated: animated, completion: completion)


    await withTaskCancellationHandler(


    handler: { // Sendable


    Task { @MainActor in


    viewController.dismiss(animated: true)


    }


    },


    operation: {


    await withCheckedContinuation { continuation in


    viewController.didDisappearHandler = { continuation.resume() }


    }


    }


    )


    }
    XJUI5BTL$BODFMMBUJPO)BOEMFS

    View Slide

  30. XJUI5BTL$BODFMMBUJPO)BOEMFS
    දࣔதͷը໘Λด͡Δ

    View Slide

  31. ·ͱΊ
    w DPOUJOVBUJPOΛར༻͠଴ػঢ়ଶΛBTZODBXBJUͰදݱͨ͠
    w ಉظίʔυ͔ΒBTZODؔ਺Λݺͼग़͢ࡍʹ͸5BTLΛར༻͢Δ
    w ඇߏ଄Խ͞ΕͨฒߦੑͷΩϟϯηϧॲཧ͸खಈͰߦ͏
    w 5BTLJT$BODFMMFE
    w 5BTLDIFDL$BODFMMBUJPO
    w XJUI5BTL$BODFMMBUJPO)BOEMFS

    View Slide

  32. ࢀߟࢿྉ
    w 4XJGUͷฒߦॲཧʹ͍ͭͯ
    w IUUQTEFWFMPQFSBQQMFDPNKQOFXT JEPFVPU[
    w $PODVSSFODZŠ5IF4XJGU1SPHSBNNJOH-BOHVBHF 4XJGU

    w IUUQTEPDTTXJGUPSHTXJGUCPPL-BOHVBHF(VJEF$PODVSSFODZIUNM
    w $PODVSSFODZc"QQMF%FWFMPQFS%PDVNFOUBUJPO
    w [email protected]@MJCSBSZ
    DPODVSSFODZ

    View Slide

  33. 5IBOLT🙌

    View Slide