iOS如何通过流式传输大型资产文件到服务器

时间:2013-08-21 04:28:30

标签: ios upload streaming

我是新的iOS程序员 我想将资产库中的大文件(视频或图片)上传到服务器, 我原来的方法是使用 NSMutableURLRequest 并附加 NSData (大型视频或大图片),并在以下代码中发生崩溃:

    ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *asset){
        //.......some code I just skip it...
        ALAssetRepresentation *rep = [asset defaultRepresentation];
        void *buffer = [rawData mutableBytes];
        [rep getBytes:buffer fromOffset:0 length:size error:nil];
        NSData *videoData = [[NSData alloc] initWithBytes:buffer length:size];//crash here
        [self startUploading:videoData];
    }

我知道这次崩溃是因为内存不够,视频文件不能只分配给NSData 我已经谷歌这2天了,听起来有几种方法可以解决这个问题。

  1. 使用第三方库:如AFNetworking,ASIHTTPRequest(但我不想使用它,因为不知道何时会停止维护或更新)
  2. 使用流媒体上传大文件
  3. 我希望使用流媒体方式(第2点)来上传内容 我找到了这个链接:http://zh.scribd.com/doc/51504708/10/Upload-Files-Over-HTTP
    看起来可以解决我的问题,但仍然不太清楚知道如何做

    问题1 : 在该链接中有一个例子,上传文件来自bundle
    如何让资产流入?或将资产复制到APP的文件夹?
    我发现此链接Copy image from assets-library to an app folder
    但仍然无法找到方法。

    问题2 : 或者是否还有其他更清晰的流媒体示例来上传大文件?



    谢谢你的热情

    updated1 :在我实现了needNewBodyStream委托后,“请求流耗尽”消息似乎解决了,但相反,遇到另一个“错误域= kCFErrorDomainCFNetwork代码= 303”操作无法完成。“如何解决它?

    -(NSInputStream *)connection:(NSURLConnection *)connection needNewBodyStream:(NSURLRequest *)request
    {
        [NSThread sleepForTimeInterval:2];
        NSInputStream *fileStream = [NSInputStream inputStreamWithFileAtPath:pathToBodyFile];
        if (fileStream == nil) {
            NSLog(@"NSURLConnection was asked to retransmit a new body stream for a request. returning nil!");
        }
        return fileStream;
    }
    

2 个答案:

答案 0 :(得分:6)

假设您的数据太大而无法放入内存:

有效且可靠的方法将使用有界CFStream对(参见CFStreamCreateBoundPair)。

有界流对输入流设置为NSMutableURLRequest&n; HTTPBodyStream属性。有界流对的输出流将用于写入从固定大小的内存缓冲区中获取的字节,该缓冲区已填充ALAssetRepresentation getBytes:fromOffset:length:error:方法

有界流对的传输缓冲区的大小应该与资产表示的缓冲区大小相同。

设置代码需要几行代码和NSStreams的一些经验并处理事件(NSStreams经常会有一些细微之处)。

这种方法的工作原理如下:

  1. 创建一个处理所有流事件的流代理。

  2. 为传输缓冲区设置具有特定大小的配对流,设置委托并在运行循环上安排它们。

  3. 为资产数据设置相同大小的内存缓冲区。

  4. 当您打开流时,会收到NSStreamEventHasSpaceAvailable个事件。您可以通过getBytes:fromOffset:length:error:读取资产数据来处理该事件,并写入内存缓冲区。当您使用一大块资产数据填充缓冲区时,请将此缓冲区写入有界流对输出流。适当追踪抵消!

  5. 现在,有界流对的输入流可以通过底层连接(将字节从内部传输缓冲区移动到网络套接字)提取出来,然后你将获得另一个NSStreamEventHasSpaceAvailable事件,因为现在内部传输缓冲区中有空间。从资产数据缓冲区到输出流写入适合有界流对输出流的字节数,并在资产数据缓冲区中提供尽可能多的字节。如果资产数据缓冲区已完全写入,请重新填充。仔细跟踪偏移和范围!

  6. 您可以处理事件,直到写入整个资产数据。然后关闭输出流。

  7. 您还需要处理其他流事件,请参阅:Stream Programming Guide

  8. 注意:您可能会注意到您的内存缓冲区只能部分写入输出流。通过跟踪偏移来处理这个问题,以便始终在缓冲区中保留连续的资产数据流,并将适当的数据范围从缓冲区写入输出流!

    警告:

    使用有限的一对流设置正确的实现可能很棘手并且可能容易出错。我确实有一个" InputStreamSource"的通用版本。 (它公开了一个普通的NSInputStream,它将用于设置HTTPBodyStream属性),它可以很容易地扩展为与任何输入源一起使用 - 例如资产数据。如果你有兴趣,我可以把这个代码放在gist上。

    AFNetworking或任何其他网络库为您解决此问题。说实话,我不建议将AFNetworking与一起用作正文部分 - 因为AFNetworking的实施在这方面仍然是可疑的。我建议使用NSURLConnection自己实现代理,或者使用另一个第三方库来正确处理POST请求的输入正文流。

    短(不完整)示例

    这个想法是,创建某种资产输入源"暴露NSInputStream的类(可用于设置HTTPBodyStream的{​​{1}}属性)并提供资产数据。

    如果"资产输入源"是一个文件,任务很简单:只需创建一个与该文件关联的NSURLRequest对象。但是,我们的资产只能通过某种表示形式的字节范围访问,该表示位于某个临时缓冲区中。

    因此,任务是用适当的字节范围填充该临时缓冲区。然后,分段,将这些字节写入绑定到输入流的私有输出流。此输入流输出流对将通过函数NSInputStream创建。

    输入流将成为"资产输入源"的公开的NSInputStream

    输出流仅在内部使用。 "资产输入源"将使用资产进行初始化。

    我们的资产输入源" class需要处理流事件,因此它将成为流委托。

    现在,我们有了实现它的一切。

    CFStreamCreateBoundPair函数创建CFStream对象。但是,由于NSStream是toll-free bridged,我们可以轻松地转换"他们到NSStreams。

    "资产输入源"的CFStreamCreateBoundPairstart方法的一部分class可以实现如下:

    init

    _buffer = (uint8_t)malloc(_bufferSize); _buffer_end = _buffer + _bufferSize; _p = _buffer; CFReadStreamRef readStream = NULL; CFWriteStreamRef writeStream = NULL; CFStreamCreateBoundPair(NULL, &readStream, &writeStream, _bufferSize); self.inputStream = CFBridgingRelease(readStream); self.internalOutputStream = CFBridgingRelease(writeStream); [self.internalOutputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:self.runLoopMode]; [self.internalOutputStream open]; // (Note: inputStream will be opened and scheduled by the request!) 是该类的公共@property(公开的输入流)。

    inputStream是该类的私有属性。

    internalOutputStream是内部缓冲区,包含资产表示的字节范围。

    请注意,有界流对的内部缓冲区大小等于保存资产数据的缓冲区。

    流委托方法_buffer可以如下所示实现:

    stream:handleEvent:

    如您所见,秘密在方法- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent { if (_isCancelled) { return; } switch (streamEvent) { case NSStreamEventNone: break; case NSStreamEventOpenCompleted: DLogInfo(@"internal output stream: open completed"); break; case NSStreamEventHasBytesAvailable: // n.a. NSAssert(0, @"bogus stream event"); break; case NSStreamEventHasSpaceAvailable: NSAssert(theStream == _internalOutputStream, @"bogus stream event"); DLogInfo(@"destination stream: has space available"); [self write]; break; case NSStreamEventErrorOccurred: DLogInfo(@"destination stream: error occurred"); [self finish]; break; case NSStreamEventEndEncountered: // weird error: the output stream is full or closed prematurely, or canceled. DLogWarn(@"destination stream: EOF encountered"); if (_result == nil) { self.result = [NSError errorWithDomain:NSStringFromClass([self class]) code:-2 userInfo:@{NSLocalizedDescriptionKey: @"output stream EOF encountered"}]; } [self finish]; break; } } 中。还有write方法和finish方法。

    基本上,方法cancel从_buffer复制到内部输出流,尽可能多地放入流中。当_buffer完全写入输出流时,它将再次从资产数据中填充。

    当没有更多数据可用于从资产写入输出流时,将调用方法write

    方法finish关闭内部输出流并取消预定流。

    完整可靠的实施可能有点棘手。 "资产输入源"也应该是可以取消的。

    如上所述,我确实有一个"抽象输入源" class,它实现除了使用资产数据填充_buffer之外的所有内容,如果需要,我可以将其作为Gist上的代码片段提供。

答案 1 :(得分:0)

我有一个快速版本,通过ALAssetNSInputStream转换为CFStreamCreateBoundPair,实现了最高回答所描述的名为ALAssetToNSInputStream。如果需要,请查看。