티스토리 뷰
과거에 Continuations 를 이용해서 짰던 코드를 보는데 헷갈렸다.. @)@
리마인드가 필요하군
우선 요 목차(?)가 머릿 속에 있어야한다.
[1] 용어 기억
가끔 용어도 잘 생각이 안날 때가 있다,,
[ Continuation ]
비동기 코드를 래핑해서 연속(continuation)을 만든다! 라고 기억하자
문서에서는 이렇게 표현한다.
To create a continuation in asynchronous code,
call the withUnsafeContinuation(function:_:) or withUnsafeThrowingContinuation(function:_:) function.
[ CheckedContinuation vs UnsafeContinuation ]
잘 까먹는 주요원인이다.
checked / unchecked 이던가.. safe / unsafe 이던가.. 하면 더 쉽게 기억할텐데 따흑,,
CheckedContinuation 은 resume이 빠졌거나 여러번 불리는 지를 런타임때 체크하고
UnsafeContinuation 은 체크안한다 (오버헤드를 피하기 위해)
CheckedContinuation performs runtime checks for missing or multiple resume operations.
UnsafeContinuation avoids enforcing these invariants at runtime because it aims to be a low-overhead mechanism for interfacing Swift tasks with event loops, delegate methods, callbacks, and other non-async scheduling mechanisms
이 글에서 자세한 실험을 볼 수 있다.
[2] 사용예시
iOS 비동기프로그래밍의 두가지 주요 패턴인
callback (또는 completion hanlder), delegate 를 async-await syntax 로 쓰고 싶을 때 사용한다.
1) completion -> async/await
completion handler 를 파라미터로 받는 function을 래핑해서 async function 을 만들고 싶을 때 사용한다.
여기 예제 잘 설명되어있음
http://minsone.github.io/swift-concurrency-continuation
2) delegate -> async/await
delegate 패턴 대신 async function 을 만들고 싶을때 사용한다.
예를들어
StoreKit 1 의 대부분은 delegate 패턴인데
ProductFetcher, ReceiptLoader 등 각각 객체를 만들고 async/await 를 사용할 수 있게 해주면 코드가 훨씬 간결해진다.
fetchAvailableProducts 을 async function으로 만드려면
request 가 성공, 실패할때 불리는 delegate function 내에서 resume이 호출되게 해주면 된다.
class IAPProductsFetcher: NSObject, SKProductsRequestDelegate {
private var productsRequest: SKProductsRequest?
private var productsRequestDoneAction: (([SKProduct]) -> Void)?
private let productIds: [String]
init(productIds: [String] = IAPProducts.identifiers) {
self.productIds = productIds
}
func fetchAvailableProducts() async -> [SKProduct] {
return await withCheckedContinuation { continuation in
self.productsRequestDoneAction = {
continuation.resume(returning: $0)
}
self.fetchProducts(matchingIdentifiers: self.productIds)
}
}
private func fetchProducts(matchingIdentifiers identifiers: [String]) {
let productIdentifiers = Set(identifiers)
let productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers)
productsRequest.delegate = self
self.productsRequest = productsRequest
productsRequest.start()
}
// MARK: - SKProductsRequestDelegate
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
let availableProducts = response.products
self.productsRequestDoneAction?(availableProducts)
self.productsRequestDoneAction = nil
}
func request(_ request: SKRequest, didFailWithError error: Error) {
self.productsRequestDoneAction?([])
self.productsRequestDoneAction = nil
}
}
혹은 Modern Cuncurrency 책 에 나오는 예제처럼 Continuation 자체를 프로퍼티로 들고 있어도 좋다.
class ChatLocationDelegate: NSObject, CLLocationManagerDelegate {
typealias LocationContinuation = CheckedContinuation<CLLocation, Error>
private var continuation: LocationContinuation?
...
}
full code 도 함께 첨부 (출처: Modern Concurrency In Swift > chapter 5)
import Foundation
import CoreLocation
class ChatLocationDelegate: NSObject, CLLocationManagerDelegate {
typealias LocationContinuation = CheckedContinuation<CLLocation, Error>
private var continuation: LocationContinuation?
init(manager: CLLocationManager, continuation: LocationContinuation) {
self.continuation = continuation
super.init()
manager.delegate = self
manager.requestWhenInUseAuthorization()
}
deinit {
continuation?.resume(throwing: CancellationError())
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch manager.authorizationStatus {
case .notDetermined:
break
case .authorizedAlways, .authorizedWhenInUse:
manager.startUpdatingLocation()
default:
continuation?.resume(
throwing: "The app isn't authorized to use location data"
)
continuation = nil
}
}
func locationManager(
_ manager: CLLocationManager,
didUpdateLocations locations: [CLLocation]
) {
guard let location = locations.first else { return }
continuation?.resume(returning: location)
continuation = nil
}
func locationManager(
_ manager: CLLocationManager,
didFailWithError error: Error
) {
continuation?.resume(throwing: error)
continuation = nil
}
}
class BlabberModel: ObservableObject {
var username = ""
var urlSession = URLSession.shared
private let manager = CLLocationManager()
private var delegate: ChatLocationDelegate?
/// Shares the current user's address in chat.
func shareLocation() async throws {
let location: CLLocation = try await
withCheckedThrowingContinuation { [weak self] continuation in
self?.delegate = ChatLocationDelegate(manager: manager, continuation: continuation)
if manager.authorizationStatus == .authorizedWhenInUse {
manager.startUpdatingLocation()
}
}
}
...
}
[3] 참고 > continuation
Modern Concurrency 책에 continuation 에 대한 자세한 설명이 나온다.
이걸 읽으면 continuation 를 더 잘 이해할 수 있음!
A continuation is an object that tracks a program’s state at a given point.
The Swift concurrency model assigns each asynchronous unit of work a continuation instead of creating an entire thread for it. This allows the concurrency model to scale your work more effectively based on the capabilities of the hardware. It creates only as many threads as there are available CPU cores, and it switches between continuations instead of between threads, making it more efficient.
You’re familiar with how an await call works: Your current code suspends execution and hands the thread and system resources over to the central handler, which decides what to do next.
When the awaited function completes, your original code resumes, as long as no higher priority tasks are pending. But how?
When the original code suspends, it creates a continuation that represents the entire captured state at the point of suspension. When it’s time to resume execution or throw, the concurrency system recreates the state from the continuation and the work… well, continues.
This all happens behind the scenes when you use async functions. You can also create continuations yourself, which you can use to extend existing code that uses callbacks or delegates.
These APIs can benefit from using await as well.
Manually creating continuations allows you to migrate your existing code gradually to the new concurrency model.
'🍏 > Swift' 카테고리의 다른 글
[Swift] KeyPath 유용한 예제 모음 (32) | 2023.10.21 |
---|---|
[Swift] RandomAccessCollection / BidirectionalCollection 의 Time Complexity (0) | 2023.09.23 |
[Swift] self in a closure in a closure (0) | 2023.07.11 |
[Swift Collections] Heap (0) | 2023.05.20 |
[Swift] Dictionary 의 subscript 3개 / KeyValuePairs (0) | 2023.04.29 |
- Total
- Today
- Yesterday
- github actions
- METAL
- Django Heroku Scheduler
- ipad multitasking
- Flutter getter setter
- SerializerMethodField
- cocoapod
- flutter dynamic link
- Django Firebase Cloud Messaging
- Python Type Hint
- flutter deep link
- Django FCM
- drf custom error
- 장고 URL querystring
- flutter 앱 출시
- Flutter Spacer
- Sketch 누끼
- 구글 Geocoding API
- flutter build mode
- DRF APIException
- Flutter Clipboard
- 플러터 얼럿
- Flutter Text Gradient
- 플러터 싱글톤
- Dart Factory
- Watch App for iOS App vs Watch App
- ribs
- Flutter 로딩
- PencilKit
- 장고 Custom Management Command
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |