Programming/iOS

[iOS/Swift] URLSession, URLRequest, Alamofire, SwiftyJSON 라이브러리를 사용한 HTTP 통신 예제

devssun 2017. 8. 5. 07:30
728x90
반응형

cocoapods을 이용한 라이브러리 설치


  • 프로젝트 열려있는 거 닫고 진행
  • sudo gem install cocoapods
  • pod setup
  • 프로젝트가 있는 폴더로 이동 (cd /Users/gg/XcodeProjects/프로젝트폴더/)
  • pod init
  • Podfile 열어서 파일 수정 (프로젝트 폴더 내에 생성됨)

     platform :ios, '10.0'
     use_frameworks!
     target 'ProjectName' do
     use_frameworks!
     
     Pods for ProjectName
     pod 'SwiftyJSON'
     pod 'Alamofire', '~> 4.4'
     end

    각 라이브러리 GitHub (설치법, 사용법이 나와있음)
    https://github.com/Alamofire/Alamofire
    https://github.com/SwiftyJSON/SwiftyJSON

  • pod install
  • 라이브러리 설치 이후부턴 xcodeproj파일이 아닌 xcworkspace로 열어서 소스코드 작성

  • 오류 발생

    Couldn't load a xcode project because it is already opened from another project or workspace
    - xcode프로젝트 상에 열려있는 파일들을 모두 닫은 후 xcode 종료, 다시 시작

  • ViewController에서 import Alamofire, import SwiftyJSON 추가


HTTP 통신


  • Swift에서 기본 제공되는 라이브러리 이용하기
  • NSURLConnection, NSSession, NSRequest
  • post방식을 이용해 전송하는 예제

    • post로 넘길 파라미터 정의
    • let parameters = ["key1":"value1", "key2":value2, "key3":value3]

    • HTTP 연결

      let url = URL(string:"url")
       
      // session 설정
      let session = URLSession.shared
      var request = URLRequest(url: url!)
       
      // post 방식
      request.httpMethod = "POST"
       
      do {
          request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
          // pass dictionary to nsdata object and set it as request body
      } catch let error {
          print(error.localizedDescription)
      }
       
      request.addValue("application/json", forHTTPHeaderField: "Content-Type")
      request.addValue("application/json", forHTTPHeaderField: "Accept")


    • 데이터 가져오기 - JSON

      let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
       
          guard error == nil else {
              return
          }
       
          guard let data = data else {
              return
          }
       
          do {
              //create json object from data
              self.dataItem = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as! NSDictionary
          } catch let error {
              print(error.localizedDescription)
          }
       
      })
      task.resume()




위의 긴 소스를 Alamofire, SwiftyJSON으로 줄이기

  • let parameters = ["key1":"value1", "key2":value2, "key3":value3]

    Alamofire.request("url", method: .post, parameters: parameters, encoding: JSONEncoding.default)
                .validate(contentType: ["application/json"])
                .responseJSON(completionHandler: { response in
                    switch response.result{
                    case .success:
                        print("Validation Successful")
                        if let values = response.result.value{
                            let json = JSON(values)
                            print(json)
                        }
                    case .failure(let error):
                        print(error)
                }
            })

    외부 라이브러리를 사용한 예제 코드에서 처음에 데이터가 html이라고 어쩌구저쩌구 오류가 났었다. encoding이 제대로 되지않아 발생한 문제였다.

    • Alamofire.request()에 encoding: JSONEncoding.default를 추가한다


발전 시키기

To-do List

  • 로그인 성공, 실패에 대한 로직 구현
  • 성공 시 화면 전환 후 이동한 화면에서 000님 반갑습니다! 텍스트 출력
  • 실패 시 로그인 화면에서 alert창 뜨게 하기
  1. 1차 도전 - 비동기와 동기식의 차이

    • 처음에는 위의 Alamofire예제 소스인데 그 안에 isLogin 플래그 값을 주어 해당 값이 true 일 때 로그인 성공 이벤트를 주기로 했다.
    • 하지만 Alamofire는 비동기식으로 동작하기때문에 데이터를 파싱한 결과(로그인 성공여부)보다 isLogin 비교 부분이 먼저 동작하였다
    • CallBack 함수를 써보자!
  2. 2차 도전 - CallBack 함수 구현하고 사용하기

    • 이전에 Android를 할 때도 콜백함수는 복붙을 하거나.. 많이 생각을 안했었는데.. 이번에 iOS를 하면서 콜백함수를 만들어보았다.
    • 아래 영상을 참고하며 만들었다. 영어지만 간단하고 딱 필요한 예제를 만들었다

      https://www.youtube.com/watch?v=YqhvVyiEpI0
      남녀가 데이트를 하기 위해 만날 때 남자가 준비 다 하면 전화해~ 하고 여자가 다 하고 전화하면 남자는 집을 나온다.
      콜백함수를 재미있게 표현한 예제였다 (블로그에서 본 예제)

    • alamofire를 사용한 부분을 함수로 getLogin() 함수로 옮겼다. 그리고 인자값에 completion: @escaping을 삽입한다.
      • func getLogin(completion: @escaping (Bool, Any?, Error?) -> Void){}
      • 꼭 completion이라고 쓸 필요는 없다 :)
    • 데이터 파싱의 success, fail에 따라 completion()에 적절하게 값을 넣어준다
       // callBack 메소드 처리
       func getLogin(completion: @escaping (Bool, Any?, Error?) -> Void) {
           let uid = idTextField.text!
           let pwd = passwordTextField.text!
           let parameters = ["key1":"value1", "key2":value2, "key3":value3]
     
           // Alamofire 비동기 통신 - HTTP POST
           Alamofire.request("url", method: .post, parameters: parameters, encoding: JSONEncoding.default)
               .validate(contentType: ["application/json"])
               .responseJSON(completionHandler: { response in
                   switch response.result{
                   case .success:
                       print("Validation Successful")
                       if let values = response.result.value{
                           completion(true, values, nil)
                       }
                   case .failure(let error):
                       print("Validation Fail")
                       print(error)
                       completion(false, nil, error)
                   }
               })
       }


    • 로그인 버튼 클릭 시 이벤트가 발생하도록 하기 위해 미리 연결해둔 버튼 액션 메소드에 getLogin 함수를 호출한다
    • 실패 시 dialog창이 뜨고 성공하면 다음 창으로 이동한다
    • JSON parsing에는 Swifty JSON Library를 사용하였다
    @IBAction func loginAction(_ sender: Any) {
           // 콜백 메소드
           getLogin { (success, response, error) in
               print("콜백메소드 실행")
               if success{
                   guard let values = response else { return }
     
                   let totalData = JSON(values)
                   if totalData["result"] != 0{
                       print("error")
                       // error dialog
                       let dialog = UIAlertController(title: "로그인 실패", message: "아이디 또는 비밀번호를 다시 확인해주세요", preferredStyle: .alert)
                       let action = UIAlertAction(title: "확인", style: UIAlertActionStyle.default)
                       dialog.addAction(action)
                       self.present(dialog, animated: true, completion: nil)
                   }else{
                       // data parsing - SwiftyJSON
                       let data = totalData["data"].string    // data 키의 value가져옴
                       let parseData = JSON.parse(data!)   // data parsing
     
                       // 화면 전환
                       OperationQueue.main.addOperation {
                           self.performSegue(withIdentifier: "showNext", sender: self)
                       }
                   }
               }else if let error = error{
                   print(error)
               }
           }
       }
    • 그런데 값을 받아온 이후 화면이 전환되어도 Storyboard에서 이미 연결되어있는 segue때문에 화면에 이름을 띄우기 전 optional value 이름 변수때문에 app이 죽는 현상이 있었다
    • 왜그럴깡
      • secondView.swift에서는 딜레이를 주어서 0.1초이후 텍스트를 받아서 화면에 표현하는 것으로 변경하였다
      • 이미 데이터는 잘 넘어가고 있었지만 optional value때문에 문제가 발생하여 0.1초를 주면 느린 것도 안보이고 그냥 슉슉 넘어간다.

        약간의 꼼수지만.. 서버가 안정적이다면 써도 괜찮다고 생각한다.

        // 화면에 이름을 띄우기 위한 delay
         let when = DispatchTime.now() + 0.1
         DispatchQueue.main.asyncAfter(deadline: when) {
             // Your code with delay
             let str = "\(self.name!)님 반갑습니다!"
             self.nameTextField.text = str
             print(str)
         }
Callback메소드를 사용하여 비동기 작업이 끝난 뒤 로그인 성공/실패 여부를 완벽하게 확인시켜주는 앱 완성!

반응형