Save 37% off PRO during our Black Friday Sale! »

今日から使える実践的Swift Concurrency / Introducing Swift Concurrency

011714704c4a925e542d426d4cdaa4e3?s=47 giginet
September 28, 2021

今日から使える実践的Swift Concurrency / Introducing Swift Concurrency

011714704c4a925e542d426d4cdaa4e3?s=128

giginet

September 28, 2021
Tweet

Transcript

  1. ࠓ೔͔Β࢖͑Δ࣮ફతSwift Concurrency @giginet 1

  2. ࣗݾ঺հ • @giginet • Core Contributor of Carthage/fastlane/ XcodeGen etc...

    • https://github.com/giginet • https://twitter.com/giginet • ΫοΫύου(2015/4~) ϞόΠϧج൫ ෦ • ؾܰʹmention͍ͯͩ͘͠͞ʂ 2
  3. iOSDC ࿩͠·ͨ͠ େن໛ͳΞϓϦͷϚϧνϞδϡʔϧߏ੒ͷ࣮ફ by giginet | τʔΫ | iOSDC Japan

    2021 #iosdc - fortee.jp 3
  4. ٕज़ސ໰giginet͞ΜΠϯλϏϡʔ ίϛϡχςΟ׆ಈ΍OSS׆ಈͷ ൿ݃Λฉ͍ͯΈ·ͨ͠ | Money Forward Engineers' Blog 4

  5. Agenda • Swift Concurrency·ͱΊ • جຊฤ • Structured Concurrency •

    actor 5
  6. • ࣮ફతSwift Concurrency • طଘͷΞϓϦͷҠߦ • Α͋͘Δ࣮૷ΛConcurrencyԽͯ͠ΈΑ͏ • callbackύλʔϯ •

    Delegateύλʔϯ • RxSwiftͱͷڠௐ • σΟεΧογϣϯ 6
  7. Swift Concurrency·ͱΊ ͍Ζ͍Ζࢿྉ͕͋ΔͷͰجຊ͸ͦͪΒΛݟΕ͹ྑ͍ɻࠓճ͸๩͠ ͍ํ޲͚ʹ͜ΕΛϕʔεʹ؆୯ʹઆ໌͍͖ͯ͠·͢ɻ • WWDC 2021ͷSwiftͷฒߦॲཧؔ࿈ͷηογϣϯҰཡ - Qiita •

    async/await΍actorͰiOSΞϓϦ։ൃ͕Ͳ͏มΘΔ͔ Before & Afterͷ۩ମྫͰֶͿ - Speaker Deck • Swift Concurrency νʔτγʔτ 7
  8. Swift Concurrencyجຊฤ 8

  9. • Α͋͘Δ΍ͭɻcallbackΛड͚औͬͯResultͰ΋Β͏ Before func downloadData(from url: URL, completion:@escaping (Result<Data, Error>)

    -> Void) downloadData(from: url) { result in switch result { case .success(let data): // data Λ࢖͏ॲཧ case .failure(let error): // ΤϥʔϋϯυϦϯά } } 9
  10. After func downloadData(from url: URL) async throws -> Data do

    { let data = try await downloadData(from: url) // data Λ࢖͏ॲཧ } catch { // ΤϥʔϋϯυϦϯά } 10
  11. ྫ2 • ·ͣϢʔβʔσʔλͷJSONΛ΋ΒͬͯɺͦͷJSONΛσίʔ υɺͦͷதʹ͋ΔURL͔Βը૾Λऔಘ͢Δ 11

  12. func fetchUserIcon(for id: User.ID, completion: @escaping (Result<Data, Error>) -> Void)

    { let url: URL = .init(string: "https://koherent.org/fake-service/api/user?id=\(id)")! downloadData(from: url) { data in // (1) do { let data = try data.get() let user = try JSONDecoder().decode(User.self, from: data) downloadData(from: user.iconURL) { icon in // (2) do { let icon = try icon.get() completion(.success(icon)) } catch { completion(.failure(error)) } } } catch { completion(.failure(error)) } } } 12
  13. After func fetchUserIcon(for id: User.ID) async throws -> Data {

    let url: URL = .init(string: "https://koherent.org/fake-service/api/user?id=\(id)")! let data = try await downloadData(from: url) let user = try JSONDecoder().decode(User.self, from: data) let icon = try await downloadData(from: user.iconURL) return icon } • Θ͔Γ΍͍͢ • खଓ͖తʹॻ͚Δ 13
  14. ྫ3 • JSONͷதʹ2ͭͷը૾URLؚ͕·Ε͍ͯΔ(smallIconImage, largeIconImage) • ·ͣJSONΛऔಘ͠ɺؚ·ΕΔը૾Λฒߦ(Parallel)ͯ͠μ΢ϯ ϩʔυ͍ͨ͠ 14

  15. • JSONͷऔಘޙʹը૾ͷऔಘʢґଘؔ܎ˠίʔϧόοΫͷ࿈࠯ʣ • 2ͭͷը૾͸Parallelʹऔಘ͢Δඞཁ͕͋Δ • GCDͷDispatchGroupͳͲΛར༻ 15

  16. ! func fetchUserIcons(for id: User.ID, completion: @escaping (Result<(small: Data, large:

    Data), Error>) -> Void) { downloadData(from: url) { data in // (1) do { let data = try data.get() let user = try JSONDecoder().decode(User.self, from: data) let group: DispatchGroup = .init() var smallIcon: Result<Data, Error>! group.enter() downloadData(from: user.smallURL) { icon in smallIcon = icon group.leave() } var largeIcon: Result<Data, Error>! group.enter() downloadData(from: user.largeURL) { icon in largeIcon = icon group.leave() } group.notify(queue: .global()) { do { let icons = try (small: smallIcon.get(), large: largeIcon.get()) completion(.success(icons)) } catch { completion(.failure(error)) } } } catch { completion(.failure(error)) } } } 16
  17. After let url: URL = .init(string: "https://koherent.org/fake-service/api/user?id=\(id)")! let data =

    try await downloadData(from: url) let user = try JSONDecoder().decode(User.self, from: data) async let smallIcon = try await downloadData(from: user.smallURL) async let largeIcon = try await downloadData(from: user.largeURL) let icons = try await (small: smallIcon, large: largeIcon) 17
  18. Structured Concurrency • ෳ਺ͷasyncॲཧΛߏ଄Խ࣮ͯ͠ߦ͢Δ࢓૊Έ • Task • ্هͷྫͷΑ͏ʹෳ਺ͷλεΫΛฒߦͯ͠ߦͬͨΓ • ࢠλεΫΛ࡞ͬͨΓ

    • Ωϟϯηϧͨ͠Γ • ͕ՄೳʹͳΔ 18
  19. • swift-evolution/0304-structured-concurrency.md at main · apple/swift-evolution • Explore structured concurrency

    in Swift - WWDC21 - Videos - Apple Developer 19
  20. async-let • 1ͭͷTask಺Ͱෳ਺ͷඇಉظॲཧ(ࢠλεΫ)Λ૸Βͤͯ଴ͪड͚ Ͱ͖Δ async let smallIcon = try await

    downloadData(from: user.smallIconURL) // (foo) async let largeIcon = try await downloadData(from: user.largeIconURL) // (bar) // ͳΜ͔௕͍ॲཧ (Task) let icons = try await (small: smallIcon, large: largeIcon) 20
  21. 21

  22. Task • ඇಉظؔ਺͸TaskͰϥοϓ͢Δ͜ͱͰಉظͷੈք͔ΒͰ΋࣮ߦ Ͱ͖Δ • ಉظੈքͱඇಉظੈքͷڮ౉͠ʹ࢖͑Δ func syncMethod() { Task

    { await doSomething() } } 22
  23. Cancel func syncMethod() { let task = Task { await

    doSomething() } task.cancel() } • Task͸cancelͰ͖Δ • ϘλϯΛԡͨ͠ΒTaskΛൃՐ͢Δ͕ɺऴΘΔલʹΩϟϯηϧϘ λϯΛԡͨ͠ΒऴྃΈ͍ͨͳͷ͕ग़དྷΔ • ࢠλεΫ΋·ͱΊͯΩϟϯηϧ͞ΕΔ 23
  24. AsyncSequence • asyncʹ஋͕औΕΔiterator • ϑΝΠϧI/Oͱ͔ • streamͱ͔(WebSocketͱ͔) • ࢠλεΫΛಈతʹ૿΍ͯ͠ɺiteratorͷΑ͏ʹѻ͑Δ Meet

    AsyncSequence - WWDC21 - Videos - Apple Developer 24
  25. actor • ඇಉظͷσʔλڝ߹Λղܾ͢Δ͘͠Έ • ॲཧΛඇಉظʹͨ͠ΓɺΩϡʔΛ੍໿ͨ͠ΓɺϩοΫͷऔಘΛ ࣗಈతʹ΍ͬͯ͘ΕΔ 25

  26. σʔλڝ߹ͱ͸ • ҎԼͷ৔߹ɺյΕͯ͠·͏ let counter: Counter = .init() DispatchQueue.global().async {

    print(counter.increment()) // ? } DispatchQueue.global().async { print(counter.increment()) // ? } 26
  27. actor Counter { private var count: Int = 0 func

    increment() -> Int { count += 1 return count } } 27
  28. • actor಺ͷશͯͷϓϩύςΟ΍ϝιου͕ඇಉظʹͳΔͨΊɺ σʔλڝ߹͔ΒकΒΕΔ • actor͸಺෦ʹઐ༻ͷΩϡʔ(serial executor)Λอ͓࣋ͯ͠ΓɺӅ ṭ͞ΕΔ await counter.increment() 28

  29. • GCDͰ࣮ݱ͠Α͏ͱ͢ΔͱɺϩοΫ΍semaphore͕ඞཁʹͳΔ final class Counter { private let queue: DispatchQueue

    = .init(label: ...) private var count: Int = 0 func increment(completion: @escaping (Int) -> Void) { queue.async { [self] in count += 1 completion(count) } } } 29
  30. Global Actor • actorͷ࢖༻ΩϡʔΛಛఆͷΩϡʔʹ੍ݶ͢ΔͨΊͷ͘͠Έ • SEͰ͸೚ҙΩϡʔ͕ఏҊ͞Ε͍ͯΔ͕ɺݱࡏ͸MainActorͷ Έ 30

  31. @MainActor final class MyViewController: UIViewController { func doSomething() { }

    } • actorͰ͸ͳ͘ɺطଘͷΫϥεʹ@MainActorΛ෇͚Δͱϝϯό͕ શ෦asyncʹͳΓϝΠϯεϨουͰ࣮ߦ͞ΕΔ 31
  32. • ֎͔Β͸ϝιου΍ϓϩύςΟ͕asyncʹݟ͑Δ • MainActorʹ͍Δ΋ͷͷத͔Β͸͔͋ͨ΋ಉظతʹݺ΂Δ • MainActorҎ֎͔Βͷ࣮ߦΛ͋Δఔ౓๷͙͜ͱ͕ग़དྷΔ • UIͷߋ৽ͳͲɺϝΠϯεϨουҎ֎Ͱ࣮ߦ͞Εͯཉ͘͠ͳ͍ ΋ͷΛؒҧͬͯݺ͹ͳ͍Α͏ʹίϯύΠϧ࣌ʹอޢ͢Δ͜ͱ ͕Ͱ͖Δ

    32
  33. @MainActor func reloadData() { // ϝΠϯεϨουͰ࣮ߦͯ͠ཉ͍͠ॲཧ } @MainActor final class

    SomeViewController: UIViewController { override func viewDidLoad() { reloadData() } } func callReloadData() { let vc = SomeViewController() vc.reloadData() // ϝΠϯεϨουอূ͕ͳ͍ͷͰasync͡Όͳ͍ͱݺ΂ͳ͍ } 33
  34. UIViewController • υΩϡϝϯτΛݟΔײ͡ɺUIViewController͸શͯ@MainActor ʹͳ͍ͬͯΔ • https://developer.apple.com/documentation/uikit/ uiviewcontroller?changes=latest_minor • ࣮ࡍʹ͜ΕΛ΍Δͱଟ͘ͷϓϩμΫτͰޓ׵ੑ͕ҡ࣋Ͱ͖ͳ͘ ͳΔΑͳͱࢥ͍ͬͯΔɻͳΜ͔Έͨײ͡΋దԠ͞Εͯແͦ͞͏

    ͰΑ͘Θ͔Βͳ͍ ! 34
  35. 35

  36. class User { var name: String var age: Int }

    actor Session { var currentUser: User? } 36
  37. func run() async { let session = Session() let user

    = await session.currentUser user?.age += 1 } 37
  38. 38

  39. ͳͥʁ • User͸ࢀরܕͰࢀরઌͰ஋͕มΘΓ͏Δ • ෳ਺ͷλεΫͰࢀর͍ͯ͠Δؒʹ1೥ܦͬͯage͕૿͑ΔՄೳੑ ͕͋Δ • σʔλڝ߹ͷةݥੑ͕͋Δ 39

  40. Sendable • ͜ͷܕ͕actorؒͰ΍ΓͱΓͰ͖Δ͜ͱΛprotocolʹ௨஌͢Δ protocol(marker protocol) • swift-evolution/0302-concurrent-value-and-concurrent- closures.md at main

    · apple/swift-evolution 40
  41. class User: Sendable { let user: String let age: Int

    } • Sendableʹద߹͢Δ͜ͱͰίϯύΠϥʹ҆શʹड͚౉͠Ͱ͖Δ ܕͰ͋Δ͜ͱΛ௨஌ • ্هͷΑ͏ͳΠϛϡʔλϒϧΫϥε(varΛ࣋ͨͣʹҰ౓࡞ͬͨ ΒมΘΒͳ͍)͸SendableʹͰ͖Δ 41
  42. • ͜ͷܯࠂ͸Xcode 13.0(Swift 5.5)ͷ࣌఺Ͱ͸ແޮʹͳ͍ͬͯΔ (؇΍͔ͳҠߦͷͨΊ) • Swift 6.0͔Βܯࠂ͕ඪ४Ͱग़ΔΑ͏ʹͳΔͷͰিܸʹඋ͑Δ • -warn-concurencyΦϓγϣϯͰܯࠂΛ༗ޮԽͰ͖Δ

    42
  43. • Swift ConcurrencyͷwithTaskCancellationHandlerͱSendable - cockscomblog? • Concurrency in Swift 5

    and 6 - Evolution / Discussion - Swift Forums 43
  44. Concurrencyͷར఺ • ॲཧͷߏ଄Խ • ॲཧͷྲྀΕΛಡΈԼͤͯ௥͍΍͍͢ • ݕࠪྫ֎ͷੵۃతͳ࢖༻ • σʔλڝ߹ͷίϯύΠϧ࣌νΣοΫ •

    ࣮ߦεϨουͷίϯύΠϧ࣌νΣοΫ 44
  45. Special Thanks • ͜͜·ͰͷεϥΠυͷ΄ͱΜͲ͸ @koher ͞Μͷࢿྉͷ௨ΓͰ ͢ ! • ૉ੖Β͍͠ͷͰαϙʔτ͠·ͨ͠ɻ͋Γ͕ͱ͏͍͟͝·͢

    45
  46. ໌೔͔Β࢖͑ΔSwift Concurrency • طଘͷΞϓϦΛ෦෼తʹSwift ConcurrencyରԠ͍ͯ͘͠ςΫ χοΫ 46

  47. Swift Concurrency backport • ݱࡏɺConcurrency͸iOS 15Ҏ্Ͱ͔͠࢖༻Ͱ͖ͳ͍͕ɺiOS 13 Ҏ্Ͱ࢖༻Ͱ͖ΔΑ͏ʹ͢Δܭը͕͋Δ(backport) • swiftcʹ͸Ϛʔδ͞Ε͍ͯΔ͕ɺXcode

    13.0ͷஈ֊Ͱ͸࢖༻ Ͱ͖ͳ͍ • ΞϓϦʹConcurrencyʹඞཁͳϥϯλΠϜΛ૊ΈࠐΜͰ͠· ͍ɺiOS 15ະຬͰ΋࣮ߦͰ͖ΔΑ͏ʹ͍ͯ͠ΔΒ͍͠ Add an option to build the concurrency library for back 47
  48. Xcode 13 Release Notes | Apple Developer Documentation 48

  49. طଘͷΞϓϦͷ࣮ફతͳasync/awaitԽ ͜ͷηογϣϯ͕ྑ͔ͬͨ Swift concurrency: Update a sample app - WWDC21

    - Videos - Apple Developer 49
  50. ߟ͑Δ͜ͱ • εϨουຖʹactorΛ෼཭ • ϝΠϯεϨουͰͷ࣮ߦΛڧ੍͍ͨ͠ॲཧ(UI) • backgroundʹڲਖ਼͍ͨ͠ॲཧʢI/O΍ॏ͍ॲཧʣ • Ͳ͜Ͱ΋Α͍ॲཧ •

    asyncΠϯλʔϑΣΠεରԠ 50
  51. 51

  52. 52

  53. 53

  54. • UI΍View͸MainActorʹ • ֎෦ͱͷI/O(͜͜Ͱ͸HealthKit)͸actorʹͯ͠ඇಉظͰѻ͏ • ͲͷεϨουͰॲཧ͢΂͖͔Λ೦಄ʹ૊Έସ͑Δͷ͕େࣄ 54

  55. طଘͷ࣮૷ͷasyncԽ • Α͋͘Δૉ๿ͳඇಉظϝιου(completion) struct ImageDownloader { func downloadImage(url: URL, completion:

    @escaping (Result<UIImage, Error>) -> Void) { var request = URLRequest(url: url) request.httpMethod = "GET" let task = URLSession.shared.dataTask(with: request) { (data, response, error) in if let error = error { completion(.failure(error)) } else { let image = data.flatMap(UIImage.init(data:)) ?? UIImage() completion(.success(image)) } } task.resume() } } 55
  56. • withCheckedContinuation asyncͰ͸ͳ͍࣮૷Λϥοϓͯ͠؆୯ ʹasyncΠϯλʔϑΣΠεʹม׵Ͱ͖Δ public func withCheckedContinuation<T>(_ body: (CheckedContinuation<T, Never>)

    -> Void) async -> T public func withCheckedThrowingContinuation<T>(_ body: (CheckedContinuation<T, Error>) -> Void) async throws -> T 56
  57. @available(iOS 15.0, *) extension ImageDownloader { func downloadImage(url: URL) async

    throws -> UIImage { try await withCheckedThrowingContinuation { continuation in downloadImage(url: url) { result in continuation.resume(with: result) } } } } 57
  58. ࣗಈੜ੒ͯ͘͠ΕΔ • Editor > Refactor > Add Async Wrapper •

    completion൛ͷϝιουʹasync interfaceΛੜ΍͢ • Editor > Refactor > Add Async Alternative • async൛ͷϝιουʹલํޓ׵༻ͷcompletion handlerΛੜ΍ ͢ 58
  59. 59

  60. Delegateύλʔϯͷasync/awaitԽ • جຊతʹ͸ಉ͡ͰCheckedContinuationΛอ࣋͢Δͱྑͦ͞͏ ྫɿURLSessionDataDelegateͷasync/awaitԽ 60

  61. struct HTTPRedirectResolver { func resolve(_ url: URL) async throws ->

    URL? { try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<URL?, Error>) in let delegate = Delegate(activeContinuation: continuation) let urlSession = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil) let request = URLRequest(url: url) urlSession.dataTask(with: request).resume() } } private class Delegate: NSObject, URLSessionDataDelegate { private let activeContinuation: CheckedContinuation<URL?, Error> init(activeContinuation: CheckedContinuation<URL?, Error>) { self.activeContinuation = activeContinuation } func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { let newURL = request.url activeContinuation.resume(returning: newURL) completionHandler(nil) } func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { activeContinuation.resume(returning: dataTask.currentRequest?.url) } func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { guard let error = error else { return activeContinuation.resume(returning: nil) } activeContinuation.resume(throwing: error) } } } 61
  62. let resolver = URLRedirectResolver() let url = "http://moved-url.com" // redirect͢Δ

    let redirectedURL = try await resolver.resolve(url) 62
  63. RxͰͷ࢖༻ • async/awaitΛObservableʹม׵ͯ͠ڠௐͯ͠࢖͑ΔΑ͏ʹ͢Δ 63

  64. extension ObservableType { public static func create(throwingAsync callee: @escaping ()

    async throws -> Element) -> Observable<Element> { Observable<Element>.create { observer in let task = Task { do { let result = try await callee() observer.onNext(result) } catch { observer.onError(error) } } return Disposables.create { task.cancel() } } } } 64
  65. Observable.create { try async doSomething() } // Observable<T> .asDriver(onErrorJustReturn: [])

    .drive(onNext: { _ in }) .dispose(by: disposeBag) • TaskΛ࢖͏͜ͱͰasyncΛӅṭ͍ͯ͠Δɻಉظͷੈք͔Β࢖͑Δ 65
  66. ײ૝ɺٞ࿦ • ͍͟࢖͑ΔΑ͏ʹͳͬͨΒ෦෼తʹऔΓೖΕ͍ͯ͘ͷ͸༰қͦ ͏ • VS Rx • ௨৴ͷ଴ͪड͚ͳͲʹRxΛར༻͍ͯ͠Δ৔߹ͳͲ͸async/ awaitʹஔ͖׵͑ͯγϯϓϧʹॻ͚ͦ͏͕ͩɺ׬શʹஔ͖׵͑

    ΒΕΔΘ͚Ͱ͸ແͦ͞͏ 66
  67. • Actor • ࠓҎ্ʹUI૚ͱͦΕҎ֎ͷ૚ͷ֊૚Խ͕ॏཁʹࢥ͑Δ • View૚͸શͯ MainActor ʹͯ͠͠·͍ɺnonisolatedͳॲཧ ͸࣋ͨͤͳ͍ੈք͕ΩϨΠͦ͏ 67

  68. • ࠓ͸back portͷ໰୊͕͋ͬͯɺطଘͷΞϓϦͰࠓ͙͢࢖͏ͷ͸ ೉͍͠ • Xcode 13.1ͱ͔ૣ͍ஈ֊Ͱಈ͘Α͏ʹͳΓͦ͏ͳ༧ײ • Development TargetΛiOS

    15ʹͯ͠༡Ϳ͜ͱ͸Ͱ͖ͨ 68
  69. ͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ 69