[iOS] moya를 combine을 이용해서 구현해보자

2026. 1. 18. 16:59·앱/Swift

https://medium.com/@islammoussa.eg/building-a-network-layer-using-combine-ios-with-a-structure-similar-to-moya-3abd9d23b16

참고해서 combine형태의 moya스타일로 처리할수 있다.

TargetType

protocol TargetType {
    var baseURL: String { get }
    var path: String { get }
    var method: ShopLiveHTTPMethod { get }
    var parameters: [String: Any]? { get }
    var headers: [String: String] { get }
    var timeoutInterval: Double { get }
    var version: ShopLiveHTTPVersion { get }
}

네트워크 서비스

final class NetworkService {
    static var shared = NetworkService()
    
    private let networkQueue = DispatchQueue(label: "com.shoplive.network.queue", qos: .background)
    
    var streamRepository: any StreamRepository
    
    private init() {
        self.streamRepository = StreamRepositoryImpl(queue: networkQueue)
    }
}

베이스 레파지토리

protocol BaseRepository {
    associatedtype T: TargetType
    var provider: NetworkProvider<T> { get }
}

레파지토리 구현체

final class StreamRepositoryImpl: StreamRepository {
    
    var provider:  NetworkProvider<StreamRouter>
    
    init(queue: DispatchQueue) {
        self.provider =  NetworkProvider<StreamRouter>(queue: queue)
    }

APIManager

protocol APIManager {
    associatedtype T: TargetType
    func request<D: ShopLiveBaseResponsable>(_ target: T) -> AnyPublisher<D, ShopLiveError>
}

Provider

final class NetworkProvider<T: TargetType>: APIManager {
    
    private let queue: DispatchQueue
    
    init(queue: DispatchQueue) {
        self.queue = queue
    }
    
    // 네트워크 시작하면 로딩인디케이터 도는거 추가해야함
    func request<D: ShopLiveBaseResponsable>(_ target: T) -> AnyPublisher<D, ShopLiveError> {
        
        // 1. URL 생성
        var urlString = target.baseURL
        urlString += target.path.hasPrefix("/") ? target.path : "/\\(target.path)"
        
        guard var components = URLComponents(string: urlString) else {
            return Fail(error: ShopLiveError.commonError).eraseToAnyPublisher()
        }
        
        var bodyData: Data? = nil
        
        switch target.method {
        case .get:
            if let parameters = target.parameters {
                components.queryItems = parameters.map {
                    URLQueryItem(name: $0.key, value: String(describing: $0.value))
                }
            }
            
        case .post, .put, .delete:
            if let parameters = target.parameters {
                bodyData = try? JSONSerialization.data(withJSONObject: parameters)
            }
        }
        
        guard let finalURL = components.url else {
            return Fail(error: ShopLiveError.commonError).eraseToAnyPublisher()
        }
        
        // 2. Request 생성
        var request = URLRequest(url: finalURL)
        
        print("URL : \\(finalURL.absoluteString)")
        print("target.parameters : \\(String(describing: target.parameters))")
        
        
        request.httpMethod = target.method.converted
        request.httpBody = bodyData
        request.timeoutInterval = target.timeoutInterval
        
        // 헤더
        var finalHeaders = SLStudioDefines.studioDeafultHeaders
        for (key, value) in target.headers {
            if value.isEmpty {
                finalHeaders[key] = nil
            } else {
                finalHeaders[key] = value
            }
        }
        
        for (key, value) in finalHeaders {
            request.addValue(value, forHTTPHeaderField: key)
        }
        
        // 3. Combine 처리
        return URLSession.shared.dataTaskPublisher(for: request)
            .subscribe(on: queue)
            .tryMap { data, response -> Data in
                guard let httpResponse = response as? HTTPURLResponse,
                      200...399 ~= httpResponse.statusCode else {
                    
                    print("error Code",( response as? HTTPURLResponse )?.statusCode ?? 0)
                    
                    throw ShopLiveError.commonError
                }
                
                print("error Code", httpResponse.statusCode)
                
                return data
            }
            .decode(type: D.self, decoder: JSONDecoder())
            .tryMap { result in
                // 서버 정의 에러코드 체크해서 에러로 throw
                if let statusCode = result._s,
                   let shopLiveError = ShopliveErrorType(rawValue: statusCode)?.convertedToError {
                    throw shopLiveError
                }
                
                print("response", result)
                
                return result
            }
        // 받을때는 ShopLiveError로 받게 됩니다. ErrorType에 정의되지 않은 에러는 전부다 commonError로 나옵니다.
            .mapError { error in
                if error is DecodingError {
                    return ShopliveErrorType.DECODING_ERROR.shopLiveError
                } else if let shop = error as? ShopLiveError {
                    return shop
                } else {
                    return ShopLiveError.commonError
                }
            }
            .eraseToAnyPublisher()
    }
}
반응형
저작자표시 비영리 변경금지 (새창열림)

'앱 > Swift' 카테고리의 다른 글

[Swift] GCD와 swift concurrency가 나오게된 배경과, concurrency 기본개념  (1) 2026.01.19
[iOS] ReactorKit을 Combine으로 구현하는 법  (0) 2026.01.18
Reactorkit의 Pulse 구현부에 대해서  (0) 2025.02.18
Moya Mock Data 사용하기(feat. test Double)  (0) 2025.01.31
reactorkit에서 testcode 작성 하는법(feat. nimble)  (0) 2025.01.31
'앱/Swift' 카테고리의 다른 글
  • [Swift] GCD와 swift concurrency가 나오게된 배경과, concurrency 기본개념
  • [iOS] ReactorKit을 Combine으로 구현하는 법
  • Reactorkit의 Pulse 구현부에 대해서
  • Moya Mock Data 사용하기(feat. test Double)
진ddang
진ddang
안녕하세요 진땅의 개발자 블로그 입니다. 피드백은 환영입니다. 깃헙 : https://github.com/it794613
    반응형
  • 진ddang
    맛동산이
    진ddang
  • 전체
    오늘
    어제
    • 분류 전체보기 (202)
      • 일기 그리고 목표 (1)
      • 웹 (20)
        • 리액트 (19)
      • 앱 (118)
        • Swift 문법 (15)
        • Swift (68)
        • SwiftUI (32)
        • 리액트 네이티브 (3)
      • CS (30)
        • 컴퓨터그래픽스 (8)
        • 운영체제 (6)
        • 네트워크 (16)
      • 알고리즘 (13)
        • 백준 (12)
        • 프로그래머스 (1)
      • 대외활동 (7)
        • ict한이음(2022.04) (2)
        • 멋쟁이 사자처럼 (5)
        • Apple Developer Academy (0)
      • 다양한 내용들 (8)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    dispatchqueue
    웹
    cs
    운영체제
    spritekit
    TCA
    대외활동
    후기
    네트워크
    스유
    Swift
    c++
    swift concurrency
    멋쟁이사자처럼
    컴퓨터그래픽스
    SwiftUI
    백준
    문법
    영남대
    widget
    widgetkit
    ReactorKit
    알고리즘
    composable architecture
    스위프트
    멋사
    uikit
    Protocol
    리액트
    위젯킷
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
진ddang
[iOS] moya를 combine을 이용해서 구현해보자
상단으로

티스토리툴바