- 이번에는 iOS 15에서 새로 생긴 SwiftUI의 새로운 modifier인
task
에 대해서 알아보겠습니다. task
는 onAppear
에 비교적 적합하지 않았던 비동기 작업을 더 적절하게 실행할수 있게 해줍니다.
소개
- SwiftUI에서는 네트워크 요청이 필요할때 대부분 View가 생성되는 시점인
onAppear
에서 진행했는데요. - 이제 네트워크 요청 같이 비동기 작업이 필요한 작업은
onAppear
보다 task
에서 진행하면 될 것 같습니다. - 하지만
task
는 iOS 15에서 소개 된 만큼, iOS 15 미만의 버전에서 사용이 불가합니다.
정의
task(id:priority:_:)
task
의 형태와 정의는 다음과 같은데요.- 쉽게 말해,
task
는 view가 나타기 전에 혹은 특정한 값이 변경 될 때 수행할 비동기 작업을 추가합니다. - 여기서 특정한 값이란 id에 들어가는 값을 의미합니다.
선언부와 리턴 값
task
의 선언부와 리턴 값은 다음과 같은데요.- parameter로 id, priority, 그리고 action이 선택적으로 들어갑니다.
func task<T>(
id value: T,
priority: TaskPriority = .userInitiated,
_ action: @escaping () async -> Void
) -> some View where T : Equatable
- 여기서 id는
Equatable
을 준수하는 타입의 값이고 priority는 TaskPriority
타입 값입니다. - action에는
onAppear
에서 처럼 실행할 코드를 넣으면 되지만, 비동기 작업을 하는 코드라는 것이 핵심입니다. - return 값으로는 view가 나타나기 전에 비동기 action을 실행하거나 id 값이 변경될때 task를 재시작하는 view가 리턴됩니다.
장점
task
가 onAppear
이 있음에도 필요한 이유는 좋은 점이 있기 때문이죠.task
의 좋은점은 task가 modifier로 추가된 view의 수명과 일치하는 수명을 갖기 때문입니다.- 즉,
task
에 넣은 작업이 완료되기 전에 View가 사라지면 SwiftUI가 task에 있던 작업을 취소하거나 재실행 해줍니다.
예시
task
를 사용하는 예시를 들어보겠습니다.- 우선 Message라는 구조체가 다음과 같이 있습니다.
struct Message: Decodable, Identifiable {
let id: Int
let text: String
}
- 그리고 다음과 같이 메시지들을 불러오는 비동기 작업을 하는
loadMessages
라는 함수가 있습니다. loadMessages
와 같은 함수를 실행할때 task
에서 실행하면 훨씬 적절한 타이밍에 실행할수 있습니다.
struct ContentView: View {
@State private var messages = [Message]()
var body: some View {
List(messages) { message in
VStack {
Text(message.text)
}
}
.task {
await loadMessages()
}
}
func loadMessages() async {
do {
let url = URL(string: "https://example.com/messages.json")!
let (data, _) = try await URLSession.shared.data(from: url)
messages = try JSONDecoder().decode([Message].self, from: data)
} catch {
messages = [
Message(id: 0, text: "Please try again later.")
]
}
}
}
추가 기능
task
를 사용하는 효과가 더욱 부각되는 것은 id 값을 활용할 때 입니다.task
에 Equatable
을 준수하는 id 값을 넣는다고 생각해봅시다.- 이 때, id 값이 변경되면 SwiftUI는 자동으로 이전의 작업을 취소하고 새로운 값으로 새 작업을 생성합니다.
- 예를들어, 다시 한번 다음과 같이
Message
구조체가 있다고 생각해봅시다.
struct Message: Decodable, Identifiable {
let id: Int
let text: String
}
- 이전의 예시와는 다르게,
selectedBox
라는 변수가 추가 되었습니다. - 이는
.task(id: selectedBox)
에서 사용되고 있는데요. selectedBox
의 값에 따라 다른 url에서 data를 fetch 해오고 작업이 재실행 되는 것을 볼 수 있습니다.
struct ContentView: View {
@State private var messages = [Message]()
@State private var selectedBox = "Inbox"
let messageBoxes = ["Inbox", "Sent"]
var body: some View {
NavigationView {
List(messages) { message in
VStack {
Text(message.text)
}
}
.task(id: selectedBox) {
await fetchData()
}
}
}
func fetchData() async {
do {
let url = URL(string: "https://example.com/\(selectedBox.lowercased()).json")!
let (data, _) = try await URLSession.shared.data(from: url)
messages = try JSONDecoder().decode([Message].self, from: data)
} catch {
messages = [
Message(id: 0, text: "Please try again later.")
]
}
}
}
- 추가적으로,
task
modifier는 priority 파라미터도 존재해서 task의 우선순위도 정해줄수 있습니다. - 예를들어,
.task(priority: .low)
는 낮은 우선 순위의 작업을 생성 할 것입니다.
참고