UI的修改
为了便于演示,我们添加了一个upload按钮,用于上传之前我们下载过的文件。

点击这个按钮时,我们将把之前下载过的文件重新上传到api.boxue.io上。为了实现这个功能,我们在ViewController里添加了下面的属性:
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表示关联upload按钮的IBOutlet。
然后,在之前定义过的dest closure里,返回目标路径之前,设置这个episodeUrl:
// TODO: Add begin downloading code here
let dest: Request.DownloadFileDestination = {
temporaryUrl, response in
// Omit for simplicity...
self.episodeUrl = episodeUrl
return episodeUrl
}
并且,在ViewController extension里,我们为cancel按钮设置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!)")
}
}
当然,我们忽略了upload按钮状态的更新,毕竟它和我们要完成的工作没什么关系。这就是我们对上一个视频中的App进行的调整。接下来,我们就来实现上传文件的功能。
在Alamofire的官网可以看到,我们可以通过四种方式上传文件:

其中前三种我们分别用一个<key style="box-sizing: border-box;">NSURL</key>,NSData以及<key style="box-sizing: border-box;">NSInputStream</key>指定要上传的内容,而第四种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方法的前两个参数很好理解,第一个参数.POST是指上传文件使用的HTTP方法,第二个参数是要上传的地址,第三个参数是一个Closure,用于向Alamofire构建的一个MultipartFormData对象中添加数据。在我们的例子里,我们只添加了一个name叫做"episode-demo"的字段,它的值是由self.episodeUrl指定的文件。如果我们上传多个文件,多次调用multipartFormData.appendBodyPart就可以了。
最后一个参数encodingCompletion是一个Closure,在前面MultipartFormData编码完成之后,这个Closure会被调用。稍后,我们会看到它的用法,现在,简单起见,我们给它传递nil。
在服务端准备接收文件
在之前我们用到的过的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);
}
}
由于我们在客户端中指定了上传文件的name为"episode-demo",因此,我们先使用:
$request->hasFile("episode-demo")
判断文件是否存在,如果文件存在我们就把它移动到assets/episodes目录里,并且重命名为episode-demo.mp4:
if ($request->hasFile("episode-demo")) {
$request->file('episode-demo')
->move(public_path().'/assets/episodes',
'episode-demo.mp4');
}
最后,我们向客户端返回一个JSON表示结果:
return response()->json([
'status' => 'successful',
'data' => 'Episodes uploaded successfully'
], 201);
发送上传请求
回到Xcode,Command + R编译执行,我们先下载一个文件,下载完成之后,点击Upload:

这时,无论是在控制台,还是App UI上我们都还看不到任何通知。为了确认上传结果,我们只能在服务器的Web目录里确认文件已经成功上传了:

接下来,我们就来处理上传进度的问题。
使用encodingCompletion
之前,我们把Alamofire.upload的encodingCompletion参数设置为了nil。实际上,它是一个Closure optional,这个Closure接受一个MultipartFormDataEncodingResult对象做为参数,并且没有返回值。
什么是MultipartFormDataEncodingResult呢?在Alamofire官网,我们可以找到它的定义:
public enum MultipartFormDataEncodingResult {
case Success(request: Request, streamingFromDisk: Bool, streamFileURL: NSURL?)
case Failure(ErrorType)
}
它是一个enum:
- 成功时,它的Associated value有三个值,第一个值表示发起上传请求的Alamofire.Request对象;后两个参数和编码大文件相关,我们可以暂时忽略它;
- 失败时,它的Associated value是一个<key style="box-sizing: border-box;">NSError</key>对象,表示具体的错误信息;
接下来,我们回到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的部分,当编码成功时,我们读取了它的第一个associated value,由于它是一个Alamofire.Request对象,我们可以像之前下载功能一样,给它"注册"进度通知(.progress)以及结果处理(.responseJSON),它们的用法和我们在下载中用到的是一样的。
而当编码失败的时候,我们只是向控制台打印了错误信息。
然后,Command + R编译执行,这次,当我们再点击upload按钮的时候,就可以看到进度条更新了。

这就是Alamofire上传文件的用法,简单来说,设置HTTP Action,设置上传地址,编码文件,自定义Completion,结束。
网友评论