[英]Memory issues when encrypting/decrypting a large file with RNCryptor on iOS
我正在嘗試使用RNCryptor加密和解密iOS上的大文件(600 + MB)。 在github上,我找到了關於如何在流上異步使用庫的示例代碼。 此代碼類似於Rob Napier關於同一主題的問題的答案。
但是,雖然我認為我正確實現了代碼,但該應用程序使用高達1.5 GB的內存(在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 GB。 負責的調用者是[NSConcreteData initWithBytes:length:copy:freeWhenDone:bytesAreVM:]
對於NSConcreteData
,負責的調用者是-[NSData(NSData) copyWithZone:]
。
當我使用泄漏儀器進行配置時,沒有發現泄漏。
我是Objective-C的新手,根據我的理解,新的ARC應該處理內存的分配和釋放。 在搜索與內存相關的任何內容時,我發現的所有信息都假設您不使用ARC(或者在編寫時它不存在)。 我確定使用ARC,因為當我嘗試手動釋放內存時,我收到編譯錯誤。
如果有人可以幫助我,我將不勝感激! 如果需要更多信息,我很樂意提供它:)另外,我是StackOverflow的新手,所以如果有什么我忽略了我應該做的事情,請通知我!
我終於嘗試了這里給出的解決方案,它使用信號量而不是依賴回調來等待流。 這完美地工作:)根據Allocations Instrument,內存使用率徘徊在1.1 MB左右。 由於信號量語法,它看起來可能不那么整潔,但至少它做了我需要做的事情。
其他建議當然仍然受歡迎:)
- (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();
});
麻煩:堆棧正在增長,自動釋放拉不會執行緩沖區釋放。
解決方案:在同一隊列中添加async ..這將讓當前塊完成執行。
這是代碼:
__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
聲明的__block NSMutableData
的引用。 如您所見, RNEncryptor
處理程序提供了自己的data
變量,這與您自己聲明的data
變量不同。
理想情況下,將所有readStreamBlock
直接放在處理程序中,甚至不將其聲明為塊。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.