问题
Strange problem, and strangely only since yesterday. I have a fetched results controller in which you can reorder the rows without any problem. After a fresh start of the app, everything works fine and fast. However if you change from this tab bar to another tab bar and edit some random textfield (which isn't even linked to core data and doesn't trigger any save), the reordering is very slow. And I was able to narrow it down only to the save context. But now I have no clue where to look further. Any advice? Here's my reorder function:
- (void)tableView:(UITableView *)tableView
moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath
toIndexPath:(NSIndexPath *)destinationIndexPath;
{
if (self.activeField && [self.activeField isFirstResponder]){
[self.activeField resignFirstResponder];
}
self.suspendAutomaticTrackingOfChangesInManagedObjectContext = YES;
//Makes only a mutable copy of the array, but NOT the objects (references) within
NSMutableArray *fetchedResults = [[self.fetchedResultsController fetchedObjects] mutableCopy];
// Grab the item we're moving
NSManagedObject *resultToMove = [self.fetchedResultsController objectAtIndexPath:sourceIndexPath];
// Remove the object we're moving from the array.
[fetchedResults removeObject:resultToMove];
// Now re-insert it at the destination.
[fetchedResults insertObject:resultToMove atIndex:[destinationIndexPath row]];
// All of the objects are now in their correct order. Update each
// object's displayOrder field by iterating through the array.
int i = 1;
for (MainCategory *fetchedResult in fetchedResults)
{
fetchedResult.position = [NSNumber numberWithInt:i++];
}
// Save
TICK;
[((AppDelegate *)[[UIApplication sharedApplication] delegate]) saveContext];
TOCK;
self.suspendAutomaticTrackingOfChangesInManagedObjectContext = NO;
}
Yesterday I've only inserted these two methods (which cannot be the problem, because after commenting them so they don't trigger, the same problem remained):
-(void) registerForKeyboardNotificationsWithTabBarHeight:(CGFloat)tabbarHeight andHeaderHeight:(CGFloat)headerHeight andFooterHeight:(CGFloat)footerHeight andBottomSpacingToTextFields:(CGFloat)spacingToTextFields
{
self.tabbarHeight = tabbarHeight;
self.footerHeight = footerHeight;
self.headerHeight = headerHeight;
self.spacingToTextFields = spacingToTextFields;
// Register notification when the keyboard will be show
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
// Register notification when the keyboard will be hide
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
FetchedResultsController:
- (void)setupFetchedResultsController
{
self.managedObjectContext = ((AppDelegate *)[[UIApplication sharedApplication] delegate]).managedObjectContext;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"MainCategory"];
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"position" ascending:YES]];
self.fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil cacheName:@"MainCategoryCache"];
}
And the view will appear is also very harmless..
- (void)viewWillAppear:(BOOL)animated
{
//Initialize database
[super viewWillAppear:animated];
[self displayBudget];
/*
This is only necessary since the sum of all categories is calculated and the FetchedResultsController
doesn't get this since it's only monitoring a MainCategory and the changes occured in the Subcategories */
[self.tableView reloadData];
}
I'm thankful for any clues what could be the problem :-(
回答1:
I would not recommend multi-threading in this situation. The issue is that your saves are being done at the wrong time. Moving them to a background queue is not going to solve the issue. You need to change your save behavior.
Writing to disk is expensive. There is no shortcut around it. Even if you move your saves into another context (there is an Apple example on this) they are still doing to take time and potentially block your UI (you cannot do a new fetch during a save). You need to change your save behavior.
You do not need to save every time a user moves a row. That is wasteful. You want to queue up your saves so that you are doing more each save. In addition, you should be saving when the user expects there to be a delay. When you push or pop a view might be a good time for a save. When your app goes into the background is a great time for a save.
Saving when the user wants smooth UI response is bad and causes a bad user experience. Look for opportunities where the user is expecting something to take time. Posting something to a server? Save at the same time.
Your NSFetchedResultsController
, etc. will read unsaved data from your main NSManagedObjectContext
just as it will saved data. You are not gaining anything on the UI by saving frequently.
Update 1
Step one in performance problems is to run your application in Instruments.
Measure the speed and then see what is costing you time. Time Profiler is your friend.
回答2:
Yes it will slow drawing your UI because you should't use saving and creating objects in main Queue; read this https://developer.apple.com/library/ios/documentation/cocoa/conceptual/CoreData/Articles/cdConcurrency.html
you should have another background thread and you should do inserting deleting stuff there and then you have to pass NSObjectID_s for newManagedObject_s and then get it in main thread:
NSManagedObjectContext * mainThreadContext;
NSManagedObject * object = [mainThreadContext objectWithId:objectID];
来源:https://stackoverflow.com/questions/20900310/ios-saving-managed-object-context-takes-over-1-second-after-a-while