草庐IT

ios - iOS如何通过流式传输将大型 Assets 文件上传到服务器

coder 2023-09-25 原文

我是新的iOS程序员。
我想将 Assets 库中的大文件(视频或图片)上传到服务器中,
我的原始方法是只使用 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天,听起来有几种方法可以解决这个问题。
  • 使用第三方库:例如AFNetworking,ASIHTTPRequest(但我不想使用它,因为不知道何时停止维护或更新)
  • 使用流媒体上传大文件

  • 我想使用流式传输方式(第2点)上传内容,
    我找到了此链接:http://zh.scribd.com/doc/51504708/10/Upload-Files-Over-HTTP
    看起来可以解决我的问题,但是仍然不很清楚该怎么做

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

    Question2 :
    或还有其他更清晰的流传输示例来上传大文件吗?

    谢谢你的热情

    Updated1 :我实现了needNewBodyStream委托(delegate)后,似乎解决了“请求流耗尽”消息,但是遇到了另一个“错误域= kCFErrorDomainCFNetwork代码= 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)。

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

    有界流对的传输缓冲区的大小应与 Assets 表示形式的缓冲区的大小相同。

    设置代码将需要几行代码,并且需要使用NSStreams并具有处理事件的经验(NSStreams通常有一些微妙之处)。

    此方法的工作方式如下:

  • 创建处理所有流事件的流的委托(delegate)。
  • 为传输缓冲区设置具有特定大小的配对流,设置委托(delegate)并将它们安排在运行循环中。
  • 为相同大小的 Assets 数据设置一个内存缓冲区。
  • 打开流时,您会收到一个NSStreamEventHasSpaceAvailable事件。您可以通过getBytes:fromOffset:length:error:读取 Assets 数据来处理该事件,然后将其写入内存缓冲区。当您用大量 Assets 数据填充缓冲区时,将此缓冲区写入有界流对的输出流中。正确跟踪偏移!
  • 现在,底层连接(将字节从内部传输缓冲区移动到网络套接字)可以很好地提取有界流对的输入流,并且您将获得另一个NSStreamEventHasSpaceAvailable事件,因为现在内部传输中有可用空间-缓冲。从 Assets 数据缓冲区中将尽可能多的字节写入有界流对的输出流中,以写入输出流, Assets 数据缓冲区中将有尽可能多的字节可用。如果 Assets 数据缓冲区已完全写入,请重新填充它。仔细跟踪偏移量和范围!
  • 您将处理事件,直到写入了整个 Assets 数据为止。然后关闭输出流。
  • 您还需要处理其他流事件,请参见:Stream Programming Guide

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

    警告:

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

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

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

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

    如果“ Assets 输入源”是一个文件,则该任务将很容易:只需创建与该文件关联的NSInputStream对象即可。但是,只能通过某个表示形式的一定范围的字节来访问我们的 Assets ,该表示形式位于某个临时缓冲区中。

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

    输入流将成为我们在“ Assets 输入源”中公开的NSInputStream。

    输出流仅在内部使用。 “ Assets 输入源”将使用 Assets 初始化。

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

    现在,我们拥有实现它的一切。
    CFStreamCreateBoundPair函数创建CFStream对象。但是,由于NSStreams是toll-free bridged,我们可以轻松地将它们“转换”为NSStreams。

    “ Assets 输入源”类的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是该类的公共(public)@property(公开的输入流)。
    internalOutputStream是该类的私有(private)属性。
    _buffer是内部缓冲区,用于保存 Assets 表示形式的字节范围。

    请注意,有界流对的内部缓冲区大小等于保存 Assets 数据的缓冲区。

    流委托(delegate)方法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;
        }
    }
    

    如您所见, secret 在write方法中。还有一个finish方法和cancel方法。

    基本上,方法write从_buffer复制到内部输出流中,并尽可能地适合该流。将_buffer完全写入输出流后,它将再次从 Assets 数据中填充。

    当没有更多数据可用于从 Assets 写入输出流时,将调用finish方法。

    方法finish关闭内部输出流并取消调度该流。

    完整而可靠的实现可能会有些棘手。 “ Assets 输入源”也应该是可取消的。

    如前所述,我确实有一个“抽象输入源”类,该类实现了除用 Assets 数据填充_buffer之外的所有内容,如果需要的话,我可以将其作为Gist上的代码段提供。

    关于ios - iOS如何通过流式传输将大型 Assets 文件上传到服务器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18348863/

    有关ios - iOS如何通过流式传输将大型 Assets 文件上传到服务器的更多相关文章

    1. ruby - i18n Assets 管理/翻译 UI - 2

      我正在使用i18n从头开始​​构建一个多语言网络应用程序,虽然我自己可以处理一大堆yml文件,但我说的语言(非常)有限,最终我想寻求外部帮助帮助。我想知道这里是否有人在使用UI插件/gem(与django上的django-rosetta不同)来处理多个翻译器,其中一些翻译器不愿意或无法处理存储库中的100多个文件,处理语言数据。谢谢&问候,安德拉斯(如果您已经在ruby​​onrails-talk上遇到了这个问题,我们深表歉意) 最佳答案 有一个rails3branchofthetolkgem在github上。您可以通过在Gemfi

    2. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

      我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

    3. ruby-on-rails - 启动 Rails 服务器时 ImageMagick 的警告 - 2

      最近,当我启动我的Rails服务器时,我收到了一长串警告。虽然它不影响我的应用程序,但我想知道如何解决这些警告。我的估计是imagemagick以某种方式被调用了两次?当我在警告前后检查我的git日志时。我想知道如何解决这个问题。-bcrypt-ruby(3.1.2)-better_errors(1.0.1)+bcrypt(3.1.7)+bcrypt-ruby(3.1.5)-bcrypt(>=3.1.3)+better_errors(1.1.0)bcrypt和imagemagick有关系吗?/Users/rbchris/.rbenv/versions/2.0.0-p247/lib/ru

    4. ruby-on-rails - s3_direct_upload 在生产服务器中不工作 - 2

      在Rails4.0.2中,我使用s3_direct_upload和aws-sdkgems直接为s3存储桶上传文件。在开发环境中它工作正常,但在生产环境中它会抛出如下错误,ActionView::Template::Error(noimplicitconversionofnilintoString)在View中,create_cv_url,:id=>"s3_uploader",:key=>"cv_uploads/{unique_id}/${filename}",:key_starts_with=>"cv_uploads/",:callback_param=>"cv[direct_uplo

    5. ruby - 如何验证 IO.copy_stream 是否成功 - 2

      这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

    6. Ruby 文件 IO 定界符? - 2

      我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

    7. ruby-on-rails - 在 Rails 中调试生产服务器 - 2

      您如何在Rails中的实时服务器上进行有效调试,无论是在测试版/生产服务器上?我试过直接在服务器上修改文件,然后重启应用,但是修改好像没有生效,或者需要很长时间(缓存?)我也试过在本地做“脚本/服务器生产”,但是那很慢另一种选择是编码和部署,但效率很低。有人对他们如何有效地做到这一点有任何见解吗? 最佳答案 我会回答你的问题,即使我不同意这种热修补服务器代码的方式:)首先,你真的确定你已经重启了服务器吗?您可以通过跟踪日志文件来检查它。您更改的代码显示的View可能会被缓存。缓存页面位于tmp/cache文件夹下。您可以尝试手动删除

    8. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

      1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

    9. ruby - 我的 Ruby IRC 机器人没有连接到 IRC 服务器。我究竟做错了什么? - 2

      require"socket"server="irc.rizon.net"port="6667"nick="RubyIRCBot"channel="#0x40"s=TCPSocket.open(server,port)s.print("USERTesting",0)s.print("NICK#{nick}",0)s.print("JOIN#{channel}",0)这个IRC机器人没有连接到IRC服务器,我做错了什么? 最佳答案 失败并显示此消息::irc.shakeababy.net461*USER:Notenoughparame

    10. ruby - 为什么不能使用类IO的实例方法noecho? - 2

      print"Enteryourpassword:"pass=STDIN.noecho(&:gets)puts"Yourpasswordis#{pass}!"输出:Enteryourpassword:input.rb:2:in`':undefinedmethod`noecho'for#>(NoMethodError) 最佳答案 一开始require'io/console'后来的Ruby1.9.3 关于ruby-为什么不能使用类IO的实例方法noecho?,我们在StackOverflow上

    随机推荐