0

Today when i was making simple test

 func testCountSales() {
    measureMetrics([XCTPerformanceMetric_WallClockTime],    automaticallyStartMeasuring: false, for: {
        let employee = self.getEmployees()
        let employeeDetails = EmployeeDetailViewController()
        self.startMeasuring()
        _ = employeeDetails.salesCountForEmployees(employee)
        self.stopMeasuring()
    })
}
func getEmployees() -> Employee {
    let coreDataStack = CoreDataStack(modelName: "EmployeeDirectory")
    let request: NSFetchRequest<Employee> = NSFetchRequest(entityName: "Employee")

    request.sortDescriptors = [NSSortDescriptor(key: "guid", ascending: true)]
    request.fetchBatchSize = 1
    let results: [AnyObject]?
    do {
        results = try coreDataStack.mainContext.fetch(request)
    } catch _ {
        results = nil
    }

    return results![0] as! Employee
}

I wondered do fetchBachSize really working? I tried to see debug section the was full array (50 elements as it supposed to be).All of them were faults. ok. Then i tried to add Observer for FetchedResultsController's fetchedObject's count property

  var window: UIWindow?
lazy var coreDataStack = CoreDataStack(modelName: "EmployeeDirectory")

let amountToImport = 50
let addSalesRecords = true

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]?) -> Bool {

    importJSONSeedDataIfNeeded()

    guard let tabController = window?.rootViewController as? UITabBarController,
        let employeeListNavigationController = tabController.viewControllers?[0] as? UINavigationController,
        let employeeListViewController = employeeListNavigationController.topViewController as? EmployeeListViewController else {
            fatalError("Application storyboard mis-configuration. Application is mis-configured")
    }

    employeeListViewController.coreDataStack = coreDataStack
    employeeListViewController.addObserver(self, forKeyPath: "fetchedResultsController", options: [.new], context: nil)

    guard let departmentListNavigationController = tabController.viewControllers?[1] as? UINavigationController,
        let departmentListViewController = departmentListNavigationController.topViewController as? DepartmentListViewController else {
            fatalError("Application storyboard mis-configuration. Application is mis-configured")
    }

    departmentListViewController.coreDataStack = coreDataStack

    return true
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if keyPath == "fetchedResultsController" {
        print ("Gold")
        let a = window?.rootViewController as? UITabBarController
        let employeeListNavigationController = a?.viewControllers?[0] as? UINavigationController
        let b = employeeListNavigationController?.topViewController as? EmployeeListViewController
       print( b?.fetchedResultsController.fetchedObjects?.count)
    }

}

it showed me that it was nil then right away 50. and apparently they are also faults. Then why do we need fetchBatchSize and when it comes to play? And how? Please if someone have any idea i would appreciate very much

Ninja
  • 309
  • 7
  • 26

1 Answers1

3

When you specify non-zero fetchBatchSize to request core data will query the underlying persistent store and on receiving the result only the objects in the fetchBatchSize range of the resulting array will be fetched to memory other entries in the array are page faults.

Quote from apple :

If you set a nonzero batch size, the collection of objects returned when an instance of NSFetchRequest is executed is broken into batches. When the fetch is executed, the entire request is evaluated and the identities of all matching objects recorded, but only data for objects up to the batchSize will be fetched from the persistent store at a time. The array returned from executing the request is a proxy object that transparently faults batches on demand. (In database terms, this is an in-memory cursor.)

For example, lets assume your query results in a array of 1000 objects and you specified fetchBatchSize as 50. Then out of 1000 objects only first 50 will be converted to actual objects/entities and will be loaded in memory. While remaining 950 will be simply a page fault. When your code tries to access first 50 objects, objects will be accessed directly from memory and when your code tries to access 51th object, because object isn't there in memory (hence page fault) core data will gracefully construct the object by fetching next batch of data from persistent store and returns it to your code.

Using fetchBatchSize you can control the number of objects that you deal with in memory while working with large data set.

it showed me that it was nil then right away 50. and apparently they are also faults.

I believe you were expecting FetchedResultsController to fetch 1 object because you specified fetchBatchSize as 1. But it eventually fetched 50. So you are confused now isn't it?

fetchBatchSize will not alter the fetched objects count of FetchedResultsController it only affects the number of objects actually loaded into memory out of search result. If you really wanted to control number Of Objects fetched by FetchedResultsController you should rather consider using fetchLimit

Read :

https://developer.apple.com/documentation/coredata/nsfetchrequest/1506622-fetchlimit

Quote from apple:

The fetch limit specifies the maximum number of objects that a request should return when executed. If you set a fetch limit, the framework makes a best effort to improve efficiency, but does not guarantee it. For every object store except the SQL store, a fetch request executed with a fetch limit in effect simply performs an unlimited fetch and throws away the unasked for rows

Hope it helps

Sandeep Bhandari
  • 19,999
  • 5
  • 45
  • 78
  • 1
    `fetchLimit` and `fetchBatchSize` are not respected by a `FetchedResultsController` at all. A `FetchedResultsController` needs all the results to be in memory in order to know when an object is removed or added. – Jon Rose Dec 14 '17 at 05:41
  • Jon Rose, don't you know it is supposed to be by Apple or it is a bug? And if it is supposed to be please can "reveal" some link to doc about it? I tried to looking for it and was confused( – Ninja Dec 14 '17 at 11:07
  • @n-khasanov : NSFetchedResultsController not honoring FetchLimit is a known bug with NSFetchedResultsController. See this : https://stackoverflow.com/questions/4858228/nsfetchedresultscontroller-ignores-fetchlimit NSFetchedResultsController still honors `fetchBatchSize`. NSFetchedResultsController definitely does not load all objects to memory, if thats the case people would use Array rather than NSFetchedResultsController. NSFetchedResultsController works on the principle of page fault to lazy load data to memory from persistent store. – Sandeep Bhandari Dec 14 '17 at 11:18
  • @n-khasanov : FetchedResults controller loads only the objects of size fetchBatchSize to memory from persistent store and for the rest of the results it adds a page fault. Thats how it nows when an object added or removed. Every time you access the object to remove/add you end up fetching the object to memory. So obviously FetchedResults controller has all the info it needs to perform add or remove or move operation on data set that does not mean FetchedResults controller loads all objects in the result to memory. – Sandeep Bhandari Dec 14 '17 at 11:28
  • The major performance difference between array and FetchedResults controller is pagefault. – Sandeep Bhandari Dec 14 '17 at 11:28
  • Sandeep Bhandari you mean results of fetching represented by array are not following pagefault conception? please if you don't bother can you mention some link to docs about fetchedresultscontroller and fetchBatchSize (that they are working well in conjuction) because i found only topic about fetchedresultscontroller not honoring fetchBatchSize (((( please(( – Ninja Dec 14 '17 at 12:21