SwiftUI 앱을 개발하다 보면, RestAPI를 통해 서버에서 데이터를 불러와 화면에 표시해야 할 때가 많습니다. 특히 SwiftUI에서 데이터를 언제, 어떻게 로드할지는 앱의 성능과 사용자 경험에 중요한 영향을 미칩니다. 이 글에서는 MVVM 패턴을 기반으로 **.task {}, .onAppear {}, ViewModel init**의 각각의 데이터 로드 방식을 비교하고, 상황별로 어떤 방법이 더 적합할지 살펴보겠습니다.
SwiftUI와 MVVM 패턴 간단히 알아보기
SwiftUI는 MVVM 패턴을 사용하기에 최적화된 프레임워크입니다. MVVM 패턴을 사용하면 UI와 비즈니스 로직을 깔끔하게 분리할 수 있어, 유지보수성이 높아지고 코드가 더욱 직관적이 됩니다. 예제에서 사용할 MVVM 패턴의 구조를 간단히 설명하자면:
- Model: 데이터 구조 정의 및 서버 응답을 처리하는 부분입니다.
- ViewModel: 네트워크 요청 등 비즈니스 로직을 관리하고, View에서 사용할 데이터를 준비합니다.
- View: ViewModel의 상태를 관찰하여 UI를 업데이트하는 부분으로, 화면의 UI를 담당합니다.
1. .task {}를 사용해 데이터 로드하기
.task {}는 SwiftUI에서 화면이 나타날 때 자동으로 비동기 작업을 실행할 수 있는 뷰 수정자입니다. 예를 들어, 화면이 로드될 때 API에서 데이터를 가져오거나, 사용자가 화면에 다시 돌아올 때 데이터를 새로고침해야 할 때 유용합니다. .task {}는 뷰가 사라지면 작업을 자동으로 취소해 주기 때문에 리소스 관리가 효율적입니다.
.task {} 예제 코드
// ExampleView.swift
import SwiftUI
struct ExampleView: View {
@StateObject private var viewModel = ExampleViewModel()
var body: some View {
VStack {
if viewModel.isLoading {
ProgressView("Loading...")
} else if let errorMessage = viewModel.errorMessage {
Text("Error: \(errorMessage)")
.foregroundColor(.red)
} else {
List(viewModel.items) { item in
VStack(alignment: .leading) {
Text(item.name).font(.headline)
Text(item.description).font(.subheadline)
}
}
}
}
.task {
await viewModel.fetchData()
}
}
}
장단점 요약
- 장점: 비동기 작업을 수행하며, 뷰가 사라질 때 작업을 자동으로 취소해 메모리 관리가 용이합니다.
- 단점: 재등장할 때마다 실행되므로 반복 실행을 제어해야 하며, SwiftUI 3.0 이상에서만 사용 가능합니다.
2. .onAppear {}를 사용해 데이터 로드하기
.onAppear {}는 SwiftUI에서 뷰가 나타날 때 특정 동작을 수행하도록 하는 뷰 수정자입니다. 간단한 동작에 적합하며, SwiftUI 2.0에서도 사용할 수 있어 하위 호환성도 좋습니다. 다만, 작업 자동 취소 기능은 없으므로 리소스 관리가 필요할 수 있습니다.
.onAppear {} 예제 코드
import SwiftUI
struct ExampleView: View {
@StateObject private var viewModel = ExampleViewModel()
var body: some View {
VStack {
if viewModel.isLoading {
ProgressView("Loading...")
} else if let errorMessage = viewModel.errorMessage {
Text("Error: \(errorMessage)")
.foregroundColor(.red)
} else {
List(viewModel.items) { item in
VStack(alignment: .leading) {
Text(item.name).font(.headline)
Text(item.description).font(.subheadline)
}
}
}
}
.onAppear {
Task {
await viewModel.fetchData()
}
}
}
}
장단점 요약
- 장점: 간단한 UI 초기화에 적합하며, 하위 호환성이 좋습니다.
- 단점: 작업 자동 취소 기능이 없으므로 비동기 작업을 수동으로 관리해야 합니다.
3. ViewModel의 init에서 데이터 로드하기
ViewModel의 init에서 데이터를 불러오는 방식은 뷰가 생성될 때 데이터를 자동으로 로드하도록 합니다. 한 번만 데이터를 불러오면 충분한 경우에 적합하며, 뷰 코드가 간결해집니다. 다만, 화면이 사라질 때 작업이 자동 취소되진 않으므로, 메모리 관리에 주의가 필요합니다.
ViewModel의 init 예제 코드
import Foundation
import SwiftUI
@MainActor
class ExampleViewModel: ObservableObject {
@Published var items: [ExampleResponse] = []
@Published var isLoading: Bool = false
@Published var errorMessage: String? = nil
private let networkManager = NetworkManager()
init() {
Task {
await fetchData()
}
}
func fetchData() async {
isLoading = true
errorMessage = nil
do {
let data: [ExampleResponse] = try await networkManager.getRequest(endpoint: "/example")
items = data
} catch let error as NetworkError {
switch error {
case .invalidResponse(let statusCode):
errorMessage = "서버 응답 오류, 상태 코드: \(statusCode)"
case .decodingFailed(let description):
errorMessage = "디코딩 실패: \(description)"
}
} catch {
errorMessage = "알 수 없는 오류: \(error)"
}
isLoading = false
}
}
장단점 요약
- 장점: 초기화 시 데이터 로드가 필요할 때 적합하며, 뷰 코드가 간결해집니다.
- 단점: 작업 자동 취소 기능이 없으며, 데이터 갱신이 필요한 경우 추가 관리가 필요합니다.
세 가지 방식 비교 요약
방법.task {} 사용.onAppear {} 사용ViewModel init 사용
적합한 상황 | 화면이 나타날 때마다 데이터를 새로고쳐야 하는 경우 | 단순한 UI 초기화가 필요한 경우 | 한 번만 데이터를 로드해도 충분한 경우 |
장점 | - 비동기 작업 수행 - 뷰가 사라질 때 자동 취소 | - 간단한 동작에 적합 - 하위 호환성이 좋음 | - 코드가 간결해짐 - 초기화 시점 명확 |
단점 | - 반복 실행 시 조건 제어 필요 | - 작업 자동 취소 기능 없음 - 비동기 작업 관리 필요 | - 작업 자동 취소 없음 - 데이터 갱신 시 별도 관리 필요 |
결론: 어떤 방식이 더 적합할까?
어떤 방식이 더 적합할지는 앱의 요구사항에 따라 달라집니다.
- 주기적으로 데이터를 새로 고쳐야 하는 경우라면 .task {}가 적합합니다.
- 간단한 초기화 작업이나 UI 설정에는 .onAppear {}를 사용하는 것이 깔끔합니다.
- 한 번만 데이터를 불러오면 충분한 경우라면 init을 사용해 뷰 코드의 간결함을 유지하는 것이 좋습니다.
'iOS > SwiftUI' 카테고리의 다른 글
SwiftUI에서 Combine을 활용해서 화면 회전 감지하기 (0) | 2024.11.10 |
---|---|
SwiftUI에서 각 탭마다 독립적인 네비게이션 스택 유지하기 (0) | 2024.11.08 |
SwiftUI에서 UIKit의 `UINavigationController`로 화면 전환 구현하기 (0) | 2024.11.07 |