Multithreading behavior using a NSFetchedResultsController - ios

How can I change my code so it could run on different queue then the main queue:
- (NSFetchedResultsController *)fetchResultController{
if (_fetchResultController != nil) {
return _fetchResultController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSManagedObjectContext *context = [self managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Recipes"
inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"headline"
ascending:YES];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(category = %#)",_categoryToShown];
[fetchRequest setPredicate: predicate];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
_fetchResultController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];
_fetchResultController.delegate = self;
return _fetchResultController;
}

A NSFetchedResultsController works with UITableViews or UICollectionViews. These are part of the UIKit and so they should be touched only on the main queue.
In addition NSFetchedResultsController has been developed to get data through lazy loading features (data batches) in order to reduce the memory footprint.
So, what's your goal? Why do you need to change the queue you are running on?
Edit 1
when i click a button in my UI its segue to table view that fill the
data according to this fetch request.i want that when i click the
button its automatically segue to the table view so i need to write
this code with multithreading
If you want to achieve what you wrote in the comment you should create a fetch request and execute it in a background thread..In this way when you'll land on the segue results will be available on internal cache of the persistent store coordinator. This technique is called as "warming up the cache" by Marcus Zarra (CoreData weird behavior when data are loaded on background thread). With the following approach the usage of NSFetchedResultsController is not useful. So, before taking this approach why don't you try to play a bit with
[fetchRequest setFetchBatchSize:20];
If the table (or collection) will display 10 elements, 20 as batch size it's ok. Anyway Instruments is your friend.
In addition, I really suggest to have a look to Core Data performance is a balance.

Related

How do I handle a relationship with a NSFetchedResultsController?

Core data is already handling the relationship for me, do I need to build a new query to use the NSFetchedResultsController?
I have "Albums" that contain "Photos".
Album *anAlbum = [...getalbum...];
in the *cellForItemAtIndexPath: I'd like to do something with:
anAlbum.photos
However, I can't convert the indexPath to a NSSet member index. (obviously)
Without Core Data I'd typically just generate the query required myself. I'd like to make use of Core Data (again, obviously).
The "Photo" entity (anAlbum.photos is the relationship) contains the
asset url. I have no issue with the displaying it was more of a
concern of how do I use the NSSet (Core Data relationship) with the
NSFectchedResultsController -- or direct with the view
(collection/table).
First of all I would use a NSFetchedResultsController for this. This component is made to work in conjunction with tables and allows to load data in a lazy loading manner.
Second, the fetch request I would use should be run against Photo entity and not against Album. In other words, you should select all the photos that belong to a specific album.
Here the code, I would use...
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Photo" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"album == %#", anAlbum];
[request setPredicate:predicate];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"takeAt" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext sectionNameKeyPath:nil
cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
return _fetchedResultsController;
}
So now in your tableView:cellForRowAtIndexPath: you would use
Photo *photo = [_fetchedResultsController objectAtIndexPath:indexPath];
// access the album the photo belongs to
Album* album = photo.album;
You can grasp the main concepts for NSFetchedResultsController at Core Data Tutorial for iOS: How To Use NSFetchedResultsController.
Usually, you're going to convert the set to an NSArray (maybe with NSSet's allobjects) and perhaps sort it or filter it with a NSSortDescriptor or NSPredicate or both. Then, you can access the array by index using the index path section/row.
You can obviously use a NSMutableArray if that's what you need, but it is a pain to try to access NSSet items individually as you essentially have to enumerate them to find what you're looking for. Just create an array. Here's an example where I've done this:
NSArray *tableViewData = [NSArray arrayWithArray:[album.photos allObjects]];
Then, you just access the tableViewData objects with:
UIImage *obj = UIImageJPEGRepresentation([tableViewData objectAtIndex:indexPath.row], 0.50f);
Something like that.

Core Data fault fulfilled from database for - is normal?

I have problem with this adnnotation.I have downloaded several tutorials e.g.: http://www.raywenderlich.com/14742/core-data-on-ios-5-tutorial-how-to-work-with-relations-and-predicates and in this sample code "fault fulfilled" message is visible too. My question is:
It is possible to create and fetch data from Core Data without this
alert?
Does a very critical alert in Core Data?
My database looks like this:
And problem occur when I want to fetch data from "FailedBankDetails" table, e.g. "closeDate".
First I fetch all rows from "FailedBankInfo" table (code above):
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"FailedBankInfo" inManagedObjectContext:self.managedObjectContext];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"name" ascending:NO];
[fetchRequest setEntity:entity];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
Next I want to display data using table cell:
if ([[_fetchedResultsController objectAtIndexPath:indexPath] isKindOfClass:[FailedBankInfo class]]) {
FailedBankInfo *info = [_fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = info.name;
cell.detailTextLabel.text = [NSString stringWithFormat:#"%#, %#", info.city, info.details.closeDate];
}
I my opinion accessing to info.details.closeDate occur "fault fulfilled" alert, because when I change this e.g. to info.name this alert not appear.
There is other way to access to "closeDate" field related with a specific "FailedBankInfo" object?
I'm tired of solving this problem, please send me some good advice.
This is my alert:
2014-02-16 09:05:45.801 FailedBankCD[57158:70b] CoreData: annotation: fault fulfilled from database for : 0x8ba2d60
What you are seeing is normal - core data logging is enabled for your app (this will be under the arguments section of your debug scheme).
A "fault", despite the name, is not a problem. It's a core data optimisation whereby full objects are not retrieved from the database until their properties are accessed, at which point the "fault" is "fulfilled".
They didn't have beginners in mind when choosing the language, I think.

Bad performance for NSFetchedResultsController

I am trying to load items from CoreData into a UITableView. The initial way I did it was to simply grab all the objects from my BankInfo entity, stuff them into an array, and then use the array to populate the UITableViewCells:
- (NSMutableArray *) bankInfos
{
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"BankInfo" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error;
NSMutableArray *bankInfos = (NSMutableArray*)[context executeFetchRequest:fetchRequest error:&error];
return bankInfos;
}
I'd heard that NSFetchedResultsController could improve performance / memory management so I tried it out (basically implemented it the way Ray Wenderlich tutorial recommended):
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"FailedBankInfo" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"details.closeDate" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext sectionNameKeyPath:nil
cacheName:#"Root"];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
What I'm finding after using instruments to profile the code is that the NSFetchedResultsController is taking about twice as long to load the objects into the UITableView as my initial method was. This line in particular:
BankInfo *bankInfo = [_fetchedResultsController objectAtIndexPath:indexPath];
is taking 292 ms whereas loading the entire array of BankInfos is taking about 150 ms. Anyone know why this is?
The problem I was having didn't have to do with CoreData performance, but had to do with the fact that I was accidentally saving / loading full size images as thumbnails in the tableview. Once I fixed this, the performance issues went away.
Well, we are talking about ms, it is still quite fast.
The fetched results controller is doing a query to sqllite for each cell. You can turn on sqllite debug in xcode: -com.apple.CoreData.SQLDebug 1 and see for yourself.
The NSArray is populated, stored, and fetched entirely in memory.
The choice between an array and fetched controller is not to be done by taking 'speed' into account.
Basically, if you have a small array of objects, immutable while on screen, then you can safely choose NSArray as table datasource.
Instead, if you have lot of objects or planning to have a growing numbers of objects, that also need to be refreshed often, NSFetchedResultsController is the preferred choice.

how to sort a view-based tableview

I can't manage to sort a view-based tableview. I use an arrayController that control an entity of core data.
I tried to select a column and in the attribute inspector I used as a sort key the attributes name relative to that column and compare: as a selector... when I build and run I click on the header and now display the arrow that change every click, but the nothing happens with the rows. no sorting.
How can I fix it?
I think I'm missing something easy, but I can't get over it.
You may want to consider using an NSFetchedResultsController. From the Apple documentation for NSFetchedResultsController,
You use a fetched results controller to efficiently manage the results returned from a Core Data fetch request to provide data for a UITableView object.
The code looks like this where the line that provides the sort descriptor starts with NSSortDescriptor.
- (NSFetchedResultsController *)fetchedResultsController
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"YourEntityName"
inManagedObjectContext:yourManagedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"YourSortKey"
ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSError *error = nil;
NSArray *fetchedObjects = [yourManagedObjectContext executeFetchRequest:fetchRequest
error:&error];
if (fetchedObjects == nil) {
// Handle the error
}
yourFetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
return yourFetchedResultsController;
}

NSPredicate predicateWithFormat with object that changes over time

I have this working code, but now i need to be able to change the NSPredicate, based on the object used in the predicateWithFormat as follows:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Item" inManagedObjectContext:_context];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"self.atrObj == %#", _currentUser.atrObj.objectID]];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortContent = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortContent]];
[fetchRequest setFetchBatchSize:10];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:_context sectionNameKeyPath:nil
cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
self.fetchedResultsController.delegate = self;
I need the fetchedResultsControlelr to use the new _currentUser object when i change the _currentUser object from the app delegate which has this tableviewcontroller a property.
[self.tableViewcontroller setCurrentUser:user];
Thank you!
You can change the predicate anytime but be sure to re-fetch via performFetch. Also from Apple docs:
Important: Important If you are using a cache, you must call deleteCacheWithName: before changing any of the fetch request, its
predicate, or its sort descriptors. You must not reuse the same
fetched results controller for multiple queries unless you set the
cacheName to nil.
Also, from the Apple docs of NSFetchedResultsController:
Modifying the Fetch Request You cannot simply change the fetch request
to modify the results. If you want to change the fetch request, you
must:
If you are using a cache, delete it (using deleteCacheWithName:).
Typically you should not use a cache if you are changing the fetch
request.
Change the fetch request.
Invoke performFetch:.
Whenever the value of currentUser changes, do this (perhaps inside the setter):
NSError *error = nil;
self.fetchedResultsController.fetchRequest.predicate = [NSPredicate predicateWithFormat:#"atrObj == %#", self.currentUser.atrObj];
if(![self.fetchedResultsController performFetch:&error]) {
// Handle errors
}

Resources