【问题标题】:iOS how to upload a large asset file into sever by streamingiOS 如何通过流式传输将大型资产文件上传到服务器
【发布时间】:2013-08-23 07:40:09
【问题描述】:

我是新的 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: 该链接中有一个示例,上传文件来自 bundle
如何使资产成为流?或者将资产复制到APP的文件夹中?
我找到了这个链接Copy image from assets-library to an app folder
但还是找不到路。

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



谢谢你的热情

updated1:在我实现needNewBodyStream委托后,“请求流耗尽”消息似乎解决了,但遇到另一个“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;
}

【问题讨论】:

    标签: ios upload streaming


    【解决方案1】:

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

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

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

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

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

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

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

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

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

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

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

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

    7. 您还需要处理其他流事件,请参阅:Stream Programming Guide

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

    警告:

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

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

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

    想法是,创建某种“资产输入源”类,该类公开NSInputStream(可用于设置NSURLRequestHTTPBodyStream 属性)并提供资产数据。

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

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

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

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

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

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

    CFStreamCreateBoundPair 函数创建 CFStream 对象。然而,由于 NSStreams 是toll-free bridged,我们可以轻松地将它们“转换”为 NSStreams。

    “资产输入源”类的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 是该类的公共@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 提供。

    【讨论】:

    • 感谢您的回复,但我对此感到困惑:1.您提到了 CFSteams,但在第 4 点中您说的是 NSStreamEventHasSpaceAvailable 事件。那两个是不同的班级吗?我怎样才能使用 CFStream 并设置委托来触发 NSStreamEventHasSpaceAvailable 事件?请分享您的代码,非常感谢
    • 好吧,我告诉过你:“使用一对有界流设置正确的实现可能很棘手”;)我会更新我的答案并举一个简短的例子。
    • 真的很感谢你的分享,我想到了你说的:“我们的资产只能通过一定表示形式的一系列字节来访问,它驻留在一些临时缓冲区中。”,如果我可以将资产复制到APP文件夹中:stackoverflow.com/questions/11961534/…然后我只需要使用NSStream进行上传对吗?
    • 请为我提供 Gist 上的代码 sn-p,这对我有很大帮助!太感谢了!!!
    • 我不知道是否可以复制或访问资产文件(由于沙箱中的限制)-但您可以尝试一下。所以,检查一下 - 并且 IFF 它是可能的,然后你可以从这个文件创建一个 NSInputStream 。这里需要注意的是:您不能指定某个预定义的资产表示 - 您会得到文件的任何格式(可能是某种原始格式)。
    【解决方案2】:

    我有一个通过CFStreamCreateBoundPairALAsset 转换为NSInputStream 的快速版本,它实现了最重要的答案所描述的称为ALAssetToNSInputStream。如果需要,请查看。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-09-22
      • 2014-06-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-16
      • 1970-01-01
      相关资源
      最近更新 更多