5

我是新的 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. 使用流式上传大文件

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

问题1:该链接中有一个示例,上传文件来自捆绑
如何将资产制作成流?或将资产复制到APP的文件夹?
我找到了这个链接Copy image from assets-library to an app folder
但仍然找不到方法。

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



感谢您的热情 更新1:在我实现

needNewBodyStream委托后,“请求流耗尽”消息似乎解决了,但遇到另一个“错误域=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;
}
4

2 回答 2

8

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

一种有效且可靠的方法将利用有界的 CFStreams 对(请参阅CFStreamCreateBoundPair)。

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

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

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

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

  1. 创建处理所有流事件的流委托。

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

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

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

  5. 现在,有界流对的输入流很好地被底层连接提取(它将字节从内部传输缓冲区移动到网络套接字),您将收到另一个NSStreamEventHasSpaceAvailable事件,因为现在内部传输缓冲区中有可用空间。将尽可能多的字节写入有界流对的输出流,从资产数据缓冲区写入输出流,并且资产数据缓冲区中有尽可能多的字节可用。如果资产数据缓冲区已完全写入,请重新填充它。仔细跟踪偏移量和范围!

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

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

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

警告:

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

AFNetworking 或任何其他网络库都不会为您解决这个问题。老实说,我不建议将 AFNetworking 与结合使用作为主体部分——因为 AFNetworking 的实现在这方面仍然值得怀疑。我建议您NSURLConnection自己实现委托,或者使用另一个第三方库来正确处理 POST 请求的输入主体流。

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

这个想法是,创建某种“资产输入源”类,它公开 a NSInputStream(可用于设置 的HTTPBodyStream属性NSURLRequest)并提供资产数据。

如果“资产输入源”是一个文件,那么任务会很简单:只需创建一个NSInputStream与该文件关联的对象。但是,我们的资产只能通过特定表示形式的一系列字节来访问,这些字节驻留在某个临时缓冲区中。

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

输入流将成为我们暴露的“资产输入源”的NSInputStream

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

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

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

CFStreamCreateBoundPair函数创建 CFStream 对象。然而,由于 NSStreams 是免费桥接的,我们可以轻松地将它们“转换”为 NSStreams。

“资产输入源”类的部分astartinit方法可以实现如下:

    _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 上的代码片段提供。

于 2013-08-21T08:20:51.223 回答
0

我有一个转换ALAssetNSInputStreamvia的快速版本,CFStreamCreateBoundPair它实现了最佳答案所描述的称为ALAssetToNSInputStream的内容。如果需要,请查看。

于 2016-05-30T02:17:32.543 回答