NSScreen leaking under GarbageCollection, Is this a bug - macos

I've been trying to debug memory leaks in an objective-c program for the last couple of days and was playing around with some very basic sample code to test some ideas and came across something that seems to be a bug to me but wanted to get some opinions from those with more knowledge when it comes to developing in the Mac world.
I have the following bit of code:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSRect desktopRect = NSZeroRect;
NSArray* screens = [[NSScreen screens] autorelease];
int x = 0;
for (x = 0; x < [screens count]; x++)
{
NSScreen *screen = [screens objectAtIndex:x];
}
[pool drain];
This code lives inside a method that I am calling from a while loop that continues until I kill the process. (Remember this is just a simple test.)
When I run this loop and watch allocations in Instruments things work perfectly fine when I am not implementing Garbage Collection. My overall number of allocations stays constant and everything with the NSScreens are getting cleaned up and released.
As soon as I enable Garbage Collection, the call to [NSScreen screens] starts leaking core graphics objects like mad by never releasing them.
I have seen in other forums where people a few years back talked about Core Graphics being very leaky under Garbage Collection. I'm wondering if that is the case here as well.
Any thoughts from the community?

Ok, I'm going to go out on a limb here and say that yes it is a bug on Apples part though I did find a solution using some Toll-Free-Bridging magic. I modified the code to be:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSRect desktopRect = NSZeroRect;
CFArrayRef screens = CFMakeCollectable((CFArrayRef)[NSScreen screens]);
int x = 0;
for (x = 0; x < [screens count]; x++)
{
NSScreen *screen = (NSScreen*)CFArrayGetValueAtIndex(screens,x);
}
[pool drain];
By calling CFMakeCollectable on the toll-free-bridged array, it allowed the background core graphics objects to be properly cleaned up when using garbage collection.
This definitely makes me think that Apple needs to do some more work with Garbage Collection and Cocoa objects that wrap Core Graphics.

Related

pointer being freed was not allocated -- how to debug in Xcode objective c?

I need some help understanding how to use the debugging tools -- the previous posts on this topic suggest using NSZombie and setting breakpoints. These are not working for me, meaning I don't understand how to use them properly -- or how to interpret what they are telling me.
(I'm operating in a vacuum, basically -- no one in my workplace or social circle does objective c programming...)
Here's the offending code. My idea is to make moving array of 100 objects. The routine motionDataArray is called by the motionManager startDeviceMotionUpdatesUsingReferenceFrame method. The array gets populated and when its length reaches 100 it has the first object removed so there will always only ever be 100 objects in the array. This way, I figure, I can take the mean of the data and get rid of a lot of the noise.
(I imagine there is probably a better way to achieve this goal, but here is what I've come up with...)
- (void) motionDataArray: (CMCalibratedMagneticField) field
{
NSDictionary *motionData = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithDouble: motionManager.deviceMotion.gravity.x],#"gravity_x",
[NSNumber numberWithDouble: motionManager.deviceMotion.gravity.y],#"gravity_y",
[NSNumber numberWithDouble: motionManager.deviceMotion.gravity.z],#"gravity_z",
[NSNumber numberWithDouble: field.field.x],#"mag_x",
[NSNumber numberWithDouble: field.field.y],#"mag_y",
[NSNumber numberWithDouble: field.field.z],#"mag_z",
[NSNumber numberWithDouble: `enter code here`locationManager.heading.magneticHeading],#"mag_heading",
[NSNumber numberWithDouble: motionManager.deviceMotion.attitude.roll],#"roll",
[NSNumber numberWithDouble: motionManager.deviceMotion.attitude.yaw],#"yaw",
[NSNumber numberWithDouble: motionManager.deviceMotion.attitude.pitch],#"roll",
nil];
//NSLog(#"%d",[motionArray count]);
if([timerArray count] > arrayCount - 1)
{
int i = [timerArray count] - arrayCount;
for (int j = 0; j < i + 1; j++)
{
[motionArray removeObjectAtIndex:0];
}
}
[motionArray addObject:motionData];
}
The code runs fine for the first two hundred iterations or so, and then comes the crash and the error: pointer being freed was not allocated.
Any good pointers on how to interpret the output from Instruments or anyother debugging methodology would be great help for me, and I'm sure for others out there too. Many thanks in advance...
Tim Redfield
OK, I think I have a solution -- it runs without crashing and the array stays at the 100 maximum. But, I don't really understand WHY it works. It seems the array cannot have an object removed from it, unless #synchronize is used, as follows:
if([timerArray count] > arrayCount - 1)
{
int i = [timerArray count] - arrayCount;
for (int j = 0; j < i + 1; j++)
{
#synchronized(timerArray) {
[timerArray removeObjectAtIndex:0];
}
}
}
I guess this has to do with the array having an object removed before it is allocated, which seems to happen because the code loop is not necessarily running at the same speed as the motionManager is pushing the data. I am still trying to wrap my head around this. Comments welcomed!

Setting a weak ivar and getting nil

In an ARC project, I'm setting a __weak ivar (declared as id __weak weakLayer;) to a CALayer which is retained by it's superlayer. Most times this works. Sometimes, the weak ivar tests as nil. I wrote some test code to simplify debugging that demonstrates the problem. I expect this to be an infinite loop but it breaks out, usually in less than 20 iterations. The number of iterations required to break out is not consistent.
array = [NSMutableArray array];
while (1) {
CALayer *layer = [CALayer layer];
[array addObject:layer];
weakLayer = layer;
if (!weakLayer) {
NSLog (#"nil");
break;
}
NSLog(#"not nil");
}
If I drop a breakpoint with an action of po weakLayer inside the if, it prints a valid object. I have tested on Xcode 4.3.3 and 4.4 with the Lion and Mountain Lion SDKs.
3 people (including myself) have now confirmed this to work on 10.8, but not on 10.7.4.

imageWithCGImage not being released or is trapped by Cache similar to imageNamed, any work around for generating dynamic images?

I'm generating UIImages with a bit-bucket, creating them on the fly and swapping the UIImageView's image. Is there a way to edit the UIImageView's Image directly? (ie. change the color of a specific pixel, without removing the UIImage from the UIImageView, and get it to redraw.)
Currently, I'm flushing the UIImage and using imageWithCGIImage to make a new one, and assigning it to the UIImageView. This works. Shows no MemLeaks. But on the iPhone (3Gs) after about 100 image replacements, CRASHES. Cache'n issue? The memory summation seems to be hitting the phone's limit if cache not releasing, however, Simulator does not show memory consumption with each image swap. Stays flatlined without leaks.
Note: topologyImage array is the RGBA pixel-bucket. The REF variables are not released. Every attempt to do so, crashes next call. Without, Instruments reports no leaks.
=========
CGColorSpaceRef colorSpaceRef=CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaLast;
CGColorRenderingIntent renderingIntent=kCGRenderingIntentDefault;
CGDataProviderRef provider=CGDataProviderCreateWithData(NULL,topologyImage,(I*I*4),NULL);
CGImageRef imageRef=CGImageCreate(I,I,8,4*8,4*I,colorSpaceRef,bitmapInfo,provider,NULL,false,renderingIntent);
UIImage *img=[UIImage imageWithCGImage:imageRef];
if( IMG[NDXtopo].vw ) {
[IMG[NDXtopo].vw setImage:img];
}
else {
IMG[NDXtopo].vw=[[UIImageView alloc] initWithImage:img];
[master.view addSubview:IMG[NDXtopo].vw];
}
Basically you should release your references, especially the CGImageRef since the imageWithCGImage doesn't take ownership of the CGImage but rather seems to copy the data internally.
The docs on this are quite unclear, but from what I have found in my testing if I don't release CGImageRefs and CGDataProviderRefs it will eventually cause the application to get memory warnings... and then crash.
Not sure why you would have a crash, but in doing a quick test with:
UIImageView *view = [[UIImageView alloc] init];
int I = 128;
unsigned char *topologyImage = malloc(I*I*4*sizeof(unsigned char));
for(int i=0; i<I*I*4; i++)
{
topologyImage[i] = 100;
}
for(int i=0; i<1000; i++)
{
CGColorSpaceRef colorSpaceRef=CGColorSpaceCreateDeviceRGB();
CGBitmapInfo bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaLast;
CGColorRenderingIntent renderingIntent=kCGRenderingIntentDefault;
CGDataProviderRef provider=CGDataProviderCreateWithData(NULL,topologyImage,(I*I*4),NULL);
CGImageRef imageRef=CGImageCreate(I,I,8,4*8,4*I,colorSpaceRef,bitmapInfo,provider,NULL,false,renderingIntent);
CGColorSpaceRelease(colorSpaceRef);
CGDataProviderRelease(provider);
UIImage *img=[UIImage imageWithCGImage:imageRef];
view.image = img;
CGImageRelease(imageRef);
}
free(topologyimage);
Seems to work just fine for me, so whatever is causing your crash seems to be because of something outside of your example, like for example how you got the image data into the topologyImage

Is there a more memory efficient way to search through a Core Data database?

I need to see if an object that I have obtained from a CSV file with a unique identifier exists in my Core Data Database, and this is the code I deemed suitable for this task:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity;
entity =
[NSEntityDescription entityForName:#"ICD9"
inManagedObjectContext:passedContext];
[fetchRequest setEntity:entity];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"uniqueID like %#", uniqueIdentifier];
[fetchRequest setPredicate:pred];
NSError *err;
NSArray* icd9s = [passedContext executeFetchRequest:fetchRequest error:&err];
[fetchRequest release];
if ([icd9s count] > 0) {
for (int i = 0; i < [icd9s count]; i++) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
NSString *name = [[icd9s objectAtIndex:i] valueForKey:#"uniqueID"];
if ([name caseInsensitiveCompare:uniqueIdentifier] == NSOrderedSame && name != nil)
{
[pool release];
return [icd9s objectAtIndex:i];
}
[pool release];
}
}
return nil;
After more thorough testing it appears that this code is responsible for a huge amount of leaking in the app I'm writing (it crashes on a 3GS before making it 20 percent through the 1459 items). I feel like this isn't the most efficient way to do this, any suggestions for a more memory efficient way? Thanks in advance!
Don't use the like operator in your request predicate. Use =. That should be much faster.
You can specify the case insensitivity of the search via the predicate, using the [c] modifier.
It's not necessary to create and destroy an NSAutoreleasePool on each iteration of your loop. In fact, it's probably not needed at all.
You don't need to do any of the checking inside the for() loop. You're duplicating the work of your predicate.
So I would change your code to be:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:...];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"uniqueID =[c] %#", uniqueIdentifier]];
NSError *err = nil;
NSArray *icd9s = [passedContext executeFetchRequest:fetchRequest error:&err];
[fetchRequest release];
if (error == nil && [icd9s count] > 0) {
return [icd9s objectAtIndex:0]; //we know the uniqueID matches, because of the predicate
}
return nil;
Use the Leaks template in Instruments to hunt down the leak(s). Your current code may be just fine once you fix them. The leak(s) may even be somewhere other than code.
Other problems:
Using fast enumeration will make the loop over the array (1) faster and (2) much easier to read.
Don't send release to an autorelease pool. If you ever port the code to garbage-collected Cocoa, the pool will not do anything. Instead, send it drain; in retain-release Cocoa and in Cocoa Touch, this works the same as release, and in garbage-collected Cocoa, it pokes the garbage collector, which is the closest equivalent in GC-land to draining the pool.
Don't repeat yourself. You currently have two [pool release]; lines for one pool, which gets every experienced Cocoa and Cocoa Touch programmer really worried. Store the result of your tests upon the name in a Boolean variable, then drain the pool before the condition, then conditionally return the object.
Be careful with variable types. -[NSArray count] returns and -[NSArray objectAtIndex:] takes an NSUInteger, not an int. Try to keep all your types matching at all times. (Switching to fast enumeration will, of course, solve this instance of this problem in a different way.)
Don't hide releases. I almost accused you of leaking the fetch request, then noticed that you'd buried it in the middle of the code. Make your releases prominent so that you're less likely to accidentally add redundant (i.e., crash-inducing) ones.

Finding name of inserted CD in Cocoa

I had a bookmark which described the process on how to do this - finding the name of a mounted CD in OS X - but I deleted the bookmark when I reformatted my Mac. :P
Reading up on the subject, this is what I think might work. Basically, I need to verify if a particular CD is mounted before continuing in the application
Access NSWorkspace
Perform 'checkForRemovableMedia'
Grab array of mounted media paths from 'mountedRemoveableMedia'
Run through array of mounted media paths to find one containing name of targeted disc
Anyway, this is what I've came up with as a possible solution. Anyone else have any other ideas/knowledge in this area in Cocoa? Suggestions :)
EDIT:
I made this code below, but isn't working. It creates an NSCFArray which contains NSCFStrings, which I read up and shouldn't be doing.
NSArray *mountedItems = [[NSWorkspace sharedWorkspace] mountedRemovableMedia];
int count = [mountedItems count];
int i = 0;
for (i = 0; i < count; i++) {
//line is not printing. contains NSCFArray and NSCFStrings
[NSLog print:[[mountedItems objectAtIndex:i] stringValue]];
}
OK, so I'm an idiot.
[[NSWorkspace sharedWorkspace] checkForRemovableMedia];
NSArray *mountedItems = [[NSWorkspace sharedWorkspace] mountedRemovableMedia];
NSUInteger count = [mountedItems count];
NSUInteger i = 0;
for (i = 0; i < count; i++) {
NSString *tempString = [mountedItems objectAtIndex:i];
NSLog(#"%#",tempString);
}
I was not only using NSLog wrong, but completely didn't even realize that perhaps calling 'stringValue' on a string is redundant. And also what caused the code to break. :P
This works now; I also added 'checkForRemovableMedia' as an extra precaution.

Resources