iOS κ°λ°μμ λ€νΈμν¬ μμ²μ μ²λ¦¬ν λ, URLSession
μ μ§μ μ¬μ©νλ κ²μ λ²κ±°λ‘μ΄ μμ
μ΄ λ μ μμ΅λλ€. μ΄λ₯Ό λ κ°κ²°νκ³ ν¨μ¨μ μΌλ‘ λ§λ€κΈ° μν΄ λ§μ κ°λ°μλ€μ΄ Moyaλ₯Ό νμ©νκ³ μμ΅λλ€. Moyaλ Alamofire κΈ°λ°μ λ€νΈμν¬ λΌμ΄λΈλ¬λ¦¬λ‘, API μμ²μ λ³΄λ€ κ΅¬μ‘°μ μΌλ‘ κ΄λ¦¬ν μ μλλ‘ λμμ€λλ€.
νμ§λ§ κΈ°μ‘΄ Moyaλ completion handler κΈ°λ°μ λΉλκΈ° μ²λ¦¬λ₯Ό μ¬μ©νκΈ° λλ¬Έμ, Swiftμ μ΅μ Concurrency(λΉλκΈ°/λμμ±) κΈ°λ₯κ³Ό μμ°μ€λ½κ² μ΄μΈλ¦¬μ§ μλ λ¬Έμ κ° μμ΅λλ€. λ€ννλ, Swiftμ async/await
μ νμ©νλ©΄ λ€νΈμν¬ μμ²μ ν¨μ¬ λ μ§κ΄μ μΌλ‘ μμ±ν μ μμΌλ©°, κ°λ
μ±κ³Ό μ μ§λ³΄μμ±μ ν¬κ² ν₯μμν¬ μ μμ΅λλ€.
μ΄ κΈμμλ Moyaμ Swift Concurrencyλ₯Ό κ²°ν©νμ¬ λ€νΈμν¬ μμ²μ λμ± κΉλνκ³ ν¨μ¨μ μΌλ‘ μ²λ¦¬νλ λ°©λ²μ μκ°ν©λλ€. withCheckedContinuation
κ³Ό withCheckedThrowingContinuation
μ νμ©νμ¬ κΈ°μ‘΄ Moyaμ completion κΈ°λ° APIλ₯Ό async/await
λ°©μμΌλ‘ λ³ννλ λ°©λ²μ μμλ³΄κ³ , μλ¬ νΈλ€λ§μ λ³΄λ€ μ μ°νκ² μ²λ¦¬ν μ μλ μ λ΅λ ν¨κ» λ€λ€λ³΄κ² μ΅λλ€. π
Result
νμ
μ΄λ Error
λ₯Ό μ¬μ©νμ§ μλ κ²½μ°)continuation.resume(returning:)
μ μ¬μ©ν΄ κ°μ λ°νfunc fetchData(completion: @escaping (String) -> Void) {
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
completion("Hello, Swift Concurrency!")
}
}
func fetchDataAsync() async -> String {
return await withCheckedContinuation { continuation in
fetchData { result in
continuation.resume(returning: result)
}
}
}
Task {
let result = await fetchDataAsync()
print(result) // "Hello, Swift Concurrency!"
}
fetchData(completion:)
μ κΈ°μ‘΄μ μ½λ°± κΈ°λ° ν¨μfetchDataAsync()
μμ withCheckedContinuation
μ μ¬μ©ν΄ async ν¨μλ‘ λ³νcontinuation.resume(returning:)
μ νΈμΆν΄ λ°μ΄ν°λ₯Ό async ν¨μμ κ²°κ³Όλ‘ λ°νcompletionHandler
κ° Result<T, Error>
λλ (T?, Error?)
ννλ‘ κ°μ λ°ννλ κ²½μ° μ ν©continuation.resume(throwing:)
μ μ¬μ©ν΄ μ€λ₯ μ λ¬ κ°λ₯enum NetworkError: Error {
case invalidResponse
}
func fetchDataWithError(completion: @escaping (String?, Error?) -> Void) {
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
let success = Bool.random()
if success {
completion("Success Data", nil)
} else {
completion(nil, NetworkError.invalidResponse)
}
}
}
func fetchDataAsyncWithError() async throws -> String {
return try await withCheckedThrowingContinuation { continuation in
fetchDataWithError { result, error in
if let error = error {
continuation.resume(throwing: error) // μ€λ₯ μ λ¬
} else if let result = result {
continuation.resume(returning: result) // μ μ λ°μ΄ν° λ°ν
} else {
continuation.resume(throwing: NetworkError.invalidResponse) // μμΈ μ²λ¦¬
}
}
}
}
Task {
do {
let result = try await fetchDataAsyncWithError()
print(result)
} catch {
print("Error:", error)
}
}
fetchDataWithError(completion:)
μ μ±κ³΅/μ€ν¨ μ¬λΆμ λ°λΌ λ°μ΄ν°λ₯Ό λ°ννλ κΈ°μ‘΄μ μ½λ°± κΈ°λ° ν¨μfetchDataAsyncWithError()
μμλ withCheckedThrowingContinuation
μ μ¬μ©ν΄ async/await λ²μ μΌλ‘ λ³νcontinuation.resume(throwing:)
μ μ¬μ©ν΄ μ€λ₯λ₯Ό λμ§withCheckedContinuation | withCheckedThrowingContinuation | |
---|---|---|
μ€λ₯ μ²λ¦¬ | Error λ₯Ό λμ§ μ μμ | Error λ₯Ό λμ§ μ μμ |
resume λ°©μ | continuation.resume(returning:) | continuation.resume(returning:) λλ continuation.resume(throwing:) |
μ¬μ© μμ | λ¨μ λΉλκΈ° μ½λ°± λ³ν | Result<T, Error> λλ (T?, Error?) ννμ λΉλκΈ° μ²λ¦¬ |
resume(_:)
μ λ°λμ ν λ²λ§ νΈμΆν΄μΌ ν¨
resume(returning:)
λλ resume(throwing:)
μ λ λ² μ΄μ νΈμΆνλ©΄ λ°νμ μ€λ₯ λ°μif let
/ else if let
μ μ¬μ©νμ¬ μ€λ³΅ νΈμΆ λ°©μ§Completion Handler κΈ°λ° APIκ° μ¬λ¬ λ² νΈμΆλμ§ μλμ§ νμΈ
URLSession
μ dataTask(with:)
λ λ€νΈμν¬ μμ²μ΄ μ·¨μλμμ λλ completionμ νΈμΆν μ μμMoya λΌμ΄λΈλ¬λ¦¬μ request
ν¨μλ λ€νΈμν¬ μμ²μ μννλ ν΅μ¬ λ©μλμ
λλ€.
μ΄ ν¨μλ TargetType
μ κΈ°λ°μΌλ‘ API μμ²μ λ§λ€κ³ , κ²°κ³Όλ₯Ό completion
ν΄λ‘μ λ₯Ό ν΅ν΄ λ°νν©λλ€.
open func request(
_ target: Target,
callbackQueue: DispatchQueue? = .none,
progress: ProgressBlock? = .none,
completion: @escaping Completion
) -> Cancellable
λ§€κ°λ³μ | νμ | μ€λͺ |
---|---|---|
target | Target | μμ²ν APIμ λμ(TargetTypeμ λ°λ₯΄λ νμ ) |
callbackQueue | DispatchQueue? | μλ΅μ μ²λ¦¬ν ν (κΈ°λ³Έκ° nil β λ΄λΆμ μΌλ‘ μ μ ν ν μ¬μ©) |
progress | ProgressBlock? | λ€μ΄λ‘λ/μ λ‘λ μ§ν μνλ₯Ό μΆμ ν ν΄λ‘μ (κΈ°λ³Έκ° nil) |
completion | @escaping Completion | μμ² μλ£ ν νΈμΆλλ ν΄λ‘μ (Result<Response, MoyaError> ννμ κ²°κ³Ό λ°ν) |
Cancellable
: μμ²μ μ·¨μν μ μλ κ°μ²΄λ₯Ό λ°νcancel()
μ νΈμΆνλ©΄ λ€νΈμν¬ μμ²μ μ€λ¨ν μ μμ)import Moya
extension MoyaProvider {
func request(
_ target: Target,
callbackQueue: DispatchQueue? = .none,
progress: ProgressBlock? = .none,
completion: @escaping Completion
) async -> Result<Moya.Response, MoyaError> {
await withCheckContinuation { continuation in
self.request(
target,
callbackQueue: callbackQueue,
progress: progress,
) { result in
continuation.resume(returning: result)
}
}
}
}
withCheckedContinuation
μ μ¬μ©νμ¬ λ€νΈμν¬ μμ²μ async
λ°©μμΌλ‘ λ³νMoya
μ Completion
νμ
(Result<Response, MoyaError>
)μ κ·Έλλ‘ λ°νthrow
μμ΄ Result
νμ
μΌλ‘ κ²°κ³Όλ₯Ό μ λ¬νλ―λ‘, λ€νΈμν¬ κ³μΈ΅μμ ν λ² λ μλ¬ νΈλ€λ§ κ°λ₯λ€νΈμν¬ μμ²μ μνν ν, Result
λ₯Ό νμ©νμ¬ μλ¬λ₯Ό λͺ¨λ λ΄λΆμμ ν λ² λ κ°κ³΅νλλ‘ ν©λλ€.
class Network {
/// λ€νΈμν¬ μμ²μ μννκ³ , λ΄λΆμμ μλ¬ νΈλ€λ§ν ν λ°ν
func task<T: Decodable, E: TargetType>(_ target: E) async throws -> T {
let provider: MoyaProvider<E> = .init()
let result = await provider.request(target)
let parsed = self.parsing(T.self, result: result)
switch parsed {
case .success(let response):
return .success(response)
case .failure(let moyaError):
let networkError = self.moyaError(moyaError)
throw networkError
}
}
/// MoyaError β NetworkErrorλ‘ λ³ννλ λ©μλ
private func moyaError(_ error: MoyaError) -> NetworkError {
switch error {
case .statusCode(let response):
return .serverError(response.statusCode)
case .underlying(let nsError, _):
return .networkFailure(nsError.localizedDescription)
case .requestMapping, .parameterEncoding, .jsonMapping:
return .invalidRequest
@unknown default:
return .unknown
}
}
/// JSON Dataλ₯Ό νμ±νλ λ©μλ
private func parsing<T: Decodable>(_ type: T.Type, result: <Moya.Response, MoyaError>) -> Reseult<T, MoyaError> {
switch result {
case .success(let response):
if let data = try? JSONDecoder().decode(type, from: response.data) {
return .success(data)
} else {
return .failuer(.jsonMapping(response))
}
case .failure(let moyaError):
return .failure(moyaError)
}
}
}
/// 컀μ€ν
λ€νΈμν¬ μλ¬ μ μ
enum NetworkError: Error {
case serverError(Int) // μλ² μν μ½λ μ€λ₯
case networkFailure(String) // λ€νΈμν¬ μ€ν¨ (μΈν°λ· μ°κ²° λ¬Έμ λ±)
case invalidRequest // μλͺ»λ μμ²
case unknown // μ μ μλ μ€λ₯
}
Network
λ₯Ό ν΅ν΄ MoyaProvider
λ₯Ό κ°μΈμ μλ¬λ₯Ό ν λ² λ μ²λ¦¬νλ κ³μΈ΅μ λ§λ¦task(_:)
μμ request(_:)
νΈμΆ ν Result<Response, MoyaError>
λ₯Ό λμ½λ© νμ
μΌλ‘ λ³ννμ¬ λ¦¬ν΄ λλ MoyaError
λ₯Ό NetworkError
λ‘ λ§€ννμ¬ throw
parsing(_:result:)
λ₯Ό ν΅ν΄ Response
λ₯Ό T.Type
μΌλ‘ λμ½λ©moyaError(_:)
μ ν΅ν΄ MoyaError
λ₯Ό NetworkError
λ‘ λ§€νlet network = Network()
Task {
do {
let target: API = .getUserName(id: 124)
let value: String = try await network.task(target)
print("β
μ μ μ΄λ¦: \(value)")
} catch {
print("β λ€νΈμν¬ μ€λ₯: \(error)")
}
}
network.task(target)
μ νΈμΆνλ©΄ T.Type
μ λ°νdo-try-catch
λ¬Έμ μ¬μ©ν΄ λ€νΈμν¬ μλ΅μ μ²λ¦¬NetworkError
λ‘ λ³νλ μλ¬λ₯Ό catch
νμ¬ λ λͺ
νν μ€λ₯ λ©μμ§λ₯Ό μ 곡async/await
μ μ¬μ©νλ©΄μλ λ³΄λ€ μ²΄κ³μ μΌλ‘ μλ¬ μ²λ¦¬λ₯Ό νκ³ μΆλ€λ©΄ μ μ ν μ νμ΄ λ κ²μ
λλ€! π