One of the main features of my recent app Classified is importing photo’s and video’s. There are a few ways you can get this done. The most obvious one is to use the UIImagePickerController. However if you use custom UI UIImagePickerController won’t do it for you. You will probably use ALAsset.

Importing photo’s is straight forward with ALAsset. Video’s on the other hand can be tricky if you do it the wrong way like I’ve experienced.

The wrong way

// Let's assume 'assetPresentation' is a ALAssetRepresentation object
Byte *buffer = (Byte*)malloc(assetPresentation.size);
NSUInteger buffered = [assetPresentation getBytes:buffer
                                         fromOffset:0.0
										 length:assetRepresentation.size
										  error:nil];    // you should check for errors tho!

// Get the data
NSData *data = [NSData dataWithBytesNoCopy:buffer length:buffered freeWhenDone:YES];

// Write data to the documents directory
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *videoPath = [documentsDirectory stringByAppendingPathComponent:@"video.mov"];
NSError *error = nil;

// Actual writing
[data writeToFile:videoPath
          options:NSDataWritingFileProtectionComplete
            error:&error];

    // Check for errors etc etc...

It looks obvious, right? Except… it’s wrong. Importing videos this way will crash your application at some point, especially with bigger videos. Your app will run out of memory since you are loading the whole video in memory first and then writing it to disk.

I’ve profiled the memory and saw crazy pikes above of 800MB before it crashed. The catchy thing about this issue is that you may not experience any problems at all if you import small videos so always test extensively.

The right way: NSFileHandle

The solution is to use NSFileHandle. You can write/copy the video directly from the source path to your destination path without loading the entire video into your memory.

 // Create a file handle to write the file at your destination path
[[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:filePath];
if (!handle)
{
	// Handle error here…
}

// Create a buffer for the asset
static const NSUInteger BufferSize = 1024*1024;
ALAssetRepresentation *rep = [asset defaultRepresentation];
uint8_t *buffer = calloc(BufferSize, sizeof(*buffer));
NSUInteger offset = 0, bytesRead = 0;

// Read the buffer and write the data to your destination path as you go
do
{
    @try
    {	
        bytesRead = [rep getBytes:buffer fromOffset:offset length:BufferSize error:error];
        [handle writeData:[NSData dataWithBytesNoCopy:buffer length:bytesRead freeWhenDone:NO]];
        offset += bytesRead;
    }
    @catch (NSException *exception)
    {
        free(buffer);
        
		 // Handle the exception here...
    }
} while (bytesRead > 0);

// 
free(buffer);