www.icesr.com
IT运维工程师的摇篮

Alamofire 上传图片入门教程(上)

Get the lowdown on Alamofire!

提示:
本教程基于 Xcode 7.3, iOS 9.3, 和 Swift 2.2

Alamofire 是一个为 iOS 和 Mac OS,基于 Swift 的 HTTP 网络库。它在 Apple 的基础网络库上提供了一个优雅的接口,简化了许多常见的网络任务。

Alamofire 提供链式地请求与响应方法,JSON 参数以及响应的序列化和授权,等等。在本教程中,你将使用 Alamofire 来完成基本的网络任务,包括上传文件以及使用第三方库 RESTful API 来请求数据。

注:关于 RESTful API 可以参考这篇博客 RESTful API

Alamofire 的优雅是一个事实,它完全由 Swift 编写,没有任何 Objective-C 的代码,也不是继承自 AFNetworking。

你需要理解一些 HTTP 的基本概念,以及了解苹果的网络类,比如 NSURLSessionNSURLConnection

虽然 Alamofire 掩盖了一些实现细节,但是如果你需要解决你的网络请求,有一些背景知识也是不错的。你也需要使用 CocoaPods 把 Alamofire 安装到你的教程工程中。

入门

下载 初始工程。下载完成后,打开工程运行,会发现中间有一个按钮可以选择图片,点击按钮会访问系统的相册,随便选择一张图片,背景就会被这张图片代替。但是现在的工程是一个初始工程,好多功能没有完成,如下图:

PhotoTagger-start.png

最终完成的效果图会是下面的样子:

PhotoTaggerDemo.gif

Imagga API

Imagga 是一个把图像识别作为服务的平台,为开发者和企业提供了可扩展的图像标注 API,以及图像密集的云应用。你可以从 他们的自动标注服务 下载一个示例程序。

你需要在 Imagga 网站创建一个免费的开发者账户,才能完成这次教程。Imagga 在每次进行 HTTP 请求的时候都需要一个授权头,所以必须注册才能使用他们的服务。去 https://imagga.com/auth/signup/hacker 完成注册吧。注册成功后你会看到类似下面的界面:

Imagga-Dashboard.png

Authorization 选项是一个 token,后面你会用到。

注意:
确保你拷贝了整个 token,因为这个 token 很长。

你将会使用 Imagga 作为上传图片的服务器,给图片标注和设置颜色。你可以在 http://docs.imagga.com 了解到所有的 API。

安装依赖包

在工程主目录创建一个名为 Podfile 的文件,并打开这个文件,可以使用下面的方式:

<code>touch Podfile
<span class="hljs-keyword">open</span> Podfile</code>

然后输入下面的内容:

<code><span class="hljs-built_in">platform</span> :ios, <span class="hljs-string">'9.0'</span>

inhibit_all_warnings!
use_frameworks!

target <span class="hljs-string">'PhotoTagger'</span> <span class="hljs-built_in">do</span>
  pod <span class="hljs-string">'Alamofire'</span>, <span class="hljs-string">'~&gt; 3.1.2'</span>
<span class="hljs-function"><span class="hljs-keyword">end</span></span></code>

接下来,如果你的电脑上没有安装 CocoaPods,点击下面链接进行安装 最新版 CocoaPods 的安装流程

关闭刚才打开的工程,打开终端进入工程目录。输入 pod install,稍等片刻,完成后会生成一个名为 PhotoTagger.xcworkspace 的文件。然后编译运行你的工程,你会发现没有任何改变,这就对了,下一步从 RESTful 服务器添加一些 HTTP 请求,会返回 JSON 数据。

Alamofire 的好处是什么?

苹果已经提供了 NSURLSession 和其他的类来下载内容,那为什么还要用 Alamofire 呢?因为 Alamofire 基于 NSURLSession,并且它可以让你编写的代码的时候更容易。你不用话费太大的精力就可以获取互联网上的数据,而且你的代码还会很简洁易读。

下面是 Alamofire 中几个主要的函数:

.upload:以分解成部分的形式,数据流的形式,整个文件的形式或者数据的形式上传文件。
.download:下载文件或者暂停后继续下载。
.request:每一个 HTTP 请求和文件传输都不相关。

这些 Alamofire 函数的范围是一个 module,而不是类或结构体。Alamofire 的底层都是类和结构体,比如有 ManagerRequest, 和Response,但是你一开始用的时候不必全都理解 Alamofire 其他的结构体。下面是一个例子:
下面直接使用的 NSURLSession

<code>// With NSURLSession
public func fetchAllRooms(completion: ([RemoteRoom]?) -&gt; Void) {
  let url = NSURL(<span class="hljs-built_in">string</span>: <span class="hljs-string">"http://localhost:5984/rooms/_all_docs?include_docs=true"</span>)!

  let urlRequest = NSMutableURLRequest(
    URL: url,
    cachePolicy: .ReloadIgnoringLocalAndRemoteCacheData,
    timeoutInterval: <span class="hljs-number">10.0</span> * <span class="hljs-number">1000</span>)
  urlRequest.HTTPMethod = <span class="hljs-string">"GET"</span>
  urlRequest.addValue(<span class="hljs-string">"application/json"</span>, forHTTPHeaderField: <span class="hljs-string">"Accept"</span>)

  let task = urlSession.dataTaskWithRequest(urlRequest)
    { (data, response, <span class="hljs-built_in">error</span>) -&gt; Void <span class="hljs-keyword">in</span>
    guard <span class="hljs-built_in">error</span> == <span class="hljs-keyword">nil</span> <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">"Error while fetching remote rooms: /(error)"</span>)
      completion(<span class="hljs-keyword">nil</span>)
      <span class="hljs-keyword">return</span>
    }

    guard let json = try? NSJSONSerialization.JSONObjectWithData(data!,
      options: []) as? [String: AnyObject] <span class="hljs-keyword">else</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Nil data received from fetchAllRooms service"</span>)
        completion(<span class="hljs-keyword">nil</span>)
        <span class="hljs-keyword">return</span>
    }

    guard let rows = json[<span class="hljs-string">"rows"</span>] as? <span class="hljs-string">[[String: AnyObject]]</span> {
      <span class="hljs-built_in">print</span>(<span class="hljs-string">"Malformed data received from fetchAllRooms service"</span>)
      completion(<span class="hljs-keyword">nil</span>)
      <span class="hljs-keyword">return</span>
    }

    var rooms = [RemoteRoom]()
    <span class="hljs-keyword">for</span> roomDict <span class="hljs-keyword">in</span> rows {
      rooms.append(RemoteRoom(jsonData: roomDict))
    }

    completion(rooms)
  }

  task.resume()
}</code>

下面是使用 Alamofire 的方式:

<code>// With Alamofire
func fetchAllRooms(completion: ([RemoteRoom]?) -&gt; Void) {
  Alamofire.request(
    .GET,
    <span class="hljs-string">"http://localhost:5984/rooms/_all_docs"</span>,
    parameters: [<span class="hljs-string">"include_docs"</span>: <span class="hljs-string">"true"</span>],
    encoding: .URL)
    .validate()
    .responseJSON { (response) -&gt; Void <span class="hljs-keyword">in</span>
      guard response.result.isSuccess <span class="hljs-keyword">else</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Error while fetching remote rooms: /(response.result.error)"</span>)
        completion(<span class="hljs-keyword">nil</span>)
        <span class="hljs-keyword">return</span>
      }

      guard let value = response.result.value as? [String: AnyObject],
        rows = value[<span class="hljs-string">"rows"</span>] as? <span class="hljs-string">[[String: AnyObject]]</span> <span class="hljs-keyword">else</span> {
          <span class="hljs-built_in">print</span>(<span class="hljs-string">"Malformed data received from fetchAllRooms service"</span>)
           completion(<span class="hljs-keyword">nil</span>)
           <span class="hljs-keyword">return</span>
      }

      var rooms = [RemoteRoom]()
      <span class="hljs-keyword">for</span> roomDict <span class="hljs-keyword">in</span> rows {
        rooms.append(RemoteRoom(jsonData: roomDict))
      }

      completion(rooms)
  }
}</code>

上传文件

打开 ViewController.swift,把下面的代码加到类的底部:

<code><span class="hljs-regexp">//</span> Networking calls
extension ViewController {
  func uploadImage<span class="hljs-function"><span class="hljs-params">(image: UIImage, progress: (percent: Float) -&gt; Void,
    completion: (tags: [String], colors: [PhotoColor]) -&gt; Void)</span> {
    <span class="hljs-title">guard</span> <span class="hljs-title">let</span> <span class="hljs-title">imageData</span> = <span class="hljs-title">UIImageJPEGRepresentation</span><span class="hljs-params">(image, <span class="hljs-number">0.5</span>)</span> <span class="hljs-title">else</span> {
      <span class="hljs-title">print</span><span class="hljs-params">(<span class="hljs-string">"Could not get JPEG representation of UIImage"</span>)</span>
      <span class="hljs-title">return</span>
    }
  }
}</span></code>

上面的代码的作用是上传图片到 Imagga。

接下来,在 imagePickerController(_:didFinishPickingMediaWithInfo:) 方法里,在 imageView.image = image 后面加入下面的代码:

<code><span class="hljs-comment">// 1</span>
takePictureButton.hidden = <span class="hljs-keyword">true</span>
progressView.progress = <span class="hljs-number">0.0</span>
progressView.hidden = <span class="hljs-keyword">false</span>
activityIndicatorView.startAnimating()

uploadImage(
  image,
  progress: { [unowned <span class="hljs-keyword">self</span>] percent in
    <span class="hljs-comment">// 2</span>
    <span class="hljs-keyword">self</span>.progressView.setProgress(percent, animated: <span class="hljs-keyword">true</span>)
  },
  completion: { [unowned <span class="hljs-keyword">self</span>] tags, colors in
    <span class="hljs-comment">// 3</span>
    <span class="hljs-keyword">self</span>.takePictureButton.hidden = <span class="hljs-keyword">false</span>
    <span class="hljs-keyword">self</span>.progressView.hidden = <span class="hljs-keyword">true</span>
    <span class="hljs-keyword">self</span>.activityIndicatorView.stopAnimating()

    <span class="hljs-keyword">self</span>.tags = tags
    <span class="hljs-keyword">self</span>.colors = colors

    <span class="hljs-comment">// 4</span>
    <span class="hljs-keyword">self</span>.performSegueWithIdentifier(<span class="hljs-string">"ShowResults"</span>, sender: <span class="hljs-keyword">self</span>)
})</code>

在 Alamofire 中所有对象都是异步的,也就意味着你会以异步的方式更新 UI:

  1. 隐藏上传按钮,同时显示显示进度条。
  2. 当文件上传的时候,progress 闭包会以百分比的形式进行更新,改变的多少同步显示在进度条上。
  3. 当上传文件完成时,completion 闭包才会执行,设置控件的状态变为原始状态。
  4. 最后,Storyboard 会将成功的(或者不成功)上茶传结果显示到屏幕上,接口并不会基于错误发生改变。

接下来,在 ViewController.swift 中添加下面的代码:

<code><span class="hljs-keyword">import</span> Alamofire</code>

上面的代可以会让你使用 Alamofire 模块提供的代码。

下面,返回到 uploadImage(_:progress:completion:) 并且在guard 判断后面添加下面的代码:

<code>Alamofire<span class="hljs-preprocessor">.upload</span>(
  <span class="hljs-preprocessor">.POST</span>,
  <span class="hljs-string">"http://api.imagga.com/v1/content"</span>,
  headers: [<span class="hljs-string">"Authorization"</span> : <span class="hljs-string">"Basic xxx"</span>],
  multipartFormData: { multipartFormData <span class="hljs-keyword">in</span>
    multipartFormData<span class="hljs-preprocessor">.appendBodyPart</span>(data: imageData, name: <span class="hljs-string">"imagefile"</span>,
      fileName: <span class="hljs-string">"image.jpg"</span>, mimeType: <span class="hljs-string">"image/jpeg"</span>)
  },
  encodingCompletion: { encodingResult <span class="hljs-keyword">in</span>
  }
)</code>

确保替换『Basic xxx』用你自己在 Imagga 网站生成的 token。

接下来,在 encodingCompletion闭包里添加下面的代码:

<code><span class="hljs-keyword">switch</span> encodingResult {
<span class="hljs-reserved">case</span> .Success(<span class="hljs-reserved">let</span> upload, _, _):
  upload.progress { bytesWritten, totalBytesWritten, totalBytesExpectedToWrite <span class="hljs-keyword">in</span>
    dispatch_async(dispatch_get_main_queue()) {
      <span class="hljs-reserved">let</span> percent = (Float(totalBytesWritten) / Float(totalBytesExpectedToWrite))
      progress(<span class="hljs-attribute">percent</span>: percent)
    }
  }
  upload.validate()
  upload.responseJSON { response <span class="hljs-keyword">in</span>
  }
<span class="hljs-reserved">case</span> .Failure(<span class="hljs-reserved">let</span> encodingError):
  <span class="hljs-built_in">print</span>(encodingError)
}</code>

这段代码调用 Alamofire 上传功能,通过在计算来更新进度条。

注意:
Alamofire 并不会保证调用过程会在主队列上回调;因此你必须通过向主队列调度来更新用户界面。有一些 Alamofire 的回调,比如 responseJSON,都会默认在主队列调用。例如:

<code>dispatch_async(queue ?? dispatch_get_main_queue()) {
  let response = <span class="hljs-keyword">...</span>
  completionHandler(response)
}</code>

为了改变默认的做法,你需要给 Alamofire 提供一个 dispatch_queue_t

添加下面的代码到 upload.responseJSON

<code>// 1.
guard response.result.isSuccess else {
  print("Error while uploading file: /(response.result.error)")
  completion(tags: [<span class="hljs-link_label">String</span>](<span class="hljs-link_url"></span>), colors: [<span class="hljs-link_label">PhotoColor</span>](<span class="hljs-link_url"></span>))
  return
}
// 2.
guard let responseJSON = response.result.value as? [String: AnyObject],
  uploadedFiles = responseJSON["uploaded"] as? [AnyObject],
  firstFile = uploadedFiles.first as? [String: AnyObject],
  firstFileID = firstFile["id"] as? String else {
<span class="hljs-code">    print("Invalid information received from service")</span>
<span class="hljs-code">    completion(tags: [String](), colors: [PhotoColor]())</span>
<span class="hljs-code">    return</span>
}
print("Content uploaded with ID: /(firstFileID)")
// 3.
completion(tags: [<span class="hljs-link_label">String</span>](<span class="hljs-link_url"></span>), colors: [<span class="hljs-link_label">PhotoColor</span>](<span class="hljs-link_url"></span>))</code>

下面是每步的解释:
1. 检查响应是否成功;如果不成功,输出错误信息并调用 completion
2. 检查响应的每个部分,验证所期望的类型是收到的实际类型,如果 firstFileID 没有被解析,那么输出错误信息,并调用completion
3. 调用 completion 来更新 UI。你没有任何下载的标志或颜色,所以简化调用数据为空的情况。

注意:
每一个响应都有一个结果,包括枚举值和类型。使用自动验证,当它返回一个合法的 HTTP 码(在200-299之间的内容类型都被认为是可以接受的)。
你可以通过添加 .validate 手动执行验证,代码如下:

<code>Alamofire<span class="hljs-preprocessor">.request</span>(<span class="hljs-preprocessor">.GET</span>, <span class="hljs-string">"https://httpbin.org/get"</span>, parameters: [<span class="hljs-string">"foo"</span>: <span class="hljs-string">"bar"</span>])
  <span class="hljs-preprocessor">.validate</span>(statusCode: <span class="hljs-number">200.</span>.&lt;<span class="hljs-number">300</span>)
  <span class="hljs-preprocessor">.validate</span>(contentType: [<span class="hljs-string">"application/json"</span>])
  <span class="hljs-preprocessor">.response</span> { response <span class="hljs-keyword">in</span>
    // response handling code
}</code>

如果你在上传期间发生错误,UI 并不会显示错误;它仅仅会返回没有标志或颜色给用户。这不是良好的用户体验,但是在本次教程中确实有上面所说的。

编译运行工程代码,选择一个 image 然后会看到进度条发生了变化,上传完成后,你会看到下面的信息:

ImaggaUploadConsole.png

你已经成功的上传图片!

sad-forever-alone-happy.jpg

下篇地址

原帖地址

未经允许不得转载:冰点网络 » Alamofire 上传图片入门教程(上)

分享到:更多 ()

评论 抢沙发

评论前必须登录!