Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
今日から使える実践的Swift Concurrency / Introducing Swift...
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
giginet
PRO
September 28, 2021
Technology
380
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
今日から使える実践的Swift Concurrency / Introducing Swift Concurrency
giginet
PRO
September 28, 2021
More Decks by giginet
See All by giginet
🀄️ on swiftc
giginet
PRO
0
500
15年メンテしてきたdotfilesから開発トレンドを振り返る 2011 - 2026
giginet
PRO
2
380
Claude Codeログ基盤の構築
giginet
PRO
7
4.3k
Inside of Swift Export
giginet
PRO
1
2.7k
Swiftビルド弾丸ツアー - Swift Buildが作る新しいエコシステム
giginet
PRO
0
2.7k
SwiftでMCPサーバーを作ろう!
giginet
PRO
3
440
Creating Intuitive Developer Tool in Swift
giginet
PRO
0
1.6k
Mergeable Libraryで 高速なアプリ起動を実現しよう!
giginet
PRO
2
7.8k
5分でわかるExplicitly Built Modules
giginet
PRO
2
1.8k
Other Decks in Technology
See All in Technology
noUncheckedIndexedAccess、3時間、1万円。 / noUncheckedIndexedAccess, 3 Hours, 10,000 JPY.
kaonavi
1
300
地元にいないローカルオーガナイザーの立ち回り
uvb_76
1
470
GoとSIMDとWasmの今。
askua
3
510
運用を見据えたAIエージェント設計実践
amacbee
1
2.9k
ブロックチェーン / Blockchain
ks91
PRO
0
110
美味しいスイスチーズを作ろう🧀🐭
taigamikami
1
240
Sony_KMP_Journey_KotlinConf2026
sony
2
210
Platform engineering for developers, architects & the rest of us (AI agents)
danielbryantuk
0
180
AI活用を推進するために ファインディが下した、一つの小さな決断
starfish719
0
250
先取りMaven4 ~16年ぶりのメジャーアップデート、その進化とは?~
ogiwarat
0
140
データ基盤をDataformで整えた話 〜 開発環境を添えて 〜
takapy
0
110
ITエンジニアを取り巻く環境とキャリアパス / A career path for Japanese IT engineers
takatama
4
1.8k
Featured
See All Featured
Google's AI Overviews - The New Search
badams
0
1k
[RailsConf 2023] Rails as a piece of cake
palkan
59
6.7k
Fireside Chat
paigeccino
42
3.9k
Connecting the Dots Between Site Speed, User Experience & Your Business [WebExpo 2025]
tammyeverts
11
940
How Software Deployment tools have changed in the past 20 years
geshan
0
34k
How to optimise 3,500 product descriptions for ecommerce in one day using ChatGPT
katarinadahlin
PRO
1
3.6k
Agile Actions for Facilitating Distributed Teams - ADO2019
mkilby
0
200
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
287
14k
Unsuck your backbone
ammeep
672
58k
Typedesign – Prime Four
hannesfritz
42
3.1k
Navigating Weather and Climate Data
rabernat
0
210
SEO in 2025: How to Prepare for the Future of Search
ipullrank
3
3.5k
Transcript
ࠓ͔Β͑Δ࣮ફతSwift Concurrency @giginet 1
ࣗݾհ • @giginet • Core Contributor of Carthage/fastlane/ XcodeGen etc...
• https://github.com/giginet • https://twitter.com/giginet • ΫοΫύου(2015/4~) ϞόΠϧج൫ ෦ • ؾܰʹmention͍ͯͩ͘͠͞ʂ 2
iOSDC ͠·ͨ͠ େنͳΞϓϦͷϚϧνϞδϡʔϧߏͷ࣮ફ by giginet | τʔΫ | iOSDC Japan
2021 #iosdc - fortee.jp 3
ٕज़ސgiginet͞ΜΠϯλϏϡʔ ίϛϡχςΟ׆ಈOSS׆ಈͷ ൿ݃Λฉ͍ͯΈ·ͨ͠ | Money Forward Engineers' Blog 4
Agenda • Swift Concurrency·ͱΊ • جຊฤ • Structured Concurrency •
actor 5
• ࣮ફతSwift Concurrency • طଘͷΞϓϦͷҠߦ • Α͋͘Δ࣮ΛConcurrencyԽͯ͠ΈΑ͏ • callbackύλʔϯ •
Delegateύλʔϯ • RxSwiftͱͷڠௐ • σΟεΧογϣϯ 6
Swift Concurrency·ͱΊ ͍Ζ͍Ζࢿྉ͕͋ΔͷͰجຊͦͪΒΛݟΕྑ͍ɻࠓճ͠ ͍ํ͚ʹ͜ΕΛϕʔεʹ؆୯ʹઆ໌͍͖ͯ͠·͢ɻ • WWDC 2021ͷSwiftͷฒߦॲཧؔ࿈ͷηογϣϯҰཡ - Qiita •
async/awaitactorͰiOSΞϓϦ։ൃ͕Ͳ͏มΘΔ͔ Before & Afterͷ۩ମྫͰֶͿ - Speaker Deck • Swift Concurrency νʔτγʔτ 7
Swift Concurrencyجຊฤ 8
• Α͋͘Δͭɻ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
After func downloadData(from url: URL) async throws -> Data do
{ let data = try await downloadData(from: url) // data Λ͏ॲཧ } catch { // ΤϥʔϋϯυϦϯά } 10
ྫ2 • ·ͣϢʔβʔσʔλͷJSONΛΒͬͯɺͦͷJSONΛσίʔ υɺͦͷதʹ͋ΔURL͔Βը૾Λऔಘ͢Δ 11
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
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
ྫ3 • JSONͷதʹ2ͭͷը૾URLؚ͕·Ε͍ͯΔ(smallIconImage, largeIconImage) • ·ͣJSONΛऔಘ͠ɺؚ·ΕΔը૾Λฒߦ(Parallel)ͯ͠μϯ ϩʔυ͍ͨ͠ 14
• JSONͷऔಘޙʹը૾ͷऔಘʢґଘؔˠίʔϧόοΫͷ࿈ʣ • 2ͭͷը૾Parallelʹऔಘ͢Δඞཁ͕͋Δ • GCDͷDispatchGroupͳͲΛར༻ 15
! 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
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
Structured Concurrency • ෳͷasyncॲཧΛߏԽ࣮ͯ͠ߦ͢ΔΈ • Task • ্هͷྫͷΑ͏ʹෳͷλεΫΛฒߦͯ͠ߦͬͨΓ • ࢠλεΫΛ࡞ͬͨΓ
• Ωϟϯηϧͨ͠Γ • ͕ՄೳʹͳΔ 18
• swift-evolution/0304-structured-concurrency.md at main · apple/swift-evolution • Explore structured concurrency
in Swift - WWDC21 - Videos - Apple Developer 19
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
Task • ඇಉظؔTaskͰϥοϓ͢Δ͜ͱͰಉظͷੈք͔ΒͰ࣮ߦ Ͱ͖Δ • ಉظੈքͱඇಉظੈքͷڮ͠ʹ͑Δ func syncMethod() { Task
{ await doSomething() } } 22
Cancel func syncMethod() { let task = Task { await
doSomething() } task.cancel() } • TaskcancelͰ͖Δ • ϘλϯΛԡͨ͠ΒTaskΛൃՐ͢Δ͕ɺऴΘΔલʹΩϟϯηϧϘ λϯΛԡͨ͠ΒऴྃΈ͍ͨͳͷ͕ग़དྷΔ • ࢠλεΫ·ͱΊͯΩϟϯηϧ͞ΕΔ 23
AsyncSequence • asyncʹ͕औΕΔiterator • ϑΝΠϧI/Oͱ͔ • streamͱ͔(WebSocketͱ͔) • ࢠλεΫΛಈతʹ૿ͯ͠ɺiteratorͷΑ͏ʹѻ͑Δ Meet
AsyncSequence - WWDC21 - Videos - Apple Developer 24
actor • ඇಉظͷσʔλڝ߹Λղܾ͢Δ͘͠Έ • ॲཧΛඇಉظʹͨ͠ΓɺΩϡʔΛ੍ͨ͠ΓɺϩοΫͷऔಘΛ ࣗಈతʹͬͯ͘ΕΔ 25
σʔλڝ߹ͱ • ҎԼͷ߹ɺյΕͯ͠·͏ let counter: Counter = .init() DispatchQueue.global().async {
print(counter.increment()) // ? } DispatchQueue.global().async { print(counter.increment()) // ? } 26
actor Counter { private var count: Int = 0 func
increment() -> Int { count += 1 return count } } 27
• actorͷશͯͷϓϩύςΟϝιου͕ඇಉظʹͳΔͨΊɺ σʔλڝ߹͔ΒकΒΕΔ • actor෦ʹઐ༻ͷΩϡʔ(serial executor)Λอ͓࣋ͯ͠ΓɺӅ ṭ͞ΕΔ await counter.increment() 28
• 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
Global Actor • actorͷ༻ΩϡʔΛಛఆͷΩϡʔʹ੍ݶ͢ΔͨΊͷ͘͠Έ • SEͰҙΩϡʔ͕ఏҊ͞Ε͍ͯΔ͕ɺݱࡏMainActorͷ Έ 30
@MainActor final class MyViewController: UIViewController { func doSomething() { }
} • actorͰͳ͘ɺطଘͷΫϥεʹ@MainActorΛ͚Δͱϝϯό͕ શ෦asyncʹͳΓϝΠϯεϨουͰ࣮ߦ͞ΕΔ 31
• ֎͔ΒϝιουϓϩύςΟ͕asyncʹݟ͑Δ • MainActorʹ͍Δͷͷத͔Β͔͋ͨಉظతʹݺΔ • MainActorҎ֎͔Βͷ࣮ߦΛ͋Δఔ͙͜ͱ͕ग़དྷΔ • UIͷߋ৽ͳͲɺϝΠϯεϨουҎ֎Ͱ࣮ߦ͞Εͯཉ͘͠ͳ͍ ͷΛؒҧͬͯݺͳ͍Α͏ʹίϯύΠϧ࣌ʹอޢ͢Δ͜ͱ ͕Ͱ͖Δ
32
@MainActor func reloadData() { // ϝΠϯεϨουͰ࣮ߦͯ͠ཉ͍͠ॲཧ } @MainActor final class
SomeViewController: UIViewController { override func viewDidLoad() { reloadData() } } func callReloadData() { let vc = SomeViewController() vc.reloadData() // ϝΠϯεϨουอূ͕ͳ͍ͷͰasync͡Όͳ͍ͱݺͳ͍ } 33
UIViewController • υΩϡϝϯτΛݟΔײ͡ɺUIViewControllerશͯ@MainActor ʹͳ͍ͬͯΔ • https://developer.apple.com/documentation/uikit/ uiviewcontroller?changes=latest_minor • ࣮ࡍʹ͜ΕΛΔͱଟ͘ͷϓϩμΫτͰޓੑ͕ҡ࣋Ͱ͖ͳ͘ ͳΔΑͳͱࢥ͍ͬͯΔɻͳΜ͔Έͨײ͡దԠ͞Εͯແͦ͞͏
ͰΑ͘Θ͔Βͳ͍ ! 34
35
class User { var name: String var age: Int }
actor Session { var currentUser: User? } 36
func run() async { let session = Session() let user
= await session.currentUser user?.age += 1 } 37
38
ͳͥʁ • UserࢀরܕͰࢀরઌͰ͕มΘΓ͏Δ • ෳͷλεΫͰࢀর͍ͯ͠Δؒʹ1ܦͬͯage͕૿͑ΔՄೳੑ ͕͋Δ • σʔλڝ߹ͷةݥੑ͕͋Δ 39
Sendable • ͜ͷܕ͕actorؒͰΓͱΓͰ͖Δ͜ͱΛprotocolʹ௨͢Δ protocol(marker protocol) • swift-evolution/0302-concurrent-value-and-concurrent- closures.md at main
· apple/swift-evolution 40
class User: Sendable { let user: String let age: Int
} • Sendableʹద߹͢Δ͜ͱͰίϯύΠϥʹ҆શʹड͚͠Ͱ͖Δ ܕͰ͋Δ͜ͱΛ௨ • ্هͷΑ͏ͳΠϛϡʔλϒϧΫϥε(varΛ࣋ͨͣʹҰ࡞ͬͨ ΒมΘΒͳ͍)SendableʹͰ͖Δ 41
• ͜ͷܯࠂXcode 13.0(Swift 5.5)ͷ࣌Ͱແޮʹͳ͍ͬͯΔ (؇͔ͳҠߦͷͨΊ) • Swift 6.0͔Βܯࠂ͕ඪ४Ͱग़ΔΑ͏ʹͳΔͷͰিܸʹඋ͑Δ • -warn-concurencyΦϓγϣϯͰܯࠂΛ༗ޮԽͰ͖Δ
42
• Swift ConcurrencyͷwithTaskCancellationHandlerͱSendable - cockscomblog? • Concurrency in Swift 5
and 6 - Evolution / Discussion - Swift Forums 43
Concurrencyͷར • ॲཧͷߏԽ • ॲཧͷྲྀΕΛಡΈԼ͍͍ͤͯ͢ • ݕࠪྫ֎ͷੵۃతͳ༻ • σʔλڝ߹ͷίϯύΠϧ࣌νΣοΫ •
࣮ߦεϨουͷίϯύΠϧ࣌νΣοΫ 44
Special Thanks • ͜͜·ͰͷεϥΠυͷ΄ͱΜͲ @koher ͞Μͷࢿྉͷ௨ΓͰ ͢ ! • ૉΒ͍͠ͷͰαϙʔτ͠·ͨ͠ɻ͋Γ͕ͱ͏͍͟͝·͢
45
໌͔Β͑ΔSwift Concurrency • طଘͷΞϓϦΛ෦తʹSwift ConcurrencyରԠ͍ͯ͘͠ςΫ χοΫ 46
Swift Concurrency backport • ݱࡏɺConcurrencyiOS 15Ҏ্Ͱ͔͠༻Ͱ͖ͳ͍͕ɺiOS 13 Ҏ্Ͱ༻Ͱ͖ΔΑ͏ʹ͢Δܭը͕͋Δ(backport) • swiftcʹϚʔδ͞Ε͍ͯΔ͕ɺXcode
13.0ͷஈ֊Ͱ༻ Ͱ͖ͳ͍ • ΞϓϦʹConcurrencyʹඞཁͳϥϯλΠϜΛΈࠐΜͰ͠· ͍ɺiOS 15ະຬͰ࣮ߦͰ͖ΔΑ͏ʹ͍ͯ͠ΔΒ͍͠ Add an option to build the concurrency library for back 47
Xcode 13 Release Notes | Apple Developer Documentation 48
طଘͷΞϓϦͷ࣮ફతͳasync/awaitԽ ͜ͷηογϣϯ͕ྑ͔ͬͨ Swift concurrency: Update a sample app - WWDC21
- Videos - Apple Developer 49
ߟ͑Δ͜ͱ • εϨουຖʹactorΛ • ϝΠϯεϨουͰͷ࣮ߦΛڧ੍͍ͨ͠ॲཧ(UI) • backgroundʹڲਖ਼͍ͨ͠ॲཧʢI/Oॏ͍ॲཧʣ • Ͳ͜ͰΑ͍ॲཧ •
asyncΠϯλʔϑΣΠεରԠ 50
51
52
53
• UIViewMainActorʹ • ֎෦ͱͷI/O(͜͜ͰHealthKit)actorʹͯ͠ඇಉظͰѻ͏ • ͲͷεϨουͰॲཧ͖͔͢Λ೦಄ʹΈସ͑Δͷ͕େࣄ 54
طଘͷ࣮ͷ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
• 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
@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
ࣗಈੜͯ͘͠ΕΔ • Editor > Refactor > Add Async Wrapper •
completion൛ͷϝιουʹasync interfaceΛੜ͢ • Editor > Refactor > Add Async Alternative • async൛ͷϝιουʹલํޓ༻ͷcompletion handlerΛੜ ͢ 58
59
Delegateύλʔϯͷasync/awaitԽ • جຊతʹಉ͡ͰCheckedContinuationΛอ࣋͢Δͱྑͦ͞͏ ྫɿURLSessionDataDelegateͷasync/awaitԽ 60
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
let resolver = URLRedirectResolver() let url = "http://moved-url.com" // redirect͢Δ
let redirectedURL = try await resolver.resolve(url) 62
RxͰͷ༻ • async/awaitΛObservableʹมͯ͠ڠௐͯ͑͠ΔΑ͏ʹ͢Δ 63
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
Observable.create { try async doSomething() } // Observable<T> .asDriver(onErrorJustReturn: [])
.drive(onNext: { _ in }) .dispose(by: disposeBag) • TaskΛ͏͜ͱͰasyncΛӅṭ͍ͯ͠Δɻಉظͷੈք͔Β͑Δ 65
ײɺٞ • ͍͑͟ΔΑ͏ʹͳͬͨΒ෦తʹऔΓೖΕ͍ͯ͘ͷ༰қͦ ͏ • VS Rx • ௨৴ͷͪड͚ͳͲʹRxΛར༻͍ͯ͠Δ߹ͳͲasync/ awaitʹஔ͖͑ͯγϯϓϧʹॻ͚ͦ͏͕ͩɺશʹஔ͖͑
ΒΕΔΘ͚Ͱແͦ͞͏ 66
• Actor • ࠓҎ্ʹUIͱͦΕҎ֎ͷͷ֊Խ͕ॏཁʹࢥ͑Δ • Viewશͯ MainActor ʹͯ͠͠·͍ɺnonisolatedͳॲཧ ࣋ͨͤͳ͍ੈք͕ΩϨΠͦ͏ 67
• ࠓback portͷ͕͋ͬͯɺطଘͷΞϓϦͰࠓ͙͢͏ͷ ͍͠ • Xcode 13.1ͱ͔ૣ͍ஈ֊Ͱಈ͘Α͏ʹͳΓͦ͏ͳ༧ײ • Development TargetΛiOS
15ʹͯ͠༡Ϳ͜ͱͰ͖ͨ 68
͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ 69