【问题标题】:Downloading large base64encoded data using NsurlConnection使用 NsurlConnection 下载大型 base64 编码数据
【发布时间】:2016-05-26 09:17:15
【问题描述】:

我正在尝试使用 POST 请求从服务器下载一个大文件。该文件作为 base64Encoded 数据接收。文件大小超过 400Mb。我使用的第一种方法是附加在 didReceiveData 中收到的数据,然后在 connectionDidFinishLoading 我用来解码数据并写入数据到文档目录中的文件。这种方法适用于小尺寸的文件。但由于文件大小非常大(400MB),它会崩溃。然后我使用 NSFileHandle 的方法在接收到数据时将文件分块写入。我使用 3MB 的块来写入数据。对于小于 3MB 的文件,接收到的数据直接从 base64 解码,文件创建成功。但是当文件大小大于 3MB 并且我尝试使用“initWithBase64EncodedData: Options”从 base64 解码块数据时,我得到的数据为零。想知道我在代码中做错了什么。

- (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response
{
    receivedData = [[NSMutableData alloc] init];

    NSString* folderPath = [NSHomeDirectory() stringByAppendingPathComponent:@"/Documents/UserDocs"];
    if(![[NSFileManager defaultManager]  fileExistsAtPath:folderPath]) {
        [[NSFileManager defaultManager] createDirectoryAtPath:folderPath withIntermediateDirectories:NO attributes:nil error:nil];
   }

NSString *strFilePath = [folderPath stringByAppendingPathComponent:strFileName];    NSString *newFilePath = [NSString stringWithFormat:@"%@.%@", strFilePath, strFileExtension];
if(![[NSFileManager defaultManager]  fileExistsAtPath:newFilePath]) {
          [[NSFileManager defaultManager] createFileAtPath:newFilePath contents:nil attributes:nil];
        }

self.fileHandle = [[NSFileHandle fileHandleForWritingAtPath:newFilePath] retain];
        [self.fileHandle seekToFileOffset:0];
}

- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
{
    [receivedData appendData:data];
    NSUInteger chunkSize = 300000;
    if( receivedData.length > chunkSize  && self.fileHandle!=nil) {

        NSData *chunk = [receivedData subdataWithRange:NSMakeRange(0, chunkSize)];
        NSData *writableData = [[NSData alloc] initWithBase64EncodedData:chunk options:NSDataBase64DecodingIgnoreUnknownCharacters];
       [receivedData replaceBytesInRange:NSMakeRange(0, chunkSize) withBytes:NULL length:0];

       [self.fileHandle seekToEndOfFile];
       [self.fileHandle writeData:writableData];
       [writableData release];
    }
}

- (void)connectionDidFinishLoading:(NSURLConnection*)connection
{
    if (receivedData)
    {
        NSData *writableData = [[NSData alloc] initWithBase64EncodedData:receivedData options:NSDataBase64DecodingIgnoreUnknownCharacters];
        [receivedData release];

        [self.fileHandle seekToEndOfFile];
        [self.fileHandle writeData:writableData];
        [writableData release];

        NSLog(@"File Write Success!!");
    }
}

【问题讨论】:

  • Base64 不是一对一的编码方案,是吗,所以您将获得当前 chunk 所需的前一个 chunk 中的数据。您应该研究使用某种形式的 base-64 流解码器的可能性,您可以在其中向其提供输入数据块并告诉它在哪里写入解码后的数据。
  • 尝试使用 NSStream、数据文件流和动态解码/编码来制作一个二进制文件,让我知道是否可以使用该代码:)
  • 您可以控制服务器吗?如果是这样,为什么不直接发送数据而不进行 base64 编码呢?例如,如果您需要发回大 blob 之外的其他数据,则可以使用多部分 MIME 响应。或者更好的是,首先提供元数据,然后提供一个 URL,您可以在该 URL 中通过第二个请求请求未编码的大型数据文件。

标签: ios objective-c nsurlconnection


【解决方案1】:

两种可能的方法:

  1. 跳过 base64 编码(如果可以的话)。
  2. 在获取数据时将数据写入磁盘,然后使用内存映射将文件映射到 RAM 中,您可以像在内存中一样使用它,但不会实际占用内存。

基本上:

#include <sys/stat.h>
#include <sys/mman.h>
...
NSURL *url = // path to file on disk
char *path = [url fileSystemRepresentation];
struct stat buf;

// Round up to an exact multiple of the page size
NSUInteger size = (buf.st_size + PAGE_MASK) & ~PAGE_MASK;
NSData *data = nil;

if (!stat(path, &buf)) {
    int fd = open(path, O_RDONLY);
    if (fd != -1) {
        mmap(NULL, size, PROT_READ, MAP_FILE|MAP_PRIVATE, fd, 0);
        data = [NSData dataWithBytesNoCopy:buf length:buf.st_size];
    }
}

// Perform a base64 decode on the NSData object and hope that
// at least the result will fit in RAM.

// Clean up.
data = nil;
if (!stat(path, &buf)) {
    int fd = open(path, O_RDONLY);
    if (fd != -1) {
        munmap(addr, size);
    }
}

创建一个可以将数据直接写入磁盘文件的 base64 解码器留给读者作为练习。 :-)

【讨论】:

  • 我找不到 base64 解码器,所以我跳过了 base64 编码。现在服务器返回文件流而不是 base64,我使用我在问题中提到的相同代码。
猜你喜欢
  • 1970-01-01
  • 2019-12-03
  • 1970-01-01
  • 2021-04-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多