비동기 작업을 실행하는 기본 단위
비동기 함수나 연산을 실행할 수 있는 컨테이너 역할
DispatchQueue.global(qos: .background).async {
let result = fetchNetworkData()
DispatchQueue.main.async {
updateUI(with: result)
}
}
func fetchNetworkData() -> String {
// 네트워크 요청 (간단히 문자열 반환하는 예시)
return "Fetched Data"
}
func updateUI(with data: String) {
print("UI 업데이트: \(data)")
}
Task {
let result = await fetchNetworkData()
await updateUI(with: result)
}
func fetchNetworkData() async -> String {
// 네트워크 요청 (간단히 문자열 반환하는 예시)
return "Fetched Data"
}
@MainActor
func updateUI(with data: String) {
print("UI 업데이트: \(data)")
}
부모 작업과 명시적인 관계없이 독립적으로 생성되는 비동기 작업
실행 컨텍스트와 생명주기를 자체적으로 관리
actor의 우선순위 등과 작업에 필요한 로컬 변수를 상속받아 작업 진행func startBackgroundTask() {
Task {
await longRunningOperation()
}
// 이 시점에서 startBackgroundTask()는 종료되지만,
// Task는 longRunningOperation()이 완료될 때까지 계속 실행됩니다.
}
최상위 비동기 작업을 생성한다.
부모 Task, 선언된 actor 컨텍스트를 전혀 상속 받지 않는다.
@MainActor
func fetchData() {
Task.detached {
let data = await fetcher.getData()
self.models = data // 백그라운드에서 self 접근(주의)
}
}
MainActor로 메인 스레드에서 선언되더라도 다른 스레드에서 처리
작업 간 부모-자식 관계를 통해 코드의 가독성과 안정성을 높이는 모델
async let과 withTaskGroup, withThrowingTaskGroup을 활용한 부모 작업 내에 자식 작업 생성func fetchData() async throws {
async let data1 = fetchFromAPI1()
async let data2 = fetchFromAPI2()
let result1 = try await data1
let result2 = try await data2
// 결과 처리
}
fetchData라는 부모 작업 내에 data1, data2라는 변수 각각에 자식 작업을 비동기, 병렬로 실행하고 각각을 기다린다.
func processFiles(files: [String]) async throws -> [ProcessedFile] {
return try await withThrowingTaskGroup(of: ProcessedFile.self) { group in
for file in files {
group.addTask {
return try await process(file)
}
}
var results = [ProcessedFile]()
for try await result in group {
results.append(result)
}
return results
}
}
withThrowingTaskGroupwithTaskGroupasync let fetchData1 = fetchData(urlString: "https://someDataRandom")
async let fetchData2 = fetchData(urlString: "https://someDataRandom")
async let fetchData3 = fetchData(urlString: "https://someDataRandom")
async let fetchData4 = fetchData(urlString: "https://someDataRandom")
async let fetchData5 = fetchData(urlString: "https://someDataRandom")
let (Data1, Data2, Data3, ...) = await (try? fetchData1, try? fetchData2, ...)
위와 같이 반복된 작업을 일일히 async let으로 선언해줘야 하는 문제를
func fetchDataGroup() async -> [UIImage] {
let urlStrings = [
"https://someDataRandom",
"https://someDataRandom",
"https://someDataRandom",
"https://someDataRandom",
"https://someDataRandom"
]
// return 해주는 게 없으니 returing 타입은 void 로 추론됨.
let images = await withTaskGroup(of: Data?.self) { group in
for urlString in urlStrings {
group.addTask {
try? await self.fetchData(urlString: urlString)
}
}
}
return []
}
withTaskGroup내에서 반복문 선언으로 간단히 해결
Task(priority: .userInitiated) {
// 중요한 작업 (ex. 사용자 액션에 즉시 반응해야 하는 것)
await doImportantWork()
}
각 Task마다 우선순위를 두어 가장 먼저 처리되어야 할 작업을 정의 할수 있다.
각 작업들은 비선점형으로 처리되며, 기존 작업보다 우선순위가 높은 작업이 들어오더라도 기존 작업을 끝내고 그 다음 처리함.
https://developer.apple.com/documentation/swift/taskpriority
| TaskPriority 값 | 의미 |
|---|---|
.high | 아주 빠르게 처리해야 함 |
.userInitiated | 사용자가 시작했고, 바로 결과가 필요한 경우 (ex. 버튼 클릭) |
.medium | 기본적인 작업 처리, 특별히 빠르거나 느릴 필요 없는 작업 |
.utility | 빠른 응답은 필요 없지만 일정 수준의 성능 요구 (ex. 다운로드, 저장) |
.background | 중요도 낮은 작업 (ex. 캐시 정리, 백업) |
.low | 아주 낮은 우선순위 (거의 중요하지 않은 작업) |
Task의 취소는 협력적으로 동작
개발자가 취소 신호 발신 ->Task내에서 취소 처리
let task = Task {
// 수행할 작업
}
task.cancel() // 작업에 취소 신호를 보냄
cancel 메서드를 활용한 Task에게 취소 신호 보내기
Task {
guard !Task.isCancelled else {
// 취소되었을 때의 처리
return
}
do {
try Task.checkCancellation()
// 작업 수행
} catch {
// 취소 시 발생하는 오류 처리
}
}
Task.isCancelled을 활용한 취소 처리Task.checkCancellation()을 활용한 취소 및 에러 처리로 활용가능let task = Task {
await withTaskGroup(of: Void.self) { group in
group.addTask {
// 자식 작업
try Task.checkCancellation()
...
}
// ...
}
}
task.cancel()
Structured Task의 경우일 때 부모 작업의 취소 신호가 전파되면 그 아래에 있는 자식 작업에게까지 취소 신호가 자동으로 전파됨.
func startParentTask() {
parentTask = Task { [weak self] in
// 자식 Task 생성
let childTask = Task {
for i in 1...5 {
try Task.checkCancellation() // 자식 Task 취소 확인
print("Child Task: \(i)")
try await Task.sleep(nanoseconds: 1_000_000_000)
}
}
// 부모 Task 작업
for i in 1...3 {
try Task.checkCancellation() // 부모 Task 취소 확인
print("Parent Task: \(i)")
try await Task.sleep(nanoseconds: 500_000_000)
}
// 부모 Task 완료 후 자식 Task 취소 (옵션)
childTask.cancel() // [1] 부모가 완료되면 자식도 취소
}
}
func cancelAll() {
parentTask?.cancel() // 부모 Task 취소 → 자식 Task는 자동 취소 X
}
Parent Task: 1
Parent Task: 2
Parent Task: 3
일일히 취소 호출을 해줘야 함.