HIT해

[iOS/SwiftUI] TCA API 통신 구현하기 with AF ( TCA 1.12.1 ) 본문

Swift/Swift 개발 노트

[iOS/SwiftUI] TCA API 통신 구현하기 with AF ( TCA 1.12.1 )

힛해 2024. 9. 5. 06:04
728x90

깃허브의 Example_Search 예제를 보고 구현을 해보자.

 

struct Peperoni: Decodable, Equatable, Sendable {
  var message : String
}

@DependencyClient
struct TmpClient {
    var regist: @Sendable () async throws -> Peperoni
}

// 실제 통신 전 테스트
extension TmpClient: TestDependencyKey {
    // 여기서의 Self는 TmpClient
    static let previewValue = Self()

    static let testValue = Self()
}

extension DependencyValues {
    var tmpClient: TmpClient {
        get { self[TmpClient.self] }
        set { self[TmpClient.self] = newValue }
    }
}

extension TmpClient: DependencyKey {
    static let liveValue = TmpClient(
        regist: {
              var components = URLComponents(string: "http://...")!
			  components.queryItems = [URLQueryItem(name: "name", value: query)]

              let (data, _) = try await URLSession.shared.data(from: components.url!)
              return try JSONDecoder().decode(Peperoni.self, from: data) //

            
        }
    )
}

 

URLSession으로 간단하게 구현된 예제다.

 

오류가 났을때의 처리를 TCA Reducer에서 처리해주기에 따로 catch 구문이 없는 것을 볼 수 있다.

 

해당 작업에 대해 Reducer에서 다음과 같은 설정들이 있어야한다.

 

  1. 통신 Client 불러오기
  2. 응답 값으로 Result<디코딩타입, Error> 받는 액션
  3. 2번 액션 성공, 실패에 따른 동작

간단하게 코드로 살펴보자

enum Action: BindableAction {
        case clickMessage
        case tmpResponse(Result<Peperoni, Error>)
    }
    
...

case .clickMessage:
  		return .run { send in
  		await send(.tmpResponse(Result{ try await self.tmpClient.regist() }))
}
    // 실패했을때
	case .tmpResponse(.failure):
			print("통신오류")
			return .none
            
    // 성공했을때 디코딩 값을 바로 리듀서 상태에 반영
	case let .tmpResponse(.success(response)):
		state.message = response.message
		return .none

 

이런 구조로 만들 수 있다.

 

이걸 AF로 바꿔보자.

 

공통적으로 사용할 수 있는 AF 코드를 유틸에 따로 뺴주고.

//
//  apiFetch.swift
//  DolHaruBang
//
//  Created by 양희태 on 9/3/24.
//

import Alamofire
import Foundation

func fetch<T: Decodable>(
    url: String,
    model: T.Type,
    method: HTTPMethod ,
    queryParameters: [String: String]? = nil,
    headers: HTTPHeaders? = nil,
    body: Data? = nil
) async throws -> T {
    return try await withCheckedThrowingContinuation { continuation in
        // URLRequest 객체 생성
        var request = URLRequest(url: URL(string: url)!)
        request.httpMethod = method.rawValue
        
        // 쿼리 파라미터 추가
        if let queryParameters = queryParameters {
            var components = URLComponents(url: request.url!, resolvingAgainstBaseURL: false)!
            components.queryItems = queryParameters.map { URLQueryItem(name: $0.key, value: $0.value) }
            request.url = components.url
        }

        // 헤더 추가
        if let headers = headers {
            request.headers = headers
        }

        // 바디 데이터 추가 (POST 및 PUT 요청 시 사용)
        if (method == .post || method == .put), let body = body {
            request.httpBody = body
        }

        // Alamofire 요청
        AF.request(request)
            .validate() // 응답 상태 코드 검증
            .responseData { response in
                switch response.result {
                case .success(let data):
                    do {
                        let jsonDecoder = JSONDecoder()
                        let decodedModel = try jsonDecoder.decode(T.self, from: data)
                        continuation.resume(returning: decodedModel)
                    } catch {
                        continuation.resume(throwing: error)
                    }
                    
                case .failure(let error):
                    continuation.resume(throwing: error)
                }
            }
    }
}

 

 

사용할때는 간단하게 사용할 수 있다.

 

extension TmpClient: DependencyKey {
    static let liveValue = TmpClient(
        regist: {
            
               let url = "http://..."
//            let queryParameters: [String: String] = ["param1": "value1"] // 필요에 따라 설정
//            let headers: HTTPHeaders = ["Authorization": "Bearer token"] // 필요에 따라 설정
//            return try await fetch(url, model: Peperoni.self, queryParameters: queryParameters, headers: headers)
               return try await fetch(url: url, model: Peperoni.self, method: .get)
                 
            
        }
    )
}