ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Raywenderlich - iOS/Swift] Alamofire 사용하기
    앱등이에게 살충제를 뿌린다./iOS 2016. 4. 15. 00:34




    Alamofire는 iOS와 OS X에서 사용하기 위한 Swift기반의 HTTP 네트워킹 라이브러리입니다. 애플의 네트워킹 파운데이션을 기반으로 많은 기능을 제공하고 있습니다.


    Alamofire는 reponse/request 메소드, JSON parameter와 response serialization, authentication 등의 기능을 제공합니다. 이번 튜토리얼에서는 파일 업로드나 RESTful API에서 데이터를 요청하는 작업과 같은 기본적인 네트워킹 작업에 대해서 소개합니다.


    Alamofire는 AFNetworking의 Objective-C코드를 전혀 상속하지 않고 모두 Swift로 새롭게 만들었다는 점에서 참 대단한 라이브러리입니다.


    시작하기 전에, HTTP네트워킹과 NSURLSession이나 NSURLConnection과 같은 애플의 기본적인 네트워킹 클래스에 대해서는 어느정도 알고 계시는게 좋습니다.

    가끔은 Alamofire가 원인이 되는 이슈가 있을 때 이런 배경지식이 있으면 도움이 되겠죠. 

    또한 CocoaPods은 필수적으로 설치되어 있어야 합니다.





    Getting Started

    starter project(클릭)를 다운로드 받으세요. 최종 완성본 소스파일은 completed version of this Alamofire tutorial’s project here.

    이 앱은 Alamofire를 연습하기 위한 프로젝트이고 이름은 PhotoTagger입니다. 촬영을 하거나 앨범에서 이미지를 Third-party 서비스에 업로드를 하면 이미지를 인식하여 이미지에 대한 태그와 이미지에 대한 색상을 알려주는 앱입니다. 아래는 완성된 앱의 작동 스크린샷입니다.

    다운로드 받은 프로젝트를 빌드하여 앱을 실행해보세요.


    Select Photo을 클릭하여 사진을 선택하시면 배경화면이 선택한 사진으로 대체될 것입니다. 

    Main.storyboard에는 이미 다른 화면에 대한 셋팅도 어느정도 되어있습니다. (태그나 컬러를 보여주는 화면)




    The Imagga API

    Imagga는 이미지 인식 Platform-as-a-Service입니다. Imagga는 이미지 태그 API를 제공하고 businesses to build scalable, image-intensive cloud apps도 제공합니다.  their auto-tagging service 에서는 데모 애플리케이션을 제공해주고 있습니다.


    먼저 Imagga에 개발자 등록을 해주셔야 합니다(무료). 등록을 해야 Imagga에 API요청에 필요한 키 값(autorization header in HTTP request)을 받을 수 있습니다. 

    https://imagga.com/auth/signup/hacker로 가셔서 가입을 진행해주세요. 가입하면 아래처럼 대시보드가 보이실겁니다.

    au섹션에 있는 token값을 나중에 사용하게 될겁니다. 이 값이 바로 위에서 말한 HTTP request에 포함시켜야할 키 값입니다. 

    Note: Make sure you copy the whole secret token, be sure to scroll over to the right and verify you copied everything. 

    더블클릭으로 복사하지 마시고 꼭 드래그해서 복사해주세요. 다 복사가 안되더라구요 ㅎㅎ


    사진을 업로드할 url은 content endpoint를, 이미지 인식에는 tagging endpoint를 색상 식별에는 colors endpoint를 사용하게 될 겁니다. Imagga API는 http://docs.imagga.com에서 자세히 살펴보실 수 있습니다.







    Installing Dependencies

    메인 디렉터리 레벨에 Podfile을 만들어서 아래와 같이 적어주세요.

    platform :ios, '9.0'   inhibit_all_warnings! use_frameworks!   target 'PhotoTagger' do pod 'Alamofire', '~> 3.1.2' end

    pod install을 통해 CocoaPods을 설치해줍시다. CocoaPods을 설치하지 않으셨다면(혹은 잘 모르신다면) How to Use CocoaPods with Swift을 참조해주세요.


    설치하셨다면 이제 프로젝트를 닫고 새로 생긴 PhotoTagger.xcworkspace을 열어줍시다. 이 workspace를 빌드한다고 해도 특별히 달라진건 없을겁니다. 

    자 이제 RESTful 서비스에 HTTP요청을 하여 JSON 몇개를 받아보는 작업을 추가해봅시다.



    REST, HTTP, JSON — What’s that?

    잠깐만~~~~~~~~~~~(잠깐만 오디오)

    인터넷 웹 서비스에 대해서 아주 간단한 설명을 하고가도록 할게요. 이 섹션의 타이틀이 굉장히 익숙하신 분이라면 스킵하셔도 좋습니다.


    인터넷 Third-party 서비스에 익숙치 않은 분들이라면 몇 가지 용어를 익히고 가는게 좋습니다.
    HTTP는 애플리케이션층 프로토콜입니다. 여러 통신 규칙을 정의하고 있는 통신 규칙의 집합이라고 볼 수 있죠. 웹사이트는 이 HTTP를 이용하여 서버의 데이터를 여러분의 화면에 보여줍니다. 웹브라우저의 주소창에 HTTP(or HTTPS) 로 시작하는 주소들을 많이 보셨을겁니다. 애플리케이션층의 다른 프로토콜들 FTP, Telnet, SSH와 같은 프로토콜도 들어보셨을텐데요. HTTP는 클라이언트(웹브라우저나 앱)에서 데이터를 요청하는 방식을 method로 아래와 같이 정의하고 있습니다.
    • GET : 웹페이지와 같은 데이터를 서버로부터 받아옵니다. 서버의 데이터를 변경하지는 않습니다.
    • HEAD : GET과 똑같은 작동을 하지만 데이터는 보내주지 않고 헤더만 보내줍니다.
    • POST : 데이터를 서버로 보낼 때 사용합니다. form양식을 채워서 submit을 클릭할 때 주로 사용하는 방식입니다.
    • PUT : 특정 location으로 데이터를 보낼 때 사용합니다.
    • DELETE : 특정 location의 데이터를 삭제할 때 사용합니다.
    REST(or REpresentational State Transfer)는 사용가능한 웹 API를  쓰기 쉽게 디자인 해놓은 규칙의 집합(a set of rules for designing consistent, easy-to-use and maintainable web APIs.)이라고 볼 수 있습니다. REST has several architecture rules that enforce things such as not persisting states across requests, making requests cacheable, and providing uniform interfaces. This makes it easy for app developers like you to integrate the API into your app — without needing to track the state of data across requests.


    JSON은 JavaScript Object Notation의 약어입니다. JSON은 사람이 알아보기 쉽고 서로 다른 두 시스템이 데이터를 교환하기에 좋은 구조를 갖고 있습니다. JSON은 한정적인 데이터 타입을 갖는데요, string, boolean, array, object/dictioary, null, number를 갖습니다. (정수와 실수구분은 없습니다.) 애플에서도 NSJSONSerialization을 통해 객체와 JSON의 상호 전환하는 메소드들을 제공하고 있습니다.


    HTTP, REST, JSON의 조합은 웹 서비스 개발에 많은 부분을 차지하고 있습니다. 세세한 부분까지 들어가면 너무 방대하구요. Alamofire같은 라이브러리가 이 서비스들에 대한 복잡도를 감소시키고 앱을 더 빠르게 만들 수 있는 환경을 제공하고 있다는 것 정도만 알고 넘어가도록 하겠습니다.




    What is Alamofire Good For?

    애플은 이미 NSURLSession과 같이 HTTP를 통해 데이터를 다운로드하는 많은 클래스들을 제공하고 있습니다. 왜 굳이 복잡하게 Third-party 라이브러리를 사용하는 걸까요?
    간단히 답변을 드리자면, Alamofire는 NSURLSession에 기반을 두고 있습니다. 하지만  하지만 NSURLSession으로 작성한 코드는 복잡한데 반해 Alamofire를 사용하면 복잡한 코드를 작성할 필요가 없어집니다. 더 쉽게 인터넷의 데이터에 접근할 수도 있고, 코드 또한 훨씬 깔끔해지고 간결해집니다.

    Alamofire에는 아래와 같은 Major Functions이 존재합니다.
    • .upload: 스트림, 파일, 데이터 형태 등으로 파일을 업로드합니다.
    • .download: 파일을 다운로드하거나 이전에 진행되고 있었던 다운로드를 재개합니다.
    • .request: 파일 전송과 관련 없는 HTTP요청을 합니다.
    이 함수들은 클래스나 구조체에 있는 것이 아니라 Alamofire 모듈에 있습니다. 그리고 Alamofire아래에 ManagerRequest, and Response와 같은 여러 클래스, 구조체들이 있습니다. Alamofire 사용법 튜토리얼에서는 이렇게 구조적인 측면까지는 모르셔도 무방합니다.

    아래에 똑같은 네트워킹 작업을 하는 두 코드를 첨부했습니다. 하나는 NSURLSession을 사용한 코드이고 하나는 Alamofire의 request를 사용한 코드입니다.

    // With NSURLSession
    public func fetchAllRooms(completion: ([RemoteRoom]?) -> Void) {
      let url = NSURL(string: "http://localhost:5984/rooms/_all_docs?include_docs=true")!
     
      let urlRequest = NSMutableURLRequest(
        URL: url,
        cachePolicy: .ReloadIgnoringLocalAndRemoteCacheData,
        timeoutInterval: 10.0 * 1000)
      urlRequest.HTTPMethod = "GET"
      urlRequest.addValue("application/json", forHTTPHeaderField: "Accept")
     
      let task = urlSession.dataTaskWithRequest(urlRequest)
        { (data, response, error) -> Void in
        guard error == nil else {
          print("Error while fetching remote rooms: \(error)")
          completion(nil)
          return
        }
     
        guard let json = try? NSJSONSerialization.JSONObjectWithData(data!,
          options: []) as? [String: AnyObject] else {
            print("Nil data received from fetchAllRooms service")
            completion(nil)
            return
        }
     
        guard let rows = json["rows"] as? [[String: AnyObject]] {
          print("Malformed data received from fetchAllRooms service")
          completion(nil)
          return
        }
     
        var rooms = [RemoteRoom]()
        for roomDict in rows {
          rooms.append(RemoteRoom(jsonData: roomDict))
        }
     
        completion(rooms)
      }
     
      task.resume()
    }

    Versus:

    // With Alamofire
    func fetchAllRooms(completion: ([RemoteRoom]?) -> Void) {
      Alamofire.request(
        .GET,
        "http://localhost:5984/rooms/_all_docs",
        parameters: ["include_docs": "true"],
        encoding: .URL)
        .validate()
        .responseJSON { (response) -> Void in
          guard response.result.isSuccess else {
            print("Error while fetching remote rooms: \(response.result.error)")
            completion(nil)
            return
          }
     
          guard let value = response.result.value as? [String: AnyObject],
            rows = value["rows"] as? [[String: AnyObject]] else {
              print("Malformed data received from fetchAllRooms service")
               completion(nil)
               return
          }
     
          var rooms = [RemoteRoom]()
          for roomDict in rows {
            rooms.append(RemoteRoom(jsonData: roomDict))
          }
     
          completion(rooms)
      }
    }

    어떤가요? Alamofire를 통해 네트워킹 작업을 요청하는 과정이 더 간단하고 쉬워보이지 않나요? responseJSON(options:completionHandler:) 로 response에 따라 원하는 분기처리를 해주시면 됩니다. Response의 validate()을 호출하면 에러 핸들링도 간단하게 할 수 있습니다.




    Uploading Files

    ViewController.swift을 열어 소스 제일 하단에 아래의 확장구문 코드를 추가해주세요.

    // Networking calls
    extension ViewController {
      func uploadImage(image: UIImage, progress: (percent: Float) -> Void,
        completion: (tags: [String], colors: [PhotoColor]) -> Void) {
        guard let imageData = UIImageJPEGRepresentation(image, 0.5) else {
          print("Could not get JPEG representation of UIImage")
          return
        }
      }
    }

    Imagga에 이미지를 업로드 하기위해 가장 먼저 해야할 작업은 바로 API에 올바른 포맷을 전달할 수 있도록 파일의 format을 검사하는 것입니다. Image Picker API에서 UIImage을 리턴해주면 이를 NSData객체로 바꾸어주고 있습니다.


    다음으로, imagePickerController(_:didFinishPickingMediaWithInfo:) 로 가서 아래의 코드를 imageView.image = image 바로 다음에 삽입해주세요.

    // 1
    takePictureButton.hidden = true
    progressView.progress = 0.0
    progressView.hidden = false
    activityIndicatorView.startAnimating()
     
    uploadImage(
      image,
      progress: { [unowned self] percent in
        // 2
        self.progressView.setProgress(percent, animated: true)
      },
      completion: { [unowned self] tags, colors in
        // 3
        self.takePictureButton.hidden = false
        self.progressView.hidden = true
        self.activityIndicatorView.stopAnimating()
     
        self.tags = tags
        self.colors = colors
     
        // 4
        self.performSegueWithIdentifier("ShowResults", sender: self)
    })

    Alamofire의 모든 작업은 비동기로 이루어집니다. 따라서 UI를 업데이트하는 작업 또한 비동기로 해주어야 합니다.

    코드설명

    1. 사진 업로드 버튼을 숨기고 Progress 뷰와 activityIndicatorView(뱅글뱅글 돌아가는것)를 띄웁니다.
    2. 사진을 업로드하는 동안, progress handler메소드를 통해 파일이 업로드된 퍼센트를 프로그레스바에 업데이트 해줍니다. 
    3. 사진 업로드가 완료되면 completion handler가 실행됩니다. 사진을 업로드하기 전의 원래상태로 되돌아가네요. 
    4. 업로드에 성공했던 실패했던 업로드 완료화면으로 이동하게 됩니다. 
    다음으로 ViewController.swift의 제일 윗쪽에 아래의 코드를 삽입해 주세요.

    import Alamofire

    이 코드를 넣어주어야 Alamofire 모듈의 기능들을 사용할 수 있습니다.


    다시 uploadImage(_:progress:completion:)로 돌아가서 아래의 코드를 UIImage를 if let으로 바인딩하는 부분 아래에 넣어주세요.

    Alamofire.upload(
      .POST,
      "http://api.imagga.com/v1/content",
      headers: ["Authorization" : "Basic xxx"],
      multipartFormData: { multipartFormData in
        multipartFormData.appendBodyPart(data: imageData, name: "imagefile",
          fileName: "image.jpg", mimeType: "image/jpeg")
      },
      encodingCompletion: { encodingResult in

    // encodingCompletion클로져 부분

    } )

    위 코드에 적힌 Basic xxx부분은 Imagga의 대시보드에 있는 Authorization 값입니다. 반드시 XXX를 교체해주세요!!

    이 코드를 통해 JPEG데이터를 MIME방식의 리퀘스트로 Imagga의 content endpoint에 전송하게 됩니다.


    다음으로 encodingCompletion클로져안에 아래의 코드를 삽입해주세요.

    switch encodingResult { case .Success(let upload, _, _): upload.progress { bytesWritten, totalBytesWritten, totalBytesExpectedToWrite in dispatch_async(dispatch_get_main_queue()) { let percent = (Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)) progress(percent: percent) } } upload.validate() upload.responseJSON { response in } case .Failure(let encodingError): print(encodingError) }

    이 코드에서는 Alamofire의 upload함수를 호출하여 progress bar를 업데이트하고 파일을 업로드하고 있습니다. progress bar는 UI에 해당하므로 dispatch_queue를 통해 메인 스레드에서 진행되고 있습니다.

    Note: Alamofire is not guaranteed to call the progress callback on the main queue; therefore you must guard against updating the UI by dispatching to the main queue. Some of Alamofire’s callbacks, such asresponseJSON, are called on the main queue by default. For example:

    dispatch_async(queue ?? dispatch_get_main_queue()) {
      let response = ...
      completionHandler(response)
    }

    To change this default behavior you would provide a dispatch_queue_t to Alamofire.



    다음으로, 아래의 코드를 upload.responseJSON:안에 넣어주세요.

    // 1.
    guard response.result.isSuccess else {
      print("Error while uploading file: \(response.result.error)")
      completion(tags: [String](), colors: [PhotoColor]())
      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 {
        print("Invalid information received from service")
        completion(tags: [String](), colors: [PhotoColor]())
        return
    }
     
    print("Content uploaded with ID: \(firstFileID)")
    // 3.
    completion(tags: [String](), colors: [PhotoColor]())

    코드설명

    1. response에서 인코딩의 결과가 성공적인지 아닌지를 확인합니다. 만약 실패했을 경우는 로그를 남기고 completion handler를 실행한 뒤 함수가 종료됩니다.
    2. 성공한 경우에는 response의 각 필드들을 확인합니다. response에서 firstFileID을 읽어옵니다. 읽어 오는데 실패한다면 에러 로그를 남기고 completion handler를 실행한 뒤 함수가 종료됩니다.
    3. UI 업데이트를 위해 completion handler를 실행합니다. 아직 사진의 태그나 색상을 다운로드 받은건 아닙니다. 그냥 빈 값으로 호출만 일단 해봅시다.

    Note: Every response has a Result enum with a value and type. Using automatic validation, the result is considered a success when it returns a valid HTTP Code between 200 and 299 and the Content Type is of a valid type specified in the Accept HTTP header field.

    You can perform manual validation by adding .validate options as shown below:

    Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"]) .validate(statusCode: 200..<300) .validate(contentType: ["application/json"]) .response { response in // response handling code }

    사진을 업로드하는 과정에서 에러가 발생하지 않았다면 에러로그없이 잘 동작될겁니다. 앱을 빌드하여 실행해보세요. 이미지를 선택한 뒤 업로드하여 progress bar가 잘 동작하는지도 확인해보세요.

    업로드가 성공적으로 되었다면 아래처럼 콘솔에 로그가 찍혀야 합니다.


    웹서버에 파일업로드하기를 성공적으로 마쳤습니다~!





    Retrieving Data

    이미지를 Imagga에 업로드했으니 Imagga가 분석한 이미지의 태그와 컬러들을 가져올 차례입니다.
    ViewController확장 구문의 uploadImage(_:progress:completion:):아래에 이 메소드를 삽입해주세요.

    func downloadTags(contentID: String, completion: ([String]) -> Void) {
      Alamofire.request(
        .GET,
        "http://api.imagga.com/v1/tagging",
        parameters: ["content": contentID],
        headers: ["Authorization" : "Basic xxx"]
        )
        .responseJSON { response in
          guard response.result.isSuccess else {
            print("Error while fetching tags: \(response.result.error)")
            completion([String]())
            return
          }
     
          guard let responseJSON = response.result.value as? [String: AnyObject] else {
             print("Invalid tag information received from service")
             completion([String]())
             return
          }
          print(responseJSON)
          completion([String]())
      }
    }

    위 코드에 적힌 Basic xxx부분을 교체해주는것!! 기억해주세요.

    이 코드에서는 tagging  endpoint에 HTTP GET 요청을 하고있네요. 사진을 업로드한 뒤에 얻은 ID값을 파라미터의 content에 담아서 요청을 보내고 있습니다.


    다음으로 uploadImage(_:progress:completion:) 로 돌아가서 success시 completion handler를 요청하는 부분을 아래의 코드로 바꿔주세요.

    self.downloadTags(firstFileID) { tags in
      completion(tags: tags, colors: [PhotoColor]())
    }

    앱을 다시 실행하셔서 이미지를 업로드해보세요. 아래와 같은 콘솔을 보게 될겁니다.

    이번 튜토리얼에서 confidence 점수는 신경쓰지 않기로하고 tag값만 갖고 작업을 하도록 합시다.


    다음으로, downloadTags(_:completion:) 로 돌아가서 .responseJSON내부의 코드를 아래코드로 변경해주세요.

    // 1.
    guard response.result.isSuccess else {
      print("Error while fetching tags: \(response.result.error)")
      completion([String]())
      return
    }
     
    // 2.
    guard let responseJSON = response.result.value as? [String: AnyObject],
      results = responseJSON["results"] as? [AnyObject],
      firstResult = results.first,
      tagsAndConfidences = firstResult["tags"] as? [[String: AnyObject]] else {
        print("Invalid tag information received from the service")
        completion([String]())
        return
    }
     
    // 3.
    let tags = tagsAndConfidences.flatMap({ dict in
      return dict["tag"] as? String
    })
     
    // 4.
    completion(tags)

    코드 설명

    1.  response에서 리퀘스트가 성공적인지를 판단합니다. 실패했다면 에러로그를 남기고 completion handler를 호출합니다.
    2. response의 각 값들을 올바른 타입으로 받아왔는지 확인합니다. response에서 tagsAndConfidences을 확인해서 올바른 타입으로 받아오지 못했다면 에러로그를 남기고 completion handler를 호출합니다.
    3. tagsAndConfidences배열 안에있는 딕셔너리를 이터레이션하여 tags와 관련된 값들을 얻습니다.
    4. tags를 completion handler에 파라미터로 넘겨 호출합니다.

    Note: You’re using the Swift flatMap method to iterate over each dictionary in the tagsAndConfidencesarray; this method handles nil values without crashing and will remove those values from the returned result. This lets you use conditional unwrapping (as?) to verify whether the dictionary value can be converted to a String.

    앱을 실행하여 사진을 업로드해보세요. 아래와 같은 결과가 나올겁니다.


    꽤나 모양새가 나옵니다. 이제 이미지의 색상을 가져와 봅시다.

    ViewController확장구문의 downloadTags(_:completion:):아래부분에 이 메소드를 넣어주세요.

    func downloadColors(contentID: String, completion: ([PhotoColor]) -> Void) {
      Alamofire.request(
        .GET,
        "http://api.imagga.com/v1/colors",
        parameters: ["content": contentID, "extract_object_colors": NSNumber(int: 0)],
        // 1.
        headers: ["Authorization" : "Basic xxx"]
        )
        .responseJSON { response in
          // 2.
          guard response.result.isSuccess else {
            print("Error while fetching colors: \(response.result.error)")
            completion([PhotoColor]())
            return
          }
     
          // 3.
          guard let responseJSON = response.result.value as? [String: AnyObject],
            results = responseJSON["results"] as? [AnyObject],
            firstResult = results.first as? [String: AnyObject],
            info = firstResult["info"] as? [String: AnyObject],
            imageColors = info["image_colors"] as? [[String: AnyObject]] else {
              print("Invalid color information received from service")
              completion([PhotoColor]())
              return
          }
     
          // 4.
          let photoColors = imageColors.flatMap({ (dict) -> PhotoColor? in
            guard let r = dict["r"] as? String,
              g = dict["g"] as? String,
              b = dict["b"] as? String,
              closestPaletteColor = dict["closest_palette_color"] as? String else {
                return nil
            }
            return PhotoColor(red: Int(r),
              green: Int(g),
              blue: Int(b),
              colorName: closestPaletteColor)
          })
     
          // 5.
          completion(photoColors)
      }
    }

    코드설명

    1. 다시한번,, Basic XXX를 수정해주세요~!
    2. response가 성공적인지를 확인합니다. 아닐 경우 로그를 남기고 completion handler를 호출합니다.
    3. response의 각 필드 값들이 올바른 타입으로 넘어왔는지를 확인합니다. 그 중 imageColors를 가져옵니다. imageColors에 대한 데이터가 잘못되어있다면 로그를 남기고 completion handler를 호출합니다.
    4. 다시 flatMap을 이용하여 imageColors을 이터레이션하여 색상관련 데이터의 R, G, B 값을 확인하여 PhotoColor객체로 변환시켜줍니다. 
    5. imageColors를 completion handler에 파라미터로 넘겨 호출합니다.

    마지막으로 uploadImage(_:progress:completion:)로 돌아가 response가 success일 때 completion handler를 호출하는 부분의 코드를 아래 코드로 바꿔주세요.

    self.downloadTags(firstFileID) { tags in self.downloadColors(firstFileID) { colors in completion(tags: tags, colors: colors) } }

    이 코드를 통해 업로드한 이미지에서 태그와 컬러를 모두 가져올 수 있게 됩니다.

    앱을 실행하면 아래의 화면과 같은 결과가 나타날겁니다.

    다운로드받은 RGB컬러로 만든 PhotoColor객체로 배경화면을 바꾸어 주었습니다. Imagga에 이미지를 업로드하여 태그와 컬러를 받아오는 작업을 모두 성공적으로 마쳤습니다. 먼 길 오셨습니다.

    하지만 PhotoTagger를 개선시킬 몇 가지 여지가 남아있습니다만.. 뒷심 부족...




    Improving PhotoTagger

    You probably noticed some repeated code in PhotoTagger. If Imagga released v2 of their API and deprecated v1, PhotoTagger would no longer function and you’d have to update the URL in each of the three methods. Similarly, if your authorization token changed you’d be updating it all over the place.

    Alamofire provides a simple method to eliminate this code duplication and provide centralized configuration. The technique involves creating a struct conforming to the URLRequestConvertible protocol and updating your upload and request calls.

    Create a new Swift file by clicking File\New\File… and selecting Swift file under iOS. Click Next, name the file ImaggaRouter.swift, select the Group PhotoTagger with the yellow folder icon and click Create.

    Replace the contents of your new file with the following:

    import Foundation
    import Alamofire
     
    public enum ImaggaRouter: URLRequestConvertible {
      static let baseURLPath = "http://api.imagga.com/v1"
      static let authenticationToken = "Basic xxx"
     
      case Content
      case Tags(String)
      case Colors(String)
     
      public var URLRequest: NSMutableURLRequest {
        let result: (path: String, method: Alamofire.Method, parameters: [String: AnyObject]) = {
          switch self {
          case .Content:
            return ("/content", .POST, [String: AnyObject]())
          case .Tags(let contentID):
            let params = [ "content" : contentID ]
            return ("/tagging", .GET, params)
          case .Colors(let contentID):
            let params = [ "content" : contentID, "extract_object_colors" : NSNumber(int: 0) ]
            return ("/colors", .GET, params)
          }
        }()
     
        let URL = NSURL(string: ImaggaRouter.baseURLPath)!
        let URLRequest = NSMutableURLRequest(URL: URL.URLByAppendingPathComponent(result.path))
        URLRequest.HTTPMethod = result.method.rawValue
        URLRequest.setValue(ImaggaRouter.authenticationToken, forHTTPHeaderField: "Authorization")
        URLRequest.timeoutInterval = NSTimeInterval(10 * 1000)
     
        let encoding = Alamofire.ParameterEncoding.URL
     
        return encoding.encode(URLRequest, parameters: result.parameters).0
      }
    }

    Replace Basic xxx with your actual authorization header. This router helps create instances ofNSMutableURLRequest by providing it one of the three cases: .Content.Tags(String), or .Colors(String). Now all of your boilerplate code is in single place, should you ever need to update it.

    Go back to uploadImage(_:progress:completion:) and replace the beginning of the call toAlamofire.upload with the following:

    Alamofire.upload(
      ImaggaRouter.Content,
      multipartFormData: { multipartFormData in
        multipartFormData.appendBodyPart(data: imageData, name: "imagefile",
          fileName: "image.jpg", mimeType: "image/jpeg")
    },
    /// original code continues...

    Next replace the call for Alamofire.request in downloadTags(_:completion:) with:

    Alamofire.request(ImaggaRouter.Tags(contentID))

    Finally, update the call to Alamofire.request in downloadColors(_:completion:) with:

    Alamofire.request(ImaggaRouter.Colors(contentID))

    Build and run for the final time; everything should function just as before, which means you’ve refactored everything without breaking your app. Awesome job!

    Where To Go From Here?

    You can download the completed version of this Alamofire tutorial’s project here. Don’t forget to replace your authorization token as appropriate!

    This Alamofire tutorial covered the very basics. You can take a deeper dive by looking at the documentation on the Alamofire site at https://github.com/Alamofire/Alamofire.

    Also, you can take some time to learn more about Apple’s NSURLSession which Alamofire uses under the hood:

    Please share any comments or questions about this Alamofire tutorial in the forum discussion below!




    출처 : https://www.raywenderlich.com/121540/alamofire-tutorial-getting-started

    댓글 7

Designed by Tistory.