Проблемы с памятью при шифровании / дешифровании большого файла с помощью RNCryptor на iOS

Я пытаюсь использовать RNCryptor для шифрования и дешифрования больших файлов (600+ МБ) на iOS. На GitHub я нашел пример кода о том, как использовать библиотеку асинхронно на потоках. Этот код похож на ответ Роба Нейпира на вопрос по этой же теме.

Однако, хотя я думаю, что я правильно реализовал код, приложение использует до 1,5 ГБ памяти (в симуляторе iPad 6.1). Я думал, что код должен был препятствовать тому, чтобы приложение держало больше чем один блок данных в памяти? Так что же не так?

В моем контроллере я создаю 'CryptController', который я отправляю с запросами шифрования / дешифрования.

  // Controller.m
  NSString *password = @"pw123";
  self.cryptor = [[CryptController alloc] initWithPassword:password];

  //start encrypting file
  [self.cryptor streamEncryptRequest:self.fileName andExtension:@"pdf" withURL:[self samplesURL]];

  //wait for encryption to finish
  NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:1];
  do {
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
                             beforeDate:timeout];
  } while (![self.cryptor isFinished]);

В CryptController у меня есть:

- (void)streamEncryptionDidFinish {
  if (self.cryptor.error) {
    NSLog(@"An error occurred. You cannot trust decryptedData at this point");
  }
  else {
    NSLog(@"%@ is complete. Use it as you like", [self.tempURL lastPathComponent]);
  }
  self.cryptor = nil;
  self.isFinished = YES;
}

- (void) streamEncryptRequest:(NSString *)fileName andExtension:(NSString *)ext withURL:(NSURL *)directory {

  //Make sure that this number is larger than the header + 1 block.
  int blockSize = 32 * 1024;

  NSString *encryptedFileName = [NSString stringWithFormat:@"streamEnc_%@", fileName];
  self.tempURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
  self.tempURL = [self.tempURL URLByAppendingPathComponent:encryptedFileName isDirectory:NO];
  self.tempURL = [self.tempURL URLByAppendingPathExtension:@"crypt"];

  NSInputStream *decryptedStream = [NSInputStream inputStreamWithURL:[[directory URLByAppendingPathComponent:fileName isDirectory:NO] URLByAppendingPathExtension:ext]];
  NSOutputStream *cryptedStream = [NSOutputStream outputStreamWithURL:self.tempURL append:NO];

  [cryptedStream open];
  [decryptedStream open];

  __block NSMutableData *data = [NSMutableData dataWithLength:blockSize];
  __block RNEncryptor *encryptor = nil;

  dispatch_block_t readStreamBlock = ^{
    [data setLength:blockSize];
    NSInteger bytesRead = [decryptedStream read:[data mutableBytes] maxLength:blockSize];
    if (bytesRead < 0) {
      //Throw an error
    }
    else if (bytesRead == 0) {
      [encryptor finish];
    }
    else {
      [data setLength:bytesRead];
      [encryptor addData:data];
      //NSLog(@"Sent %ld bytes to encryptor", (unsigned long)bytesRead);
    }
  };

  encryptor = [[RNEncryptor alloc] initWithSettings:kRNCryptorAES256Settings
                                           password:self.password
                                            handler:^(RNCryptor *cryptor, NSData *data) {
                                              //NSLog(@"Encryptor received %ld bytes", (unsigned long)data.length);
                                              [cryptedStream write:data.bytes maxLength:data.length];
                                              if (cryptor.isFinished) {
                                                [decryptedStream close];
                                                //call my delegate that i'm finished with decrypting
                                                [self streamEncryptionDidFinish];
                                              }
                                              else {
                                                readStreamBlock();
                                              }
                                            }];

  // Read the first block to kick things off
  self.isFinished = NO;
  readStreamBlock();
}

Когда я выполняю профилирование с использованием инструмента распределения, категории распределения, которые я вижу, постоянно растут malloc 32.50 KB, malloc 4.00 KB, NSConcreteData а также NSSubrangeData, Особенно malloc 32.50 KB растет большой, более 1 ГБ. Ответственный абонент [NSConcreteData initWithBytes:length:copy:freeWhenDone:bytesAreVM:]За NSConcreteData ответственный абонент-[NSData(NSData) copyWithZone:],

При профилировании с использованием инструмента для утечек утечки не обнаруживаются.

Я новичок в Objective-C, и, как я понял, новый ARC должен обрабатывать выделение и освобождение памяти. При поиске в поисках чего-либо, связанного с памятью, вся информация, которую я нахожу, предполагает, что вы не используете ARC (или ее не было на момент написания). Я уверен, что использую ARC, так как я получаю ошибки компиляции, когда я пытаюсь вручную освободить память.

Если кто-нибудь может помочь мне с этим, это будет очень цениться! Если потребуется дополнительная информация, я буду рад предоставить ее:) Кроме того, я новичок в Stackru, поэтому, если я что-то упустил из виду, что мне следовало сделать, просим сообщить мне!

4 ответа

Решение

Я наконец попробовал решение, данное здесь, которое использует семафоры вместо зависимости от обратного вызова для ожидания потока. Это работает отлично:) Использование памяти колеблется около 1,1 МБ в соответствии с инструментом распределения. Он может выглядеть не очень аккуратно из-за синтаксиса семафора, но, по крайней мере, он делает то, что мне нужно.

Другие предложения все еще приветствуются, конечно:)

- (void)encryptWithSemaphore:(NSURL *)url {
  dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

  __block int total = 0;
  int blockSize = 32 * 1024;

  NSString *encryptedFile = [[url lastPathComponent] stringByDeletingPathExtension];
  NSURL *docsURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
  self.tempURL = [[docsURL URLByAppendingPathComponent:encryptedFile isDirectory:NO] URLByAppendingPathExtension:@"crypt"];

  NSInputStream *inputStream = [NSInputStream inputStreamWithURL:url];
  __block NSOutputStream *outputStream = [NSOutputStream outputStreamWithURL:self.tempURL append:NO];
  __block NSError *encryptionError = nil;

  [inputStream open];
  [outputStream open];

  RNEncryptor *encryptor = [[RNEncryptor alloc] initWithSettings:kRNCryptorAES256Settings
                                                        password:self.password
                                                         handler:^(RNCryptor *cryptor, NSData *data) {
                                                           @autoreleasepool {
                                                             [outputStream write:data.bytes maxLength:data.length];
                                                             dispatch_semaphore_signal(semaphore);

                                                             data = nil;
                                                             if (cryptor.isFinished) {
                                                               [outputStream close];
                                                               encryptionError = cryptor.error;
                                                               // call my delegate that I'm finished with decrypting
                                                             }
                                                           }
                                                         }];
  while (inputStream.hasBytesAvailable) {
    @autoreleasepool {
      uint8_t buf[blockSize];
      NSUInteger bytesRead = [inputStream read:buf maxLength:blockSize];
      if (bytesRead > 0) {
        NSData *data = [NSData dataWithBytes:buf length:bytesRead];

        total = total + bytesRead;
        [encryptor addData:data];

        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
      }
    }
  }

  [inputStream close];
  [encryptor finish];  
}

Просто беги:

self.cryptorQueue = dispatch_queue_create([queueName cStringUsingEncoding:NSUTF8StringEncoding], NULL);

dispatch_async(self.cryptorQueue, ^{
        readStreamBlock();
    });

проблема: стек растет, и функция автоматического восстановления не выполнит освобождение для буфера.

решение: добавить асинхронную в ту же очередь.. это позволит текущему блоку завершить выполнение.

Вот код:

__block NSMutableData *data = [NSMutableData dataWithLength:blockSize];
__block RNDecryptor *decryptor = nil;


dispatch_block_t readStreamBlock = ^{

    [data setLength:blockSize];

    NSInteger bytesRead = [inputStream read:[data mutableBytes] maxLength:blockSize];
    if (bytesRead < 0) {
        // Throw an error
    }
    else if (bytesRead == 0) {
        [decryptor finish];
    }
    else {

        [data setLength:bytesRead];
        [decryptor addData:data];
    }
};

decryptor = [[RNDecryptor alloc] initWithPassword:@"blah" handler:^(RNCryptor *cryptor, NSData *data) {

        [decryptedStream write:data.bytes maxLength:data.length];
        _percentStatus = (CGFloat)[[decryptedStream propertyForKey:NSStreamFileCurrentOffsetKey] intValue] / (CGFloat)_inputFileSize;
        if (cryptor.isFinished)
        {
            [decryptedStream close];
            [self decryptFinish];
        }
        else
        {
            dispatch_async(cryptor.responseQueue, ^{
                readStreamBlock();
            });
            [self decryptStatusChange];
        }

}];


// Read the first block to kick things off

decryptor.responseQueue = self.cryptorQueue;
[self decryptStart];
dispatch_async(decryptor.cryptorQueue, ^{
    readStreamBlock();
});

Я могу ошибаться, но я думаю, что ваш do...while петля предотвращает частое сливание пула автоматического выпуска.

Почему вы используете этот цикл, чтобы дождаться окончания работы расшифровщика? Вместо этого вы должны использовать блок завершения, чтобы уведомить свой контроллер о завершении работы drcryptor.

(Кстати, добро пожаловать на SO, ваш вопрос действительно хорошо задан, и это высоко ценится).

Я мог бы снова ошибаться, но в вашем readStreamBlock, data должен быть параметром для блока, а не ссылкой на __block NSMutableData объявил вне этого. Как видите, RNEncryptor Обработчик предоставляет свой собственный data переменная, которая отличается от той, которую вы объявили сами.

В идеале, поставить все свои readStreamBlock непосредственно внутри обработчика, даже не объявляя его блоком.

Другие вопросы по тегам