Memory error while accessing ALAssetsLibrary
-
21-12-2019 - |
Question
I'm getting a memory error. I'm getting a memory error because the memory usage increases exponentially. Clearly I am not releasing something, any ideas what that may be?
Here is my method to determine red pixels present in a UI image. It returns the count.
- (NSUInteger)getRedPixelCount:(UIImage*)image
{
NSUInteger numberOfRedPixels = 0;
struct pixel* pixels = (struct pixel*) calloc(1, image.size.width * image.size.height * sizeof(struct pixel));
if (pixels != nil)
{
CGContextRef context = CGBitmapContextCreate((void *) pixels,
image.size.width,
image.size.height,
8,
image.size.width * 4,
CGImageGetColorSpace(image.CGImage),
(CGBitmapInfo)kCGImageAlphaPremultipliedLast);
if (context != NULL)
{
CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, image.size.width, image.size.height), image.CGImage);
NSUInteger numberOfPixels = image.size.width * image.size.height;
while (numberOfPixels > 0) {
if (pixels->r == 255) {
numberOfRedPixels++;
}
pixels++;
numberOfPixels--;
}
CGContextRelease(context);
}
}
return numberOfRedPixels;
}
This is the code to iterate through the photo library images and determine each of their red pixels.
[self.library enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
if (group) {
[group setAssetsFilter:[ALAssetsFilter allPhotos]];
[group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop){
if (asset) {
ALAssetRepresentation *rep = [asset defaultRepresentation];
CGImageRef iref = [rep fullResolutionImage];
if (iref){
UIImage *myImage = [UIImage imageWithCGImage:iref scale:[rep scale] orientation:(UIImageOrientation)[rep orientation]];
NSLog(@"%i", [self getRedPixelCount:myImage]);
}
}
}];
}
} failureBlock:^(NSError *error) {
NSLog(@"error enumerating AssetLibrary groups %@\n", error);
}];
Regards, C.
Solution
You're not releasing the memory allocated by
struct pixel* pixels = (struct pixel*) calloc(1, image.size.width * image.size.height * sizeof(struct pixel));
You need to add:
free(pixels);
at the bottom of the if(pixels != nil) block.
Make the first block look like:
- (NSUInteger)getRedPixelCount:(UIImage*)image
{
NSUInteger numberOfRedPixels = 0;
struct pixel* pixels = (struct pixel*) calloc(1, image.size.width * image.size.height * sizeof(struct pixel));
if (pixels != nil)
{
CGContextRef context = CGBitmapContextCreate((void *) pixels,
image.size.width,
image.size.height,
8,
image.size.width * 4,
CGImageGetColorSpace(image.CGImage),
(CGBitmapInfo)kCGImageAlphaPremultipliedLast);
if (context != NULL)
{
CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, image.size.width, image.size.height), image.CGImage);
NSUInteger numberOfPixels = image.size.width * image.size.height;
struct pixels* ptr = pixels;
while (numberOfPixels > 0) {
if (ptr->r == 255) {
numberOfRedPixels++;
}
ptr++;
numberOfPixels--;
}
CGContextRelease(context);
}
free(pixels);
}
return numberOfRedPixels;
}
Also it will help if the second block changes to include:
@autoreleasepool {
ALAssetRepresentation *rep = [asset defaultRepresentation];
CGImageRef iref = [rep fullResolutionImage];
if (iref){
UIImage *myImage = [UIImage imageWithCGImage:iref scale:[rep scale] orientation:(UIImageOrientation)[rep orientation]];
NSLog(@"%i", [self getRedPixelCount:myImage]);
<#statements#>
}
}
although the major leak is not freeing the pixel buffer.
OTHER TIPS
You can embrace 'enumerateAssetsUsingBlock' block with @autorelease:
@autorelease {
if (asset) {
...
}
}
This force all autoreleasing objects to release immediately.
UPD: Use [asset thumbnail] instead of fullScreenImage or fullResolutionImage. These methods generate huge amount of pixel data.
UPD2: if even [asset thumbnail] did not help, than you must find a way to release this image data, it could be little bit tricky as long as you can't to release it directly calling CGImageRelease. Try something like this:
NSMutabelArray* arr = [NSMutabelArray new];
In your enumerateAssetsUsingBlock just put asset object in this array:
[arr addObject:asset];
And do nothing else in this block.
Then iterate through this array in a way:
while (arr.count > 0)
{
ALAsset* asset = [arr lastObject];
// do smth with asset
[arr removeLastObject]; // this will remove object from memory immediatelly
}