[iOS] Alamofire Error Data 받기

pola·2025년 2월 26일

Company

목록 보기
4/7

Alamofire를 사용할 때 서버에서 에러 응답을 받을 경우, AFError 내부에서 response body를 직접 받을 수 없었음... 하지만 response.data에서 직접 처리해야 한다는 점을 발견!

문제 상황

API 요청 중 에러가 발생했을 때, 서버에서 아래와 같은 JSON 형식으로 응답이 옴

{
    "Code": 1,
    "Message": "에러메시지 입니다."
}

하지만 AFError에서는 이 응답 데이터를 직접 못받았음..
String으로 변환하면 아래와 같이 응답이 와서, 원하는 응답이 아니였다

{"timestamp":1735176875297,"status":500,"error":"Internal Server Error","path":"/path"}

찾아본 결과error response bodyerror가 아닌 response.data에서 처리해야했음

해결 방법

1. CustomAFError 정의

  • CustomAFError 구조체를 만들어 AFErrorresponseData를 가질 수 있도록 확장
struct CustomAFError: Error {
    let underlyingError: AFError
    let responseData: Data?
    let errorCode: Int?
}

2. APIError 모델 생성

  • 서버에서 응답하는 JSON을 Codable로 변환할 수 있도록 모델 정의
struct APIError: Codable {
    let code: String
    let message: String
}

3. AFError 확장

  • Alamofire의 AFError를 확장
  • responseData, errorCode를 가져오고, APIError를 디코딩하는 기능 추가
extension AFError {
    var responseData: Data? {
        switch self {
        case let .responseValidationFailed(reason):
            if case let .customValidationFailed(error as CustomAFError) = reason {
                return error.responseData
            }
        case let .sessionTaskFailed(error as CustomAFError):
            return error.responseData
        case let .responseSerializationFailed(_):
            return nil
        default:
            if let error = self.underlyingError as? CustomAFError {
                return error.responseData
            }
        }
        return nil
    }

    var errorCode: Int? {
        switch self {
        case let .responseValidationFailed(reason):
            if case let .customValidationFailed(error as CustomAFError) = reason {
                return error.errorCode
            }
        case let .sessionTaskFailed(error as CustomAFError):
            return error.errorCode
        case let .responseSerializationFailed(_):
            return 111111
        default:
            if let error = self.underlyingError as? CustomAFError {
                return error.errorCode
            }
        }
        return nil
    }

    func decodedAPIError() -> APIError? {
        guard let data = self.responseData else { return nil }
        return decodeAPIError(from: data)
    }

    func decodeAPIError(from data: Data?) -> APIError? {
        guard let data = data else { return nil }
        do {
            let decoder = JSONDecoder()
            return try decoder.decode(APIError.self, from: data)
        } catch {
            print("Failed to decode APIError: \\(error)")
            return nil
        }
    }
}

4. API 요청 및 에러 처리

  • 아래 fetch 함수에서 AFError를 감싸 CustomAFError로 변환
  • 활용해서 응답 데이터를 AFError에 포함
func fetch(completion: @escaping (Result<Data, AFError>) -> Void) {
    let url = "url"

    var params: Parameter = [:]
    params["param"] = "param"

    NetworkManager.shared.request(url, method: .post, parameters: params, headers: registerDeviceHttpHeaders) { response in
        switch response.result {
        case .success:
            // 성공 처리
            completion(.success(response.data ?? Data()))
        case .failure(let error):
            if response.data == nil {
                return completion(.failure(error))
            } else {
                let customError = AFError.responseValidationFailed(reason: .customValidationFailed(error: CustomAFError(underlyingError: error, responseData: response.data, errorCode: error.responseCode)))
                return completion(.failure(customError))
            }
        }
    }
}

5. 에러 응답 처리

  • API 호출 후 에러 발생 시 decodedAPIError()를 활용하여 응답 데이터를 처리할 수 있도록 handleError 함수 구현 (공통 처리를 위해서 진행)
private func fetchFunc(completion: @escaping (Result<Data, ErrorMessage>) -> Void) {
    getRepository.fetch { result in
        switch result {
        case .success(let data):
            // 성공 처리
            completion(.success(data))
        case .failure(let error):
            self.handleError(error: error, completion: completion)
        }
    }
}

private func handleError(error: AFError, completion: @escaping (Result<Data, ErrorMessage>) -> Void) {
    guard let errorData = error.decodedAPIError() else {
        // error.decodedAPIError()가 nil일 경우 처리
        return
    }

    // error.decodedAPIError()가 있을 경우 로그 출력
    print("errorData.code", errorData.code)
    print("errorData.message", errorData.message)
}

결론

  • AFError 자체에서는 responseData를 직접 받을 수 없음
  • response.data에서 응답 데이터를 확인해야 함
  • CustomAFError를 정의하여 AFError 내부에서 응답 데이터를 관리할 수 있도록 확장
  • decodedAPIError()를 활용하여 JSON 데이터를 디코딩하고 활용 가능

Alamofire에서 발생하는 에러를 보다 효율적으로 관리하고 서버에서 전달하는 에러 메시지를 쉽게 확인할 수 있었다

profile
pola

0개의 댓글