작성
·
54
0
지난번과 동일하게 자꾸, @Observation
을 활용하려다보니 문제가 많이 생기는 것 같습니다.
정확하게 차이를 이해하려고 강의를 돌려봐도 제가 이해하고 있는 바와 다른 점들이 있는 것 같아 질문 드립니다.
강의의 내용은 CoreData를 다룰 때, fetch로 불러온 것을 View로 넘기기위한 변수
var savedEntities: [Fish]
를 만들고, :ObservableObject protocol
을 활용하셨습니다.
이후 View에서 ViewModel을 처음 초기화할 때 사용하기 위해서 @Stateobject
를 활용하여 설명을 하셨고요.
그렇다면, Observable Macro
로 Migrating하기 진행하는데에도 문제가 없다고 생각을 했습니다.
그래서, 아래와 같이 코드를 바꾸어 보았습니다.
그런데 문제는 Viewmodel
에서 정의된 updateFish()
method 가 적용되지 않습니다.
List에서 주어진 생선 이름을 tap했을 경우, "~" 가 추가가 되어야 하는데,
func updateFish(fish: Fish){
let currentName = fish.name ?? ""
let newName = currentName + "~"
fish.name = newName
saveData()
print("Updating fish name to: \(newName)")
}
와 같이 print를 찍어보았을 때, 디버그 창에서는 print가 되지만,
View에서는 "~"가 추가되지 않는 모습을 확인할 수 있었습니다.
CoreData
의 변화를 감지하지 못하는건지..
기초강의에서 FruitModel 예시에서는 ObservableObject
대신 Observable Macro
를 적용해도 되었었거든요.
여기서도 fruitArray이라는 배열을 사용했는데 잘 적용되었습니다.
@Observable Macro
를 적용하는데에 있어서 제가 잘못 이해하고 있는 부분이 있을까요?
var fruitArray: [FruitModel]
는 View에서 변화를 감지하여 실시간 Update가 가능한 반면
var savedEntities: [Fish]
는 왜 변경되어도 View에서 변화를 감지 하지 못하는지 궁금합니다. 감사합니다.
답변 2
1
안녕하세요 ycc3819 님.
질문해주신 @Observable을 활용한 코드에서, 클릭 시 ~가 View에 바로 반영되지 않고, 앱을 재시작하거나 새로운 데이터를 입력할 때 한 번에 업데이트되는 현상을 발견했습니다.
이 문제를 해결하기 위해 여러 시도를 해본 결과, @State와 @StateObject의 차이로 인해 Core Data와 View의 상태가 즉시 동기화되지 않았음을 알게 되었습니다.
1. Core Data에서 @StateObject를 사용하는 이유
• Core Data의 상태는 View가 재생성되더라도 유지되어야 합니다. 따라서, @StateObject로 ViewModel을 관리하는 것이 적합합니다.
• @StateObject는 ViewModel과 View의 생명주기를 연결하고, 상태가 변경될 때 View에 자동으로 반영되도록 보장합니다.
2. @State와 @StateObject의 선택 기준
• @State:
• 단순 상태(문자열, 정수, 부울 등)를 관리할 때 적합.
• View 외부와의 상태 공유가 필요하지 않은 경우 사용.
• @StateObject:
• Core Data, 비동기 작업, ViewModel 등 객체 기반 상태 관리가 필요할 때 적합.
• View와 ViewModel의 생명주기를 연결하고 상태를 유지.
3. 적용 예시
• Core Data와 같은 외부 데이터베이스를 관리할 때는 ViewModel에서 Core Data를 초기화하고, @StateObject를 통해 ViewModel과 View를 연결해야 데이터가 재사용되고 상태가 유지됩니다.
저 역시 질문을 통해 새로운 관점을 배울 수 있었고, 여러 시도를 통해 이 문제를 해결할 수 있었습니다. 질문해주셔서 감사드리며, 앞으로도 궁금한 점이 있으면 언제든지 알려주세요.
감사합니다.
Jacob
import SwiftUI
import CoreData
import Observation
@Observable // Observable 사용
class CoreDataInterViewModel2 {
let container: NSPersistentContainer
var savedEntities: [Fish] = [] // @Published 사용 안함
init() {
container = NSPersistentContainer(name: "FishContainer")
container.loadPersistentStores { (description, error) in
if let error = error {
print("ERROR LOADING CORE DATA. \(error)")
} else {
print("SUCCESSFULLY LOADED CORE DATA: \(description)")
}
}
fetchFish()
}
func fetchFish() {
let request = NSFetchRequest<Fish>(entityName: "Fish")
do {
savedEntities = try container.viewContext.fetch(request)
} catch {
print("ERROR FETCHING CORE DATA: \(error)")
}
}
func saveData() {
do {
try container.viewContext.save()
fetchFish()
} catch {
print("ERROR SAVING CORE DATA: \(error)")
}
}
func addFish(text: String) {
let newFish = Fish(context: container.viewContext)
newFish.name = text
saveData()
}
func deleteFish(indexSet: IndexSet) {
guard let index = indexSet.first else { return }
let entity = savedEntities[index]
container.viewContext.delete(entity)
saveData()
}
func updateFish(fish: Fish) {
let currentName = fish.name ?? ""
fish.name = currentName + "~"
saveData()
}
}
// MARK: - VIEW
struct CoreDataInter2: View {
@State private var textFieldText: String = ""
@StateObject var vm: CoreDataInterViewModel = .init() // 기존 @StateObject 를 사용
var body: some View {
NavigationStack {
VStack(spacing: 20) {
TextField("새로운 생선을 입력하세요", text: $textFieldText)
.textFieldStyle(.roundedBorder)
Button {
guard !textFieldText.isEmpty else { return }
vm.addFish(text: textFieldText)
textFieldText = ""
} label: {
Text("추가하기")
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
List {
ForEach(vm.savedEntities, id: \.self) { fish in
Text(fish.name ?? "이름 없음")
.onTapGesture {
vm.updateFish(fish: fish)
}
}
.onDelete { indexSet in
vm.deleteFish(indexSet: indexSet)
}
}
.listStyle(.plain)
}
.padding()
.navigationTitle("Fish Market")
}
}
}
#Preview {
CoreDataInter2()
}
0
안녕하세요, 인프런 AI 인턴입니다. ycc3819님.
SwiftUI에서 @Observable
과 @ObservableObject
의 차이에 대한 이해는 중요한 부분입니다. CoreData
와 연계한 프로젝트에서 @Observable
매크로를 사용할 때 몇 가지 주의할 점과 원리를 설명드리겠습니다.
Observable의 원리:
@ObservableObject
는 SwiftUI에서 일반적으로 사용되는 관찰 가능한 객체로, @Published
프로퍼티를 통해 상태 변화를 트리거하여 뷰를 업데이트합니다.@Observable
매크로는 Swift 5.5에서 새롭게 도입된 기능으로, @Published
의 역할을 더 자동화해서 상태 변화를 감지합니다.CoreData와의 통합:
NSManagedObject
들은 자체적으로 KVO(Key-Value Observing)를 지원하지 않습니다. 따라서 직접적으로 변화를 감지하기 보다는 NSFetchedResultsController
나 변경 사항을 수동으로 반영하는 구조가 필요합니다.CoreData
로부터 데이터를 불러오고, 관리하는 NSManagedObject
인스턴스를 @Observable
로만 감지하기 힘들 수 있습니다.기본 데이터 컨테이너와의 차이:
fruitArray: [FruitModel]
의 경우, Swift의 기본 타입 배열이기 때문에 SwiftUI에서 손쉽게 변화를 감지하고 반영할 수 있습니다.savedEntities: [Fish]
는 CoreData에서 관리되는 엔티티로, ViewModel에서 수동으로 반영해야 하는 경우가 많습니다.수동 반영: CoreData의 데이터가 변경될 때 직접적으로 savedEntities
배열을 다시 설정하거나, 해당 배열의 변화가 뷰에 반영되도록 추가적으로 처리해야 합니다.
NSFetchedResultsController 사용**: CoreData의 데이터 변경 사항을 실시간으로 감지하여 뷰 모델에서 이를 반영하는 방법 중 하나입니다.
MVC 구조 검토 및 리팩터링: 상태 관리 방식이나 뷰 모델의 상태 반영 구조를 재검토하고 리팩터링하는 것도 좋은 방법입니다.
기존 문제 해결 관련 게시물 참조:
- 강의 Weak Self 코드 실행시 오류에 대해: 기본적인 상태 관리와 뷰 모델 사용하는 방법에 대해 확인할 수 있는 레퍼런스입니다.
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.