관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - SwiftUI] FetchRequest, FetchedResults 사용 방법 (Core Data) 본문

iOS 기본 (SwiftUI)

[iOS - SwiftUI] FetchRequest, FetchedResults 사용 방법 (Core Data)

jake-kim 2022. 10. 30. 23:40

목차) SwiftUI의 기본 - 목차 링크

FetchRequest 란?

https://developer.apple.com/documentation/swiftui/fetchrequest

  • SwiftUI에서는 Core Data 가져오기 요청 작업을 위한 전용 property wrapper를 지원하고 이것이 FetchRequest
  • FetchRequest는 SwiftUI 뷰에 별도 로직 없이 직접 추가가 가능
  • @FetchRequest 사용 시 데이터 id와 정렬에 사용되는 값을 선언하여 사용
@FetchRequest(
  entity: Quake.entity(), 
  sortDescriptors: [SortDescriptor(\.time, order: .reverse)]
)
private var quakes: FetchedResults<Quake>
  • 이 밖에도 predicate에는 NSPredicate(format:_:)를 추가하여 쿼리 조건을 추가 가능
@FetchRequest(
  entity: Quake.entity(), 
  sortDescriptors: [SortDescriptor(\.time, order: .reverse)],
  predicate: NSPredicate(format: "name == %jake", ascending: true)
)
private var quakes: FetchedResults<Quake>

Core Data 모델 추가 (FetchRequest 예제를 위해서 데이터 준비)

  • Data Model

  • Add Entity 클릭 -> Item이라는 Entity 추가

  • name attribute도 추가

  • 만약 SwiftUI의 @FetchRequest를 사용하지 않으면 서비스 레이어에서 fetchRequest를 정의하여 사용

FetchRequest

ex) SwiftUI 없이 그냥 Swift 사용할땐 아래처럼 fetchAll() 하여 데이터를 불러오는 코드가 존재

* CoreData를 다루는 상세한 개념은 이전 포스팅 글 참고

import CoreData

struct MyItemModel {
  var name: String
}

class MyItemRepository {
  let coreDataStorage: CoreDataStorage
  
  init(coreDataStorage: CoreDataStorage = CoreDataStorage.shared) {
    self.coreDataStorage = coreDataStorage
  }
  
  func add(name: String) {
    let context = coreDataStorage.taskContext()
    if let savedItem = fetch(name, in: context) {
      savedItem.updatedDate = Date()
    } else {
      create(name, in: context)
    }
    
    context.performAndWait {
      do {
        try context.save()
      } catch {
        print(error)
      }
    }
  }
  
  private func fetch(_ name: String, in context: NSManagedObjectContext) -> MyItem? {
    let fetchRequest = MyItem.fetchRequest()
    fetchRequest.predicate = NSPredicate(format: "name == %@", argumentArray: [name])
    do {
      return try context.fetch(fetchRequest).first
    } catch {
      print("fetch for update Person error: \(error)")
      return nil
    }
  }
  
  fileprivate func create(_ name: String, in context: NSManagedObjectContext) {
    let item = MyItem(context: context)
    item.name = name
    item.updatedDate = Date()
  }
  
  func getItems() -> [MyItemModel] {
    fetchAll()
      .compactMap(\.name)
      .map(MyItemModel.init)
  }
  
  private func fetchAll() -> [MyItem] {
    let request = MyItem.fetchRequest()
    request.sortDescriptors = [NSSortDescriptor(keyPath: \MyItem.updatedDate, ascending: false)]
    do {
      return try coreDataStorage.viewContext.fetch(request)
    } catch {
      print("fetch Person error: \(error)")
      return []
    }
  }
}
  • SwiftUI에서는 위 fetchAll() 없이도, @FetchRequest로 데이터 로드를 간편하게 구현이 가능
    • Fetch만 SwiftUI 방법을 쓸 것이고, 저장은 위에서 구현한 Swift방식대로 할것이기 때문에 위에서 정의한 MyItemRepository()를 통해 예제 데이터 추가
import SwiftUI
import CoreData

struct ContentView: View {
  let repo = MyItemRepository()
  
  var body: some View {
    Text("Hello, world!")
      .padding()
      .onAppear {
        repo.add(name: "jake")
        repo.add(name: "kim")
        print(repo.getItems())
        // [ExCoredata.MyItemModel(name: "kim"), ExCoredata.MyItemModel(name: "jake")]
      }
    }
}
  • NSManagedObjectContext값을 해당 뷰에 주입해야 coreData 스토리지에 접근할 수 있기 때문에 부모로 부터 주입받을 수 있는 @Environment 사용하여 주입
@Environment(\.managedObjectContext) private var viewContext

* NSManagedObjectContext 란? 위 CoreDataStorage를 정의할때도 나왔지만, 이 값은 유효성 검사, redu/undo와 같은 기능 처리를 할 수 있게끔 Core Data 스토리지 라이프 사이클을 담당

https://developer.apple.com/documentation/coredata/nsmanagedobjectcontext

context 주입 코드)

  • core data 사용하는 뷰
struct ContentView: View {
  @Environment(\.managedObjectContext) private var viewContext
  ...
}
  • 부모 뷰에서 .environment를 통해 CoreDataStorage의 context 주입
@main
struct ExCoredataApp: App {
  let viewContext = CoreDataStorage.shared.viewContext
  var body: some Scene {
    WindowGroup {
      ContentView()
        .environment(\.managedObjectContext, viewContext)
    }
  }
}
  • @FetchRequest를 선언하고, 타입은 FetchResults로 정의하여 CoreData에 접근하는 프로퍼티 items 추가
struct ContentView: View {
  @Environment(\.managedObjectContext) private var viewContext
  @FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \MyItem.updatedDate, ascending: false)])
  private var items: FetchedResults<MyItem> // <-

  ...
}
  • items 사용은 ForEach로 접근하여 사용
struct ContentView: View {
  @Environment(\.managedObjectContext) private var viewContext
  @FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \MyItem.updatedDate, ascending: false)])
  private var items: FetchedResults<MyItem>
  let repo = MyItemRepository()
  
  var body: some View {
    VStack {
      ForEach(items) { item in
        Text(item.name!)
      }
      Text("Hello, world!")
        .padding()
        .onAppear {
          print(items.count)
          print(repo.getItems().count)
          // 샘플 데이터 준비
          // repo.add(name: "jake")
          // repo.add(name: "kim")
        }
    }
  }
}

결과) kim, jake 데이터가 정상 표출되는 것을 확인

@FetchRequest로 CoreData 조회

  • 따로 CoreDataSotorage를 정의하여 사용하지 않아도, SwiftUI 스럽게 해당 뷰에서 addItem 메소드를 추가하여 코어데이터에 접근하는 방식으로 구현도 가능
  // ContentView.swift
  
  private func addItem(_ name: String) {
    withAnimation {
      let newItem = MyItem(context: viewContext)
      newItem.updatedDate = Date()
      newItem.name = name
      do {
        try viewContext.save()
      } catch {
        print(error)
      }
    }
  }
  • 버튼을 누를경우 addItem 호출
var body: some View {
  VStack {
    ForEach(items) { item in
      Text(item.name!)
    }
    Text("Hello, world!")
      .padding()
      .onAppear {
        print(items.count)
        print(repo.getItems().count)
        // 샘플 데이터 준비
        // repo.add(name: "jake")
        // repo.add(name: "kim")
      }
    Button("Add 'jake123'") {
      addItem("jake123")
    }
  }
}

완성) 버튼을 누를때마다 Core Data에 write하면 FetchResult 프로퍼티인 items이 업데이트

 

* 전체 코드: https://github.com/JK0369/ExCoreData

 

* 참고

https://developer.apple.com/documentation/coredata/nsmanagedobjectcontext

https://medium.com/@kyang3200/ios-and-swift-how-to-use-core-data-with-fetchrequest-5e7be62a3092

https://developer.apple.com/documentation/swiftui/fetchrequest

 

Comments