문제 상황
앱에서 fetch를 연속적으로 하게 되면 위와 같이 data race가 발생하는 것을 확인했습니다.
해결 과정
문제 파악
간단한 Top100Store 부터 살펴보니 fetch() 함수 부분에서 top100 = fetchedTop100 이 fetch 마다 호출이 되기 때문에 발생하는 문제였습니다. 비동기적으로 top100 이 여러 번 업데이트되면서 동시성 문제가 발생하기 때문입니다.
@Observable
final class Top100Store {
let useCase = FetchUseCase()
var top100: Top100Entity?
@ObservationIgnored
@AppStorage("userId") var userId = ""
init() {
fetch()
}
init(top100: Top100Entity) {
self.top100 = top100
}
func fetch() {
Task {
do {
let fetchedTop100 = try await useCase.fetchTop100(userId: userId)
top100 = fetchedTop100 // 🐛 문제 발생 부분
} catch {
print("Error to fetch top100")
}
}
}
}
문제 해결
1. isFetching 변수 추가 : 중복 fetch 호출을 방지하기 위해 isFetching 변수를 추가했습니다.
- fetch 함수가 여러 번 호출되는 것을 방지하기 위해, isFetching 변수를 도입했습니다.
- fetch 함수 시작 시, isFetching을 true로 설정하고, 작업이 끝나면 false로 설정합니다.
2. 메인 스레드에서 상태 업데이트: 비동기 작업이 완료된 후, DispatchQueue.main.async를 사용해 메인 스레드에서 상태를 업데이트하도록 했습니다.
- 비동기 작업이 완료된 후, DispatchQueue.main.async를 사용해 top100을 메인 스레드에서 업데이트합니다. 이렇게 하면 UI 관련 업데이트가 메인 스레드에서 안전하게 이루어질 수 있습니다.
수정 코드
import SwiftUI
final class Top100Store: ObservableObject {
private let useCase = FetchUseCase()
@Published var top100: Top100Entity?
@Published var isFetching = false
init() {
fetch()
}
init(top100: Top100Entity) {
self.top100 = top100
}
func fetch() {
guard !isFetching else { return }
guard let userId = UserDefaults.standard.string(forKey: "userId") else {
print("Failed to retrieve userId from UserDefaults")
return
}
isFetching = true
Task {
defer {
DispatchQueue.main.async {
self.isFetching = false
}
}
do {
let fetchedTop100 = try await useCase.fetchTop100(userId: userId)
DispatchQueue.main.async {
self.top100 = fetchedTop100
}
} catch {
print("Error to fetch top100")
}
}
}
}
'→ Solver' 카테고리의 다른 글
[Project-Solver] 프로젝트 2차 배포 (v1.2.0) (0) | 2024.07.08 |
---|---|
[Project-Solver] 프로젝트 1차 배포 (v1.0.1) (0) | 2024.05.01 |
[Project-Solver] SwiftData 문제 기록 (0) | 2024.04.25 |
[Project-Solver] 앱 기본 설계 (2) | 2024.04.22 |
[Project-Solver] 아키텍처 설계 (0) | 2024.04.21 |