iOS圖庫大視頻上傳

最近工作中遇到一個需求,從系統相冊中選擇圖片和視頻,使用HTTP上傳到服務器端。在這個過程中也踩了一些坑,在這里和大家分享一下,共同進步。

選擇圖片和視頻

首先是同系統相冊選擇圖片和視頻。iOS系統自帶有UIImagePickerController,可以選擇或拍攝圖片視頻,但是最大的問題是只支持單選,由于項目要求需要支持多選,只能自己自定義。獲取系統圖庫的框架有兩個,一個是ALAssetsLibrary,兼容iOS低版本,但是在iOS9中是不建議使用的;另一個是PHAsset,但最低要求iOS8以上。我們的項目需要兼容到iOS7,所以選擇了ALAssetsLibrary。具體的實現可以參考我之前寫的仿微信iOS相冊選擇 MTImagePicker,github地址https://github.com/luowenxing/MTImagePicker ,這里就不再贅述啦。

HTTP上傳

接下來就是使用HTTP上傳到服務器端。通常來說,文件服務器一般會有兩種實現的方式。

  • 一種是純的二進制文件上傳,對應的HTTP Content-Type可以是application/octet-streamapplication開頭的MIME-Type,即HTTP報文的Body的內容就是文件的二進制內容,其他的文件名、鑒權等附加信息則放在cookieHTTP Header里。
  • 另一種就是HTML表單傳輸,對應的HTTP Content-Typemultipart/form-data,HTTP報文的 Body內容除了文件的二進制內容,還多了附加的表單字段信息和分割符等。表單上傳文件瀏覽器有原生的支持,如果iOS端需要使用這種方式就需要按照報文格式去拼裝你的HTTP Body,具體的報文格式可以參考iOS里實現multipart/form-data格式上傳文件。主流的網絡庫比如AFNetworking就已經有了這類功能的封裝,比較方便。

我們的服務器端這兩種方式都支持,所以這里就直接使用二進制上傳的方式。在沒有第三方的網絡庫的情況下,使用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的內容有兩種方式

  • request.HTTPBodyStream = NSInputStream()
  • request.HTTPBody = NSData()
    這兩者設置其中任何一個都會使得另一個失效。

大文件處理

通常,對于小文件,我們可以任意選擇其中任何一種方式進行設置。對于比較大的文件,處理的原則是,不能把文件直接裝入內存中,否則會造成內存不足而使得App崩潰。具體的做法是:

  • 對于沙箱內的文件,推薦使用NSInputStream(fileAtPath: fileUrl)初始化為文件流,不占內存。也可以使用NSData(contentsOfFile: String>, options: NSDataReadingOptions.DataReadingMappedAlways),使用內存映射的方式獲取NSData,在StackOverflow上有對這個問題的解釋。

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.
類似虛擬內存的技術,簡單來說就是一次拷貝一頁的內存大小(頁是內存映射的最小單位),而不是整個拷貝到內存中。

  • 對于系統相冊的文件,在此處具體來說就是一個ALAsset對象,我們能夠通過ALAssetRepresentationgetBytes方法獲取到文件的內容到一段緩沖區,繼而生成NSData,但是這個NSData并不是內存映射的,所以文件多大,就會占用多少內存。
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/writeStream,readStream就作為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暫時還沒有,有時間會補上??垂賯冸S手給個Star唄 ~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,785評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,662評論 25 708
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結起來就是把...
    Dove_iOS閱讀 27,195評論 30 471
  • 平平出生在一個小鎮里。 或許是因為爸爸媽媽不在身邊的緣故,經常被別人看不起…… (1) 平平和爺爺奶奶一起...
    赫赫h閱讀 192評論 0 0
  • 最近一直在練習演講,看完這本書,我覺得這是練習演講必須要看的一本書。先和大家分享看到的這幾個方法,剩下的可以自己去...
    王子木閱讀 983評論 0 0