채널톡 아이콘

인프런 커뮤니티 질문&답변

ycc3819님의 프로필 이미지

작성한 질문수 8

[Lv.2] SwiftUI 중급 - Core Data와 SwiftData로 앱 완성하기

Core Data with MVVM

CoreData Array의 변화에 따른 SwiftUI View 변화 적용(with @Observable Macro)

작성

·

54

0

지난번과 동일하게 자꾸, @Observation을 활용하려다보니 문제가 많이 생기는 것 같습니다.

정확하게 차이를 이해하려고 강의를 돌려봐도 제가 이해하고 있는 바와 다른 점들이 있는 것 같아 질문 드립니다.

 

강의의 내용은 CoreData를 다룰 때, fetch로 불러온 것을 View로 넘기기위한 변수

var savedEntities: [Fish] 를 만들고, :ObservableObject protocol을 활용하셨습니다.

image.png

 

이후 View에서 ViewModel을 처음 초기화할 때 사용하기 위해서 @Stateobject를 활용하여 설명을 하셨고요.

image.png

 

그렇다면, Observable MacroMigrating하기 진행하는데에도 문제가 없다고 생각을 했습니다.

그래서, 아래와 같이 코드를 바꾸어 보았습니다.

image.pngimage.png

 

그런데 문제는 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를 적용하는데에 있어서 제가 잘못 이해하고 있는 부분이 있을까요?

image.png

var fruitArray: [FruitModel] 는 View에서 변화를 감지하여 실시간 Update가 가능한 반면

var savedEntities: [Fish] 는 왜 변경되어도 View에서 변화를 감지 하지 못하는지 궁금합니다. 감사합니다.

 

 

 

 

답변 2

1

Jacob(제이콥)님의 프로필 이미지
Jacob(제이콥)
지식공유자

안녕하세요 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 인턴님의 프로필 이미지

안녕하세요, 인프런 AI 인턴입니다. ycc3819님.

SwiftUI에서 @Observable@ObservableObject의 차이에 대한 이해는 중요한 부분입니다. CoreData와 연계한 프로젝트에서 @Observable 매크로를 사용할 때 몇 가지 주의할 점과 원리를 설명드리겠습니다.

차이점 및 주의할 점

  1. Observable의 원리:

    • @ObservableObject는 SwiftUI에서 일반적으로 사용되는 관찰 가능한 객체로, @Published 프로퍼티를 통해 상태 변화를 트리거하여 뷰를 업데이트합니다.
    • @Observable 매크로는 Swift 5.5에서 새롭게 도입된 기능으로, @Published의 역할을 더 자동화해서 상태 변화를 감지합니다.
  2. CoreData와의 통합:

    • CoreData에서 NSManagedObject들은 자체적으로 KVO(Key-Value Observing)를 지원하지 않습니다. 따라서 직접적으로 변화를 감지하기 보다는 NSFetchedResultsController나 변경 사항을 수동으로 반영하는 구조가 필요합니다.
    • CoreData로부터 데이터를 불러오고, 관리하는 NSManagedObject 인스턴스를 @Observable로만 감지하기 힘들 수 있습니다.
  3. 기본 데이터 컨테이너와의 차이:

    • fruitArray: [FruitModel] 의 경우, Swift의 기본 타입 배열이기 때문에 SwiftUI에서 손쉽게 변화를 감지하고 반영할 수 있습니다.
    • 반면에 savedEntities: [Fish]는 CoreData에서 관리되는 엔티티로, ViewModel에서 수동으로 반영해야 하는 경우가 많습니다.

어떻게 해결할 수 있을까요?

  • 수동 반영: CoreData의 데이터가 변경될 때 직접적으로 savedEntities 배열을 다시 설정하거나, 해당 배열의 변화가 뷰에 반영되도록 추가적으로 처리해야 합니다.

  • NSFetchedResultsController 사용**: CoreData의 데이터 변경 사항을 실시간으로 감지하여 뷰 모델에서 이를 반영하는 방법 중 하나입니다.

  • MVC 구조 검토 및 리팩터링: 상태 관리 방식이나 뷰 모델의 상태 반영 구조를 재검토하고 리팩터링하는 것도 좋은 방법입니다.

기존 문제 해결 관련 게시물 참조:
- 강의 Weak Self 코드 실행시 오류에 대해: 기본적인 상태 관리와 뷰 모델 사용하는 방법에 대해 확인할 수 있는 레퍼런스입니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.