One of the more recent apps which we developed had to take photos of confidential documents and eventually upload them to a remote server. Taken photos are stored within the app itself and password encrypted so they are not accessible by a third party in any way.

The encryption is done via RNCryptor library and, since each file is up to 10 MB in size, consumes up to 50 MB of memory. RNCryptor supports ARC and this temporary memory load doesn’t cause any issues. However, our server supports upload of up to 20 files and, when files are decrypted one after another, memory overload accumulates.

In order to solve this, we created a recursive method which decrypts files in background and calls a finish block when there are no more files to decrypt. The same method appends decrypted files to an output file which is later uploaded to server

/*    Method which decrypts each file from arrayImages list and appends decrypted data to JSON output file    */
- (void)writeImages:(NSMutableArray*)arrayImages toPath:(NSString*)path withFileHandler:(NSFileHandle*)fileHandle finished:(void (^)())finished
{    
    // Get file path of encrypted image and remove it from the list
    NSString *imagePath = arrayImages[0];
    [arrayImages removeObjectAtIndex:0];
    
    // Load encrypted data
    NSData *encryptedData = [NSData dataWithContentsOfFile:imagePath];
    
    // Decrypt data
    NSError *error = nil;
    NSData *decryptedData = [RNCryptor decryptData:encryptedData password:_encryptionPassword error:&error];
    
    // Handle decryption error
    if(error != nil)
    {
   	 // End if all files are decrypted
   	 if(arrayImages.count == 0)
   	 {
   		 dispatch_async(dispatch_get_main_queue(), ^(void)
   		 {
   			 if(finished)
   				 finished();
   		 });
   	 }
   	 // Decrypt next file
   	 else
   	 {
   		 [self writeImages:arrayImages toPath:path withFileHandler:fileHandle finished:finished];
   	 }
   	 
   	 return;
    }
    
    UIImage *image = [UIImage imageWithData:decryptedData];
    
    // Write JSON output to file
    [fileHandle writeData:[@"\"" dataUsingEncoding:NSUTF8StringEncoding]];
    [fileHandle writeData:UIImagePNGRepresentation(image)];
    [fileHandle writeData:[@"\"" dataUsingEncoding:NSUTF8StringEncoding]];
    
    if(arrayImages.count > 0)
   	 [fileHandle writeData:[@",\n" dataUsingEncoding:NSUTF8StringEncoding]];
    
    dispatch_async(dispatch_get_main_queue(), ^(void)
    {
   	 // Report progress for decrypted file here
   	 NSLog(@"Progress: %.2f", 1-((float)arrayImages.count / (float)_noOfFilesForUpload));
   	 
   	 // End if all files are decrypted
   	 if(arrayImages.count == 0)
   	 {
   		 if(finished)
   			 finished();
   		 return;
   	 }
   	 
   	 // Decrypt next file
   	 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
   	 {
   		 [self writeImages:arrayImages toPath:path withFileHandler:fileHandle finished:finished];
   	 });
    });
}

 

The previous method is called from another method which creates the output file and, since our server accepts files in a JSON list format, appends the start and end of JSON message to the file.

/*    Method which creates and closes the output JSON file, and calls the decryption method    */
- (void)createAndSaveJsonDataFromImages:(NSArray*)arrayImages finished:(void (^)(NSString *path))finished
{
    // Variable for progress calculation
    _noOfFilesForUpload = arrayImages.count;
    
    // Construct path for upload, delete the old file and create a new file
    NSString *outputFilePath = [NSString stringWithFormat:@"%@/outputData", _uploadDirectoryPath];
    if([[NSFileManager defaultManager] fileExistsAtPath:outputFilePath])
   	 [[NSFileManager defaultManager] removeItemAtPath:outputFilePath error:nil];
    [[NSFileManager defaultManager] createFileAtPath:outputFilePath contents:nil attributes:nil];
    
    // Create output file handle
    NSFileHandle *outputFileHandle = [NSFileHandle fileHandleForWritingAtPath:outputFilePath];
    
    // Append JSON key and start JSON list
    [outputFileHandle writeData:[@"{\"Files\":" dataUsingEncoding:NSUTF8StringEncoding]];
    [outputFileHandle writeData:[@"[" dataUsingEncoding:NSUTF8StringEncoding]];
    
    // Append each file to JSON list
    __block typeof(outputFileHandle) blockFileHandle = outputFileHandle;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
    {
   	 [self writeImages:[[NSMutableArray alloc] initWithArray:arrayImages] toPath:outputFilePath withFileHandler:outputFileHandle finished:^
   	 {
   		 // End JSON list and close the output file
   		 [blockFileHandle writeData:[@"]}" dataUsingEncoding:NSUTF8StringEncoding]];
   		 [blockFileHandle closeFile];
   		 
   		 if(finished)
   			 finished(outputFilePath);
   	 }];
    });

So always keep track of the “Debug navigator”, even if your app is not doing memory intensive tasks. Most often it gives you enough insight to notice lifecycle problems before they cause issues in the app functionality, without the need to launch separate “Instruments” app.