在iOS上使用RNCryptor加密/解密大文件时出现内存问题

在iOS上使用RNCryptor加密/解密大文件时出现内存问题,ios,objective-c,memory,rncryptor,Ios,Objective C,Memory,Rncryptor,我正在尝试使用RNCryptor在iOS上加密和解密大文件(600+MB)。在上,我找到了关于如何在流上异步使用库的示例代码。此代码类似于Rob Napier关于的答案 然而,尽管我认为我正确地实现了代码,但该应用程序使用了高达1.5 GB的内存(在iPad6.1模拟器中)。我以为代码应该阻止应用程序在内存中保存多个数据块?那么到底出了什么问题 在我的控制器中,我创建了一个“CryptController”,通过加密/解密请求发送消息 // Controller.m NSString *

我正在尝试使用RNCryptor在iOS上加密和解密大文件(600+MB)。在上,我找到了关于如何在流上异步使用库的示例代码。此代码类似于Rob Napier关于的答案

然而,尽管我认为我正确地实现了代码,但该应用程序使用了高达1.5 GB的内存(在iPad6.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();
}
-(无效)流加密完成{
if(自加密错误){
NSLog(@“发生错误。此时您不能信任decryptedData”);
}
否则{
NSLog(@“%@已完成。请随意使用,[self.tempURL lastPathComponent]);
}
self.cryptor=nil;
self.isFinished=是;
}
-(void)streamEncryptRequest:(NSString*)文件名和扩展名:(NSString*)带URL的ext:(NSURL*)目录{
//确保此数字大于页眉+1块。
int blockSize=32*1024;
NSString*encryptedFileName=[NSString stringWithFormat:@“streamEnc”%@,文件名];
self.tempURL=[[NSFileManager defaultManager]URLForDirectory:NSDocumentDirectory-inDomain:NSUserDomainMask-ApproverForURL:nil创建:无错误:nil];
self.tempURL=[self.tempURL URLByAppendingPathComponent:encryptedFileName isDirectory:NO];
self.tempURL=[self.tempurlurlbyAppendingPathExtension:@“crypt”];
NSInputStream*decryptedStream=[NSInputStream inputStreamWithURL:[[directory URLByAppendingPathComponent:fileName isDirectory:NO]URLByAppendingPathExtension:ext]];
NSOutputStream*cryptedStream=[NSOutputStream outputStreamWithURL:self.tempulAppend:NO];
[加密流打开];
[解密流打开];
__块NSMutableData*数据=[NSMutableData数据长度:块大小];
__块RNEncryptor*加密器=nil;
调度块读取流块=^{
[数据集长度:块大小];
NSInteger bytesRead=[decryptedStream read:[data mutableBytes]maxLength:blockSize];
如果(字节读取<0){
//出错
}
else if(bytesRead==0){
[加密机完成];
}
否则{
[数据集长度:字节读取];
[加密机添加数据:数据];
//NSLog(@“已将%ld个字节发送到加密程序”,(无符号长)字节读取);
}
};
encryptor=[[RNEncryptor alloc]initWithSettings:kRNCryptorAES256Settings
密码:self.password
处理程序:^(RNCryptor*cryptor,NSData*data){
//NSLog(@“加密程序接收到%ld字节”(无符号长)数据。长度);
[cryptedStream写入:data.bytes最大长度:data.length];
如果(加密完成){
[解密流关闭];
//给我的代表打电话,告诉他我已完成解密
[自流加密完成];
}
否则{
readStreamBlock();
}
}];
//读第一段,开始做一些事情
self.isFinished=否;
readStreamBlock();
}
当我使用分配工具进行配置时,我看到的不断增长的分配类别是
malloc 32.50kb
malloc 4.00kb
NSConcreteData
NSSubrangeData
。特别是
malloc32.50kb
的容量越来越大,超过1GB。负责的来电者是
[nsconcretedatainitwithbytes:length:copy:freewwhendone:bytesAreVM:
对于
NSConcreteData
-[NSData(NSData)copyWithZone:

当我使用泄漏仪器分析时,没有发现泄漏

我是Objective-C的新手,据我所知,新的ARC应该处理内存的分配和释放。在谷歌搜索任何与内存相关的信息时,我发现的所有信息都是假设您没有使用ARC(或者在撰写本文时它并不存在)。我确实在使用ARC,因为当我试图手动释放内存时,会出现编译错误

如果有人能帮我,我将不胜感激!如果需要更多信息,我很乐意提供:)
另外,我是StackOverflow的新手,所以如果有什么我忽略了的事情我应该做,请告诉我

我可能错了,但我认为您的
应该这样做,而
循环会阻止自动释放池频繁地排空

为什么要使用此循环等待解密程序完成?您应该使用完成块通知控制器drcryptor已完成


(顺便说一句,欢迎这么说,你的问题问得很好,这是非常感谢的)。

我可能又错了,但在你的
readStreamBlock
中,
数据应该是块的参数,而不是块外声明的
\u块NSMutableData
的引用。如您所见,
RNEncryptor
处理程序提供了自己的
数据
变量,这与您自己声明的变量不同


理想情况下,将所有的
readStreamBlock
直接放在处理程序中,甚至不将其声明为块。

我最后尝试了给出的解决方案,它使用信号量而不是依赖回调来等待流。这非常有效:)根据分配工具,内存使用量在1.1MB左右。由于信号量语法的原因,它看起来可能不那么整洁,但是
- (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();
});