Accessing Realm property on main thread crashes - ios

I am trying to fetch realm objects and store them in a dictionary with key being a date string.
I start fetching the objects in a realm queue
//im doing this as data fetch is taking too much time
dispatch_async( [[ActivityManager sharedInstance]getRealmQueue], ^{
[self getActivitiesForStartDate:_startDate endDate:_endDate];
});
what getActivitiesForStartDate does is:
Get array of dates
For each date in the array get the realm objects to show on tableview
RLMResults *activityResults = [[[self getActivitySource]activities] objectsWhere:#"my predicate string"];
and enumerate through the fetched activities, and add them to an array based on condition
for (RealmActivity *tempActivity in tempActArr){
if (tempActivity.startTime) {
//condition met
[dataArr addObject:#{#"activity":tempActivity,
#"occurence":selectedDate}];
}else{
[dataArr addObject:#{#"activity":tempActivity,
#"occurence":tempActivity.startTime}];
}
}
after the data fetch is done, I'm trying to load the realm objects on tableview.
However when I try to access Realm object in cellForRow method, it crashes the app saying trying to access Realm from incorrect thread:
- (void)configureCell:(TaskListViewCell *)cell forIndexPath:(NSIndexPath *)indexPath
{
NSArray *ar = self.dataDictionary[[[self.monthView dateSelected]shortDateString]];
RealmActivity *activity = ar[indexPath.section];
cell.dateTime.text = activity.Type; <- crashes here
cell.regarding.text = activity.regarding;
cell.location.text =activity.location;
}
I see that configureCell is getting called on main thread, but how can I use Realm properly to avoid crashes like these ?
I have no idea how to use RLMThreadSafeReference for preparing datasource.

Related

Realm: the right way to make add,update,remove operations from background thread and get results on the main thread

I am using Realm for my app, i want to be able to query results on a background thread and receive them on the main thread.
What is the best way to achieve this? and what is the best practice to use realm (having different method for main thread and background? and in main using a static instance of realm across the app? maybe another good way?)
I've read and saw this options are available:
- parsing the realm object to my own object and return them (kind of a copy of the results).
- returning a the key of the object and querying again from main thread.
Thanks for any help anyone can give me, i really think realm has great potential but there is a lack of good tutorials and best practices.
First, since Realm is fast enough in most cases, you do not need to run a query in the background.
So the basic strategy is; update in background, fetch in the main thread.
The most general way is to take advantage of the feature of the live update.
RLMResults and Results have live update. You can hold RLMResults/Results instances by query. Then you'll make any changes in background thread, the changes are notified and reflected automatically when committed.
// Hold RLMResults for the data source
self.array = [[DemoObject allObjects] sortedResultsUsingProperty:#"date" ascending:YES];
 
// Reload table view when changed by other threads
__weak typeof(self) weakSelf = self;
self.notification = [RLMRealm.defaultRealm addNotificationBlock:^(NSString *note, RLMRealm *realm) {
[weakSelf.tableView reloadData];
}];
 
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// RLMResults is updated automatically
return self.array.count;
}
 
// Update in background
- (void)backgroundAdd
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// Import many items in a background thread
dispatch_async(queue, ^{
// Get new realm and table since we are in a new thread
RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
for (NSInteger index = 0; index < 5; index++) {
// Add row via dictionary. Order is ignored.
[DemoObject createInRealm:realm withValue:#{#"title": [self randomString],
#"date": [self randomDate]}];
}
[realm commitWriteTransaction];
});
}
For more details, you can see the table view example in Realm's repo.
If a few cases that Realm doesn't fast enough when fetch on the main thread, you can fetch in background thread. Then aggregate an array of primary keys. Then pass the array and re-fetch on the main thread using the primary keys.
FYI: We are working to add support for running queries asynchronously https://github.com/realm/realm-cocoa/pull/2842
If this feature will be released, you don't need to aggregate primary keys and re-fetch.

NSMutable array exception

I am getting data from web service and displayed it in a tableview and at button click which is at tableview i am storing data id in nsmutablearray . Then i am comparing clicked data id which is stored in nsmutable array with web service data in cellForRowAtIndexPath function.
Error:
At code comparing in tableview, exception index not found.
At button click i tried these all::::
[myidmutablearray addObject:[[webservicedata valueForKey:#"id"]objectAtIndex:clickedButtonIndexPath.row]];
[myidmutablearray insertObject:[webservicedata valueForKey:#"id"] atIndex:clickedButtonIndexPath.row];
[myidmutablearray replaceObjectAtIndex:clickedButtonIndexPath.row withObject:[webservicedata valueForKey:#"id"]];
At CellForRowAtIndexPath I tried this,
if([myidmutablearray count]!=0)
{
if([[webservicedata valueForKey:#"id"] containsObject:[myidmutablearray objectAtIndex:indexPath.row]])
NSLog(#"not empty");
}
else
NSLog(#"empty");
NSMutableArray is not an array with an indefinite number of entries, whose index you can assign.
As you add objects to an NSMutableArray, the first entry added will be at index 0. No matter what index you give to it when adding.
I think What you really want is an NSDictionary, with the key as an NSNumber made from the indexpath.row. Then you can retrieve it later using an NSNumber again.
When saving, do something like this:
//defining somewhere
NSMutableDictionary *myIdMutableDictionary;
//then in the button click:
[myIdMutableDictionary setObject:[webservicedata valueForKey:#"id"] forKey:#(indexPath.row)];
//then later to check it, you test value would be in this:
[[myIdMutableDictionary objectForKey:#(indexPath.row)

data not being written to NSMutableArray

I have an iOS app that pulls data from a server and persists it using CoreData. I have a UITableView that I am trying to populate with only select portions from a given core data attribute.
Before the table is populated I cycle through the data and pass what I want into a NSMutableArray. The problem is when I find an item I want it is not being added to the array.
I declare the array in my .h file like so...
#property (strong, nonatomic) NSMutableArray *theNewSource;
And Synthesize it in the .m file
#synthesize theNewSource = _theNewSource;
Here is the method...
-(NSMutableArray *)setDataSourceArray
{
for(int i = 0; i < rcount ; i++)
{
NSIndexPath *countingInteger = [NSIndexPath indexPathForItem:i inSection:0];
NSManagedObject *object = [self.fetchedResultsController objectAtIndexPath:countingInteger];
NSString *action = [object valueForKey:#"theActionName"];
if (![action isEqual:#"Login"])
{
[_theNewSource addObject:action];
}
}
NSLog(#"the array is now %#",_theNewSource);
return _theNewSource;
}
I've set a breakpoint in the line [_theNewSource addObject:action]. I can see in the console that the variable action does have a value but it is never added to _theNewSource array... I'm sure this is Objective C 101 but I can't get it figured out. Please Help!
Have you even created your _theNewSource array? It seems like you haven't done the following:
_theNewSource = [[NSMutableArray alloc] init];
Make sure you are creating your instance before trying to use it.
You should use a predicate in the NSFetchedResultsController's fetchRequest directly:
[NSPredicate predicateWithFormat:#"theActionName != %#", #"Login"];
NSFetchResultsControllers are particularly useful for driving table views and collection views, so filtering their results to create a separate data source is a code smell.
Doing it this way means that you can use the NSFetchedResultsController directly as the data source for your table instead of using it to create a filtered array to act as the datasource.

Fetching Core Data objects in the background: objects not faulted

I need some help in using objects from Core Data with GCD; I seem to get NSManagedObjects that are aren't faulted into the main thread, even when I access their properties. Would appreciate some help.
This is what I'm doing: on launch, I need to load a list of Persons from the Core Data DB, do some custom processing in the background, then reload the table to show the names. I am following the guidelines for Core Data multi-threading by only passing in the objectIDs into the GCD queues. But when I reload the tableview on the main thread, I never see the name (or other properties) displayed for the contacts, and on closer inspection, the NSManagedObjects turn out to be faults on the main thread, even though I access various properties in cellForRowAtIndexPath. The name property is visible in the background thread when I NSLog it; and it's also showing correctly on the main thread in NSLogs in cellForRowAtIndexPath. But they don't show in the tableView no matter what I do. I tried accessing the name property using the dot notation, as well as valueForKey, but neither worked.
Here's my code …. it's called from the FRC initializer:
- (NSFetchedResultsController *)fetchedResultsController
{
if (__fetchedResultsController != nil)
{
return __fetchedResultsController;
}
__fetchedResultsController = [self newFetchedResultsControllerWithSearch:nil]; // creates a new FRC
[self filterAllContactsIntoDictionary: __fetchedResultsController];
return [[__fetchedResultsController retain] autorelease];
}
- (void) filterAllContactsIntoDictionary: (NSFetchedResultsController *) frc
{
NSArray *fetchedIDs = [[frc fetchedObjects] valueForKey:#"objectID"];
NSArray *fetched = [frc fetchedObjects];
if (filterMainQueue == nil) {
filterMainQueue = dispatch_queue_create("com.queue.FilterMainQueue", NULL);
}
dispatch_async(self.filterMainQueue, ^{
NSManagedObjectContext *backgroundContext = [[[NSManagedObjectContext alloc] init] autorelease];
[backgroundContext setPersistentStoreCoordinator:[[self.fetchedResultsController managedObjectContext] persistentStoreCoordinator]];
NSMutableArray *backgroundObjects = [[NSMutableArray alloc] initWithCapacity: fetchedIDs.count];
// load the NSManagedObjects in this background context
for (NSManagedObjectID *personID in fetchedIDs)
{
Person *personInContext = (Person *) [backgroundContext objectWithID: personID];
[backgroundObjects addObject:personInContext];
}
[self internal_filterFetchedContacts: backgroundObjects]; // loads contacts into custom buckets
// done loading contacts into character buckets ... reload tableview on main thread before moving on
dispatch_async(dispatch_get_main_queue(), ^{
CGPoint savedOffset = [self.tableView contentOffset];
[self.tableView reloadData];
[self.tableView setContentOffset:savedOffset];
});
});
}
What am I doing wrong here? Is there any other way to explicitly make the Person objects fire their faults on the main thread? Or am I doing something wrong with GCD queues and Core Data that I'm not aware of?
Thanks.
Why not take the easy route, since you are not saving anything new ?
Instead of creating an extra context for the background thread and working with IDs, use the main managedObjectContext in the background thread after locking it.
for example:
- (void) filterAllContactsIntoDictionary: (NSFetchedResultsController *) frc
{
if (filterMainQueue == nil) {
filterMainQueue = dispatch_queue_create("com.queue.FilterMainQueue", NULL);
}
dispatch_async(self.filterMainQueue, ^{
NSManagedObjectContext *context = ... // get the main context.
[context lock]; // lock the context.
// do something with the context as if it were on the main thread.
[context unlock]; // unlock the context.
dispatch_async(dispatch_get_main_queue(), ^{
CGPoint savedOffset = [self.tableView contentOffset];
[self.tableView reloadData];
[self.tableView setContentOffset:savedOffset];
});
});
}
This works for me when I call a method with performSelectorInBackground, so I guess it should work for GCD dispatch too.
Well, mergeChangesFromContextDidSaveNotification: is your friend. You'll need to tell the MOC on the main thread that there have been changes elsewhere. This will do the trick.
Here's Apple's documentation. To quote from there:
This method refreshes any objects which have been updated in the other context, faults in any newly-inserted objects, and invokes deleteObject:: on those which have been deleted.
EDIT: original answer removed, OP is not fetching in the background
I looked closer at your code and it doesn't look like you are doing anything that will change data and/or affect the context on the main thread.
You have a fetchedResultsController on the main thread. Presumably, this is working and your table is populating with data. Is this true?
When filterAllContentsIntoDictionary is invoked, you pass an array of the fetchedResultsController's current objectIDs to a background thread and do some processing on them (presumably filtering them based on some criteria) but you are not changing data and saving backgroundContext.
internalFilterFetchedContents is a black box. Without knowing what you intend for it to do, hard to say why it's not working.
When this is done, you reload the table on the main thread.
You haven't made any changes to the store, the context, or the fetchedResultsController so of course, the table shows the same data it did before. The missing details to help further are:
Is your tableView showing correct data from the fetchedResultsController to begin with? If not, most likely your only problem is in handling the tableView delegate and dataSource methods and the rest of this isn't really relevant.
What do you intend to happen in filterAllContentsIntoDictionary and internalFilterFetchedContents?
If your intent is to filter the data as displayed by the fetchedResultsController not sure you need to do anything in the background. If you modify the fetchRequest and do performFetch again your table will reload based on the new results.
I you need more help, please answer my questions, add more relevant code to your post and let me know if I'm missing anything wrt the problem and what you're trying to accomplish.
good luck!

Obj - C: Having trouble creating a UITableView that updates cells from an HTTP API in real-time (one at a time)

I am polling an HTTP API - it returns one item at a time, in real-time (about every 4 seconds). As each item is received, I would like a new UITableView cell to be populated. The full list of received items must remain in a class property, I'm guessing an NSMutableArray. What is the best way to initialize an NSMutableArray as a class property, update it as new information comes in, and then use the count to update a new UITableViewCell?
Here's how I'm adding content to an NSMutableDictionary:
NSMutableDictionary *messageContents = [[NSMutableDictionary alloc] init];
[messageContents retain];
[messageContents setValue:messageText forKey:#"text"];
[messageContents setValue:image forKey:#"image"];
[self addMessageToDataArray:messageContents];
Here's the method stuffing objects into the array:
- (void)addMessageToDataArray:(NSArray *)messageDictionary {
[self.messageDataArray addObject:messageDictionary];
NSLog(#"count = %#", [self.messageDataArray count]);
[self reloadTableData];
}
At this point, calling count on the messageDataArray class property crashes the application. I'm very used to working with arrays in Actionscript, Obj-C is obviously totally different. Please explain the method for instantiating an NSMutableArray as a class property, filling it with NSMutableDictionary's and then finding the NSMutableArray count (which will be dynamically updating in real-time) so I can use that info to update a UITableView (on the fly).
Or... tell me I'm being silly and suggest a much easier solution.
From your description I would guess you're not allocating the messageDataArray before using it.
The init function for your table view (controller?) class should have a line like this
messageDataArray = [[NSMutableArray alloc] initWithCapacity:20];
It's also worth checking that you have [messageDataArray release]; in your dealloc method.

Resources