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. 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 }
  2. ͭͷτϦΨʔͰෳ਺ͷը໘ΛϞʔμϧදࣔ͢Δ৔߹ 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) }
  3. දࣔதͷ଴ػΛࣔ͢ܕͷఆٛ TVTQFOE͢Δը໘Λࣔ͢QSPUPDPM @MainActor protocol SuspendableViewControllerProtocol: AnyObject { var didDisappearHandler: (()

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

    -> Void)? override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) didDisappearHandler?() } }
  5. 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() } } } }
  6. 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) } }
  7. 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) } }
  8. 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() } } }
  9. 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) } }
  10. 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() } } }
  11. 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) } } }
  12. Ωϟϯηϧ࣌ʹ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
  13. ࢀߟࢿྉ 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