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天,听起来有几种方法来解决这个问题。
- 使用第三方库:如AFNetworking,ASIHTTPRequest(但我不想使用它,因为不知道什么时候会停止维护或更新)
- 使用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被设置为NSMutableURLRequest
的HTTPBodyStream
属性。 有界stream对的输出stream将用于写入从填充了ALAssetRepresentation
的getBytes:fromOffset:length:error:
方法的固定大小的内存缓冲区获得的字节。
有界stream对的传输缓冲区的大小应该与资产表示的缓冲区的大小相同。
设置代码将需要几行代码和NSStream的一些经验和处理事件(通常NSStreams有一些微妙之处)。
这种方法的工作原理如下:
-
创build一个处理所有stream事件的stream委托。
-
为传输缓冲区设置特定大小的配对stream,设置代理并将其安排在运行循环中。
-
为相同大小的资产数据设置内存缓冲区。
-
当你打开stream,你会得到一个
NSStreamEventHasSpaceAvailable
事件。 您可以通过getBytes:fromOffset:length:error:
来从资产数据读取并处理该事件,然后写入内存缓冲区。 当你用一大块资产数据填充你的缓冲区时,把这个缓冲区写入有界stream对的输出stream 。 跟踪偏移量! -
现在,有界stream对的inputstream可以通过底层连接(将字节从内部传输缓冲区移动到networking套接字)很好地解压缩,并且您将获得另一个
NSStreamEventHasSpaceAvailable
事件,因为现在在内部传输缓冲区。 从资产数据缓冲区中写入尽可能多的字节以适合有界stream对的输出stream ,以便在资产数据缓冲区中提供尽可能多的字节。 如果资产数据缓冲区已经写完,请重新填写。 仔细追踪偏移和范围! -
您处理事件直到整个资产数据被写入。 然后closures输出stream。
-
您还需要处理其他stream事件,请参阅: stream编程指南
注意:您可能会注意到您的内存缓冲区只能部分写入输出stream。 通过跟踪偏移量来处理这个问题,以便始终在缓冲区中保持连续的资产数据stream,并将适当范围的数据从缓冲区写入输出stream!
警告:
用一对有限的stream设置一个正确的实现可能会非常棘手,也许是容易出错的。 我有一个“InputStreamSource”的通用版本(它公开了一个普通的NSInputStream,它将用于设置HTTPBodyStream属性),可以很容易地扩展到任何input源(例如资产数据)。 如果你有兴趣,我可以把这个代码放在要点上。
AFNetworking或任何其他networking库不会为您解决这个问题。 说实话,我不会推荐使用AFNetworking与stream作为主体部分 – 因为在这方面AFNetworking的实施仍然是可疑的。 我build议使用NSURLConnection
自己实现委托,或者使用另一个正确处理POST请求的input正文stream的第三方库。
一个简短(不完整)的例子
这个想法是创build某种“资源input源”类,它暴露了一个NSInputStream
(可以用来设置NSURLRequest
的HTTPBodyStream
属性)并提供资产数据。
如果“资源input源”是一个文件,任务将很容易:只需创build一个与该文件关联的NSInputStream
对象。 但是,我们的资产只能通过一定范围内的字节进行访问,这些字节驻留在一些临时缓冲区中。
所以,任务是用适当的字节范围填充临时缓冲区。 然后,分段,将这些字节写入绑定到inputstream的私有输出 stream 。 这个inputstream和输出stream对将通过函数CFStreamCreateBoundPair
创build。
inputstream将成为我们公开的“资产input源”的NSInputStream 。
输出stream仅在内部使用。 资产input源将随着资产被初始化。
我们的“资源input源”类需要处理stream事件,因此它将成为stream代理。
现在,我们有一切来实现它。
CFStreamCreateBoundPair
函数创buildCFStream对象。 但是,由于NSStreams是免费桥接,我们可以很容易地将它们“转换”为NSStreams。
“资产input源”类的start
或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!)
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
。
方法finish
closures内部输出stream并取消调度stream。
一个完整可靠的实现可能有点棘手。 “资产input源”也应该是可以取消的。
如上所述,我有一个“抽象input源”类,它实现了除了使用资产数据填充_buffer之外的所有内容,如果需要,我可以将其作为代码片段提供给Gist。
我有一个通过CFStreamCreateBoundPair
将ALAsset
转换为NSInputStream
的快速版本,它实现了顶级的答案描述称为ALAssetToNSInputStream 。如果需要检查出来。