5

In my iOS app, I download game assets from s3. My artist updated one of the assets, and we immediately started seeing crashes with the new asset.

Here's the code that processes them:

+ (UIImage *)imageAtPath:(NSString *)imagePath scaledToSize:(CGSize)size
{
    // Create the image source (from path)
    CGImageSourceRef imageSource = CGImageSourceCreateWithURL((__bridge CFURLRef) [NSURL fileURLWithPath:imagePath], NULL);
    NSParameterAssert(imageSource);

    // Get the image dimensions (without loading the image into memory)
    CGFloat width = 512.0f, height = 384.0f;
    CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);
    NSParameterAssert(imageProperties);

    if (imageProperties) {

        CFNumberRef widthNumRef  = CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelWidth);
        if (widthNumRef != NULL) {
            CFNumberGetValue(widthNumRef, kCFNumberCGFloatType, &width);
        }

        CFNumberRef heightNumRef = CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelHeight);
        if (heightNumRef != NULL) {
            CFNumberGetValue(heightNumRef, kCFNumberCGFloatType, &height);
        }
         CFRelease(imageProperties);

    } else {

        // If the image info is somehow missing, make up some numbers so we don't divide by zero
        width = 512;
        height = 384;
    }

    // Create thumbnail options
    CGFloat maxDimension = size.height;

    if (useDeviceNativeScale) {
        maxDimension *= [UIScreen mainScreen].scale;
    }
    NSParameterAssert(maxDimension);

    // If we have a really wide image, scaling it to the screen height will make it too blurry.
    // Here we calculate the maximum dimension we want (probably width) to make the image be the full height of the device
    CGFloat imageAspectRatio = width / height;

    if (width > height) {
        maxDimension *= imageAspectRatio;
    }

    CFDictionaryRef options = (__bridge CFDictionaryRef) @{
                                                       (id) kCGImageSourceCreateThumbnailWithTransform : @YES,
                                                       (id) kCGImageSourceCreateThumbnailFromImageAlways : @YES,
                                                       (id) kCGImageSourceShouldCache : @YES,
                                                       (id) kCGImageSourceThumbnailMaxPixelSize : @(maxDimension)
                                                       };
    // Generate the thumbnail
    CGImageRef thumbnail = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options); 
    CFRelease(imageSource);
    //  Crashlytics says the crash is on this line, even though the line above is the one that uses `CFRelease()`
    UIImage *image = [UIImage imageWithCGImage:thumbnail];
    CGImageRelease(thumbnail); 
    NSAssert(image.size.height - SCREEN_MIN_LENGTH * [UIScreen mainScreen].scale < 1, @"Image should be the height of the view");

    return image;
}

And here's a stack trace from Fabric's Crashlytics:

0. Crashed: com.apple.main-thread

0 CoreFoundation 0x1820072a0 CFRelease + 120

1 Homer 0x10014e7f0 +[UIImage(ResizeBeforeLoading) imageAtPath:scaledToSize:] (UIImage+ResizeBeforeLoading.m:98)

With this crash info key:

CRASH_INFO_ENTRY_0

* CFRelease() called with NULL *

The odd thing is that this crash is not 100%, it's happening to a very low percentage of users. I can't reproduce it on any of my own devices. There is also no pattern to what version of iOS it happens on, nor what iOS hardware it happens on.

Here is a file that does not cause a crash:

The not-crashy one

And here is a file that does cause a crash:

The crashy one

And here are links to them in my production image host (s3 with cloudfront):

(good) http://d3iq9oupxk0b1m.cloudfront.net/PirateDinosaur/2x/dinoBack._k91G0.jpg

(crashy) http://d3iq9oupxk0b1m.cloudfront.net/PirateDinosaur/2x/PirateDino.3LHRmc.jpg

I can't see a meaningful difference between them, even when I look at them using Imagemagick's identify -verbose <filename>. Can any jpeg experts weigh in?

Note: In my next app version, to be released, I have added guards around releasing NULL references, to protect against this crash:

if (imageSource) { CFRelease(imageSource), imageSource = nil; }
...
if (thumbnail) { CGImageRelease(thumbnail), thumbnail = nil; }

However, I would still like to know what in the world is wrong with this jpeg that is causing this crash, so my artist can avoid it in the future.

commanda
  • 4,841
  • 1
  • 25
  • 34
  • The error message tells you what to do, and you are doing it correctly — always check for NULL before calling `CFRelease`. It's hard to see what more you can do. I just have one thought: you are not calling this method before the image has been fully downloaded, are you? – matt Aug 29 '17 at 14:54
  • @matt Right. I want to know what's wrong with this one jpeg though - that's the point of this question. – commanda Aug 29 '17 at 18:32
  • You need to give links that file share the original jpeg files (_eg:_ using DropBox or Google Drive). – VC.One Aug 30 '17 at 11:07
  • @VC.One I've edited the question to include links to the originals. Thanks! – commanda Sep 05 '17 at 19:33

0 Answers0