Search code examples
swiftsqlitecore-datansfetchedresultscontrolleruisearchbardelegate

Working with big base in Core Data via fetchedResultsController


Maybe someone has experience of work with big Core Data bases (i have 32k of rows in my base, and i need to show all this base to the user and make some search in it) and when i'll try to read the base in to my fetchedResultsController i have 3-5 seconds delay, how i can fix this? Can i read this base in some background thread? or if i make separate my base on some parts via relations is it help? i try to load my base in viewDidAppear

    override func viewDidAppear(_ animated: Bool) {
        DispatchQueue.main.async {
            self.connectFetchedRequest()
            self.tableView.reloadData()
            self.indicatorLoad.stopAnimating()
        }
    }

    func connectFetchedRequest() {
        do {
            try self.fetchedResultsController.performFetch()
        }catch {
            print(error)
        }
    }

for search in the base i use UISearchBarDelegate method (but i still have some "lags" when add characters to the searchText)

    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        block?.cancel()
        block = DispatchWorkItem {
            let predicate = NSPredicate(format: "cardName contains[c] %@", searchText)
            self.fetchedResultsController.fetchRequest.predicate = predicate
            self.connectFetchedRequest()
            DispatchQueue.main.async(execute: {
                self.tableView.reloadData()
            })
        }
        DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 0.5, execute: block!)
    }

i created fetchedResultsController with the following code

    lazy var context: NSManagedObjectContext = {
        let appDelegate = (UIApplication.shared.delegate as? AppDelegate)
        return appDelegate!.managedObjectContext
    }()

    lazy var fetchedResultsController: NSFetchedResultsController<CardsBaseClass> = {
        let fetchRequest = NSFetchRequest<CardsBaseClass>(entityName: "CardsBase")
        let sortDescriptor = NSSortDescriptor(key: "cardName", ascending: true)
        fetchRequest.sortDescriptors = [sortDescriptor]

        fetchRequest.fetchBatchSize = 50
        fetchRequest.returnsObjectsAsFaults = false

        let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.context, sectionNameKeyPath: nil, cacheName: nil)
        fetchedResultsController.delegate = self
        return fetchedResultsController
    }()

for add new predicates from other viewController (which i open .overFullScreen so my main view with base is open too) i use the following code

predicateArrayClass.addPredicate(predicate: predicateType)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "updateTableViewByNewPredicate"), object: nil, userInfo: nil)
self.dismiss(animated: true, completion: nil)

where i called the update fetchResultController func

func updateTableViewByNewPredicate() {
        searchController.searchBar.text! = ""
        let predicateArray = predicateArrayClass.arrayOfPredicates
        block = DispatchWorkItem {
            self.fetchedResultsController.fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicateArray)
            self.connectFetchedRequest()
            DispatchQueue.main.async(execute: {
                self.tableView.reloadData()
            })
        }
        DispatchQueue.global(qos: .background).async(execute: block!)
}

Solution

  • Here's some guidelines:

    1. Use batching. Set fetchBatchSize of your fetch request to something like 50, and returnsObjectsAsFaults to NO.
    2. Use estimated heights in your table view. Either estimatedRowHeight or -tableView:estimatedHeightForRowAtIndexPath: of UITableViewDelegate will do.

    This will prevent loading all of the 32K records in memory on each reload.