在iOS上使用RNCryptorencryption/解密大文件时存在内存问题

我正在尝试使用RNCryptor在iOS上encryption和解密大文件(600 + MB)。 在github上,我find了关于如何在stream上asynchronous使用库的示例代码。 这个代码类似于Rob Napier在关于这个相同主题的问题上的答案。

不过,尽pipe我认为我正确实施了代码,但是应用程序使用了高达1.5 GB的内存(在iPad 6.1模拟器中)。 我认为代码应该防止应用程序保持在内存中的多个数据块? 那么是怎么回事?

在我的控制器中,我创build了一个encryption/解密请求消息的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 KBmalloc 4.00 KBNSConcreteDataNSSubrangeData 。 尤其是malloc 32.50 KB增长很大,超过1 GB。 负责的调用者是[NSConcreteData initWithBytes:length:copy:freeWhenDone:bytesAreVM:]对于NSConcreteData负责的调用者是-[NSData(NSData) copyWithZone:]

当我使用泄漏仪进行分析时,没有发现泄漏。

我是Objective-C的新手,根据我的理解,新的ARC应该处理内存的分配和释放。 当search到与内存有关的东西时,我发现所有的信息都假设你没有使用ARC(或者在写作时不存在)。 我确定使用ARC,因为我得到编译错误说,所以当我尝试手动释放内存。

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

我终于尝试了这里给出的解决scheme,它使用信号量,而不是根据callback来等待stream。 这工作完美:)根据分配仪器内存使用徘徊在1.1 MB左右。 它可能看起来不整齐,因为信号语法,但至less它做我所需要做的。

其他build议仍然欢迎:)

 - (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(); }); 

麻烦:堆栈正在增长,autorelease pull将不会执行释放缓冲区。

解决scheme:在同一队列中添加asynchronous。这将使当前块完成执行。

这里是代码:

 __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已经完成。

(顺便说一下,欢迎来到你的问题,真的很好问,这是高度赞赏)。

我可能会再次错误,但是在您的readStreamBlockdata应该是该块的参数,而不是对在其__block NSMutableData声明的__block NSMutableData的引用。 正如你所看到的, RNEncryptor处理程序提供了自己的datavariables,这与你自己声明的不同。

理想情况下,把所有的readStreamBlock直接放在处理程序中,甚至没有声明它是一个块。