Library

Alamofire

GREEN.1229 2022. 4. 28. 10:48

안녕하세요. 그린입니다🟢

이번 포스팅에서는 Alamofire(알라모파이어)에 대해 학습해보겠습니다🙋🏻

 

우선 외부 라이브러리에 대해 학습할때 아주 기본적으로 이 알라모파이어는 많이 들어보셨을거에요!

즉 아요스 개발자한테 널리 사용되기도 하고 친숙하기도하고 뭐 그런..?! 외부 라이브러리입니다😀

알라모파이어 깃헙 레포로 가보면 스타가 무려 3만7천개가 넘고 포크도 많이 따가셨어요🥳

 

 

그럼 왜 이 라이브러리가 딱 외부 라이브러리하면 떠오르고 흔히들 사용하고 있을까요?🤔

우선 이유를 찾으려면 이 알라모파이어가 뭐하는 친구인지 아는것이 우선일것 같아요.

 

Alamofire?

공식 깃헙 레포 리드미에서 알라모파이어를 이렇게 소개하고 있습니다.

Alamofire는 Swift로 작성된 HTTP 네트워킹 라이브러리

자 이말이 뭘까요?

아주 간단해요.

우리는 기존에 HTTP 네트워크 통신을 할때 애플에서 기본적으로 만들어놔서 제공해주는 URLSession을 이용해서 통신을 했어요.

근데 막상 이게 익숙해지지 않을때는 어렵고 익숙해져도 번거로운 작업들이 많다고 느껴졌어요.

즉 어렵다고 느껴지는 네트워크 통신 작업을 알라모파이어 구현으로 내부로 감추고 우리는 쉽게 겉으로 구현된 녀석들을 사용하면 되는 것입니다.

어떻게 보면 불필요한걸 싸매고 네트워크 통신을 할때 많이 사용하는 메서드들을 단순하게 사용하게끔 분리해놓았다고 보면 됩니다👍

그래서 제가 생각하기론 URLSession을 보다 개발자가 쉽게 사용할 수 있게 만들어준게 요 알라모파이어다! 입니다.

그래서 알라모파이어의 내부 구성을 까보면 당연하겠지만 URLSession을 기반으로 만들었습니다🙌

 

자 그럼 알라모파이어를 사용하기전 설치부터 해야겠죠?

 

Alamofire 설치하기

우리가 알고 있는 의존성 관리도구 모두 다 사용해서 설치할 수 있습니다.

즉 코코아팟, SPM, 카르타고 모두 모두 가능해요👍

각 설치 방법은 요 짤로 대신하겠습니다!

알라모파이어를 사용하기 위한 최소 타겟은 iOS 10.0 이상입니다!

뭐 거의 최소 타겟을 신경쓰지 않아도 될 정도 입니다😀

 

Alamofire 메서드

알라모파이어로 가장 기본적으로 사용되는 네트워킹 메서드로는 3가지가 있습니다.

1. request

- 우리가 흔히 알고있는 단순한 HTTP 통신 요청

2. upload

- 파일, 멀티파트 폼 데이터, 스트림 등을 통해 파일 업로드

3. download

- 파일을 다운로드

 

Alamofire의 Request와 Response

어떤 라이브러리 및 URLSession들도 그렇지만 당연하게 서버와 통신을 할때 가장 중요하게 생각되는게 2가지가 있습니다.

첫번째로 request이고 두번째로 response입니다.

당연히 요청을 담아 서버에 보내야될것이고, 이 요청을 서버에서 처리 후 응답을 클라에 주어야합니다.

여기서 요청은 request이고 응답은 response입니다.

그럼 간단히 하나씩 살펴보죠!

1. request

요청으로 보낼 수 있는 HTTPMethod를 알라모파이어에서는 아래와 같이 enum으로 정의해뒀습니다.

public struct HTTPMethod: RawRepresentable, Equatable, Hashable {
    /// `CONNECT` method.
    public static let connect = HTTPMethod(rawValue: "CONNECT")
    /// `DELETE` method.
    public static let delete = HTTPMethod(rawValue: "DELETE")
    /// `GET` method.
    public static let get = HTTPMethod(rawValue: "GET")
    /// `HEAD` method.
    public static let head = HTTPMethod(rawValue: "HEAD")
    /// `OPTIONS` method.
    public static let options = HTTPMethod(rawValue: "OPTIONS")
    /// `PATCH` method.
    public static let patch = HTTPMethod(rawValue: "PATCH")
    /// `POST` method.
    public static let post = HTTPMethod(rawValue: "POST")
    /// `PUT` method.
    public static let put = HTTPMethod(rawValue: "PUT")
    /// `QUERY` method.
    public static let query = HTTPMethod(rawValue: "QUERY")
    /// `TRACE` method.
    public static let trace = HTTPMethod(rawValue: "TRACE")

    public let rawValue: String

    public init(rawValue: String) {
        self.rawValue = rawValue
    }
}

여기서 거의 Get / Post / Delete / Patch / Put 정도?를 저는 썼던것 같은데 그외에 이렇게 다양한것을 제공했네요🧐

이렇게 정의된 요청을 아래와 같이 request 메서드를 통해 원하는 API 서버에 찔러줄 수 있습니다.

AF.request("https://green.com/get")
AF.request("https://green.com/post", method: .post)
AF.request("https://green.com/delete", method: .delete)

Get 요청은 바디에 값을 넣어 요청할 수 없습니다!

 

2. response

요청을 받았으면 뭐다? 응답을 받아야한다~

기본적으로 아래 코드와 같이 요청을 하고 response 메서드를 통해 받을 수 있어요.

AF.request("https://green.com/get")
  .response()

간단하죠?

알라모파이어가 구현된 소스코드를 까보면 ResponseSerialization.swift 파일에서 요 response를 각기 다른 데이터로 받아올 수 있도록 Response Handler들이 익스텐션으로 여러가지로 구분되어 있어요!

이걸 한번 보시죠..!

1. response

- 단순히 요청에 의해 반환된 응답값을 전달

AF.request("https://green.com/get")
  .response { data in
  // data 후속 처리 
}

2. responseData

- 아래 코드와 같이 정의되어 있으며 응답으로 넘어온 데이터를 유효성을 체크하고 데이터로 전달

public func responseData(
  queue: DispatchQueue = .main,
  dataPreprocessor: DataPreprocessor = DataResponseSerializer.defaultDataPreprocessor,
  emptyResponseCodes: Set<Int> = DataResponseSerializer.defaultEmptyResponseCodes,
  emptyRequestMethods: Set<HTTPMethod> = DataResponseSerializer.defaultEmptyRequestMethods,
  completionHandler: @escaping (AFDataResponse<Data>) -> Void
) -> Self {
  response(
    queue: queue,
    responseSerializer: DataResponseSerializer(
      dataPreprocessor: dataPreprocessor,
      emptyResponseCodes: emptyResponseCodes,
      emptyRequestMethods: emptyRequestMethods
    ),
    completionHandler: completionHandler
  )
}

보면 DataResponseSerializer를 이용하네요🧐

아래와 같이 간단히 사용할 수 있습니다.

AF.request("https://green.com/get")
  .responseData { data in
  // data 후속 처리 
}

3. responseString

- 위 responseData와 비슷하게 응답으로 넘어온 데이터를 인코딩을 시켜 String 즉 문자열로 전달

public func responseString(
  queue: DispatchQueue = .main,
  dataPreprocessor: DataPreprocessor = StringResponseSerializer.defaultDataPreprocessor,
  encoding: String.Encoding? = nil,
  emptyResponseCodes: Set<Int> = StringResponseSerializer.defaultEmptyResponseCodes,
  emptyRequestMethods: Set<HTTPMethod> = StringResponseSerializer.defaultEmptyRequestMethods,
  completionHandler: @escaping (AFDataResponse<String>) -> Void
) -> Self {
  response(
    queue: queue,
    responseSerializer: StringResponseSerializer(
      dataPreprocessor: dataPreprocessor,
      encoding: encoding,
      emptyResponseCodes: emptyResponseCodes,
      emptyRequestMethods: emptyRequestMethods
    ),
    completionHandler: completionHandler
  )
}

요 친구는 StringResponseSerializer를 사용해 인코딩시키고 문자열로 변경해주는것 같아요!

사용은 아래처럼~

AF.request("https://green.com/get")
  .responseString { data in
  // data 후속 처리 
}

4. responseJSON

- 넘어온 데이터를 JSONResponseSerializer를 이용해서 Any 타입으로 변경해 전달

public func responseJSON(
  queue: DispatchQueue = .main,
  dataPreprocessor: DataPreprocessor = JSONResponseSerializer.defaultDataPreprocessor,
  emptyResponseCodes: Set<Int> = JSONResponseSerializer.defaultEmptyResponseCodes,
  emptyRequestMethods: Set<HTTPMethod> = JSONResponseSerializer.defaultEmptyRequestMethods,
  options: JSONSerialization.ReadingOptions = .allowFragments,
  completionHandler: @escaping (AFDataResponse<Any>) -> Void
) -> Self {
  response(
    queue: queue,
    responseSerializer: JSONResponseSerializer(
      dataPreprocessor: dataPreprocessor,
      emptyResponseCodes: emptyResponseCodes,
      emptyRequestMethods: emptyRequestMethods,
      options: options
    ),
    completionHandler: completionHandler
  )
}

위에서도 마찬가지지만 컴플리션 핸들러를 보면 Any타입으로 들어갑니다.

요것도 사용은 쉽게..!

AF.request("https://green.com/get")
  .responseJSON { data in
  // data 후속 처리 
}

5. responseDecodable

- 넘어온 데이터를 DecodableResponseSerializer를 이용해 Decodable 타입으로 변경해 전달

public func responseDecodable<T: Decodable>(
  of type: T.Type = T.self,
  queue: DispatchQueue = .main,
  dataPreprocessor: DataPreprocessor = DecodableResponseSerializer<T>.defaultDataPreprocessor,
  decoder: DataDecoder = JSONDecoder(),
  emptyResponseCodes: Set<Int> = DecodableResponseSerializer<T>.defaultEmptyResponseCodes,
  emptyRequestMethods: Set<HTTPMethod> = DecodableResponseSerializer<T>.defaultEmptyRequestMethods,
  completionHandler: @escaping (AFDataResponse<T>) -> Void
) -> Self {
  response(
    queue: queue,
    responseSerializer: DecodableResponseSerializer(
      dataPreprocessor: dataPreprocessor,
      decoder: decoder,
      emptyResponseCodes: emptyResponseCodes,
      emptyRequestMethods: emptyRequestMethods
    ),
    completionHandler: completionHandler
  )
}

정의는 요렇고 사용은 아래처럼 쉽게!

AF.request("https://green.com/get")
  .responseDecodable { data in
  // data 후속 처리 
}

6. responseStream

넘어온 데이터를 DataStreamSerializer를 이용해 스트림으로 변경해 전달

public func responseStream<Serializer: DataStreamSerializer>(
  using serializer: Serializer,
  on queue: DispatchQueue = .main,
  stream: @escaping Handler<Serializer.SerializedObject, AFError>
) -> Self {
  let parser = { [unowned self] (data: Data) in
    self.serializationQueue.async {
      // Start work on serialization queue.
      let result = Result { try serializer.serialize(data) }
        .mapError { $0.asAFError(or: .responseSerializationFailed(reason: .customSerializationFailed(error: $0))) }
      // End work on serialization queue.
      self.underlyingQueue.async {
        self.eventMonitor?.request(self, didParseStream: result)
        
        if result.isFailure, self.automaticallyCancelOnStreamError {
          self.cancel()
        }
        
        queue.async {
          self.capturingError {
            try stream(.init(event: .stream(result), token: .init(self)))
          }
          
          self.updateAndCompleteIfPossible()
        }
      }
    }
  }
  
  $streamMutableState.write { $0.streams.append(parser) }
  appendStreamCompletion(on: queue, stream: stream)
  
  return self
}

요건 사실 써본적이 없어서 학습이 더 필요해보입니다!

자 이렇게 request와 response을 알아봤어요!
그럼 이제 실제 어떻게 사용하는지 해봐야겠죠?
예제 코드로 한번 어떻게 사용되는지 봅시다..!

Alamofire 사용하기

각기 사용은 다르겠지만 에러처리도 해줘야하는 상황에서 저는 요렇게 사용하고 있습니다!

alamofireSession.request(url, method: .patch, parameters: body, encoder: JSONParameterEncoder.default)
  .validate(statusCode: 200..<300)
  .publishData()
  .tryMap({ dataResponse -> ResponseDto in
    switch dataResponse.result {
    // 응답에 대해 정상일 경우
    case let .success(data):
      do {
        return try JSONDecoder().decode(ResponseDto.self, from: data)
      } catch {
        // Response 디코딩 실패에 대한 에러 처리
        throw ErrorFactory.decodeResponseDtoFailed(
          url: url,
          body: body,
          data: data,
          underlying: error
        )
      }
    // 응답에 대해 실패했을때
    case let .failure(error):
      // 요청 실패
      throw ErrorFactory.requestFailed(
        url: url,
        body: body,
        underlying: error
      )
    }
  })
  .tryMap({ dto -> ResponseDto in
    if dto.response.success == false {
      throw ErrorFactory.actionFailed(
        url: url,
        body: body,
        responseDto: dto
      )
    } else {
      return dto
    }
  })

코드가 좀 장황하지만 간단히 보시죠!

우선 patch로 요청하는데 body는 만들어줬다 가정하고 파라미터로 담아서 요청합니다.

그리고 보면 소개안한 validate 메서드가 있는데요.

이 메서드는 요청에 대해 유효성 검사를 해줍니다.

즉 응답을 받기전에 이 요청에 대해서 유효한지를 검사해주는것이죠!

그래서 우리는 200번대 응답을 받아야 서버 혹은 요청/클라 어떤 이슈도 아니니 200~299까지 상태를 인자로 넣어 이 상태일때 아래 로직을 돌려줄거다! 라고 심어줍니다.

그리고 들어온 응답값으로는 publishData로 데이터를 가져옵니다.

다음으로 두개의 tryMap으로 첫번째는 응답에 대한 결과를 정상과 실패로 나눠 에러 처리를 해줍니다.

정상 응답일경우 응답에 대해 디코딩을 시켜줘야겠죠?

디코딩에서도 실패할 경우는 에러처리를 해줍니다.

참고로 저 ErrorFactory로 사용한 에러들은 임의로 만든겁니다!

(따라하시려면 만드셔야합니다😄)

그리고 정상적으로 응답을 디코딩했고 다 끝났다면 마지막으로 요청에 대해 정상적인지 성공여부를 서버에서 보내줄것이고 이 값을 저는 

success 응답값으로 받으니 저렇게 한번 더 tryMap으로 성공이면 그냥 해당 응답 dto를 리턴하고 실패면 에러처리를 해주도록 했어요!

 

자.. 이렇게 마무리가 되었답니다🙌

 

마무리

어떠신가요?

왜 알라모파이어를 많이 쓰고 URLSession보다 편리하다고 하는지 이해가 가시나요?ㅎㅎ

저는 네트워크 통신은 매번 해도 통신 머리가 없는지 항상 헷갈리고 어렵네요🥲

그래도 이왕 라이브러리 쓰는거 한번 코드를 까보자 했었는데 이렇게 까보게 되네요..!

어느정도 또 이해가 되었는데 안쓰다보면 또 까먹을것 같기도하고 허허...ㅋㅋ

꾸준히 해야겠습니다😁

 

[참고자료]

https://github.com/Alamofire/Alamofire

 

GitHub - Alamofire/Alamofire: Elegant HTTP Networking in Swift

Elegant HTTP Networking in Swift. Contribute to Alamofire/Alamofire development by creating an account on GitHub.

github.com