
Swift Concurrency - Task 알아보기 (2)
--- 핵심만 일단 정리하였다.
Delaying an asynchronous Swift Task
사용 예제
Task {
try await Task.sleep(nanoseconds: 1_000_000_000)
}언제 사용할까?
- 사용자가 무언가를 검색할 때, debouncing 을 주고, 현재까지 입력한 텍스트로 일정 시간 두었을 때?
검색 로직이 실행되게끔 하기! -> 바로 검색하게 하면, 매 텍스트마다 호출이 되기 때문.
- 로딩 화면을 보여줄 때! - 바로 로딩화면을 보여주지 말고, 데이터를 가져오는 시간이 일정 시간이 걸릴때부터 보여주는 것이 사용자 입장에서 더 좋음! 이때, delay Task를 사용하자.
가장 간단한 방법은 위에서 설명한 Task.sleep(nanosecond: ) 를 사용하는 방법 (이 때 await를 사용해야 함)
class VideoViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let loadingSpinnerTask = Task {
try await Task.sleep(nanoseconds: 150_000_000) // 여기서 작업 지연!
showLoadingSpinner()
}
Task {
await prepareVideo()
loadingSpinnerTask.cancel()
hideLoadingSpinner()
}
}
}참고로 위 코드는 뷰컨을 빠르게 왔다갔다 하면 loading Task가 중복으로 실행되거나, 뷰를 떠났는데도 백그라운드에서 실행이 될 수 있으므로, viewWillDisAppear 에서 Task를 cancel 하는 작업이 필요하다.
간단하게 작업에 지연을 주는 방법은 TimeInterval 이다.
TimeInterval은 아래와 같이 정의되어있다.
extension Task where Failure == Error {
static func delayed(
byTimeInterval delayInterval: TimeInterval,
priority: TaskPriority? = nil,
operation: @escaping @Sendable () async throws -> Success
) -> Task {
Task(priority: priority) {
let delay = UInt64(delayInterval * 1_000_000_000)
try await Task<Never, Never>.slepp(nanoseconds: delay)
return try await operation()
}
}
}아직 이해가 완전히 되지 않거나, 지금 당장 사용하지 않을 부분은 스킵하였으나, 중요한 부분은 꼭 이해하고 넘어가야한다.
- @escaping: 보통 비동기 메서드에서 completion 부분에 붙이는 attribute 이다. 기본적으로는 클로저가 끝날 때 completion을 실행하면 오류가 나지만 (non - escaping 이기 때문), 그러나, @escaping 이 붙으면 클로저의 실행이 끝난 뒤에도 completion 클로저를 호출할 수 있다.
위 예제에서는 Task 타입의 operation 을 호출한다.
- @Sendable: 여러 concurrent Context 에서도 실행할 수 있게 함. 그리고 데이터 레이스 방지를 위해 필요하다.
값 타입(struct, enum)은 기본적으로 Sendable 하지만, 참조타입(class)은 Sendable 프로토콜을 준수해야 한다.
그럼 아까 코드가 아래와 같이 된다.
class VideoViewController: UIViewController {
...
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let loadingSpinnerTask = Task.delayed(byTimeInterval: 0.15) {
self.showLoadingSpinner()
}
Task {
await prepareVideo()
loadingSpinnerTask.cancel()
hideLoadingSpinner()
}
}
...
}