참고해서 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 |