Skip to content

Commit 2e87e50

Browse files
authored
[NL-14]: 네트워크 통신 로직 구현 (#16)
* [NL-14]: Moya Async 인터페이스 추가 * [NL-14]: 네트워크 통신 로직 구현
1 parent fd6ea5a commit 2e87e50

File tree

8 files changed

+154
-2
lines changed

8 files changed

+154
-2
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/// The original source of this code is https://github.com/Moya/Moya/pull/2233.
2+
3+
import Foundation
4+
import Moya
5+
6+
final class AsyncMoyaRequestWrapper {
7+
8+
typealias MoyaContinuation = CheckedContinuation<Response, Error>
9+
10+
private let performRequest: (MoyaContinuation) -> Moya.Cancellable?
11+
private var cancellable: Moya.Cancellable?
12+
13+
init(_ performRequest: @escaping (MoyaContinuation) -> Moya.Cancellable?) {
14+
self.performRequest = performRequest
15+
}
16+
17+
func perform(continuation: MoyaContinuation) {
18+
cancellable = performRequest(continuation)
19+
}
20+
21+
func cancel() {
22+
cancellable?.cancel()
23+
}
24+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/// The original source of this code is https://github.com/Moya/Moya/pull/2233.
2+
3+
import Foundation
4+
import Moya
5+
6+
extension MoyaProvider {
7+
8+
func request(_ target: Target) async throws -> Response {
9+
let asyncRequestWrapper = AsyncMoyaRequestWrapper { [weak self] continuation in
10+
guard let self else {
11+
continuation.resume(throwing: NetworkError.unknown(nil))
12+
return nil
13+
}
14+
return self.request(target) { result in
15+
switch result {
16+
case .success(let response):
17+
continuation.resume(returning: response)
18+
case .failure(let moyaError):
19+
continuation.resume(throwing: moyaError)
20+
}
21+
}
22+
}
23+
24+
return try await withTaskCancellationHandler(operation: {
25+
try await withCheckedThrowingContinuation({ continuation in
26+
asyncRequestWrapper.perform(continuation: continuation)
27+
})
28+
}, onCancel: {
29+
asyncRequestWrapper.cancel()
30+
})
31+
}
32+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//
2+
// BaseTargetTyoe.swift
3+
// NetworkCore
4+
//
5+
// Created by ttozzi on 7/24/25.
6+
//
7+
8+
import Foundation
9+
import Moya
10+
11+
public protocol BaseTargetType<Response>: TargetType {
12+
associatedtype Response: Decodable
13+
}
14+
15+
extension BaseTargetType {
16+
public var baseURL: URL {
17+
return URL(string: "https://satto.io.kr")! // TODO: 수정 필요
18+
}
19+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//
2+
// NetworkError.swift
3+
// NetworkCore
4+
//
5+
// Created by ttozzi on 7/20/25.
6+
//
7+
8+
import Foundation
9+
10+
public enum NetworkError: Error {
11+
case noInternet
12+
case timeout
13+
case serverError(statusCode: Int)
14+
case invalidResponse
15+
case decodingError
16+
case unknown(Error?)
17+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//
2+
// NetworkProvider.swift
3+
// NetworkCore
4+
//
5+
// Created by ttozzi on 7/20/25.
6+
//
7+
8+
import Foundation
9+
import Moya
10+
11+
public final class NetworkProvider {
12+
13+
public static let shared: NetworkProvider = .init(internalProvider: MoyaProvider<MultiTarget>())
14+
private let internalProvider: MoyaProvider<MultiTarget>
15+
16+
public init(internalProvider: MoyaProvider<MultiTarget>) {
17+
self.internalProvider = internalProvider
18+
}
19+
20+
public func request<T: BaseTargetType>(target: T) async throws -> T.Response {
21+
// TODO: Reachability 확인 필요할지
22+
do {
23+
let responseData = try await internalProvider.request(MultiTarget(target))
24+
let response = try JSONDecoder().decode(T.Response.self, from: responseData.data)
25+
return response
26+
} catch {
27+
throw mapToNetworkError(error)
28+
}
29+
}
30+
31+
private func mapToNetworkError(_ error: Error) -> NetworkError {
32+
if let moyaError = error as? MoyaError {
33+
switch moyaError {
34+
case .underlying(let nsError as NSError, _):
35+
if nsError.domain == NSURLErrorDomain {
36+
switch nsError.code {
37+
case NSURLErrorNotConnectedToInternet:
38+
return .noInternet
39+
case NSURLErrorTimedOut:
40+
return .timeout
41+
default:
42+
return .unknown(nsError)
43+
}
44+
} else {
45+
return .unknown(nsError)
46+
}
47+
case .statusCode(let response):
48+
return .serverError(statusCode: response.statusCode)
49+
case .requestMapping, .parameterEncoding, .objectMapping, .encodableMapping:
50+
return .invalidResponse
51+
default:
52+
return .unknown(moyaError)
53+
}
54+
} else if error is DecodingError {
55+
return .decodingError
56+
} else {
57+
return .unknown(error)
58+
}
59+
}
60+
}

Core/NetworkCore/Sources/empty.swift

Whitespace-only changes.

Tuist/Package.resolved

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Tuist/Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import PackageDescription
1818
let package = Package(
1919
name: "Satto",
2020
dependencies: [
21-
.package(url: "https://github.com/Moya/Moya.git", from: "15.0.0"),
21+
.package(url: "https://github.com/Moya/Moya.git", from: "15.0.3"),
2222
.package(url: "https://github.com/devxoul/Then.git", from: "3.0.0"),
2323
.package(url: "https://github.com/SnapKit/SnapKit.git", from: "5.7.1"),
2424
.package(url: "https://github.com/Swinject/Swinject.git", from: "2.9.1"),

0 commit comments

Comments
 (0)