iOSSwift编程iOS移动开发社区

iOS图库大视频上传

2016-05-31  本文已影响4201人  文兴

最近工作中遇到一个需求,从系统相册中选择图片和视频,使用HTTP上传到服务器端。在这个过程中也踩了一些坑,在这里和大家分享一下,共同进步。

选择图片和视频

首先是同系统相册选择图片和视频。iOS系统自带有UIImagePickerController,可以选择或拍摄图片视频,但是最大的问题是只支持单选,由于项目要求需要支持多选,只能自己自定义。获取系统图库的框架有两个,一个是ALAssetsLibrary,兼容iOS低版本,但是在iOS9中是不建议使用的;另一个是PHAsset,但最低要求iOS8以上。我们的项目需要兼容到iOS7,所以选择了ALAssetsLibrary。具体的实现可以参考我之前写的仿微信iOS相册选择 MTImagePicker,github地址https://github.com/luowenxing/MTImagePicker ,这里就不再赘述啦。

HTTP上传

接下来就是使用HTTP上传到服务器端。通常来说,文件服务器一般会有两种实现的方式。

我们的服务器端这两种方式都支持,所以这里就直接使用二进制上传的方式。在没有第三方的网络库的情况下,使用NSURLConnectionNSURLSession发起网络请求前,我们都需要一个NSURLRequest对象,在这个对象上完成请求初始化。

let request = NSMutableURLRequest(URL: url, cachePolicy: .UseProtocolCachePolicy, timeoutInterval: 10)
request.HTTPMethod = "POST"
request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")

设置好相关的HTTP Header之后,设置HTTP Body的内容有两种方式

大文件处理

通常,对于小文件,我们可以任意选择其中任何一种方式进行设置。对于比较大的文件,处理的原则是,不能把文件直接装入内存中,否则会造成内存不足而使得App崩溃。具体的做法是:

Memory-mapped files copy data from disk into memory a page at a time. Unused pages are free to be swapped out, the same as any other virtual memory, unless they have been wired into physical memory using mlock(2). Memory mapping leaves the determination of what to copy from disk to memory and when to the OS.
类似虚拟内存的技术,简单来说就是一次拷贝一页的内存大小(页是内存映射的最小单位),而不是整个拷贝到内存中。

let rept =  asset.defaultRepresentation()
let imageBuffer = UnsafeMutablePointer<UInt8>.alloc(Int(rept.size()))
let bufferSize = rept.getBytes(imageBuffer, fromOffset: Int64(0),length: Int(rept.size()), error: nil)
let data =  NSData(bytesNoCopy:imageBuffer ,length:bufferSize, freeWhenDone:true)

此时我们需要把ALAsset转化为NSInputStream,通过CFStreamCreateBoundPair这个类。在苹果的官方文档上有对这个类的使用场景介绍,但是没有官方例子。

For large blocks of constructed data, call CFStreamCreateBoundPair to create a pair of streams, then call the setHTTPBodyStream: method to tell NSMutableURLRequest to use one of those streams as the source for its body content. By writing into the other stream, you can send the data a piece at a time.

其他的参考资料也很少,我找到的对我有帮助的资料之一就是StackOverflow上的这个问题:ios-how-to-upload-a-large-asset-file-into-sever-by-streaming

根据官方文档,以及我收集的资料,具体的做法是使用CFStreamCreateBoundPair创建一对readStream/writeStreamreadStream就作为HTTPBodyStream,设置NSStream的代理,writeStream加入Runloop,监测其NSStreamEventHasSpaceAvailable时,调用getBytes方法获取一段NSData,写入到writeStream中。主要的代码如下。

    func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) {
        switch (eventCode) {
        case NSStreamEvent.None:
            break
            
        case NSStreamEvent.OpenCompleted:
            break
            
        case NSStreamEvent.HasBytesAvailable:
            break
            
        case NSStreamEvent.HasSpaceAvailable:
            self.write()
            break
            
        case NSStreamEvent.ErrorOccurred :
            self.finish()
            break
        case NSStreamEvent.EndEncountered:
            // weird error: the output stream is full or closed prematurely, or canceled.
            self.finish()
            break
        default:
            break
        }
    }
    
    func write() {
        let rept =  asset.defaultRepresentation()
        let length = self.assetSize - self.offset > self.bufferSize ? self.bufferSize :  self.assetSize - self.offset
        if length > 0 {
            let writeSize = rept.getBytes(assetBuffer, fromOffset: self.offset ,length: length, error:nil)
            let written = self.writeStream.write(assetBuffer, maxLength: writeSize)
            self.offset += written
        } else {
            self.finish()
        }
    }
    
    func finish() {
        self.writeStream.close()
        self.writeStream.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes)
        self.strongSelf = nil
    }

完整的代码我上传在了github,ALAssetToNSInputStream,把ALAssetToNSInputStream.swift加入工程即可使用,Demo暂时还没有,有时间会补上。看官们随手给个Star呗 ~

上一篇 下一篇

猜你喜欢

热点阅读