iOS如何通过stream式传输将大型资产file upload到服务器

我是新的iOS程序员。
我想上传一个大的文件(video或图像)从资产库到服务器,我原来的方式只是使用NSMutableURLRequest并附加NSData (大video或大图像),崩溃发生在下面的代码:

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]; } 

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

  1. 使用第三方库:如AFNetworking,ASIHTTPRequest(但我不想使用它,因为不知道什么时候会停止维护或更新)
  2. 使用stream式上传大文件

我想用stream式的方式(点2)做上传的东西,
我发现这个链接:http: //zh.scribd.com/doc/51504708/10/Upload-Files-Over-HTTP
看起来可以解决我的问题,但是还不是很清楚要知道该怎么做

问题1 :该链接中有一个示例,上传文件来自捆绑
如何使资产进入stream? 或将资产复制到APP的文件夹?
我发现这个链接从资产库复制图像到一个应用程序文件夹
但还是找不到方法。

问题2 :还是有其他更清晰的stream式上传大文件的例子吗?

谢谢你的激情

updated1 :在我实现了needNewBodyStream委托后,“请求stream枯竭”消息似乎解决了,反而遇到了另一个“Error Domain = kCFErrorDomainCFNetwork Code = 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; } 

假设你的数据太大而不能适应内存:

一个有效而可靠的方法将利用有限的CFStream对(参见CFStreamCreateBoundPair )。

有界stream对的inputstream被设置为NSMutableURLRequestHTTPBodyStream属性。 有界stream对的输出stream将用于写入从填充了ALAssetRepresentationgetBytes:fromOffset:length:error:方法的固定大小的内存缓冲区获得的字节。

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

设置代码将需要几行代码和NSStream的一些经验和处理事件(通常NSStreams有一些微妙之处)。

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

  1. 创build一个处理所有stream事件的stream委托。

  2. 为传输缓冲区设置特定大小的配对stream,设置代理并将其安排在运行循环中。

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

  4. 当你打开stream,你会得到一个NSStreamEventHasSpaceAvailable事件。 您可以通过getBytes:fromOffset:length:error:来从资产数据读取并处理该事件,然后写入内存缓冲区。 当你用一大块资产数据填充你的缓冲区时,把这个缓冲区写入有界stream对的输出stream 。 跟踪偏移量!

  5. 现在,有界stream对的inputstream可以通过底层连接(将字节从内部传输缓冲区移动到networking套接字)很好地解压缩,并且您将获得另一个NSStreamEventHasSpaceAvailable事件,因为现在在内部传输缓冲区。 从资产数据缓冲区中写入尽可能多的字节以适合有界stream对的输出stream ,以便在资产数据缓冲区中提供尽可能多的字节。 如果资产数据缓冲区已经写完,请重新填写。 仔细追踪偏移和范围!

  6. 您处理事件直到整个资产数据被写入。 然后closures输出stream。

  7. 您还需要处理其他stream事件,请参阅: stream编程指南

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

警告:

用一对有限的stream设置一个正确的实现可能会非常棘手,也许是容易出错的。 我有一个“InputStreamSource”的通用版本(它公开了一个普通的NSInputStream,它将用于设置HTTPBodyStream属性),可以很容易地扩展到任何input源(例如资产数据)。 如果你有兴趣,我可以把这个代码放在要点上。

AFNetworking或任何其他networking库不会为您解决这个问题。 说实话,我不会推荐使用AFNetworking与stream作为主体部分 – 因为在这方面AFNetworking的实施仍然是可疑的。 我build议使用NSURLConnection自己实现委托,或者使用另一个正确处理POST请求的input正文stream的第三方库。

一个简短(不完整)的例子

这个想法是创build某种“资源input源”类,它暴露了一个NSInputStream (可以用来设置NSURLRequestHTTPBodyStream属性)并提供资产数据。

如果“资源input源”是一个文件,任务将很容易:只需创build一个与该文件关联的NSInputStream对象。 但是,我们的资产只能通过一定范围内的字节进行访问,这些字节驻留在一些临时缓冲区中。

所以,任务是用适当的字节范围填充临时缓冲区。 然后,分段,将这些字节写入绑定到inputstream的私有输出 stream 。 这个inputstream输出stream对将通过函数CFStreamCreateBoundPair创build。

inputstream将成为我们公开的“资产input源”的NSInputStream

输出stream仅在内部使用。 资产input源将随着资产被初始化。

我们的“资源input源”类需要处理stream事件,因此它将成为stream代理。

现在,我们有一切来实现它。

CFStreamCreateBoundPair函数创buildCFStream对象。 但是,由于NSStreams是免费桥接,我们可以很容易地将它们“转换”为NSStreams。

“资产input源”类的startinit方法的一部分可以如下实现:

  _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!) 

inputStream是该类的public @property(暴露的inputstream)。

internalOutputStream是类的私有属性。

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

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

stream委托方法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: // na 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的副本write内部输出stream,尽可能适合stream。 当_buffer完全写入输出stream时,它将从资产数据中再次填充。

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

方法finishclosures内部输出stream并取消调度stream。

一个完整可靠的实现可能有点棘手。 “资产input源”也应该是可以取消的。

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

我有一个通过CFStreamCreateBoundPairALAsset转换为NSInputStream的快速版本,它实现了顶级的答案描述称为ALAssetToNSInputStream 。如果需要检查出来。