ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Ch5. NSFetchedResultsController
    Ray Wenderlich/CoreData 2022. 10. 27. 22:34

    NSFetchedResultsController의 세 가지 장점

    1. Section
    2. Cache
    3. Monitoring

    이전 챕터에서 봤듯, CoreData는 UITableView와 잘 어울린다.

    NSFetchedResultsController를 사용하면 UITableView와의 조합을 더 잘 활용할 수 있다.

    NSFetchedResultsController는 NSFetchRequest를 감싸며 FetchResult를 내부에 갖고 있다. 그래서 fetch하는 메소드의 리턴타입이 Void다.

    lazy var fetchedResultsController: NSFetchedResultsController<Team> = {
        let fetchRequest: NSFetchRequest<Team> = Team.fetchRequest()
        let zoneSort = NSSortDescriptor(key: #keyPath(Team.qualifyingZone), ascending: true)
        let scoreSort = NSSortDescriptor(key: #keyPath(Team.wins), ascending: false)
        let nameSort = NSSortDescriptor(key: #keyPath(Team.teamName), ascending: true)
        fetchRequest.sortDescriptors = [zoneSort, scoreSort, nameSort]
        
        let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
                                                                  managedObjectContext: coreDataStack.managedContext,
                                                                  sectionNameKeyPath: #keyPath(Team.qualifyingZone),
                                                                  cacheName: "worldCup")
        return fetchedResultsController
      }()
    
    // 리턴값을 사용하지 않음
    do {
      try fetchedResultsController.performFetch()
    } catch let error as NSError {
      print("Fetching error: \\(error), \\(error.userInfo)")
    }
    
    // fetchedResultsController는 내부에 sections를 갖고 있음
    // open var sections: [NSFetchedResultsSectionInfo]? { get }
    func numberOfSections(in tableView: UITableView) -> Int {
      fetchedResultsController.sections?.count ?? 0
    }
    
    func tableView(_ tableView: UITableView,
                   numberOfRowsInSection section: Int) -> Int {
      guard let sectionInfo = fetchedResultsController.sections?[section] else {
        return 0
      }
      
      return sectionInfo.numberOfObjects
    }
    

    Modifying data

    NSFetchRequest 쓸 때랑 비슷하다.

    func tableView(_ tableView: UITableView,
                   didSelectRowAt indexPath: IndexPath) {
      let team = fetchedResultsController.object(at: indexPath)
      team.wins += 1
      coreDataStack.saveContext()
    }
    

    Cache

    on-disk 캐시다. 앱을 재실행해도 캐시의 혜택을 볼 수 있다.

    NSFetchedResultsController를 선언할 때, cacheName을 지정할 수 있음.

    캐시는 변화에 민감하기 때문에, 변경사항이 있다면 캐시 이름을 바꾸거나 deleteCache(withName:)를 사용하여 캐시를 삭제하자.

    Grouping a set of fetched results into sections is an expensive operation. Avoid having to compute sections multiple times by specifying a cache name on your fetched results controller.

    Monitoring changes

    NSFetchedResultsControllerDelegate를 구현하여 변화를 감지할 수 있다. 하지만 NSFetchedResultsController를 생성할 때 사용된 ManagedObjectContext에서 발생한 변화만 감지가능하다.

    extension ViewController: NSFetchedResultsControllerDelegate {
      func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        tableView.reloadData()
      }
    }
    
    extension ViewController: NSFetchedResultsControllerDelegate {
      func controllerWillChangeContent(_ controller:
        NSFetchedResultsController<NSFetchRequestResult>) {
          tableView.beginUpdates()
      }
      func controller(_ controller:
                      NSFetchedResultsController<NSFetchRequestResult>,
                      didChange anObject: Any,
                      at indexPath: IndexPath?,
                      for type: NSFetchedResultsChangeType,
                      newIndexPath: IndexPath?) {
        switch type {
        case .insert:
          tableView.insertRows(at: [newIndexPath!], with: .automatic)
        case .delete:
          tableView.deleteRows(at: [indexPath!], with: .automatic)
        case .update:
          let cell = tableView.cellForRow(at: indexPath!) as! TeamCell
          configure(cell: cell, for: indexPath!)
        case .move:
          tableView.deleteRows(at: [indexPath!], with: .automatic)
          tableView.insertRows(at: [newIndexPath!], with: .automatic)
        @unknown default:
          print("Unexpected NSFetchedResultsChangeType")
        }
      }
      
      func controller(_ controller:
                      NSFetchedResultsController<NSFetchRequestResult>,
                      didChange sectionInfo: NSFetchedResultsSectionInfo,
                      atSectionIndex sectionIndex: Int,
                      for type: NSFetchedResultsChangeType) {
        let indexSet = IndexSet(integer: sectionIndex)
        switch type {
        case .insert:
          tableView.insertSections(indexSet, with: .automatic)
        case .delete:
          tableView.deleteSections(indexSet, with: .automatic)
        default: break
        }
      }
      
      func controllerDidChangeContent(_ controller:
        NSFetchedResultsController<NSFetchRequestResult>) {
          tableView.endUpdates()
      }
    }
    

    Inserting an underdog

    let team = Team(context: self.coreDataStack.managedContext)
    team.teamName = nameTextField.text
    team.qualifyingZone = zoneTextField.text
    team.imageName = "wenderland-flag"
    self.coreDataStack.saveContext()
    

    'Ray Wenderlich > CoreData' 카테고리의 다른 글

    Ch8. Measuring & Boosting Performance  (0) 2022.10.31
    Ch7. Unit Testing  (0) 2022.10.31
    Ch6. Versioning & Migration  (0) 2022.10.27
    Ch2. NSManagedObject Subclasses  (0) 2022.10.27
Designed by Tistory.