After executing fetch request properties of fetched object are nil - ios

I am fetching an array of managed objects from some entity. After executeFetchRequest returns I NSLog elements and their properties and everything is fine. After I return my newly generated array and try to use it in a background thread or some other method the properties of managed objects inside the array become nil. This is the code:
Utakmice -NSManagedObject subclass
- (NSArray*)ucitajPodatke:(NSDate*)zaDatum drzavaId:(int)_drzavaId
{
NSManagedObjectContext *con = [[NSManagedObjectContext alloc] init];
[con setPersistentStoreCoordinator:persistentStoreCoordinator];
[con setStalenessInterval:0];
[con setUndoManager:nil];
// create request and predicate
// set return result type to NSManagedObjectResultType
...
...
return fetchedObjects; -> This works cause I can log everything and all values R OK...
}
-(void)SomeMethod
NSArray *array = [self ucitajPodatke:danas drzavaId:self.drzavaId];
Utakmice *tekma = [array objectAtIndex:0];
NSLog(#"%i", [tekma.uniqueId intValue]); -> everything is fine
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 0.35 * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
if (array != nil && [array count]>0)
{
Utakmice *tekma1 = [array objectAtIndex:0];
DLog(#"%#", tekma1.uniqueId);
DLog(#"%i", [tekma1.uniqueId intValue]); -> all properties have nil value
...
...
Any suggestions?? I really dont know where to go from here...
One more thing. In ucitajPodatke method, when I set return result type to NSDictionaryResultType -> everything is ok... (I need managed objects cause I need relations)...
Thx in advanced

The first thing you need to do, to keep strong reference of fetched array,
after that you can do your dispatch.
That will be good to call
[self performSelector:#selector(someMethod) withObject:nil afterDelay:0.35];
instead of dispatching.
This issue also can be related with "Data faulting".
The data is being fetched when you access to object fields.
If you want to fully fetch objects without faulting you can use
NSFetchRequest *request = ...;// your fetch request here
[request setReturnsObjectsAsFaults:YES];
// Fetch here

Ok I finally google it:
Core Data - sharing NSManagedObjects among multiple threads
It appears that U can not pass managed objects between threads, Instead U should pass managed object id's

Related

sort data using Realm

I have recently moved to Realm from Coredata. In my app I am showing 50K + contacts .
The contact object is in the format:
Contact: firstName, lastName ,company
I am trying to fetch all the contacts in the Realm , and I am trying to display those contacts similar to the native contacts app in iPhone.
First I am creating the section header titles based on the contact first name:
-(NSArray *)getSectionTitleBasedOn:(NSString*)sortBy{
RLMResults *results = [self getMainDataSetFromRealm];
ContactSource *contactSource = results.firstObject;
NSMutableDictionary *nameDic = [NSMutableDictionary dictionary];
for (RealmContact *contact in contactSource.contacts){
if (contact.firstName.length>0) {
if ([sortBy isEqualToString:#"FirstName"]) {
[nameDic setObject:#"firstletter" forKey:[contact.firstName substringToIndex:1]];
}
}
}
NSLog(#"dic %#",nameDic);
return [[nameDic allKeys]sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
}
This gets me an array of letters which represent the title of section.
Now I am preparing the datasource for each section, so for section A, I am fetching all the contacts that begin with letter 'A'
-(void)prepareDataSource:(NSArray *)titleArr{
RLMResults *results = [self getMainDataSetFromRealm];
ContactSource *contactSource = results.firstObject;
__block NSMutableDictionary *dataSource = [NSMutableDictionary dictionary];
[titleArr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSString *sectionHeader = obj;
RLMResults *contactResults = [contactSource.contacts objectsWhere:[NSString stringWithFormat:#"firstName BEGINSWITH '%#'",sectionHeader]];
NSMutableArray *contactRowArr = [NSMutableArray array];
for (Contact *contact in contactResults){
[contactRowArr addObject:contact];
}
[dataSource setObject:contactRowArr forKey:sectionHeader];
}];
_dataSource = [dataSource copy];
[self.tableView reloadData];
}
This works really well, but takes 3-5 seconds to load table which is fine but I am looking for ways to improve this data fetch .
Realm works on a principle of lazy-loading, where objects and their properties aren't loaded until you actually 'touch' them for the first time.
As a result, if you do any operations where you're manually iterating through all Realm objects in a results set at once, or manually copying specific objects to an array, you're going to incur a performance hit that will increase the more objects you persist in Realm.
The best way to minimize the performance hit is to try and mitigate how many times you iterate through the results sets and avoid copying objects out of the array as much as possible. RLMResults behaves like an array, so for most scenarios, you can usually just use that object instead.
In the prepareDataSource method, instead of looping through each object and passing them to that NSMutableArray, instead you could consider passing the RLMResults object itself instead.
The method getSectionTitleBasedOn: also seems quite inefficient since you're iterating through every single object in order to check if an entry with a particular first character exists. Instead, you could create an index of the alphabet, and then do a Realm query for entries that start with each letter, and then check to see if the resulting RLMResults object has a positive count (Though I'm not sure if this will actually be any faster).
But in the end, sometimes when you're doing complex sorting like this, where there's no 'clever' way to avoid iterating through each object in a database (Even Realm has to internally load each object when performing a sort), performance hits are unavoidable, in which case you should also make sure your UI has provisions to show a 'working' indicator to the user.

indexPathForObject returns nil

I am passing an object from my UITableViewController to a singleton, then back to my UITableViewController through a notification. The UITableViewController uses a NSFetchedResultsController. It then want the indexPath of the Object in the fetchedResultsController.
Object *obj = (Object *)notification.object;
NSIndexPath *index = [self.fetchedResultsController indexPathForObject:obj];
I set the notification in the AFNetworking/AFDownloadRequestOperation progress block:
- (void)downloadObject:(Object *)object
{
........
[operation1 setProgressiveDownloadProgressBlock:^(AFDownloadRequestOperation *operation, NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpected, long long totalBytesReadForFile, long long totalBytesExpectedToReadForFile) {
////// Sending Notification
////// Try to send notification only on significant download
totalBytesRead = ((totalBytesRead *100)/totalBytesExpectedToReadForFile);
NSLog(#"TOTAL BYTES IN PROGRESS BLOCK: %llu",totalBytesRead);
NSMutableDictionary *userDictionary = [[NSMutableDictionary alloc]init];
[userDictionary setObject:[NSNumber numberWithLongLong:totalBytesRead] forKey:#"progress"];
[userDictionary setObject:[NSNumber numberWithLongLong:totalBytesExpectedToReadForFile] forKey:#"totalBytes"];
[[NSNotificationCenter defaultCenter] postNotificationName:[NSString stringWithFormat:#"DownloadProgress%#",object.uniqueID] object:object userInfo:userDictionary];
}
There is only one object in [self.fetchedResultsController fetchedObjects]; and it matches the object the passed back in the notification. The index is always nil, but the fetchedResultsController is not nil, and the [self.fetchedResultsController fetchedObjects] returns the correct object. Why does indexPathForObject nil?
I had the same problem. In the configuration of my NSFetchedResultsController the sectionNameKeyPath was set to refer to an (optional) related object.
It turned out that this relation must be fulfilled (that is the related object must not be nil).
After ensuring, that every relation of the sectionNameKeyPath is fulfilled, the indexPathForObject method worked again as expected.
Looks to me like you are attempting to locate an object in a table view from the notification object...
You should be calling the index path method on your table view object, not your notification object.
Try matching your local variable:
Object *obj = (Object *)notification.object;
with:
[self.fetchedResultsController fetchedObjects];
(if it is the only one that should be relatively easy)...
NSArray *arrayFetchedObjects = [self.fetchedResultsController fetchedObjects];
Object *tableObject = [arrayObjects lastObject];
(if there is more than one then you might need to filter the arrayFetchedObjects but that is another question)
then locating the position of that fetched object in the table view:
NSIndexPath *indexPath = [self.fetchedResultsController indexPathForObject:tableObject];

Fast Enumeration of a NSFetchedResult

So I have a NSFetchedResultsController. I have it working fine for normally displaying the data. I have a situation where I need to be able to enumarate through them. So I fetch the results as seen here:
if (![[self fetchedResultsController] performFetch:&error]) {
exit(-1); // Fail
}
I need to do some work with the data before I display it so I am assigning it to an array like this:
arrVacationResults = [fetchedResultsController fetchedObjects];
Works perfectly so far. I now have an array of fetchedObjects. I tried to use fast enumeration but how do I reference whats in each array. I assumed it was a dictionary of sort so I tried to do something like
for (NSDictionary *myVacation in arrVacationResults)
{
}
That fails because in arrVacationResults they are not NSDictionaries, so what are they?
It is an array of NSManagedObjects:
for (NSManagedObject *myVacation in arraVacationResults)
{
//
// if you need to cast it as your entity
//
VacationResultEntity *entity = (VacationResultEntity *) myVacation;
}

Set a lastModificationDate attribute after NSManagedObjectsDidChangeNotification creates an infinite loop

I added a lastModifiedDate attribute to all my entities to avoid duplicates when syncing the UIManagedDocument with iCloud, something that I found it can happen if I create new entities with an offline device (iPad) and, at the same time, I create the same entities using another online device (iPhone).
I wanted to set this attribute whenever an object changes so I subscribed for NSManagedObjectContextObjectsDidChangeNotification. The code I wrote to set the lastModifiedDate creates an infinite loop because by setting the lastModificationDate attribute it creates a change that will be notified again by NSManagedObjectContextObjectsDidChangeNotification and so on...
Is it possible to fix it? Is there a better way to accomplish my goal? Should I subclass managedObjectContext and override willSave:?
//At init...
[[NSNotificationCenter defaultCenter] addObserver:applicationDatabase
selector:#selector(objectsDidChange:)
name:NSManagedObjectContextObjectsDidChangeNotification
object:applicationDatabase.managedDocument.managedObjectContext];
(void) objectsDidChange: (NSNotification*) note
{
// creates an infinite loop
NSDate *dateOfTheLastModification = [NSDate date];
NSMutableArray *userInfoKeys = [[note.userInfo allKeys] mutableCopy];
for(int i=0; i< userInfoKeys.count;i++){
NSString *key = [userInfoKeys objectAtIndex:i];
if([key isEqualToString:#"managedObjectContext"]){
[userInfoKeys removeObject:key];
}
}
for(NSString *key in userInfoKeys){
NSArray *detail = [note.userInfo objectForKey:key];
for (id object in detail){
[object setValue:dateOfTheLastModification forKey:#"lastModifiedDate"];
}
}
To avoid the infinite loop, you could set the last modification date using the
primitive accessor:
[object setPrimitiveValue:dateOfTheLastModification forKey:#"lastModifiedDate"];
because that does not fire another "change" notification. But that also implies that
no observers will see the change.
Overriding willSave in the managed object subclass would suffer from the same problem.
The Apple documentation for willSave states:
For example, if you set a last-modified timestamp, you should check
whether either you previously set it in the same save operation, or
that the existing timestamp is not less than a small delta from the
current time. Typically it’s better to calculate the timestamp once
for all the objects being saved (for example, in response to an
NSManagedObjectContextWillSaveNotification).
So you should register for NSManagedObjectContextWillSaveNotification instead,
and set the timestamp on all updated and inserted objects in the managed object
context. The registered method could look like this:
-(void)contextWillSave:(NSNotification *)notify
{
NSManagedObjectContext *context = [notify object];
NSDate *dateOfTheLastModification = [NSDate date];
for (NSManagedObject *obj in [context insertedObjects]) {
[obj setValue:dateOfTheLastModification forKey:#"lastModifiedDate"];
}
for (NSManagedObject *obj in [context updatedObjects]) {
[obj setValue:dateOfTheLastModification forKey:#"lastModifiedDate"];
}
}
This assumes that all your entities have a lastModifiedDate attribute, otherwise
you have to check the class of the objects.

fetching results twice returns nil

Assume I have 3 files in my project:
data model file, dealing with Core Data and fetching info
viewController 1
viewController 2
In the model file I get results us follows:
- (NSArray *) getColonyData
{
NSManagedObjectContext *cxt = [self managedObjectContext];
NSEntityDescription *colonyDesc = [NSEntityDescription entityForName:#"Colony" inManagedObjectContext:cxt];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:colonyDesc];
NSError *error;
NSArray *colonyResults = [cxt executeFetchRequest:request error:&error];
return colonyResults;
}
I run this part of code in viewDidLoad section of the 1st viewController and I get proper results:
NSArray *colonyResults = [model getColonyData];
if (colonyResults != nil)
{
colonyName.text = [[colonyResults objectAtIndex:0] valueForKey:#"name"];
}
else
{
colonyName.text = #"nothing setup yet";
}
Then I move via a segue to a 2nd viewController, when I execute exactly same code (of course updating different UI elements). But this time the result is nil. What am I doing wrong here? Should I release results manually first? No other errors appear.
Thanks.
As suggested in comments:
experimentModel *model; doesn't assign anything; it just declares that a variable exists. Somewhere, you must be setting model to an actual object for the first controller (and probably not doing that for the second one).
Sending messages to nil objects is a common way to not get the results one was expecting. :)

Resources