I have an issue with memory usage relating to images and I've searched the docs and watched the videos from cs193p and the iphone dev site on memory mgmt and performance. I've searched online and posted on forums, but I still can't figure it out.
The app uses core data and simply lets the user associate text with a picture and stores the list of items in a table view that lets you add and delete items. Clicking on a row shows the image and related text. that's it.
Everything runs fine on the simulator and on the device as well. I ran the analyzer and it looked good, so i then starting looking at performance. I ran leaks and everything looked good. My issue is when running Object Allocations as every time i select a row and the view with the image is shown, the live bytes jumps up a few MB and never goes down and my app eventually crashes due to memory usage. Sorting the live bytes column, i see 2 2.72MB mallocs (5.45Mb total), 14 CFDatas (3.58MB total), 1 2.74MB malloc and everything else is real small. the problem is all the related info in instruments is really technical and all the problem solving examples i've seen are just missing a release and nothing complicated. Instruments shows Core Data as the responsible library for all but one (libsqlite3.dylib the other) with [NSSQLCore _prepareResultsFromResultSet:usingFetchPlan:withMatchingRows:] as the caller for all but one (fetchResultSetReallocCurrentRow the other) and im just not sure how to track down what the problem is. i've looked at the stack traces and opened the last instance of my code and found 2 culprits (below). I havent been able to get any responses at all on this, so if anyone has any tips or pointers, I'd really appreciate it!!!!
//this is from view controller that shows the title and image
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.title = item.title;
self.itemTitleTextField.text = item.title;
if ([item.notes length] == 0)
{
self.itemNotesTextView.hidden = YES;
} else
{
self.itemNotesTextView.text = item.notes;
} //this is the line instruments points to
UIImage *image = item.photo.image;
itemPhoto.image = image;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
// Delete the managed object for the given index path
NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];
[context deleteObject:[fetchedResultsController objectAtIndexPath:indexPath]];
// Save the context.
NSError *error = nil;
if (![context save:&error]) //this is the line instruments points to
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1);
}
}
}
I suggest you to store image URL instead of image data itself. This prevent CoreData to work with and cache big data.
Related
I am capturing frames from the camera using the code:
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
:(AVCaptureConnection *)connection
{
// Create a UIImage from the sample buffer data
UIImage *image = [self imageFromSampleBuffer:sampleBuffer];
if(delegate && [delegate respondsToSelector:#selector(captureManagerCapturedFrame:withFrameImage:withFrameBuffer:)]) {
[delegate captureManagerCapturedFrame:self withFrameImage:image withFrameBuffer:sampleBuffer];
}
}
I am doing this because in the delegate method captureManagerCapturedFrame:withFrameImage:withFrameBuffer: I have a flag which tells the app to use either the returned uiimage OR the returned sampleBuffer.
The delegate method is:
- (void) captureManagerCapturedFrame:(AVCamCaptureManager *)captureManager
withFrameImage:(UIImage *)image
withFrameBuffer:(CMSampleBufferRef)frameBuffer {
if(_screen1) {
NSLog(#"Only display camera image\n");
}
else if(_screen2) {
//Enable IR
NSLog(#"Display AND Process camera image\n");
[self imageReconigitionProcessFrame:frameBuffer];
}
}
where imageReconigitionProcessFrame: is:
-(void)imageReconigitionProcessFrame:(CMSampleBufferRef)frameBuffer {
//CFRetain(frameBuffer);
MSImage *qry = [[MSImage alloc] initWithBuffer:frameBuffer orientation:AVCaptureVideoOrientationPortrait]; //MEMORY LEAK HERE???
qry = nil;
//CFRelease(frameBuffer);
}
This code effectively works. But here is my problem. When this code is run and profiled in instruments, I see a rapid increase in the overall bytes used, but the allocations profiler doesn't appear to increase. Nor do a see any 'leaks' using the leaks tool. But clearly, there is a rapid memory gain each time imageReconigitionProcessFrame: is called and the app crashes after a few seconds. When I set frameBuffer to nil, there is NO increase in memory (or course I also don't have the frame buffer to do any processing with).
I have tried transfering ownership of frameBuffer using CFRetain and CFRelease (commented out in the above code), but these don't seem to do anything either.
Does anyone have any idea where I could be leaking memory inside this function???
The method [[MSImage alloc] initWithBuffer: is form a third party SDK (Moodstocks, which is an awesome image recognition SDK) and it works just fine in their demos, so I don't think the problem is inside this function.
First of all, thanks for mentioning Moodstocks (I work for them): we're happy that you find our SDK useful!
To answer your question, I guess your code does indeed contain a leak: at the end of the imageReconigitionProcessFrame method, you should call [qry release]. The rule in Obj-C is quite simple: whenever you manually call alloc on an object, it should also be manually released!
That's BTW what is done in the Moodstocks SDK wrapper: if you look at the [MSScannerSession session: didOutputSampleBuffer:] method, you'll see that we do manually release the MSImage object after it's been processed.
As to why the profiler doesn't find this leak, I guess that it's due to the fact that leaks are analyzed every 10 seconds by default: in this case, the memory leak is so heavy (1280x720 frames, at 15+ FPS if you're on an iPhone 5, for 10 seconds: at least 130 MB leaked) that the code must crash before the first 10 seconds are reached.
Hope this helps!
I have populated a table view with some data from server and saved it to the core data.Now i have to delete the object from the core data when the user clicks on the delete option in the table view.
What i have tried is this:`
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
NSError *error;
[[Server serverInfo].context deleteObject:[self.couponList objectAtIndex:indexPath.row]];
if(![ [Server serverInfo].context save:&error]) {
// Handle error
NSLog(#"Unresolved error series %#, %#", error, [error userInfo]);
}
[self.couponList removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
if ([self.couponList count]==0) {
[self.table setEditing:NO animated:YES];
[self.editBt setStyle:UIBarButtonItemStyleBordered];
}
}
`
But it gives an exception and crashes.This is i am getting in the log :"Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'An NSManagedObjectContext cannot delete objects in other contexts".'Can anyone solve this?Thanks in advance
you have to make some manageobject context, a fetch request and then remove object using some predicate.
Apparently you are using more than one managed object context. This is indicated by your error message. Make sure that you are using only one managed object context, i.e. that there are no background tasks that use a different one.
You are keeping the data for your table view in a separate array. This might be another problem. The proper way to deal with core data and table views is to employ the NSFetchedResultsController.
Agree with Mundi.
On top of that, if you require many instances of the managedObjectContext, do not create that many but rather use the lock and unlock functions of the NSManagedObjectContext to enable multi-threading without issues on faults and invalidation of the objects.
EDIT
Maybe you can try creating just one NSManagedObjectContext in the AppDelegate and call the same managedObjectContext from the controllers where you need to use them. This, and together with the lock and unlock methods solved my issue with the multi-threading and invalidation of the objects.
Hope this helps.
For the last weeks I am learning Restkit (v0.10.0) and core data and the possibilities are endless with these great tools. Problem is I am a bit overwhelmed on how to see the bigger picture here. And because of the very fast paced updating of Restkit most of the tutorials/demo code is out of date and not working properly any more.
I have managed to get my tableview filled with data from my json on a remote server. I also worked out on how to make the remote data leading in combination with caching working now, but I am struggling with the NSManagedObjectContext/NSEntityDescription (Core data) and how it works out with Restkit when using POST commands.
If I understand it correctly the record is created in Core Data (after the comment line // Create a new instance ) and after that that data is used to create a POST request so that the record is posted to the server.
This code is being used to create a new record on the server but when the code is executed (I see a record being created on my server) but my tableview is not updated accordingly, the table view is not updated and therefore the new record is first visible when restarting the app. Manually refreshing the data from the server does not help either.
Hopefully someone can give me some pointers, or maybe a tutorial with Restkit/core data and a POST combined. Thanks!
- (void)createGoalWithName:(NSString *)name andDescription:(NSString *)goalDescription
{
Goal* goal = [Goal object];
goal.identifier = 0;
goal.name = name;
goal.goalDescription = goalDescription;
// Create a new instance of the entity managed by the fetched results controller.
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
[NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
[self saveContext];
[[RKObjectManager sharedManager].router routeClass:[Goal class] toResourcePath:#"/api/goals" forMethod:RKRequestMethodPOST];
[[RKObjectManager sharedManager] postObject:goal delegate:self];
[self.tableView reloadData];
}
- (void)saveContext {
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSError *error = nil;
if (![context save:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate.
You should not use this function in a shipping application,
although it may be useful during development.
If it is not possible to recover from the error,
display an alert panel that instructs the user to quit the application by pressing the Home button.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
You have to use blocks when using RestKit+CoreData, and forget the router setup:
NSString *postUrl = #"/someurl/newelement";
[ [RKObjectManager sharedManager] loadObjectsAtResourcePath:postUrl usingBlock:^(RKObjectLoader* loader) {
loader.serializationMIMEType = RKMIMETypeJSON;
loader.delegate = nil;
loader.targetObject = nil;
loader.method= RKRequestMethodPOST; // change to GET, POST, PUT etc
}];
Since you don't include the UI code, it's hard to diagnose this problem fully, but one thing that might be happening since the updates are showing up when you restart the app, is that you're not properly synchronizing changes between the various thread's local managed object contexts. RestKit has its own managed object context since it doesn't run on the main UI thread.
The concept of working with multiple threads in Core Data is covered in this Apple document: http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/CoreData/Articles/cdConcurrency.html but the gist of it is that you need to register for the notification NSManagedObjectContextDidSaveNotification and then invoke mergeChangesFromContextDidSaveNotification: on the UI thread's managed object context to safely merge the changes done on the RestKit thread.
Keep in mind that the notification will be posted on the RestKit thread, so you probably have to run the update on the main UI thread, e.g. something like this in the method receiving the notification:
[self.managedObjectContextForMainThread performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:YES];
Where the property NSManagedObjectContext* managedObjectContextForMainThread has been properly initialized to point to the UI thread's managed object context.
Hope this helps (if you haven't abandoned RestKit altogether...)
I'm still using a slightly older version of Restkit. But one key element is that a primary key attribute must be defined.
So that Restkit can keep your local stored objects and server objects in sync.
In your case, when defining mappings for your Goal object, you would do it like so:
goalMapping.primaryKeyAttribute = #"identifier";
I have a problem with core data when setting relationships:
The code below crashes randomly when setting the relationships between country and region.
If I disable the second for-loop, the method completes without errors.
Everything happens within the context living on the background-thread.
Again: I can create the objects for the regions and countries without trouble and they show up in the Simulators database just fine - but as soon as I try to set the relationships between then, the app crashes randomly.
Any thoughts ?
for (Region* region in regions) {
// only store if region code isn't empty
if (region.m_RegionCode != nil && [region.m_RegionCode length] > 0) {
NSManagedObject* cdRegion = [NSEntityDescription insertNewObjectForEntityForName:CDREGION inManagedObjectContext:self.objectContextBackground];
[cdRegion setValue:region.m_RegionCode forKey:#"code"];
[cdRegion setValue:region.m_regioncodedescription forKey:#"name"];
}
}
[self saveBackgroundContext];
for (Region* region in regions) {
if (region.m_RegionCode != nil && [region.m_RegionCode length] > 0) {
NSManagedObject* cdRegion = [self getManagedObject:CDREGION withCode:region.m_RegionCode];
NSManagedObject* CDCountry = [self getManagedObject:CDCOUNTRY withCode:region.m_countrycode];
[cdRegion setValue:CDCountry forKey:#"country"];
}
}
well - just to let you know: it actually was the problem (I know - it is listed on top of all pages regarding this topic :-) that I used a context between threads.
I mixed up queues and threads. I created a single background-queue where I used my "background-context" ... but of course i created several threads within that, who where interacting with the context... so...
btw: it was this excellent article that finally clarified it for me:
(came right in time :-)
http://www.cimgf.com/2011/08/22/importing-and-displaying-large-data-sets-in-core-data/
This error message is likely related to memory problem, about using (or releasing) already deallocated object.
You should run profiler to spot the memory problem, or debug with a breakpoint after the save method and following a line by line execution until it crashes.
Just guessing, if you are creating or getting the Regions object with some sort of factory method, probably it gets deallocated in the middle of execution by that method, sometimes the routine is fast enough to complete before the dealloc, sometimes not, that could explain the randomness.
Try to retain the Regions at the beginning and release at the end of the second loop.
I have been working with Apple's iPhone CoreDateRecipes sample code to learn more about tableviews and core data. I have coded my own test app based off of that sample, and it works well except for one thing. When I choose a photo for the 'recipe', no matter if it is from the camera or the library, when I hit "Done" to leave editing mode, it takes about 15 seconds before returning control to the user. This happens when testing on the device - in simulator, there is still a delay, but it is only 2-4 seconds.
I tested the "edit/done" button without choosing a photo and editing other data, and it saves instantaneously, so I'm pretty sure the image is to blame. Below is the code where it leaves editing mode, and the image processing code - what can I add/change/remove to speed this up? I know these sample code pieces are just proofs of concept, but I can't believe they published an example with such a crappy user experience!
Thanks, as always, for any guidance...let me know if there is any other code you need to see, or you can see the whole sample project here
- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
[super setEditing:editing animated:animated];
[self updatePhotoButton];
nameTextField.enabled = editing;
overviewTextField.enabled = editing;
[self.navigationItem setHidesBackButton:editing animated:YES];
if (!editing) {
NSManagedObjectContext *context = recipe.managedObjectContext;
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Error in RecipeDetailViewController:setEditing -- %#, %#",error, [error userInfo]);
abort();
}
}
}
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)selectedImage editingInfo:(NSDictionary *)editingInfo {
NSManagedObject *oldImage = recipe.image;
if (oldImage != nil) {
[recipe.managedObjectContext deleteObject:oldImage];
}
NSManagedObject *image = [NSEntityDescription insertNewObjectForEntityForName:#"Image" inManagedObjectContext:recipe.managedObjectContext];
recipe.image = image;
[image setValue:selectedImage forKey:#"image"];
CGSize size = selectedImage.size;
CGFloat ratio = 0;
if (size.width > size.height) {
ratio = 70.0 / size.width;
} else {
ratio = 70.0 / size.height;
}
CGRect rect = CGRectMake(0.0, 0.0, ratio * size.width, ratio * size.height);
UIGraphicsBeginImageContext(rect.size);
[selectedImage drawInRect:rect];
recipe.thumbnailImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self dismissModalViewControllerAnimated:YES];
}
First, as Gilbert pointed out, example code is not meant for production use and will be slow.
Second, you should not store images in Core Data. The example may show how to do it but it is generally a very bad idea. You should be storing images on disk and then keeping a pointer (file path) to the image in Core Data. There are some exceptions to this rule (very small images) but you should rethink your design.
Lastly, A lot of the slowness you are seeing may not be Core Data related. The image picker code is very slow on its own. I would recommend changing the code to just write to disk and see how slow that is compared to the writing to Core Data. I would be surprised if it was much faster.
Update
You can store small images inside of Core Data and my advice from other posts stands, mores for the desktop than iOS. The reason for this is the cache. On iOS, the cache that Core Data uses is very small. If you store images in the database that are large, you can blow out that cache easily and cause future calls that should be in the cache to hit the disk instead. This is not a hard and fast rule of "don't store binary data" but more of a rule of watch your performance and if you are hitting the disk more than you should, binary data could easily be the cause.
Contacts
With regard to contacts, you can be slower because they are doing things differently than you are and they could easily be using private APIs to access the camera. Because it is Apple, they don't necessarily play by the same rules as us.
I'm not an iPhone developer, but generally, example code does not take into account the user experience. Example code shows examples.
In general, you need to perform expensive (long running) operations in additional threads.
Maybe this blog post will help: Respect the Main Thread
I downloaded the project, built it and ran it in the simulator. I couldn't reproduce your problem. I found that the time it took to save an image was visually instantaneous i.e. no longer than the view transition.
If you see the same issue on the original, unmodified project, then you have something else going on. Make sure you have the latest version of the project which should be the one at your link. I know there is at least one really old one floating around because I hit it recently.