Write image data in dispatch_async

2.2k views Asked by At

I'm having a problem when I try to save an UIImage as a PNG file by using GCD. Here's what am I writing :


        NSString *fileName = [[NSString stringWithFormat:@"%@.png",url] substringFromIndex:5];
        dispatch_queue_t queue = dispatch_queue_create("screenshotQueue", NULL);
        dispatch_async(queue, ^{
            NSFileManager *fileManager = [NSFileManager defaultManager];
            NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
            NSString *filePath = [documentsDirectory stringByAppendingPathComponent:fileName];
            [fileManager createFileAtPath:filePath contents:UIImagePNGRepresentation(image) attributes:nil];
        });
        dispatch_release(queue);

It's working a first time but the other times I have nothing created. And this %@.png is weird because my only file created is not recognize by Finder. I have to add .png extension to the file (so filename.png.png) and I can open it then.

1

There are 1 answers

1
Nathan Eror On

It looks like a race condition. You are releasing the queue immediately after dispatching the block to it. Any blocks that have not been executed before the call to dispatch_release will be cancelled, and there is a chance that GCD is not done preparing itself before you call dispatch_release.

Since you're only ever submitting a single block to the screenshotQueue, you are better off using one of the global system queues. That way, you don't have to deal with managing the queue's lifecycle.

This will probably give you more consistent results:

NSString *fileName = [[NSString stringWithFormat:@"%@.png",url] substringFromIndex:5];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_async(queue, ^{
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSString *filePath = [documentsDirectory stringByAppendingPathComponent:fileName];
    [fileManager createFileAtPath:filePath contents:UIImagePNGRepresentation(image) attributes:nil];
});

I'm not sure about the PNG file type problem. It sounds like Finder doesn't know the file's type. You can use this command to find the type Spotlight has in its index:

$ mdls -name kMDItemContentType <filename>.png

It should report back something like this:

kMDItemContentType = "public.png" 

I typically use [NSData writeToFile:atomically:] for saving bytes to disk, and it usually handles the details properly. It might be overkill, but you could also use Image I/O to be doubly sure:

//The following 4 lines should preplace this line in your block:
//[fileManager createFileAtPath:filePath contents:UIImagePNGRepresentation(image) attributes:nil];
CGImageDestinationRef imageDest = CGImageDestinationCreateWithURL((__bridge CFURLRef)[NSURL fileURLWithPath:filePath], kUTTypePNG, 1, NULL);
CGImageDestinationAddImage(imageDest, [image CGImage], NULL);
CGImageDestinationFinalize(imageDest);
CFRelease(imageDest);

I do all of my image loading/saving/thumbnailing with Image I/O now. It's really fast. Of course, make sure you add ImageIO.framework to your project if you decide to use it.