假设您的数据太大而无法放入内存:
一种有效且可靠的方法将利用有界的 CFStreams 对(请参阅CFStreamCreateBoundPair)。
有界流对的输入流设置为NSMutableURLRequest
'HTTPBodyStream
属性。有界流对的输出流将用于写入从已用ALAssetRepresentation
'getBytes:fromOffset:length:error:
方法填充的固定大小内存缓冲区中获得的字节。
有界流对的传输缓冲区的大小应与资产表示的缓冲区大小相同。
设置代码需要几行代码和一些 NSStreams 和处理事件的经验(NSStreams 通常有一些微妙之处)。
这种方法的工作原理如下:
创建处理所有流事件的流委托。
为传输缓冲区设置具有特定大小的配对流,设置委托并将它们安排在运行循环上。
为相同大小的资产数据设置内存缓冲区。
当您打开流时,您会收到一个NSStreamEventHasSpaceAvailable
事件。您可以通过读取资产数据来处理该事件getBytes:fromOffset:length:error:
,然后写入内存缓冲区。当您用一大块资产数据填充缓冲区时,将此缓冲区写入有界流对的输出流。适当地跟踪偏移!
现在,有界流对的输入流很好地被底层连接提取(它将字节从内部传输缓冲区移动到网络套接字),您将收到另一个NSStreamEventHasSpaceAvailable
事件,因为现在内部传输缓冲区中有可用空间。将尽可能多的字节写入有界流对的输出流,从资产数据缓冲区写入输出流,并且资产数据缓冲区中有尽可能多的字节可用。如果资产数据缓冲区已完全写入,请重新填充它。仔细跟踪偏移量和范围!
您处理这些事件,直到整个资产数据都被写入。然后关闭输出流。
您还需要处理其他流事件,请参阅:流编程指南
注意:您可能会注意到您的内存缓冲区只能部分写入输出流。通过跟踪偏移量来解决这个问题,以便您始终在缓冲区中保持资产数据的连续流,并将适当范围的数据从缓冲区写入输出流!
警告:
使用一对有界流设置正确的实现可能很棘手,并且可能容易出错。我确实有一个通用版本的“InputStreamSource”(它公开了一个普通的 NSInputStream,它将用于设置 HTTPBodyStream 属性),可以很容易地扩展它以用于任何输入源 - 例如资产数据。如果您有兴趣,我可以将此代码放在要点上。
AFNetworking 或任何其他网络库都不会为您解决这个问题。老实说,我不建议将 AFNetworking 与流结合使用作为主体部分——因为 AFNetworking 的实现在这方面仍然值得怀疑。我建议您NSURLConnection
自己实现委托,或者使用另一个第三方库来正确处理 POST 请求的输入主体流。
一个简短(不完整)的例子
这个想法是,创建某种“资产输入源”类,它公开 a NSInputStream
(可用于设置 的HTTPBodyStream
属性NSURLRequest
)并提供资产数据。
如果“资产输入源”是一个文件,那么任务会很简单:只需创建一个NSInputStream
与该文件关联的对象。但是,我们的资产只能通过特定表示形式的一系列字节来访问,这些字节驻留在某个临时缓冲区中。
因此,任务是用适当的字节范围填充该临时缓冲区。然后,将这些字节分段写入绑定到输入流的私有输出流中。这个输入流和输出流对将通过函数创建。CFStreamCreateBoundPair
输入流将成为我们暴露的“资产输入源”的NSInputStream。
输出流仅在内部使用。“资产输入源”将使用资产进行初始化。
我们的“资产输入源”类需要处理流事件,因此它将成为流委托。
现在,我们拥有实现它的一切。
该CFStreamCreateBoundPair
函数创建 CFStream 对象。然而,由于 NSStreams 是免费桥接的,我们可以轻松地将它们“转换”为 NSStreams。
“资产输入源”类的部分astart
或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
是该类的公共@property(公开的输入流)。
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;
}
}
如您所见,秘诀在于 method write
。还有一种finish
方法和一种cancel
方法。
基本上,方法write
从 _buffer 复制到内部输出流中,尽可能适合流。当 _buffer 完全写入输出流时,将从资产数据中再次填充。
当没有更多数据可从资产写入输出流时,finish
将调用方法。
方法finish
关闭内部输出流并取消调度流。
一个完整可靠的实现可能有点棘手。“资产输入源”也应该是可取消的。
如前所述,我确实有一个“抽象输入源”类,它实现了除了用资产数据填充 _buffer 之外的所有内容,如果需要,我可以将其作为 Gist 上的代码片段提供。