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