1. async
데이터를 받아오는 코드를 실행할 때 Task
안에 넣어서 실행하는 방법
2. Task
의 우선순위 파악하기
3. Task
내부의 스레드 환경 확인하기
4. Task
내부 작업을 취소하는 방법 확인하기
Task
를 실행할 때 우선순위를 커스텀하기Task.yield()
사용해보기Task.detached()
사용해보기Task.cancel()
사용해보기.task
사용해보기 func fetchImage() async {
guard let url = getURL() else { return }
do {
let (data, _) = try await URLSession.shared.data(from: url, delegate: nil)
await MainActor.run {
self.image = UIImage(data: data)
}
} catch {
print(error.localizedDescription)
}
}
async await
로 받기MainActor
를 통해 동기화 이슈 사전에 해결하기 .task {
try? await Task.sleep(nanoseconds: 5_000_000_000)
await viewModel.fetchImage()
await viewModel.fetchImage2()
}
.task
블럭을 통해 컴파일러가 자체적으로 cancel
상황을 고려함import SwiftUI
class TaskBootCampViewModel: ObservableObject {
@Published var image: UIImage? = nil
@Published var image2: UIImage? = nil
func getURL() -> URL? {
guard let url = URL(string: "https://picsum.photos/1000") else { return nil }
return url
}
func fetchImage() async {
guard let url = getURL() else { return }
do {
let (data, _) = try await URLSession.shared.data(from: url, delegate: nil)
await MainActor.run {
self.image = UIImage(data: data)
}
} catch {
print(error.localizedDescription)
}
}
func fetchImage2() async {
guard let url = getURL() else { return }
do {
let (data, _) = try await URLSession.shared.data(from: url, delegate: nil)
await MainActor.run {
self.image2 = UIImage(data: data)
}
} catch {
print(error.localizedDescription)
}
}
func printTaskPriority() {
Task(priority: .low) {
print("LOW: \(Thread.current) : \(Task.currentPriority)")
}
Task(priority: .medium) {
print("MEDIUM: \(Thread.current) : \(Task.currentPriority)")
}
Task(priority: .high) {
print("HIGH: \(Thread.current) : \(Task.currentPriority)")
}
Task(priority: .background) {
print("BACKGROUND: \(Thread.current) : \(Task.currentPriority)")
}
Task(priority: .userInitiated) {
print("USERINITIATED: \(Thread.current) : \(Task.currentPriority)")
}
Task(priority: .utility) {
print("UTILITY: \(Thread.current) : \(Task.currentPriority)")
}
/*
HIGH: <NSThread: 0x600002266980>{number = 4, name = (null)} : TaskPriority(rawValue: 25)
USERINITIATED: <NSThread: 0x600002266980>{number = 4, name = (null)} : TaskPriority(rawValue: 25)
MEDIUM: <NSThread: 0x60000224bc80>{number = 6, name = (null)} : TaskPriority(rawValue: 21)
LOW: <NSThread: 0x60000223d9c0>{number = 5, name = (null)} : TaskPriority(rawValue: 17)
UTILITY: <NSThread: 0x60000223d9c0>{number = 5, name = (null)} : TaskPriority(rawValue: 17)
BACKGROUND: <NSThread: 0x60000224bc80>{number = 6, name = (null)} : TaskPriority(rawValue: 9)
*/
}
func printTaskPrioirty2() async {
Task(priority: .low) {
print("LOW: \(Thread.current) : \(Task.currentPriority)")
}
Task(priority: .medium) {
print("MEDIUM: \(Thread.current) : \(Task.currentPriority)")
}
Task(priority: .high) {
// try? await Task.sleep(nanoseconds:2_000_000_00)
await Task.yield()
print("HIGH: \(Thread.current) : \(Task.currentPriority)")
}
Task(priority: .background) {
print("BACKGROUND: \(Thread.current) : \(Task.currentPriority)")
}
Task(priority: .userInitiated) {
print("USERINITIATED: \(Thread.current) : \(Task.currentPriority)")
}
Task(priority: .utility) {
print("UTILITY: \(Thread.current) : \(Task.currentPriority)")
}
/*
USERINITIATED: <NSThread: 0x600003c5a400>{number = 7, name = (null)} : TaskPriority(rawValue: 25)
MEDIUM: <NSThread: 0x600003c5f7c0>{number = 5, name = (null)} : TaskPriority(rawValue: 21)
LOW: <NSThread: 0x600003c469c0>{number = 6, name = (null)} : TaskPriority(rawValue: 17)
UTILITY: <NSThread: 0x600003c469c0>{number = 6, name = (null)} : TaskPriority(rawValue: 17)
BACKGROUND: <NSThread: 0x600003c469c0>{number = 6, name = (null)} : TaskPriority(rawValue: 9)
HIGH: <NSThread: 0x600003c469c0>{number = 6, name = (null)} : TaskPriority(rawValue: 25)
-> YEILD THREAD
*/
}
func printParentChildTask() {
Task(priority: .low) {
print("Parent: \(Thread.current) : \(Task.currentPriority)")
Task {
print("Child: \(Thread.current) : \(Task.currentPriority)")
}
Task.detached(priority: .high) {
print("Child Detached: \(Thread.current) : \(Task.currentPriority)")
}
}
/*
Parent: <NSThread: 0x600002b7cc80>{number = 6, name = (null)} : TaskPriority(rawValue: 17)
Child Detached: <NSThread: 0x600002b11a00>{number = 4, name = (null)} : TaskPriority(rawValue: 25)
Child: <NSThread: 0x600002b7cc80>{number = 6, name = (null)} : TaskPriority(rawValue: 17)
-> Don't use detached method if possible as official Doc says
*/
}
func resetImages() {
image = nil
image2 = nil
}
}
async await
를 활용한 데이터 패치 함수Task.yield()
를 통해 태스크를 할당하거나 양보하는 부분을 지정 → 우선순위가 가장 높은 HIGH
스레드는 printTaskPriority
에서 작동한 바와 같이 다른 스레드보다 먼저 실행되어야 하지만 printTaskPriority2
에서 작동한 바와 같이 스레드 양보를 통해 마지막에 실행됨Task.detached
를 통해 부모 태스크와 자식 태스크를 분리 가능, 자식 태스크의 우선순위를 커스텀 가능 → 공식 문서에 따르자면 가능한 사용하지 않아야 할 방법struct TaskBootCamp: View {
@StateObject private var viewModel = TaskBootCampViewModel()
@State private var count: Int = 0
@State private var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
@State private var fetchImageTask: Task<(), Never>? = nil
var body: some View {
NavigationView {
ZStack {
NavigationLink {
imageView
} label: {
Text("Image Download")
}
}
}
}
}
extension TaskBootCamp {
private var imageView: some View {
VStack {
Text("TIMER COUNT: \(count)")
if let image = viewModel.image {
Image(uiImage: image)
.resizable()
.scaledToFit()
}
if let image2 = viewModel.image2 {
Image(uiImage: image2)
.resizable()
.scaledToFit()
}
}
.onReceive(timer, perform: { _ in
count = count + 1 <= 5 ? count + 1 : count
})
.onAppear {
viewModel.resetImages()
count = 0
// taskFetchImages2()
}
// .onDisappear {
// count = 0
// fetchImageTask?.cancel()
// }
.task {
try? await Task.sleep(nanoseconds: 5_000_000_000)
await viewModel.fetchImage()
await viewModel.fetchImage2()
}
}
func taskFetchImages() {
Task {
try? await Task.sleep(nanoseconds: 5_000_000_000)
await viewModel.fetchImage()
await viewModel.fetchImage2()
}
}
func taskFetchImages2() {
fetchImageTask = Task {
try? await Task.sleep(nanoseconds: 5_000_000_000)
await viewModel.fetchImage()
}
}
}
TaskBootCamp
뷰 내부에서 네비게이션 뷰 활용, 유저의 중도 취소(백 버튼 기능) → Task.cancel
상황을 구현