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

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

giginet
September 28, 2021

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

giginet

September 28, 2021
Tweet

More Decks by giginet

Other Decks in Technology

Transcript

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

    • https://github.com/giginet • https://twitter.com/giginet • ΫοΫύου(2015/4~) ϞόΠϧج൫ ෦ • ؾܰʹmention͍ͯͩ͘͠͞ʂ 2
  2. • Α͋͘Δ΍ͭɻ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
  3. After func downloadData(from url: URL) async throws -> Data do

    { let data = try await downloadData(from: url) // data Λ࢖͏ॲཧ } catch { // ΤϥʔϋϯυϦϯά } 10
  4. 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
  5. 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
  6. ! 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
  7. 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
  8. 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
  9. 21

  10. Cancel func syncMethod() { let task = Task { await

    doSomething() } task.cancel() } • Task͸cancelͰ͖Δ • ϘλϯΛԡͨ͠ΒTaskΛൃՐ͢Δ͕ɺऴΘΔલʹΩϟϯηϧϘ λϯΛԡͨ͠ΒऴྃΈ͍ͨͳͷ͕ग़དྷΔ • ࢠλεΫ΋·ͱΊͯΩϟϯηϧ͞ΕΔ 23
  11. σʔλڝ߹ͱ͸ • ҎԼͷ৔߹ɺյΕͯ͠·͏ let counter: Counter = .init() DispatchQueue.global().async {

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

    increment() -> Int { count += 1 return count } } 27
  13. • 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
  14. @MainActor final class MyViewController: UIViewController { func doSomething() { }

    } • actorͰ͸ͳ͘ɺطଘͷΫϥεʹ@MainActorΛ෇͚Δͱϝϯό͕ શ෦asyncʹͳΓϝΠϯεϨουͰ࣮ߦ͞ΕΔ 31
  15. @MainActor func reloadData() { // ϝΠϯεϨουͰ࣮ߦͯ͠ཉ͍͠ॲཧ } @MainActor final class

    SomeViewController: UIViewController { override func viewDidLoad() { reloadData() } } func callReloadData() { let vc = SomeViewController() vc.reloadData() // ϝΠϯεϨουอূ͕ͳ͍ͷͰasync͡Όͳ͍ͱݺ΂ͳ͍ } 33
  16. 35

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

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

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

  20. class User: Sendable { let user: String let age: Int

    } • Sendableʹద߹͢Δ͜ͱͰίϯύΠϥʹ҆શʹड͚౉͠Ͱ͖Δ ܕͰ͋Δ͜ͱΛ௨஌ • ্هͷΑ͏ͳΠϛϡʔλϒϧΫϥε(varΛ࣋ͨͣʹҰ౓࡞ͬͨ ΒมΘΒͳ͍)͸SendableʹͰ͖Δ 41
  21. 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
  22. 51

  23. 52

  24. 53

  25. طଘͷ࣮૷ͷ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
  26. • 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
  27. @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
  28. ࣗಈੜ੒ͯ͘͠ΕΔ • Editor > Refactor > Add Async Wrapper •

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

  30. 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
  31. let resolver = URLRedirectResolver() let url = "http://moved-url.com" // redirect͢Δ

    let redirectedURL = try await resolver.resolve(url) 62
  32. 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
  33. Observable.create { try async doSomething() } // Observable<T> .asDriver(onErrorJustReturn: [])

    .drive(onNext: { _ in }) .dispose(by: disposeBag) • TaskΛ࢖͏͜ͱͰasyncΛӅṭ͍ͯ͠Δɻಉظͷੈք͔Β࢖͑Δ 65