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

suspend-view-controller-sample

3925ee6eaa41031bac799de0f4f528ec?s=47 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

3925ee6eaa41031bac799de0f4f528ec?s=128

to4iki

March 15, 2022
Tweet

More Decks by to4iki

Other Decks in Programming

Transcript

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

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

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

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

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

  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 }
  7. ը໘දࣔͷ࣮૷Ͱར༻͢Δ

  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) }
  9. BTZODBXBJUରԠ  දࣔதͷ଴ػΛࣔ͢ܕͷఆٛ  ฒߦॲཧͷ࣮૷  ฒߦॲཧͷݺͼग़͠

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

  11. දࣔதͷ଴ػΛࣔ͢ܕͷఆٛ TVTQFOE͢Δը໘Λࣔ͢QSPUPDPM @MainActor protocol SuspendableViewControllerProtocol: AnyObject { var didDisappearHandler: (()

    -> Void)? { get set } } typealias SuspendableViewController = UIViewController & SuspendableViewControllerProtocol
  12. දࣔதͷ଴ػΛࣔ͢ܕͷఆٛ TVTQFOE͢Δը໘ͷ࣮૷ class CoverViewController: UIHostingController<CoverView>, SuspendableViewControllerProtocol { var didDisappearHandler: (()

    -> Void)? override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) didDisappearHandler?() } }
  13. BTZODBXBJUରԠ  දࣔதͷ଴ػΛࣔ͢ܕΛ༻ҙ  ฒߦॲཧͷ࣮૷  ฒߦॲཧͷݺͼग़͠

  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() } } } }
  15. BTZODBXBJUରԠ  දࣔதͷ଴ػΛࣔ͢ܕΛ༻ҙ  ฒߦॲཧͷ࣮૷  ฒߦॲཧͷݺͼग़͠

  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) } }
  17. ฒߦॲཧͷΩϟϯηϧ

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

  19. 5BTLDBODFM 5BTLΠϯελϯεΛอ࣋͠ɺΩϟϯηϧΛݺͼग़͢ var task: Task<Void, Never>? 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) } }
  20. ڠௐతͳΩϟϯηϧ 5BTLDBODFM ͸ɺ λεΫʹΩϟϯηϧ͞Εͨͱ͍͏ϑϥάΛཱͯΔ͚ͩͰɺ Ωϟϯηϧ͞Ε͔ͨͲ͏͔͸֤λεΫଆͰ֬ೝ͢Δඞཁ͕͋Δ IUUQTEPDTTXJGUPSHTXJGUCPPL-BOHVBHF(VJEF$PODVSSFODZIUNM

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

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

  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() } } }
  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) } }
  25. 5BTLJT$BODFMMFE ޙଓͷը໘දࣔ͸࣮ߦ͞Εͳ͍

  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() } } }
  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) } } }
  28. 5BTLDIFDL$BODFMMBUJPO BTZODؔ਺ͷ࣮ߦλΠϛϯάͰ ྫ֎Λݕ஌ɺը໘Λด͡Δ

  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
  30. XJUI5BTL$BODFMMBUJPO)BOEMFS දࣔதͷը໘Λด͡Δ

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

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

    w IUUQTEPDTTXJGUPSHTXJGUCPPL-BOHVBHF(VJEF$PODVSSFODZIUNM w $PODVSSFODZc"QQMF%FWFMPQFS%PDVNFOUBUJPO w IUUQTEFWFMPQFSBQQMFDPNEPDVNFOUBUJPOTXJGUTXJGU@TUBOEBSE@MJCSBSZ DPODVSSFODZ
  33. 5IBOLT🙌