Created
March 25, 2025 12:42
-
-
Save hhhello0507/bad94130665822fbf7fda2f1064369a7 to your computer and use it in GitHub Desktop.
Moya snippet
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import Moya | |
// NetRunner 클래스에서 사용. | |
// 원래 NetRunner는 BaseEndpoint를 준수 받는 Endpoint (e.g. MealEndpoint, AIEndpoint)를 Generic하게 사용하고 있었음. | |
// 이런 방식으로 하니 각 Endpoint 별로 NetRunner를 만들어야 함. | |
// | |
// 이 프로젝트에서는 각 Endpoint 별로 다른 NetRunner를 쓸 필요성을 못 느낌. | |
// 따라서 NetRunner는 Generic하게 사용하는 오브젝트를 알 필요가 없음. | |
// | |
// 결론적으로 NetRunner가 특정 Endpoint를 알지 못하게 하기 위해 한 단계 더 고수준의 enum인 AnyEndpoint를 도입함. | |
// | |
// 🔽 아래 깃헙 참고해서 구현함 🔽 | |
// https://github.com/GSM-MSG/Emdpoint/blob/master/Sources/Emdpoint/EndpointType/EndpointType.swift | |
// https://github.com/Team-Ampersand/Dotori-iOS/blob/master/Projects/Core/Networking/Sources/NetworkingImpl.swift | |
public enum AnyTarget { | |
case endpoint(any MyTarget) | |
var endpoint: any MyTarget { | |
switch self { | |
case .endpoint(let endpoint): | |
return endpoint | |
} | |
} | |
public init(_ endpoint: MyTarget) { | |
self = .endpoint(endpoint) | |
} | |
} | |
extension AnyTarget: MyTarget { | |
public var host: String { endpoint.host } | |
public var route: Route { endpoint.route } | |
public var baseURL: URL { endpoint.baseURL } | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import Moya | |
import MyFoundationUtil | |
public extension Encodable { | |
func toRequestParameters(encoding: ParameterEncoding, using encoder: JSONEncoder) -> Moya.Task { | |
if let data = try? encoder.encode(self), | |
let object = (try? JSONSerialization.jsonObject( | |
with: data, | |
options: .allowFragments | |
)).flatMap({ $0 as? [String: Any] }) { | |
let parameters = object.mapValues { value -> Any in | |
if let enumValue = value as? (any RawRepresentable) { | |
return enumValue.rawValue | |
} | |
return value | |
} | |
return .requestParameters( | |
parameters: parameters, | |
encoding: encoding | |
) | |
} | |
return .requestPlain | |
} | |
func toJSONParameters(using encoder: JSONEncoder = .myEncoder) -> Moya.Task { | |
toRequestParameters(encoding: JSONEncoding.default, using: encoder) | |
} | |
func toURLParameters(using encoder: JSONEncoder = .myEncoder) -> Moya.Task { | |
toRequestParameters(encoding: URLEncoding.default, using: encoder) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public enum MealEndpoint: MyTarget { | |
public var baseURL: URL { | |
URL(string: "http://localhost:1234")! | |
} | |
case fetchMeals(schoolId: Int) | |
} | |
extension MealEndpoint { | |
public var host: String { | |
"meals" | |
} | |
public var route: Route { | |
switch self { | |
case .fetchMeals(let schoolId): .get("\(schoolId)") | |
} | |
} | |
} | |
var sub = Set<AnyCancellable>() | |
let run = DefaultNetRunner(provider: .init(), authProvider: .init()) | |
func request() { | |
print("Net") | |
run | |
.deepDive(MealEndpoint.fetchMeals(schoolId: 1393), res: String.self) | |
.sink { | |
print("Result : \($0)") | |
} receiveValue: { | |
print("Value : \($0)") | |
} | |
.store(in: &sub) | |
print("Net") | |
} | |
request() | |
RunLoop.main.run(until: Calendar.current.date(byAdding: .second, value: 10, to: .now)!) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Moya | |
extension MoyaProvider { | |
func request(_ target: Target) async -> Result<Response, MoyaError> { | |
return await withCheckedContinuation { continuation in | |
self.request(target) { result in | |
continuation.resume(returning: result) | |
} | |
} | |
} | |
func request(_ target: Target) async throws -> Response { | |
return try await withCheckedThrowingContinuation { continuation in | |
self.request(target) { result in | |
switch result { | |
case .success(let response): | |
continuation.resume(returning: response) | |
case .failure(let error): | |
continuation.resume(throwing: error) | |
} | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Moya | |
import Foundation | |
/// MyTarget | |
/// Moya.TargetType을 추상화한 protocol | |
public protocol MyTarget: TargetType { | |
var host: String { get } | |
var route: Route { get } | |
var authorization: Authorization { get } | |
} | |
public extension MyTarget { | |
/** | |
Devide route | |
*/ | |
var path: String { | |
route.path | |
} | |
var method: Moya.Method { | |
route.method | |
} | |
var task: Moya.Task { | |
route.task | |
} | |
/** | |
Just default value. | |
You can change another value. | |
*/ | |
var headers: [String: String]? { | |
["Content-type": "application/json"] | |
} | |
var validationType: ValidationType { | |
.successCodes | |
} | |
var authorization: Authorization { | |
.refresh | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import Moya | |
import CombineMoya | |
import Combine | |
import MyFoundationUtil | |
/// NetRunner | |
/// NetRunner가 deinit 되면 provider도 deinit됨. | |
/// provider가 deinit되면 request가 cancel됨. | |
public protocol NetRunner { | |
var provider: MoyaProvider<AnyTarget> { get } | |
var authProvider: MoyaProvider<AnyTarget> { get } | |
func deepDive<DTO: Decodable>( | |
_ target: MyTarget, | |
res: DTO.Type, | |
completion: @escaping (Result<DTO, MoyaError>) -> Void | |
) | |
func deepDive<DTO: Decodable>( | |
_ target: MyTarget, | |
res: DTO.Type | |
) -> AnyPublisher<DTO, MoyaError> | |
func deepDive<DTO: Decodable>( | |
_ target: MyTarget, | |
res: DTO.Type | |
) -> AnyPublisher<Result<DTO, MoyaError>, Never> | |
func deepDive<DTO: Decodable>( | |
_ target: MyTarget, | |
res: DTO.Type | |
) async -> Result<DTO, MoyaError> | |
func deepDive<DTO: Decodable>( | |
_ target: MyTarget, | |
res: DTO.Type | |
) async throws -> DTO | |
} | |
public class DefaultNetRunner: NetRunner { | |
public let provider: MoyaProvider<AnyTarget> | |
public let authProvider: MoyaProvider<AnyTarget> | |
public let decoder: JSONDecoder | |
public init( | |
provider: MoyaProvider<AnyTarget> = .init(), | |
authProvider: MoyaProvider<AnyTarget> = .init(), | |
decoder: JSONDecoder = .myDecoder | |
) { | |
self.provider = provider | |
self.authProvider = authProvider | |
self.decoder = decoder | |
} | |
private func selectProvider(_ target: MyTarget) -> MoyaProvider<AnyTarget> { | |
switch target.authorization { | |
case .none: | |
provider | |
case .refresh: | |
authProvider | |
} | |
} | |
public func deepDive<DTO>( | |
_ target: MyTarget, | |
res: DTO.Type, | |
completion: @escaping (Result<DTO, Moya.MoyaError>) -> Void | |
) where DTO : Decodable { | |
selectProvider(target).request(AnyTarget(target)) { result in | |
switch result { | |
case .success(let response): | |
let response = self.unwrapThrowable { | |
try response.filterSuccessfulStatusCodes() | |
.map(res, using: self.decoder) | |
} | |
completion(response) | |
case .failure(let error): | |
completion(.failure(error)) | |
} | |
} | |
} | |
public func deepDive<DTO: Decodable>( | |
_ target: MyTarget, | |
res: DTO.Type | |
) -> AnyPublisher<DTO, MoyaError> { | |
return selectProvider(target) | |
.requestPublisher(AnyTarget(target)) | |
.filterSuccessfulStatusCodes() // 200..<300 | |
.map(DTO.self, using: decoder) | |
.receive(on: DispatchQueue.main) | |
.eraseToAnyPublisher() | |
} | |
public func deepDive<DTO>( | |
_ target: MyTarget, | |
res: DTO.Type | |
) -> AnyPublisher<Result<DTO, Moya.MoyaError>, Never> where DTO : Decodable { | |
return selectProvider(target) | |
.requestPublisher(AnyTarget(target)) | |
.filterSuccessfulStatusCodes() | |
.map(DTO.self, using: decoder) | |
.map { Result.success($0) } | |
.catch { Just(Result.failure($0)) } | |
.receive(on: DispatchQueue.main) | |
.eraseToAnyPublisher() | |
} | |
public func deepDive<DTO>( | |
_ target: MyTarget, | |
res: DTO.Type | |
) async -> Result<DTO, MoyaError> where DTO : Decodable { | |
return await selectProvider(target) | |
.request(AnyTarget(target)) | |
.flatMap { response in | |
unwrapThrowable { | |
try response | |
.filterSuccessfulStatusCodes() | |
.map(res, using: self.decoder) | |
} | |
} | |
} | |
public func deepDive<DTO>( | |
_ target: MyTarget, | |
res: DTO.Type | |
) async throws -> DTO where DTO : Decodable { | |
return try await selectProvider(target) | |
.request(AnyTarget(target)) | |
.filterSuccessfulStatusCodes() | |
.map(res, using: decoder) | |
} | |
private func unwrapThrowable<T>( | |
throwable: @escaping () throws -> T | |
) -> Result<T, MoyaError> { | |
do { | |
return .success(try throwable()) | |
} catch { | |
if let moyaError = error as? MoyaError { | |
return .failure(moyaError) | |
} else { | |
return .failure(.underlying(error, nil)) | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// SwiftUIView.swift | |
// | |
// | |
// Created by hhhello0507 on 9/10/24. | |
// | |
import Moya | |
/// Route | |
/// Moya.TargetType에서 method, path, task를 추상화 한 struct | |
/// | |
/// Ex. | |
/// Route.get() | |
/// Route.get("hello") | |
/// Route.get("hello").task(.requestJSONEncodable(...)) | |
public struct Route { | |
struct VoidRes: Decodable {} | |
public let method: Moya.Method | |
public let path: String | |
public let task: Moya.Task | |
private init(method: Moya.Method, path: String, task: Moya.Task) { | |
self.method = method | |
self.path = path | |
self.task = task | |
} | |
public static func connect(_ path: String) -> Self { | |
Self.init(method: .connect, path: path, task: .requestPlain) | |
} | |
public static func delete(_ path: String = "") -> Self { | |
Self.init(method: .delete, path: path, task: .requestPlain) | |
} | |
public static func get(_ path: String = "") -> Self { | |
Self.init(method: .get, path: path, task: .requestPlain) | |
} | |
public static func head(_ path: String = "") -> Self { | |
Self.init(method: .head, path: path, task: .requestPlain) | |
} | |
public static func options(_ path: String = "") -> Self { | |
Self.init(method: .options, path: path, task: .requestPlain) | |
} | |
public static func patch(_ path: String = "") -> Self { | |
Self.init(method: .patch, path: path, task: .requestPlain) | |
} | |
public static func post(_ path: String = "") -> Self { | |
Self.init(method: .post, path: path, task: .requestPlain) | |
} | |
public static func put(_ path: String = "") -> Self { | |
Self.init(method: .put, path: path, task: .requestPlain) | |
} | |
public static func query(_ path: String = "") -> Self { | |
Self.init(method: .query, path: path, task: .requestPlain) | |
} | |
public static func trace(_ path: String = "") -> Self { | |
Self.init(method: .trace, path: path, task: .requestPlain) | |
} | |
public func task(_ task: Moya.Task) -> Self { | |
Self.init(method: method, path: path, task: task) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment