9

说明: 我需要在 HTTP POST 参数中发送一个 base 64 编码的字符串,该参数表示要上传的文件。

当前方法:我正在使用 ASIFormDataRequest 并将我的文件编码为 base64 字符串,如下所示:

NSData *data = [[NSFileManager defaultManager] contentsAtPath:@"my path here"];
NSString *base64Data = [data encodeBase64ForData];

但是,当上传大文件时,应用程序会耗尽内存并死于可怕的死亡!

建议的解决方案:现在有人知道我将如何处理,例如,从磁盘读取文件,将其转换为块中的 base64 字符串并将转换后的 base64 字符串附加到 HTTP 请求中的参数吗?

有效地避免将整个文件读入内存的任何方式。

任何投入将不胜感激!

我的完整http请求代码:

NSURL *url = [NSURL URLWithString:requestProgressURLString];
NSData *data = [[NSFileManager defaultManager] contentsAtPath:evidence.uri];
NSString *base64Data = [data encodeBase64ForData];
NSURL *fileURL = [[NSURL alloc] initWithString:evidence.uri];

request = [ASIFormDataRequest requestWithURL:url];

request.shouldAttemptPersistentConnection = false;
[request setShowAccurateProgress:YES];

[request setPostValue:accessToken forKey:@"AccessToken"];
[request setPostValue:[[fileURL path] lastPathComponent] forKey:@"Filename"];
[request setPostValue:evidence.name forKey:@"Title"];
[request setPostValue:base64Data forKey:@"FileData"]; //wants string value (base64 encoded)          
[request addRequestHeader:@"Content-Type" value:@"application/x-www-form-urlencoded"];
[request setTimeOutSeconds:60];
[request setUploadProgressDelegate:self];
[request setDownloadProgressDelegate:self];
[request setDelegate:self];
[request setDidFinishSelector:@selector(requestDone:)];
[request setDidFailSelector:@selector(requestWentWrong:)];
4

2 回答 2

3

我会把这个问题分成两个:

1. 将大文件分块编码为 Base64
2. 上传该文件


1、大文件分块编码为Base64:

找到一些将实例编码NSData为 Base64 的代码。然后使用一个NSInputStream读取块中的数据,对其进行编码并用于NSOutputStream写入临时文件。我找到了这个问题/答案:Is it possible to base64-encode a file in chunks? 使块大小成为 3 的倍数,它应该可以工作。您可能希望将其移至后台线程。

重要提示:这将创建一个比原始文件大的临时文件。在那一刻,设备上必须有足够的空间。开始上传后,您可以将其删除。


2. 上传该文件

看来你已经完成了。您可以ASIFormDataRequest像在示例中一样使用它。由于它制作了文件的私有副本(它构建了多部分 POST 文件),因此您可以删除您的副本。

如果您想通过并行化这些步骤来优化它,您将需要找到另一种上传多部分文件的方法。

于 2012-12-02T12:42:13.167 回答
0

您需要分块读取文件,而不是一次性读取文件

您可以使用输入流来执行此操作 Apple 在此处提供了一些信息

基本上,您需要创建一个指向您的文件的 NSInputStream,如示例中所示

- (void)setUpStreamForFile:(NSString *)path {
    // iStream is NSInputStream instance variable
    iStream = [[NSInputStream alloc] initWithFileAtPath:path];
    [iStream setDelegate:self];
    [iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
        forMode:NSDefaultRunLoopMode];
    [iStream open];
}

然后,您可以使用 NSStreamDelegate 方法分块读取数据 - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode

苹果再次举一个例子

- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {

    switch(eventCode) {
        case NSStreamEventHasBytesAvailable:
        {
            if(!_data) {
                _data = [[NSMutableData data] retain];
            }
            uint8_t buf[1024];
            unsigned int len = 0;
            len = [(NSInputStream *)stream read:buf maxLength:1024];
            if(len) {
                [_data appendBytes:(const void *)buf length:len];
                // bytesRead is an instance variable of type NSNumber.
                [bytesRead setIntValue:[bytesRead intValue]+len];

                // DO SOMETHING with DATA HERE

            } else {
                NSLog(@"no buffer!");
            }
            break;
        }

您选择的块大小需要平衡内存性能与网络性能,因为 HTTP 标头显然存在一些开销

您还可以通过直接发送您的 NSData 来进一步优化(从而节省 base64 开销)检查这篇文章

iphone编程中的文件上传到HTTP服务器

于 2012-11-13T12:24:46.647 回答