[Swift] Alamofire 알아보기

Yi Joon Choi·2021년 9월 25일
6

Road To iOS Developer

목록 보기
2/3

1. Alamofire가 뭐야?

Alamofire 공식 문서: https://github.com/Alamofire/Alamofire

Alamofire는 iOS, macOS를 위한 스위프트 기반 HTTP 네트워킹 라이브러리 이다.

애플에서 자체적으로 데이터/서버 통신을 위해 URLSession 이라는 것을 제공하지만, 여기서 불편한 점을 한번 더 보완한 것이 Alamofire라고 생각하면 된다.

Alamofire는 URLSession을 기반으로 하여 어려운 네트워킹 작업을 감춰주기 때문에 주요 로직에 집중할 수 있게 해준다. 그렇기 때문에 조금 더 쉽게 데이터에 접근할 수 있을 뿐만 아니라 코드의 가독성이 좋아진다.

정리를 하자면
Alamofire는 HTTP네트워킹을 하는데 자주 사용하게 되는 코드나 함수를 더 쉽게 사용할 수 있도록 모아놓은 것 이라고 생각하면 된다!

2. Alamofire의 주요 함수

  • AF.upload : 멀티파트, 스트림, 파일 메소드를 통해 파일을 업로드 한다
  • AF.download : 파일을 다운로드 하거나, 이미 진행중인 다운로드를 재개한다
  • AF.request : 파일과 관련없는 기타 HTTP 요청
    *생각해보니까 개인적으로는 AF.request만 써보고 upload나 download는 써본적이 없는 것 같다!

3. Request

Request는 말 그대로 요청을 보내기 위해 사용하는 함수이다.
Alamofire에는 HTTPMethod라는 열거형이 저장되어있다.이 값들은 나중에 요청을 보낼때 method에 넣을 인자로 사용된다.공식문서에는 get 메소드에서 body에 값을 넣어서 보내는 것은 URLSession이나 Alamofire에서 지원하지 않는다고 나와있다.

4. Response

Request가 있다면... Response가 있겠지..?
Response를 확인하고 싶다면 request 뒤에 response를 붙여주면 된다.

AF.request(URL).response()

그런데! Alamofire에는 6가지 Response Handler가 있다!

  • Response
    : URLSessionDelegate에서 직접 모든 정보를 전달하고 응답 데이터를 평가하지 않는다 -> 2가지 방식으로 사용된다
 AF.request(url).response { response in 
    print(response)
 }
  • responseData
    :DataResponseSerializer를 사용해 서버에서 반환된 데이터를 추출하고 유효성 검사를 진행한다.
 AF.request(url).responseData { response in 
   print(response)
}
  • responseString
    :StringResponseSerializer를 사용해 서버에서 반환된 데이터를 지정된 인코딩을 사용하는 문자열로 변환한다.
 AF.request(url).responseString { response in 
   print(response)
}
  • responseJSON
    :JSONResponseSerializer를 사용해 서버에서 반환된 데이터를 지정된 JSONSerialization.ReadingOptions를 사용하는 Any타입으로 변환한다.
 AF.request(url).responseJSON { response in 
   print(response)
}
  • responseDecodable
    :DecodableResponseSerializer를 사용해 서버에서 반환된 데이터를 지정된 DataDecoder을 사용하는 Decodable타입으로 변환한다.
 AF.request(url).responseDecodable { response in 
   print(response)
}

5. Validation

.validate()를 호출함으로써 요청의 대한 유효성 검사를 실시한다.(유효하지 않을경우 response가 넘어올 수 없다)
일단 일반적인 사용법은 이렇지만...

AF.request(url)
  .validate()
  .response { response in 
     print(response)
}

만약에 조건을 걸어주고 싶다면...

AF.request(url, method: .get, parameters: nil, headers: Constant.HEADER)
            .validate(statusCode: 200..<500)
            //200~500사이 상태만 허용
            .validate(contentType: ["application/json"])
            //JSON 포맷만 허용
            .responseDecodable(of: PresentMoaHomeResponse.self) { response in
                switch response.result {
                case .success(let response):
                    viewController.serverData = response.data
                    viewController.didSuccessGot(message: response.message!)
                case .failure(let error):
                    print(error.localizedDescription)
                }
            }

개인적으로 처음 앱잼 시작할때 이 validation 때문에 난관에 봉착했었다. 분명히 success로 빠져야 하는 것들이 자꾸 fail로 빠졌던 것... 그래서 저렇게 validate에 조건을 걸어줬더니 성공으로 잘 빠졌던 기억이 있다.

6. Router

Alamofire Advanced Usage를 검색하던 도중에 Request Routing이라는 것을 발견했다.

Route: 경로, 길

집에 있는 라우터를 생각해보면, 하나의 네트워크 선이 라우터를 통해 여러 컴퓨터에 연결할 수도 있고, 와이파이도 만들어준다. 이처럼 앱에서도 수많은 API 요청이 발생하는데, Request Router를 통해 모든 요청을 생성할 수 있고, 이를 통해 하나의 파일에서 일관적으로 관리할 수 있다. 개인적으로 DataManager라는 파일을 뷰컨마다 한개씩 만들어서 쓰는 스타일이라서 여기저기 흩어져 있는 서버통신 관련 파일들을 하나로 볼 수 있다면 좋겠다는 생각이 들었다.

let baseURL = "https://httpbin.org"
class APIPath{
  static let postPractice = "/post"
  static let getPractice = "/get"
}
import Foundation
import Alamofire

class APIRouter: URLRequestConvertible {
    // 1
    enum APIType {
        case auth
        case service
        
        var baseURL: String {
            switch self {
            case .auth:
                switch enviroment {
                case .dev: return "https://auth.dev"
                case .stage: return "https://auth.dev"
                case .real: return "https://auth.real"
                }
            case .service:
                return "https://httpbin.org"
            }
        }
    }
    
    var path: String
    var httpMethod: HTTPMethod
    var parameters: Data?
    var apiType: APIType
    
    init(path: String, httpMethod: HTTPMethod? = .get, parameters: Data? = nil, apiType: APIType = .service) {
        self.path = path
        self.httpMethod = httpMethod ?? .get
        self.parameters = parameters
        self.apiType = apiType
    }
    
    func asURLRequest() throws -> URLRequest {
        // 2. base URL + path
        let fullURL = apiType.baseURL + path
        let encodedURL = fullURL.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
        var urlCompoenet = URLComponents(string: encodedURL)!
        
        // 3. get> query parmeter 추가
        if httpMethod == .get, let params = parameters {
            if let dictionary = try? JSONSerialization.jsonObject(with: params, options: []) as? [String:Any] {
                var queries = [URLQueryItem]()
                for (name, value) in dictionary {
                    let encodedValue = "\(value)".addingPercentEncodingForRFC3986()
                    let queryItem = URLQueryItem(name: name, value: encodedValue)
                    queries.append(queryItem)
                }
                urlCompoenet.percentEncodedQueryItems = queries
            }
        }
        
        // 4. request 생성
        var request = try URLRequest(url: urlCompoenet.url!, method: httpMethod)
        
        // 5. post> json parameter 추가
        if httpMethod == .post, let params = parameters {
            request.httpBody = params
            request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        }
        
        // 6. header 추가
//        if hasHeader {
//            var headers = [String: String]()
//            headers["Authorization"] = "toekn " + accessToken
//            urlRequest.allHTTPHeaderFields = headers
//        }
        
        // 7. print to console
        print("[REQUEST]")
        print("URI: \(request.url?.absoluteString ?? "nil")")
        print("Body: \(request.httpBody?.toPrettyPrintedString ?? NSString())")
        
        return request
    }
    
}

그럼 이제 실제로 어떻게 사용하냐!

import UIKit
import Alamofire

class ViewController: UIViewController {
    
    var userInfo: UserInfo = {
        var userInfo = UserInfo()
        userInfo.userName = "yungso"
        userInfo.age = 20
        return userInfo
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        basicRequest()
        getRequestByRouter()
        postRequestByRouter()
        requstUsingSession()
    }
    
    func basicRequest() {
        let params: Parameters = ["userName": "yungso",
                                  "age": 20]
        
        AF.request("https://httpbin.org/post",
                   method: .post,
                   parameters: params).responseDecodable(of: HttpbinResponse.self, completionHandler: {(response: DataResponse<HttpbinResponse, AFError>) -> Void in
                    switch response.result {
                    case .success(let value):
                        print("[RESPONSE]")
                        print(response.data?.toPrettyPrintedString)
                    case .failure(let err):
                        print("API Failure")
                        print(err)
                    }
                   })
    }
          func postRequestByRouter() {
          let router = APIRouter(path: APIPath.postPractice, httpMethod: .post, parameters: userInfo.toData, apiType: .service)
          AF.request(router).responseDecodable(of: HttpbinResponse.self, completionHandler: {(response: DataResponse<HttpbinResponse, AFError>) -> Void in
              switch response.result {
              case .success(let value):
                  print("[RESPONSE]")
                  print("URL: \(value.url ?? "")")
                  print("json body: \(value.json ?? UserInfo())")
                  print("Response Data: \(response.data?.toPrettyPrintedString ?? "")")
              case .failure(let err):
                  print("API Failure")
                  print(err)
              }
          })
      }

7. Reference

Alamofire 공식문서
[Swift] Alamofire 사용해보기_(2)
Alamofire에 대해
[Swifit]Router로 API 요청하기-Alamafire Advanced Usage

profile
최이준

3개의 댓글

comment-user-thumbnail
2021년 9월 26일

Router 굉장히.. 좋은 방식인 것 같아 기술 공유 컨퍼런스 때 발표해주심 좋을 것 같다는 생각이 드네요..🌼👀ㅋ

1개의 답글
comment-user-thumbnail
2022년 11월 10일

잘보고갑니다 ..

답글 달기