Like the title says how does one go about accessing an NSManagedObject that has been created in one block and then needs to be accessed in the other. I have the following implementation and was wondering if it's correct.
__block Person *newPerson;
#weakify(self);
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
newPerson = [Person MR_createInContext:localContext];
newPerson.name = #"Bob";
} completion:^(BOOL success, NSError *error) {
#strongify(self);
// Called on main thread
PersonViewController *personVC = [[PersonViewController alloc] initWithPerson:newPerson];
[self.navigationController pushViewController:personVC animated:YES];
}];
Am I correct in not needing to access newPerson from a localContext in the completion handler because it'll be executed on the main thread?
EDIT
It looks like the following is the proposed way:
__block NSManagedObjectID *newPersonObjectID;
#weakify(self);
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
Person *newPerson = [Person MR_createInContext:localContext];
newPerson.name = #"Bob";
newPersonObjectID = newPerson.objectID;
} completion:^(BOOL success, NSError *error) {
#strongify(self);
// Called on main thread
Person *savedPerson = [[NSManagedObjectContext MR_defaultContext] objectWithID:newPersonObjectID];
PersonViewController *personVC = [[PersonViewController alloc] initWithPerson:savedPerson];
[self.navigationController pushViewController:personVC animated:YES];
}];
Solution
This answer and comments lead to the following solution.
A TemporaryID is being assigned to the object whilst it's being saved and therefore when trying to fetch the object with the TempID an exception occurs.
Rather than creating a whole new fetch request what can be done is asking the context to obtain the permanent IDs early and than acquiring the permanent ID of the object. For example:
__block NSManagedObjectID *newPersonObjectID;
#weakify(self);
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
Person *newPerson = [Person MR_createInContext:localContext];
newPerson.name = #"Bob";
[localContext obtainPermanentIDsForObjects:#[newPerson] error:NULL];
newPersonObjectID = newPerson.objectID;
} completion:^(BOOL success, NSError *error) {
#strongify(self);
// Called on main thread
Person *savedPerson = [[NSManagedObjectContext MR_defaultContext] objectWithID:newPersonObjectID];
PersonViewController *personVC = [[PersonViewController alloc] initWithPerson:savedPerson];
[self.navigationController pushViewController:personVC animated:YES];
}];
You can't directly pass managed objects between contexts. Each NSManagedObject can only be accessed by its own context.
You'll need to pass its objectID to the completion block, then have the main context fetch the object by calling one of the following methods:
-(NSManagedObject *)objectWithID:(NSManagedObjectID *)objectID
This will create a fault to an object with the specified objectID, whether or not it actually exists in the store. If it doesn't exist, anything that fires the fault will fail with an exception.
-(NSManagedObject *)existingObjectWithID:(NSManagedObjectID *)objectID
error:(NSError **)error
This will fetch the object from the store that has that ID, or return nil if it doesn't exist. Unlike objectWithID, the object won't be faulted; all its attributes will have been retrieved.
In either case, your local context must have saved the Person object to the store for the main context to be able to fetch it.
More details about objectID can be found in the Core Data Programming Guide
Edit by User Asking Question
This answer and comments lead to the correct solution.
A TemporaryID is being assigned to the object whilst it's being saved and therefore when trying to fetch the object with the TempID an exception occurs.
Rather than creating a whole new fetch request what can be done is asking the context to obtain the permanent IDs early and than acquiring the permanent ID of the object. For example:
__block NSManagedObjectID *newPersonObjectID;
#weakify(self);
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
Person *newPerson = [Person MR_createInContext:localContext];
newPerson.name = #"Bob";
[localContext obtainPermanentIDsForObjects:#[newPerson] error:NULL];
newPersonObjectID = newPerson.objectID;
} completion:^(BOOL success, NSError *error) {
#strongify(self);
// Called on main thread
Person *savedPerson = [[NSManagedObjectContext MR_defaultContext] objectWithID:newPersonObjectID];
PersonViewController *personVC = [[PersonViewController alloc] initWithPerson:savedPerson];
[self.navigationController pushViewController:personVC animated:YES];
}];
You should access the objectID outside of your block (on the main thread, for example) and then use it within your block. Something like:
NSManagedObjectID *objectID = appDelegate.myObject.objectId;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// use objectID here.
}
Related
SETUP (You can read this later and skip to the scenario section first)
It's an old app, with manually setup CoreData stack like this:
+ (NSManagedObjectContext *)masterManagedObjectContext
{
if (_masterManagedObjectContext) {
return _masterManagedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self createPersistentStoreCoordinator];
if (coordinator != nil) {
_masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
_masterManagedObjectContext.retainsRegisteredObjects = YES;
_masterManagedObjectContext.mergePolicy = NSOverwriteMergePolicy;
_masterManagedObjectContext.persistentStoreCoordinator = coordinator;
}
return _masterManagedObjectContext;
}
+ (NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext) {
return _managedObjectContext;
}
NSManagedObjectContext *masterContext = [self masterManagedObjectContext];
if (masterContext) {
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_managedObjectContext.retainsRegisteredObjects = YES;
_managedObjectContext.mergePolicy = NSOverwriteMergePolicy;
_managedObjectContext.parentContext = masterContext;
}
return _managedObjectContext;
}
+ (NSManagedObjectContext *)newManagedObjectContext
{
__block NSManagedObjectContext *newContext = nil;
NSManagedObjectContext *parentContext = [self managedObjectContext];
if (parentContext) {
newContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
newContext.parentContext = parentContext;
}
return newContext;
}
And then save context recursively:
+ (void)saveContext:(NSManagedObjectContext *)context
{
[context performBlockAndWait:^{
if (context.hasChanges && context.persistentStoreCoordinator.persistentStores.count) {
NSError *error = nil;
if ([context save:&error]) {
NSLog(#"saved context: %#", context);
// Recursive save parent context.
if (context.parentContext) [self saveContext:context.parentContext];
}
else {
// do some real error handling
NSLog(#"Could not save master context due to %#", error);
}
}
}];
}
SCENARIO
The app load lots of data from a server, then perform update inside newContext first, then merge into mainContext -> masterContext -> persistentStore.
Because lots of data, the sync process has been divided into about 10 async threads => we have 10 newContext at a time.
Now, the data is complicated, with things like parents <-> children (same class). 1 parent can have many children, and a child can have a mother, father, god father, step mother..., so it's n-n relationship. First, we fetch parent, then perform fetch child and then set the child to parent, and so on.
The server is kinda stupid, it can't send disabled objects. However the customer would like to control the display of app's objects from the back end, so I have 2 properties to do that:
hasUpdated: At the beginning of loading process, perform a batch update, set all object's hasUpdated to NO. When got data from the server, update this property to YES.
isActive: When all loading was done, perform batch update this property to NO if hasUpdate == NO. Then, I have a filter that won't show object with isActive == NO
ISSUE
Customers complain why some objects being missing even if they're enable in the backend. I've struggle and debugging for so long after got to this strange issue:
newContext.updatedObjects : { obj1.ID = 100, hasUpdated == YES }
"saved newContext"
mainContext.updatedObjects: {obj1.ID = 100, hasUpdated == NO }
// I'll stop here. Obviously, master got updated = NO and finally isActive will set to no, which cause missing objects.
If it happened every time, then probably easier to fix (¿maybe?). However, it occurs like this:
First time running (by first time, I mean app start from where appDidFinishLaunch... got called): all correct
2nd time: missing (153 objects)
3rd time: all correct
4th time: missing (153 objects) (again? exactly those with multiple parents, I believe so!)
5th time: correct again
... so on.
Also, it looks like this happened for objects which have the same context (same newContext). Unbelievable.
QUESTIONS
Why is this happening? How do I fix this? If those objects don't have children, my life would be easier!!!!
BONUS
In case you'd like to know how the batch update is, it's below. Note:
Download requests are in async queue: _shareInstance.apiQueue = dispatch_queue_create("product_request_queue", DISPATCH_QUEUE_CONCURRENT);
Parse response and update properties are syncronous in a queue: _shareInstance.saveQueue = dispatch_queue_create("product_save_queue", DISPATCH_QUEUE_SERIAL);
Whenever parse complete, I perform save newContext and call for updateProductActiveStatus: in the same serial queue. If all requests are finished, then perform batch update status. Since request are done in concurent queue, it's always finished earlier than save (serial) queue, so it's pretty much fool proof process.
Code:
// Load Manager
- (void)resetProductUpdatedStatus
{
NSBatchUpdateRequest *request = [NSBatchUpdateRequest batchUpdateRequestWithEntityName:NSStringFromClass([Product class])];
request.propertiesToUpdate = #{ #"hasUpdated" : #(NO) };
request.resultType = NSUpdatedObjectsCountResultType;
NSBatchUpdateResult *result = (NSBatchUpdateResult *)[self.masterContext executeRequest:request error:nil];
NSLog(#"Batch update hasUpdated: %#", result.result);
[self.masterContext performBlockAndWait:^{
[self.masterContext refreshAllObjects];
[[CoreDataUtil managedObjectContext] performBlockAndWait:^{
[[CoreDataUtil managedObjectContext] refreshAllObjects];
}];
}];
}
- (void)updateProductActiveStatus:(SyncComplete)callback
{
if (self.apiRequestList.count) return;
NSBatchUpdateRequest *request = [NSBatchUpdateRequest batchUpdateRequestWithEntityName:NSStringFromClass([Product class])];
request.predicate = [NSPredicate predicateWithFormat:#"hasUpdated = NO AND isActive = YES"];
request.propertiesToUpdate = #{ #"isActive" : #(NO) };
request.resultType = NSUpdatedObjectsCountResultType;
NSBatchUpdateResult *result = (NSBatchUpdateResult *)[self.masterContext executeRequest:request error:nil];
NSLog(#"Batch update isActive: %#", result.result);
[self.masterContext performBlockAndWait:^{
[self.masterContext refreshAllObjects];
NSManagedObjectContext *maincontext = [CoreDataUtil managedObjectContext];
NSLog(#"Refreshed master");
[maincontext performBlockAndWait:^{
[maincontext refreshAllObjects];
NSLog(#"Refreshed main");
// Callback
if (callback) dispatch_async(dispatch_get_main_queue(), ^{ callback(YES, nil); });
}];
}];
}
mergePolicy is evil. The only correct mergePolicy is NSErrorMergePolicy any other policy is asking core-data to silently fail and not update when you expect it too.
I suspect that your problem is that you are writing simultaneously to core-data with the background contexts. (I know that you say you have a serial queue - but if you call performBlock inside the queue then each block is executed simultaneously). When there is a conflict stuff gets overwritten. You should only write to core-data in one synchronous way.
I wrote an answer on how to accomplish this with a NSPersistentContainer:
NSPersistentContainer concurrency for saving to core data and I would suggest that you migrate your code to it. It really should not be that hard.
If you want to keep the code as close to what is currently is as possible that also is not that hard.
Make a serial operation queue:
_persistentContainerQueue = [[NSOperationQueue alloc] init];
_persistentContainerQueue.maxConcurrentOperationCount = 1;
And do all writing using this queue:
- (void)enqueueCoreDataBlock:(void (^)(NSManagedObjectContext* context))block{
void (^blockCopy)(NSManagedObjectContext*) = [block copy];
[self.persistentContainerQueue addOperation:[NSBlockOperation blockOperationWithBlock:^{
NSManagedObjectContext* context = [CoreDataUtil newManagedObjectContext];
[context performBlockAndWait:^{
blockCopy(context);
[CoreDataUtil saveContext:context];
}];
}]];
}
Also it could be that the objects ARE updated, but you aren't seeing it because you are relying on a fetchedResultsController to be updated. And fetchedResultsController don't update from batch update requests.
I have two UIViewControllers in a Tab Bar
In one of the TabBar I am making an api call using AFNetworking and this api call is saving data in CoreData.
Here is my code
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < cartList.count; i++)
{
NSDictionary *dict = [cartList objectAtIndex:i];
NSFetchRequest *request = [Orders fetchRequest];
request.predicate = [NSPredicate predicateWithFormat:#"orderId = %#", [dict objectForKey:kiD]];
NSError *error = nil;
NSArray *itemsList = context executeFetchRequest:request error:&error];
if (itemsList.count == 0)
{
Orders *order = [NSEntityDescription insertNewObjectForEntityForName:#"Orders" inManagedObjectContext:appDel.persistentContainer.viewContext];
[order updateWithDictionary:dict];
order.isNew = NO;
}
else
{
Orders *order = [itemsList objectAtIndex:0];
[order updateWithDictionary:dict];
order.isNew = NO;
}
}
dispatch_async(dispatch_get_main_queue(), ^{
[appDel saveContext];
[self refreshValues:NO];
});
});
In second VIewController I am doing something like that. If I switch the tab controllers very fast the app crashes at
[appDel saveContext];
most probably because the last time viewContext was used by other UIviewController in Background thread.
What is the work around I can adopt to fix this problem
If this is correctly implemented
[appDel.persistentContainer performBackgroundTask:^(NSManagedObjectContext * _Nonnull context)
{
NSFetchRequest *request = [Categories fetchRequest];
NSBatchDeleteRequest *deleteReq = [[NSBatchDeleteRequest alloc] initWithFetchRequest:request];
NSError *deleteError = nil;
[appDel.persistentContainer.viewContext executeRequest:deleteReq error:&deleteError];
for (int i = 0; i < dataArr.count; i++)
{
Categories *category = [NSEntityDescription insertNewObjectForEntityForName:#"Categories" inManagedObjectContext:appDel.persistentContainer.viewContext];
[category updateWithDictionary:[dataArr objectAtIndex:i]];
}
#try {
NSError *error = nil;
[context save:(&error)];
} #catch (NSException *exception)
{
}
[self getCategoryItems];
}];
Core-data is not thread-safe, neither for reading for for writing. If you violate this ever core-data can fail in unexpected ways. So even if it appears to work you can find core-data suddenly crashing for no apparent reasons. In other words, accessing core-data from the wrong thread is undefined.
There are a few possible solutions:
1) only use the main thread for reading and writing to core-data. This is an OK solution for simple apps that don't do a lot of data import or export and have relatively small data sets.
2) Wrap NSPersistentContainer's performBackgroundTask in an operation queue and only write to core-data through that method and never write to the viewContext. When you use performBackgroundTask the method gives you a context. You should use the context to fetch any objects that you need, modify them, save the context and then discard the context and the objects.
If you try to write using both performBackgroundTask and writing directly to the viewContext you can get write conflicts and lose data.
Create a child NSManagedObjectContext object with NSPrivateQueueConcurrencyType your data processing in background queue.
Read Concurrency guide for more info.
According to apple guideline, In order to use managed objects on the main thread, they need to be fetched by a context confined to the main thread only ,Ok thats fine. Below is my code...
AppDelegate *del = [[UIApplication sharedApplication] delegate];
dispatch_queue_t queue1 = dispatch_queue_create("com.MyApp.AppTask",NULL);
dispatch_queue_t main = dispatch_get_main_queue();
dispatch_async(queue1, ^{
NSManagedObjectContext *workerContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
workerContext.persistentStoreCoordinator = del.persistentStoreCoordinator;
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Person"];
NSArray *managedObject = [workerContext executeFetchRequest:fetchRequest error:nil];
dispatch_async(main, ^{
NSLog(#"%#",managedObject);
Person *objperson = [managedObject objectAtIndex:0];
objperson.firstname = #“test”;
BOOL s = [workerContext save:nil];
if (s) {
NSLog(#"done");
}
});
});
Now as per the guideline I can not modify or save managed object context created by another thread. But above code works fine and modify and save my object without any error. Hence I am able to modify the MO which is fetched by another thread and even I can save MOC which is created by another thread.
Please let me know if my way of doing this is wrong or not because ideally i could not save MOC of Background thread from main thread.
thank you.
Its wrong because its thread-unsafe NOT thread impossible to cross threads with contexts and managed objects.
So your trivial example might work some of the time but not all of time in all situations. Sooner or later you are going to get a crash with that pattern.
if you wish to access objects between threads you must send the objectID across the thread.
When you create a context with NSPrivateQueueConcurrencyType it creates and manages its own queue.
Your example is expressed better as
AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *workerContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
//set the parent NOT the persistent store coordinator
workerContext.parentContext = delegate.managedObjectContext;
[workerContext performBlock:^{
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Person"];
NSArray *results = [workerContext executeFetchRequest:fetchRequest error:nil];
if (results.count) {
Person *person = [results objectAtIndex:0];
person.firstname = #“test”;
BOOL success = [workerContext save:nil];
if (success) {
NSLog(#"done");
}
//you pass ObjectID's NOT managed objects across threads
NSManagedObjectID *objectID = [person objectID];
dispatch_async(dispatch_get_main_queue(), ^{
//update your UI here
Person *thePerson = (Person *)[[delegate managedObjectContext] objectWithID:objectID];
self.myUIElement.text = person.firstname;
});
}
}];
I am using nested contexts pattern to support multithreaded work with CoreData.
I have CoredDataManager singleton class and the inits of contexts are:
self.masterContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
self.masterContext.persistentStoreCoordinator = self.persistentStoreCoordinator;
self.mainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
self.mainContext.parentContext = self.masterContext;
For each insert operation on response from web service I use API of my CoreDataManager to get new managed context:
- (NSManagedObjectContext *)newManagedObjectContext {
NSManagedObjectContext *workerContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
workerContext.parentContext = self.mainContext;
return workerContext;
}
It looks something like (PlayerView class is subclass of NSManagedObject class):
[PlayerView insertIfNeededByUniqueKey:#"playerViewId" value:playerViewId inBackgroundWithCompletionBlock:^(NSManagedObjectContext *context, PlayerView *playerView) {
playerView.playerViewId = playerViewId;
playerView.username = playerViewDictionary[#"name"];
[context saveContextWithCompletionBlock:^{
//do something
} onMainThread:NO];//block invocation on background thread
}];
saveContextWithCompletionBlock method is implemented in NSManagedObjectContext category:
- (void)saveContextWithCompletionBlock:(SaveContextBlock)completionBlock onMainThread:(BOOL)onMainThread {
__block NSError *error = nil;
if (self.hasChanges) {
[self performBlock:^{
[self save:&error];
if (error) {
#throw [NSException exceptionWithName:NSUndefinedKeyException
reason:[NSString stringWithFormat:#"Context saving error: %#\n%#\n%#", error.domain, error.description, error.userInfo]
userInfo:error.userInfo];
}
if (completionBlock) {
if (onMainThread && [NSThread isMainThread]) {
completionBlock();
} else if (onMainThread) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
} else if ([NSThread isMainThread]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul), ^{
completionBlock();
});
} else {
completionBlock();
}
}
}];
}
}
Then on some stage I'm calling method of CoreDataManager to save master context:
- (void)saveMasterContext; {
__block NSError *error;
[self.mainContext performBlock:^{
[self.mainContext save:&error];
[self treatError:error];
[self.masterContext performBlock:^{
[self.masterContext save:&error];
[self treatError:error];
}];
}];
}
I have two main classes, subclasses of NSManagedObject - PlayerView and Post.
PlayerView has relation one to many to Post.
PlayerView is saved and is ok. The Post is never saved and I get error:
CoreData: error: Mutating a managed object 0x17dadd80 (0x17daf930) after it has been removed from its context.
I think, that the problem is in contexts saving logic.
First of all, the error you're experiencing usually happens when the context in which you created the new managed object goes away (released) before you had the chance to save it.
Secondly, the best way to make sure the context is saved in the appropriate thread is to use performBlock or performBlockAndWait instead of trying to figure out which thread the context belongs to. Here's a sample "save" function that saves the context safely:
+ (BOOL)save:(NSManagedObjectContext *)context {
__block BOOL saved = NO;
[context performBlockAndWait: {
NSError *error;
saved = [context save:&error];
if (!saved) {
NSLog("failed to save: %#", error);
}
}]
return saved;
}
As for using nested private contexts (with main thread context as the parent), our team experienced some issues with that model (can't recall exactly what it was), but we decided to listen for NSManagedObjectContextDidSaveNotification and use mergeChangesFromContextDidSaveNotification to update contexts.
I hope this helps.
A great tutorial by Bart Jacobs entitled: Core Data from Scratch: Concurrency describes two approaches in detail, the more elegant solution involves parent/child managed object contexts, including how to properly save context.
Part of my UITableViewCell's content creation is delayed by the fault that happens on one object's (CoreData NSManagedObject) initial access. This manifests itself in a small hiccup the cell is first scrolled into view. I decided to push that access of those objects off to a background thread.
This is how I implemented it and it works well, but we all know that we are not supposed to access one thread(the main thread)'s NSManagedObjectContext in another thread, but can we get the objectID of an object in a second thread if it was originally fetched in the first thread?
Getting the objectID takes a small amount of time, which I was hoping to push into the background with everything else.
MyRecord *record = [self.frc objectAtIndexPath: indexPath];
// Should the following be here or can it be below in the background thread?
// NSManagedObjectID *recordObjectID = record.objectID;
dispatch_async(_recordViewQueue, ^(void) {
if ([cell.origIndexPath isEqual:indexPath]) {
// should the following be here or above? It works here, but am I just lucky?
// this call seems to take about 2/100 of a second
NSManagedObjectID *recordObjectID = record.objectID;
NSManagedObjectContext *bgndContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
bgndContext.persistentStoreCoordinator = App.sharedApp.storeCoordinator;
MyRecord *newRecord = (MyRecord *) [bgndContext objectWithID:recordObjectID];
[self updateCell:cell withRecord:newRecord];
if ([cell.origIndexPath isEqual:indexPath]) {
dispatch_async(dispatch_get_main_queue(), ^{
[(UIView*) cell.recordView setNeedsDisplay];
});
}
}
});
Is this safe? Or do I have to get the objectID in the mainThread?
It is safe to pass the objectID of a managed object between threads. It is not safe to use a managed object between threads. Use the objectID and your thread's managed object context to call existingObjectWithID:error: to get an instance of the managed object for that thread.
I would update your code like so:
MyRecord *record = [self.frc objectAtIndexPath: indexPath];
NSManagedObjectID *recordObjectID = record.objectID;
dispatch_async(_recordViewQueue, ^(void) {
if ([cell.origIndexPath isEqual:indexPath]) {
NSManagedObjectContext *bgndContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
bgndContext.persistentStoreCoordinator = App.sharedApp.storeCoordinator;
NSError * error = nil;
MyRecord *newRecord = (MyRecord *) [bgndContext existingObjectWithID:recordObjectID error:&error];
if (newRecord) {
[self updateCell:cell withRecord:newRecord];
if ([cell.origIndexPath isEqual:indexPath]) {
dispatch_async(dispatch_get_main_queue(), ^{
[(UIView*) cell.recordView setNeedsDisplay];
});
}
}
else {
NSLog(#"unable to find existing object! error: %# (userInfo: %#)", [error localizedDescription], [error userInfo]);
}
}
});