- 이번에는 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)는 낮은 우선 순위의 작업을 생성 할 것입니다.
참고