使用Alamofire上传文件

2020-03-14  本文已影响0人  醉看红尘这场梦

UI的修改

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

image

点击这个按钮时,我们将把之前下载过的文件重新上传到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的官网可以看到,我们可以通过四种方式上传文件:

image

其中前三种我们分别用一个<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:

image

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

image

接下来,我们就来处理上传进度的问题。


使用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:

接下来,我们回到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按钮的时候,就可以看到进度条更新了。

image

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

上一篇 下一篇

猜你喜欢

热点阅读