UI的修改
為了便于演示,我們添加了一個(gè)upload按鈕纸镊,用于上傳之前我們下載過的文件倍阐。
點(diǎn)擊這個(gè)按鈕時(shí),我們將把之前下載過的文件重新上傳到api.boxue.io上逗威。為了實(shí)現(xiàn)這個(gè)功能峰搪,我們?cè)赩iewController里添加了下面的屬性:
class ViewController: UIViewController {
// Omit for simlicity...
var episodeUrl: NSURL?
@IBOutlet weak var uploadBtn: UIButton!
// Omit for simlicity...
}
其中episodeUrl用于保存下載文件的<key style="box-sizing: border-box;">NSURL</key>,uploadBtn表示關(guān)聯(lián)upload按鈕的IBOutlet凯旭。
然后概耻,在之前定義過的dest closure里使套,返回目標(biāo)路徑之前,設(shè)置這個(gè)episodeUrl:
// TODO: Add begin downloading code here
let dest: Request.DownloadFileDestination = {
temporaryUrl, response in
// Omit for simplicity...
self.episodeUrl = episodeUrl
return episodeUrl
}
并且鞠柄,在ViewController extension里侦高,我們?yōu)閏ancel按鈕設(shè)置IBAction:
extension ViewController {
@IBAction func uploadFile(sender: AnyObject) {
guard self.episodeUrl != nil else {
print("Does not have any downloaded file.")
return
}
// TODO: add uploading code here
print("Uploading \(self.episodeUrl!)")
}
}
當(dāng)然,我們忽略了upload按鈕狀態(tài)的更新厌杜,畢竟它和我們要完成的工作沒什么關(guān)系奉呛。這就是我們對(duì)上一個(gè)視頻中的App進(jìn)行的調(diào)整。接下來夯尽,我們就來實(shí)現(xiàn)上傳文件的功能瞧壮。
在Alamofire的官網(wǎng)可以看到,我們可以通過四種方式上傳文件:
其中前三種我們分別用一個(gè)<key style="box-sizing: border-box;">NSURL</key>匙握,NSData以及<key style="box-sizing: border-box;">NSInputStream</key>指定要上傳的內(nèi)容咆槽,而第四種MultipartFormData則是我們熟悉的模擬表單上傳。
首先圈纺,我們就從MultipartFormData開始秦忿。
模擬表單上傳文件
在uploadFile里,添加下面的代碼:
@IBAction func uploadFile(sender: AnyObject) {
guard self.episodeUrl != nil else {
print("Does not have any downloaded file.")
return
}
print("Uploading \(self.episodeUrl!)")
// TODO: add uploading code here
Alamofire.upload(
.POST,
"https://apidemo.boxue.io/alamofire",
multipartFormData: { multipartFormData in
multipartFormData.appendBodyPart(
fileURL: self.episodeUrl!,
name: "episode-demo")
},
encodingCompletion: nil
)
}
Alamofire.upload方法的前兩個(gè)參數(shù)很好理解蛾娶,第一個(gè)參數(shù).POST是指上傳文件使用的HTTP方法灯谣,第二個(gè)參數(shù)是要上傳的地址,第三個(gè)參數(shù)是一個(gè)Closure茫叭,用于向Alamofire構(gòu)建的一個(gè)MultipartFormData對(duì)象中添加數(shù)據(jù)酬屉。在我們的例子里,我們只添加了一個(gè)name叫做"episode-demo"的字段揍愁,它的值是由self.episodeUrl指定的文件。如果我們上傳多個(gè)文件杀饵,多次調(diào)用multipartFormData.appendBodyPart就可以了莽囤。
最后一個(gè)參數(shù)encodingCompletion是一個(gè)Closure,在前面MultipartFormData編碼完成之后切距,這個(gè)Closure會(huì)被調(diào)用朽缎。稍后,我們會(huì)看到它的用法谜悟,現(xiàn)在话肖,簡單起見,我們給它傳遞nil葡幸。
在服務(wù)端準(zhǔn)備接收文件
在之前我們用到的過的apidemo.boxue.io例子里最筒,我們給AlamofireController添加下面的代碼來處理上傳的文件:
class AlamofireController extends Controller
{
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
if ($request->hasFile("episode-demo")) {
$request->file('episode-demo')
->move(public_path().'/assets/episodes',
'episode-demo.mp4');
}
return response()->json([
'status' => 'successful',
'data' => 'Episodes uploaded successfully'
], 201);
}
}
由于我們?cè)诳蛻舳酥兄付松蟼魑募膎ame為"episode-demo",因此蔚叨,我們先使用:
$request->hasFile("episode-demo")
判斷文件是否存在床蜘,如果文件存在我們就把它移動(dòng)到assets/episodes目錄里辙培,并且重命名為episode-demo.mp4:
if ($request->hasFile("episode-demo")) {
$request->file('episode-demo')
->move(public_path().'/assets/episodes',
'episode-demo.mp4');
}
最后,我們向客戶端返回一個(gè)JSON表示結(jié)果:
return response()->json([
'status' => 'successful',
'data' => 'Episodes uploaded successfully'
], 201);
發(fā)送上傳請(qǐng)求
回到Xcode邢锯,Command + R編譯執(zhí)行扬蕊,我們先下載一個(gè)文件,下載完成之后丹擎,點(diǎn)擊Upload:
這時(shí)尾抑,無論是在控制臺(tái),還是App UI上我們都還看不到任何通知蒂培。為了確認(rèn)上傳結(jié)果蛮穿,我們只能在服務(wù)器的Web目錄里確認(rèn)文件已經(jīng)成功上傳了:
接下來,我們就來處理上傳進(jìn)度的問題毁渗。
使用encodingCompletion
之前践磅,我們把Alamofire.upload的encodingCompletion參數(shù)設(shè)置為了nil。實(shí)際上灸异,它是一個(gè)Closure optional府适,這個(gè)Closure接受一個(gè)MultipartFormDataEncodingResult對(duì)象做為參數(shù),并且沒有返回值肺樟。
什么是MultipartFormDataEncodingResult呢檐春?在Alamofire官網(wǎng),我們可以找到它的定義:
public enum MultipartFormDataEncodingResult {
case Success(request: Request, streamingFromDisk: Bool, streamFileURL: NSURL?)
case Failure(ErrorType)
}
它是一個(gè)enum:
- 成功時(shí)么伯,它的Associated value有三個(gè)值疟暖,第一個(gè)值表示發(fā)起上傳請(qǐng)求的Alamofire.Request對(duì)象;后兩個(gè)參數(shù)和編碼大文件相關(guān)田柔,我們可以暫時(shí)忽略它俐巴;
- 失敗時(shí),它的Associated value是一個(gè)<key style="box-sizing: border-box;">NSError</key>對(duì)象硬爆,表示具體的錯(cuò)誤信息欣舵;
接下來,我們回到uploadFile缀磕,給它添加下面的代碼:
Alamofire.upload(
.POST,
"https://apidemo.boxue.io/alamofire",
multipartFormData: { multipartFormData in
multipartFormData.appendBodyPart(
fileURL: self.episodeUrl!,
name: "episode-demo")
},
encodingCompletion: { encodingResult in
switch encodingResult {
case .Success(let upload, _, _):
upload
.progress {
bytesWritten,
totalBytesWritten,
totalBytesExpectedToWrite in
print(totalBytesWritten)
// This closure is NOT called on the main queue for performance
// reasons. To update your ui, dispatch to the main queue.
dispatch_async(dispatch_get_main_queue()) {
// Calculate the download percentage
let progress =
Float(totalBytesWritten) /
Float(totalBytesExpectedToWrite)
self.downloadProgress.progress = progress
}
}
.responseJSON { response in
debugPrint(response)
}
case .Failure(let encodingError):
print(encodingError)
}
}
)
我們著重看encodingCompletion的部分缘圈,當(dāng)編碼成功時(shí),我們讀取了它的第一個(gè)associated value袜蚕,由于它是一個(gè)Alamofire.Request對(duì)象糟把,我們可以像之前下載功能一樣,給它"注冊(cè)"進(jìn)度通知(.progress)以及結(jié)果處理(.responseJSON)牲剃,它們的用法和我們?cè)谙螺d中用到的是一樣的遣疯。
而當(dāng)編碼失敗的時(shí)候,我們只是向控制臺(tái)打印了錯(cuò)誤信息颠黎。
然后另锋,Command + R編譯執(zhí)行滞项,這次,當(dāng)我們?cè)冱c(diǎn)擊upload按鈕的時(shí)候夭坪,就可以看到進(jìn)度條更新了文判。
這就是Alamofire上傳文件的用法,簡單來說室梅,設(shè)置HTTP Action戏仓,設(shè)置上傳地址,編碼文件亡鼠,自定義Completion赏殃,結(jié)束。