Swift 6과 async/await 기반의 iOS 네트워크 라이브러리입니다.
HTTP 요청, Error 처리, Retry, Interceptor, Response Validation, Multipart Upload, Download를 한 흐름으로 다루기 위해 만들었습니다.
실무에서 반복적으로 다음 문제를 경험했습니다.
URLSession코드가 프로젝트마다 달라짐- Error 처리 방식이 통일되지 않음
- Header 추가가 중복됨
- Retry 구현이 프로젝트마다 다름
- 로그 출력 방식이 제각각
이 문제를 해결하기 위해 NetworkKit을 만들었습니다.
NetworkKit은 네트워크 레이어의 책임을 Endpoint, Client, Session, Interceptor, ResponseValidator, Monitor, Logger로 나누어 요청 정의와 실행 흐름을 분리합니다.
| 기능 | 설명 |
|---|---|
| async/await request | URLSession 기반 비동기 요청을 실행합니다. |
| Endpoint 추상화 | base URL, path, method, headers, parameters, encoder를 한 타입에 모읍니다. |
| Response Validation | Endpoint별 성공 status code와 content type 조건을 검증합니다. |
| Interceptor | 요청 전 adapt, 실패 후 retry를 처리합니다. |
| RetryPolicy | max attempts와 seconds 기반 backoff를 설정합니다. |
| Multipart Upload | 단일 UploadData 기반 multipart/form-data upload를 지원합니다. |
| Download | 파일을 메모리에 올리지 않고 지정 위치로 저장합니다. |
| Testable Session | API.Session protocol로 URLSession을 mock할 수 있게 했습니다. |
요청 흐름은 다음과 같습니다.
Endpoint
-> Client.request
-> Monitor
-> endpoint.asURLRequest()
-> Interceptor.adapt
-> Session.data(for:)
-> ResponseValidator.validate
-> JSONDecoder.decode
-> Decodable Model
업로드 흐름은 다음과 같습니다.
Endpoint.uploadData
-> multipart body 생성
-> Content-Type boundary 설정
-> Session.upload(for:from:)
-> ResponseValidator.validate
-> JSONDecoder.decode
다운로드 흐름은 다음과 같습니다.
Endpoint
-> Session.download(for:)
-> ResponseValidator.validate
-> temporary file 이동
-> DownloadResponse
Retry는 session/transport error 또는 HTTP status validation failure 발생 후 Interceptor가 결정합니다.
Session 실패 또는 retry 가능한 status code 실패
-> Interceptor.retry(...)
+-- .retry -> backoff -> newRequest로 재시도
+-- .doNotRetry(error) -> Error 반환
상세 설명은 Architecture 문서에서 확인할 수 있습니다.
Swift Package Manager로 추가합니다.
dependencies: [
.package(url: "https://github.com/HenryVoid/Network.git", branch: "main")
]targets: [
.target(
name: "YourTarget",
dependencies: [
.product(name: "NetworkKit", package: "Network")
]
)
]Xcode에서는 File > Add Package Dependencies...에서 https://github.com/HenryVoid/Network.git를 추가하고 NetworkKit product를 선택합니다.
Repository 이름은 Network, package/product 이름은 NetworkKit입니다.
Endpoint를 정의하고 Client에서 호출합니다.
import Foundation
import NetworkKit
struct UserResponse: Decodable {
let id: Int
let name: String
}
struct UserEndpoint: API.Endpoint {
let baseURL: any API.URLConvertible = "https://api.example.com"
let path: String? = "users/1"
let method: API.HTTPMethod = .get
let headers: API.HttpHeaders? = [
.init(key: "Accept", value: "application/json")
]
let parameters: API.Parameters? = nil
let encoder: any API.ParameterEncodable = API.URLParameterEncoder()
}
let client = API.Client(
session: URLSession.shared,
interceptors: [],
retryPolicy: .default
)
let user = try await client.request(
UserEndpoint(),
decode: UserResponse.self
)request는 async throws(API.Error)로 동작합니다. 성공하면 Decodable 모델을 반환합니다.
Endpoint별 성공 조건이 다르면 validator를 재정의합니다.
struct CreateUserEndpoint: API.Endpoint {
let baseURL: any API.URLConvertible = "https://api.example.com"
let path: String? = "users"
let method: API.HTTPMethod = .post
let headers: API.HttpHeaders? = [
.init(key: "Accept", value: "application/json")
]
let parameters: API.Parameters? = ["name": "Henry"]
let encoder: any API.ParameterEncodable = API.JSONParameterEncoder()
let validator = API.ResponseValidator(
acceptableStatusCodes: 201..<202,
acceptableContentTypes: ["application/json"]
)
}| 문서 | 내용 |
|---|---|
| Architecture | 전체 요청 흐름과 책임 분리 |
| Client | request/upload/download 실행 흐름 |
| Endpoint | 요청 정의 방식 |
| Session | URLSession 추상화와 테스트 주입 |
| Response Validation | status code, content type 검증 |
| Retry | RetryPolicy와 Interceptor.retry |
| Interceptor | request adapt와 retry 확장 지점 |
| Multipart Upload | 단일 파일 multipart upload |
| Download | 파일 download와 destination 이동 |
| Monitor | actor 기반 네트워크 상태 확인 |
| Logger | masking, body limit, output injection |
| Error Handling | API.Error 계층 |
| Encoding | JSON/URL parameter encoding |
| HTTP Header | header 모델과 request 적용 |
| URLConvertible | URL 변환 추상화 |
| Testing | Mock 기반 테스트 전략 |
| 타입 | 역할 |
|---|---|
API.Client |
request/upload/download 실행 |
API.Endpoint |
요청 정의 |
API.Session |
세션 추상화 |
API.Interceptor |
request adapt, retry 결정 |
API.ResponseValidator |
response 검증 |
API.RetryPolicy |
retry 횟수와 backoff 계산 |
API.DownloadDestination |
download 저장 위치와 overwrite 정책 |
API.DownloadResponse |
저장된 file URL과 response metadata |
API.Monitorable |
연결 상태 확인 |
API.Loggable |
logging 추상화 |
API.Error |
typed error 모델 |
Swift Testing으로 실패 상황과 요청 생성 흐름을 검증했습니다.
실패 테스트는 API.Error의 구체 case를 pattern matching으로 확인합니다.
| 테스트 대상 | 검증 내용 |
|---|---|
| Client Request | invalid URL, connection failure, session failure, decode failure, invalid status code, invalid URLResponse |
| Client Response Validation | custom status code success/failure, content type failure |
| Client Retry | session retry, HTTP status retry, retry exceeded, doNotRetry, cancellation |
| Client Upload | upload success, missing uploadData failure, upload failure |
| Client Download | download success, validation failure, existing file failure, overwrite success, retry success, HTTP status retry, cancellation |
| Endpoint | JSON parameter request, URL parameter request, default response validator |
| ResponseValidator | default/custom status code, content type match/mismatch, invalid URLResponse |
| HTTP Header | dictionary 변환, string 변환, URLRequest header 추가 |
| Interceptor | adapt로 header 추가 |
| JSON/URL Encoder | JSON body encoding, URL query item encoding |
| Logger | 민감 header masking, body 길이 제한, logging 비활성화 |
| Monitor | API.Monitor.shared 연결 상태 확인, status timeout |
| RetryPolicy | delay 계산, maxAttempts 최소값 보정 |
| URLConvertible | String to URL 변환, invalid URL failure |
테스트 파일 일부는 #if !os(macOS) 조건 안에 있어 iOS 테스트 환경을 기준으로 실행됩니다.
| 항목 | 설명 |
|---|---|
| Download progress/resume | progress, pause/resume, background download를 분리해 확장합니다. |
| Retry-After/Jitter | Retry-After header와 jitter 기반 backoff를 분리해 확장합니다. |
| Multipart form field | 단일 파일 외 일반 form field와 여러 파일 업로드를 지원합니다. |
| Metrics | 요청 시간, payload 크기, retry 횟수 같은 측정값을 노출합니다. |
| 항목 | 값 |
|---|---|
| Swift tools version | 5.9 |
| Swift language version | 6 |
| Platform | iOS 17+ |
| Package manager | Swift Package Manager |
| Dependencies | 없음 |