Understanding iOS Instruments - iphone

I am creating an iPhone app. Running into memory issues I started using Instruments to track down any memory problems. Am running into some strange behavior that leads me to believe that I am either mis-using Instruments or mis-reading its data.
These are the LiveBytes values recorded when moving in and out of a location:
**Expensive Location-**
World (12 MB)
Loc (27 MB)
World (13 MB )
Loc (28 MB)
World (14 MB)
-Crash
**Cheap Location-**
World (12 MB)
Loc (23 MB)
World (13 MB )
Loc (24 MB)
World (14 MB)
-Crash
Notice how I still crash even though the cheap location's memory has come no where near the expensive locations memory. Could anyone help me out here?

I'm not sure if this is related to the problem you have but I hope it helps: I was recently tracking the memory footprint of an app and I noticed that even though the dealloc message was being sent to a view controller after hitting "back" on the UINavigator controller, I still had a few dozen live objects left over from this operation (you can see this in the 'Allocations' panel of the instruments app). To solve this I used a mix of a few things:
First, I added the following three methods to NSLog the retain counters of my Custom subviews (found here on SO at iOS4 - fast context switching):
#pragma mark - RETAIN DEBUG MAGIC
// -----------------------------------------------------------------------------
- (id)retain
{
NSLog(#"retain \t%s \tretainCount: %i", __PRETTY_FUNCTION__ , [self retainCount]);
return [super retain];
}
- (void)release
{
NSLog(#"release \t%s \tretainCount: %i", __PRETTY_FUNCTION__ , [self retainCount]);
[super release];
}
- (id)autorelease
{
NSLog(#"autorelease \t%s \tretainCount: %i", __PRETTY_FUNCTION__ , [self retainCount]);
return [super autorelease];
}
Then, I isolated each one of the view building blocks leaving only one simple task (for example loading a UIButton as a subview) and went back to the instruments app to track the live objects (under Product > Profile in Xcode) and disabled all the objects with 'NS', 'CF' and 'Malloc' prefixes (you can do this clicking on the little i button next to the 'Allocations' tab). After this, selected "Call Trees" on the bottom right pane and kept drilling until I found a few places where the object counter went up as I navigated back and forth.
Notice that you can double click on the symbol to see the details related to the calls made to the processor. Additionally, clicking on the little i icon will bring a pop up with the backtraces for the highlighted call.
When looking at the backtraces you will see that some of them have a small icon that depicts a person on a frame (the text next to these icons is significantly darker as a visual cue). Double clicking on these will take you to the line in your code responsible for this call.
Below are a few links that might give you a hand in understanding more about instruments:
http://www.raywenderlich.com/2696/how-to-debug-memory-leaks-with-xcode-and-instruments-tutorial
http://developer.apple.com/library/mac/#documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/ViewingandAnalyzingData/ViewingandAnalyzingData.html
Note:
At the end of my journey, all I had to do was release my views after adding them to their 'super' views to ensure they would be dealloc'd. i.e.,
[[self view] addSubView:aButton];
[aButton release];

Related

Terminated App due to memory Pressure

I have an app to take images in Burst mode ,but once when the image is take and about to come the preview its getting crashed and error shows that "Terminated App due to memory Pressure "
I need to take more number of images when user holds the camera button...after Leave the button ,i need to show all the images as slideshow..what i have to do ?
My code is:
- (void)longPress:(UILongPressGestureRecognizer*)gesture {
if (gesture.state == UIGestureRecognizerStateBegan) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(takePictures)
name:AVCaptureSessionDidStartRunningNotification object:nil];
}
else if (gesture.state == UIGestureRecognizerStateEnded)
{
}
-(void)takePictures
{
[imagePicker takePicture];
}
Help me..
Storing images in RAM is costly, due to the high resolution of the images. What more, your observer cause takePicture to be called multiple times - too many. As you take multiple pictures using the imagePicker, all these images quickly consume RAM, and since iOS has no swap - you run out of RAM. Jetsam/memorystatus then kicks in, and kills your app, for having consumed so much memory.
Ways around this:
A) Take less pictures in burst mode. Use some global variable , say j, increment it in takePictures, but only take the actual picture on j % 2 == 0, or j %3 ==0 (you'll need to play around with the value)
B) try to save at least some of the photos to storage, then release them from RAM (remove the reference to them).

iOS - CMSampleBufferRef is not being released from captureOutput:didOutputSampleBuffer:fromConnection

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!

NSCache is not evicting data

NSCache is a rarely used tool which actually looks quite useful. I created a simple experiment to see how it works and it looks like it does not auto-evict data in low memory situations (or I am doing something wrong!)
- (void)viewDidLoad
{
_testCache = [[NSCache alloc] init];
// Allocate 600 MB of zeros and save to NSCache
NSMutableData* largeData = [[NSMutableData alloc] init];
[largeData setLength:1024 * 1024 * 600];
[_testCache setObject:largeData forKey:#"original_Data"];
}
- (IBAction)buttonWasTapped:(id)sender {
// Allocate & save to cache 300 MB each time the button is pressed
NSMutableData* largeData = [[NSMutableData alloc] init];
[largeData setLength:1024 * 1024 * 300];
static int count = 2;
NSString* key = [NSString stringWithFormat:#"test_data_%d", count++];
[_testCache setObject:largeData forKey:key];
NSMutableData* dataRecoveredFromCache = [_testCache objectForKey:#"original_Data"];
if (dataRecoveredFromCache) {
NSLog(#"Original data is ok");
} else {
NSLog(#"Original data is missing (purged from cache)");
}
}
So I ran the app in the simulator, and taped the button a few times however no items were evicted... The app eventually crashed:
2012-07-17 14:19:36.877 NSCacheTest[15302:f803] Data is ok
2012-07-17 14:19:37.365 NSCacheTest[15302:f803] Data is ok
2012-07-17 14:19:37.861 NSCacheTest[15302:f803] Data is ok
2012-07-17 14:19:38.341 NSCacheTest[15302:f803] Data is ok
2012-07-17 14:19:38.821 NSCacheTest[15302:f803] Data is ok
NSCacheTest(15302,0xac0942c0) malloc: *** mmap(size=393216000) failed (error code=12)
*** error: can't allocate region
From the doc (Emphasis mine): The NSCache class incorporates various auto-removal policies, which ensure that it does not use too much of the system’s memory. The system automatically carries out these policies if memory is needed by other applications. When invoked, these policies remove some items from the cache, minimizing its memory footprint.
Apple does not state that the memory will be freed on memory warning - in my experience, the cache is most often purged when the app goes to background or when you add more large elements.
Here's quoted docs ...
The NSCache class incorporates various auto-removal policies, which
ensure that it does not use too much of the system’s memory. The
system automatically carries out these policies if memory is needed by
other applications. When invoked, these policies remove some items
from the cache, minimizing its memory footprint.
... as you can see it states that it removes some items, not all items. It depends on NSCache internal policies, available memory, device status, etc. You shouldn't worry about these policies.
You can control them with countLimit, totalCostLimit properties and you can add object with cost, look at setObject:forKey:cost:.
Also you can evict objects by yourself. Add NSDiscardableContent protocol implementation to your objects and setEvictsObjectsWithDiscardedContent: to YES.
I am using that class too. Note that the documentation states the NSCache is tied into the OS and probably has access to memory information deep within the OS. The memory warning is just that - it just sends a memory warning to the appDelegate/viewControllers.
If you really want to test your code out, you will probably need a test mode where you start mallocing lots of memory (creating a huge leak so to speak). You might need parcel this out in chunks during each main runloop, so the OS has the opportunity to see he memory going down (I have an app that chew ups lots of memory, and it does it so fast on the 3GS it just gets killed never having got a memory warning.

Memory issue or something else?

I'm running an application on my iPad that loads images into a carousel. In total there are 138 images. If I reduce that number to 100, the application loads fine. At 138, however, the application pauses, rather than crashes.
The viewDidLoad and first for statement are being reached, as indicated by a breakpoint. The issue lies in the second for statement.
// loading images into the queue
loadImagesOperationQueue = [[NSOperationQueue alloc] init];
NSMutableArray *tmpArray = [[NSMutableArray alloc] initWithCapacity:14];
for (int i = 0; i < 137; i++) {
[tmpArray addObject:[NSString stringWithFormat:#"cover_%d.jpg", i]];
}
for (int i = 0; i < 137; i++) {
int index = arc4random() % [tmpArray count];
NSString *imageName = [tmpArray objectAtIndex:index];
[tmpArray removeObjectAtIndex:index];
[(AFOpenFlowView *)self.view setImage:[UIImage imageNamed:imageName] forIndex:i];
}
I'm guessing that there is a memory issue, although since I'm using iOS 5 with ARC I shouldn't have to be doing any manual memory management.
Could it possibly be that there is just too much going on to hold in memory? It's 138 images # ~146 KB per. That's roughly 20 MB, but I don't think that alone could cause the issue.
GDB quits without any useful output, actually no output at all. Running instruments reveals that when the pause takes place real memory usage is only at 6.11 MB, 77.4% CPU but 175 MB of virtual mem.
What I'm concerned about is the fact that there is no memory warning or even an actual crash, the thread just pauses and cannot be resumed or killed automatically, you have to kill it from within xcode.
It looks pretty like your application gets killed because it takes too much memory. Indeed, 20MB is the limit that I have experienced, and this also corresponds to what you could find by googling it.
About the fact that you don't receive any memory warning, this happens because you are loading the images in memory in a tight loop that does not returns control to the main loop. So you don't have the chance to receive the message didReceiveMemoryWarning.
The solution is pretty simple, pre-load a number of images and as you move through the carousel, pre-load some more images, while also releasing the older ones. In reality, you don't need to have in memory more than 5 images at any time to keep the balls running. If you are concerned about not having to load the images almost each and every time, then you can increase that number.

core data exc_bad_access when setting relationships

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.