Reputation: 3931
I have the following function that opens an image, scales it and saves it to another file.
-(void)writeFileToIcon:(NSString *)fullPath :(NSString *)finalPath :(NSSize)outputSize
{
NSData *dataToWrite;
NSBitmapImageRep *rep;
rep = [NSBitmapImageRep imageRepWithData:[[self scaleImage:[[NSImage alloc]initWithContentsOfFile:fullPath] toSize:outputSize] TIFFRepresentation]];
dataToWrite = [rep representationUsingType:NSPNGFileType properties:nil];
[dataToWrite writeToFile:finalPath atomically:YES];
}
- (NSImage *)scaleImage:(NSImage *)image toSize:(NSSize)targetSize
{
if ([image isValid])
{
NSSize imageSize = [image size];
float width = imageSize.width;
float height = imageSize.height;
float targetWidth = targetSize.width;
float targetHeight = targetSize.height;
float scaleFactor = 0.0;
float scaledWidth = targetWidth;
float scaledHeight = targetHeight;
NSPoint thumbnailPoint = NSZeroPoint;
if (!NSEqualSizes(imageSize, targetSize))
{
float widthFactor = targetWidth / width;
float heightFactor = targetHeight / height;
if (widthFactor < heightFactor)
{
scaleFactor = widthFactor;
}
else
{
scaleFactor = heightFactor;
}
scaledWidth = width * scaleFactor;
scaledHeight = height * scaleFactor;
if (widthFactor < heightFactor)
{
thumbnailPoint.y = (targetHeight - scaledHeight) * 0.5;
}
else if (widthFactor > heightFactor)
{
thumbnailPoint.x = (targetWidth - scaledWidth) * 0.5;
}
NSImage *newImage = [[NSImage alloc] initWithSize:NSMakeSize(scaledWidth, scaledHeight)];
[newImage lockFocus];
NSRect thumbnailRect;
thumbnailRect.origin = NSZeroPoint;
thumbnailRect.size.width = scaledWidth;
thumbnailRect.size.height = scaledHeight;
[image drawInRect:thumbnailRect
fromRect:NSZeroRect
operation:NSCompositeSourceOver
fraction:1.0];
[newImage unlockFocus];
return newImage;
}
return nil;
}
return nil;
}
However each time this function is called, the memory usage is getting higher (up to 5 GB for 1000 calls).
The issue is the drawRect function which seems to take a lot of memory (according to the analyser) but does not release it.
How can I "ask" ARC to release it ?
Thanks.
Upvotes: 3
Views: 235
Reputation: 53010
My guess is your issue is related to caching in the image classes, but that could be wrong. What does appear to improve matters:
-(void)writeFileToIcon:(NSString *)fullPath :(NSString *)finalPath :(NSSize)outputSize
{
// wrap in autorelease pool to localise any use of this by the image classes
@autoreleasepool
{
NSImage *dstImage = [self scaleImageFile:finalPath toSize:outputSize];
NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithData:[dstImage TIFFRepresentation]];
NSData *dataToWrite = [rep representationUsingType:NSPNGFileType properties:nil];
[dataToWrite writeToFile:finalPath atomically:YES];
}
}
- (NSImage *)scaleImageFile:(NSString *)fullPath toSize:(NSSize)targetSize
{
NSImageRep *srcImageRep = [NSImageRep imageRepWithContentsOfFile:fullPath];
if (srcImageRep == nil)
return nil;
NSSize imageSize = NSMakeSize(srcImageRep.pixelsWide, srcImageRep.pixelsHigh);
NSSize scaledSize;
NSPoint thumbnailPoint = NSZeroPoint;
NSRect thumbnailRect;
if (!NSEqualSizes(imageSize, targetSize))
{
// your existing scale calculation
...
scaledSize = NSMakeSize(scaledWidth, scaledHeight);
}
else
scaledSize = imageSize;
srcImageRep.size = scaledSize;
NSImage *newImage = [[NSImage alloc] initWithSize:scaledSize];
[newImage lockFocus];
thumbnailRect.origin = NSZeroPoint;
thumbnailRect.size = scaledSize;
[srcImageRep drawInRect:thumbnailRect];
[newImage unlockFocus];
return newImage;
}
This uses NSImageRep
which appears in this case to reduce memory footprint. On a sample run using full screen desktop images scaled to 32x32 the above hovered around 16Mb while the original NSImage
based version steadily grew to 32Mb. YMMV of course.
HTH
Upvotes: 1
Reputation: 1255
One may need to look at the whole code to find the problem. One idea follows, though: under ARC you cannot call "release" on objects, but if you set the pointer to the object to "nil", the object will be released (unless other strong references to that object exist somewhere).
I suggest you track your code and make sure you don't hold objects you don't need anymore. If your code is well encapsulated and structured, this shouldn't happen.
If your code is well designed, though, there is the chance that this amount of memory is actually needed (unlikely, but don't know without more details). If this would be the case, then let the system manage the memory, it will release the objects when it is appropriate. This, and try to make optimizations somewhere if memory usage is a concern.
Off-topic: these long nested if's with multiple return points within the method are not a very good idea; I suggest you reestructure your code slightly. If you write clearer code, you'll have more control over it, and you will find solutions to problems faster.
Upvotes: 2
Reputation: 44886
Are you calling this from a loop or without returning to the main event loop? Adding an explicit @autoreleasepool
might help.
-(void)writeFileToIcon:(NSString *)fullPath :(NSString *)finalPath :(NSSize)outputSize
{
@autoreleasepool {
NSBitmapImageRep *rep = [NSBitmapImageRep imageRepWithData:[[self scaleImage:[[NSImage alloc]initWithContentsOfFile:fullPath] toSize:outputSize] TIFFRepresentation]];
NSData *dataToWrite = [rep representationUsingType:NSPNGFileType properties:nil];
[dataToWrite writeToFile:finalPath atomically:YES];
}
}
Theoretically, this isn't necessary as code compiled with ARC short circuits the autoreleasepool in some circumstances. However, you may be defeating that optimization here somehow.
Note that it's generally better to do this in the place where the memory allocation becomes the problem logically. So your for
loop where you call this method would be a better place for the @autoreleasepool
.
Upvotes: 1