iOS Tech Talk【みてね x Mirrativ】 Swift Concurrencyを利用したUIViewController表示の排他制御の実装 https://mirrativ.connpass.com/event/240385/
sample code: https://github.com/to4iki/suspend-view-controller-sample
4XJGU$PODVSSFODZΛར༻ͨ͠6*7JFX$POUSPMMFSදࣔͷഉଞ੍ޚͷ࣮J045FDI5BMLʲΈͯͶY.JSSBUJWʳ!UPJLJ
View Slide
ࣗݾհ!UPJLJUPJLJ5PTIJLJ5BLF[BXBw .JSSBUJW *ODw αφʔ🧖
࣍w BTZODBXBJUͱw ը໘දࣔͷ࣮Ͱར༻͢Δw ฒߦॲཧͷΩϟϯηϧ
4XJGU$PODVSSFODZw ݴޠߏจͱͯ͠ͷBTZODBXBJUw ֤ฒߦॲཧؒͷؔੑΛཧ͢ΔͨΊͷ֓೦Ͱ͋Δ4USVDUVSFE$PODVSSFODZw ҆શͳฒߦॲཧΛهड़͢ΔͨΊͷ৽ͨͳܕͰ͋Δ"DUPS
BTZODBXBJUඇಉظॲཧɺฒߦॲཧΛಉظॲཧͱಉ͡Α͏ͳॻ͖ํͰɺ؆͔ܿͭਖ਼֬ʹॻ͚ΔΑ͏ʹͨ͠ߏจ
BTZODBXBJUBTZODͰඇಉظͷ݁ՌΛΓͱͯ͠ఆٛ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}
ը໘දࣔͷ࣮Ͱར༻͢Δ
ͭͷτϦΨʔͰෳͷը໘ΛϞʔμϧදࣔ͢Δ߹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)}
BTZODBXBJUରԠ දࣔதͷػΛࣔ͢ܕͷఆٛ ฒߦॲཧͷ࣮ ฒߦॲཧͷݺͼग़͠
දࣔதͷػΛࣔ͢ܕͷఆٛTVTQFOE͢Δը໘Λࣔ͢QSPUPDPM@MainActorprotocol SuspendableViewControllerProtocol: AnyObject {var didDisappearHandler: (() -> Void)? { get set }}typealias SuspendableViewController= UIViewController & SuspendableViewControllerProtocol
දࣔதͷػΛࣔ͢ܕͷఆٛTVTQFOE͢Δը໘ͷ࣮class CoverViewController: UIHostingController,SuspendableViewControllerProtocol {var didDisappearHandler: (() -> Void)?override func viewDidDisappear(_ animated: Bool) {super.viewDidDisappear(animated)didDisappearHandler?()}}
BTZODBXBJUରԠ දࣔதͷػΛࣔ͢ܕΛ༻ҙ ฒߦॲཧͷ࣮ ฒߦॲཧͷݺͼग़͠
XJUI$IFDLFE$POUJOVBUJPOը໘දࣔ TVTQFOEdը໘ඇදࣔ 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 inviewController.didDisappearHandler = {continuation.resume()}}}}
5BTLTZODίʔυ͔Β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)}}
ฒߦॲཧͷΩϟϯηϧ
ը໘දࣔΛΩϟϯηϧ͍ͨ͠߹ޙଓͷը໘දࣔΛΩϟϯηϧ͢ΔFYάϩʔόϧͳΤϥʔ͕ൃੜ࣌ʹը໘Λด͍ͨ͡
5BTLDBODFM 5BTLΠϯελϯεΛอ࣋͠ɺΩϟϯηϧΛݺͼग़͢var task: Task?func tapButton() {DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] inself?.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)}}
ڠௐతͳΩϟϯηϧ5BTLDBODFM ɺλεΫʹΩϟϯηϧ͞Εͨͱ͍͏ϑϥάΛཱͯΔ͚ͩͰɺΩϟϯηϧ͞Ε͔ͨͲ͏͔֤λεΫଆͰ֬ೝ͢Δඞཁ͕͋ΔIUUQTEPDTTXJGUPSHTXJGUCPPL-BOHVBHF(VJEF$PODVSSFODZIUNM
ߏԽ͞Ε͍ͯͳ͍ฒߦੑw 5BTLܕΛѻ͏Α͏ͳ߹w ߏԽ͞Ε͍ͯͳ͍ฒߦੑͰɺฒߦॲཧͷϥΠϑαΠΫϧΩϟϯηϧΛ࣮ऀ͕ཧ͢ΔIUUQTEFWFMPQFSBQQMFDPNWJEFPTQMBZXXED
ΩϟϯηϧͷϋϯυϦϯάw 5BTLJT$BODFMMFEw 5BTLDIFDL$BODFMMBUJPOw XJUI5BTL$BODFMMBUJPO)BOEMFS
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 inviewController.didDisappearHandler = {continuation.resume()}}}
5BTLJT$BODFMMFEݺͼग़͠ଆfunc tapButton() {DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] inself?.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)}}
5BTLJT$BODFMMFEޙଓͷը໘ද࣮ࣔߦ͞Εͳ͍
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 inviewController.didDisappearHandler = {continuation.resume()}}}
5BTLDIFDL$BODFMMBUJPOݺͼग़͠ଆͰͷΩϟϯηϧ࣌ͷϋϯυϦϯάfunc tapButton() {DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] inself?.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 { // CancellationErrordismiss(animated: true)}}}
5BTLDIFDL$BODFMMBUJPOBTZODؔͷ࣮ߦλΠϛϯάͰྫ֎Λݕɺը໘Λด͡Δ
Ωϟϯηϧ࣌ʹBTZODؔଆͰଈ࣌ʹߦ͏ϋϯυϦϯάfunc presentAsync(_ viewController: SuspendableViewController, …) async {if Task.isCancelled {return}present(viewController, animated: animated, completion: completion)await withTaskCancellationHandler(handler: { // SendableTask { @MainActor inviewController.dismiss(animated: true)}},operation: {await withCheckedContinuation { continuation inviewController.didDisappearHandler = { continuation.resume() }}})}XJUI5BTL$BODFMMBUJPO)BOEMFS
XJUI5BTL$BODFMMBUJPO)BOEMFSදࣔதͷը໘Λด͡Δ
·ͱΊw DPOUJOVBUJPOΛར༻͠ػঢ়ଶΛBTZODBXBJUͰදݱͨ͠w ಉظίʔυ͔ΒBTZODؔΛݺͼग़͢ࡍʹ5BTLΛར༻͢Δw ඇߏԽ͞ΕͨฒߦੑͷΩϟϯηϧॲཧखಈͰߦ͏w 5BTLJT$BODFMMFEw 5BTLDIFDL$BODFMMBUJPOw XJUI5BTL$BODFMMBUJPO)BOEMFS
ࢀߟࢿྉw 4XJGUͷฒߦॲཧʹ͍ͭͯw IUUQTEFWFMPQFSBQQMFDPNKQOFXT JEPFVPU[w $PODVSSFODZ5IF4XJGU1SPHSBNNJOH-BOHVBHF 4XJGUw IUUQTEPDTTXJGUPSHTXJGUCPPL-BOHVBHF(VJEF$PODVSSFODZIUNMw $PODVSSFODZc"QQMF%FWFMPQFS%PDVNFOUBUJPOw [email protected]@MJCSBSZDPODVSSFODZ
5IBOLT🙌