-
Notifications
You must be signed in to change notification settings - Fork 0
Networking
JEONG edited this page Jun 29, 2026
·
3 revisions
Moya 기반 네트워크 레이어. Router 설계 규칙과 Response DTO 디코딩 규칙을 다룹니다.
API Router는 엔드포인트 메타데이터(Path / Method / Encoding)만 책임집니다. 파라미터 키 이름과 직렬화 규칙은 Request/Query DTO가 캡슐화합니다.
-
Router의
task안에 인라인 딕셔너리 금지 — key 문자열이 Router에 흩어지면 스펙 변경 시 모든 case를 뒤져야 한다. - Body →
EncodableDTO +.requestJSONEncodable(body) -
Query → DTO +
toParameters+.requestParameters(..., encoding: URLEncoding.queryString)직렬화 책임을var toParameters: [String: Any]computed property로 캡슐화. 네이밍은toParameters로 통일. -
Path 변수(
{id})는 case associated value로 받아path에서만 사용 — 쿼리/바디와 섞지 않는다. - 단일 path 변수만 받는 case는 DTO 불필요 — 파라미터 컬렉션이 생기는 순간 DTO 분리.
// Router가 파라미터 키 이름까지 알고 있음
case .getNoticeReadStatusList(_, let cursorId, let filterType, let organizationIds, let status):
return .requestParameters(
parameters: [
"cursorId": cursorId,
"filterType": filterType,
"organizationIds": organizationIds,
"status": status
],
encoding: URLEncoding.queryString
)// 1. Query/Body DTO 정의
struct NoticeReadStatusListQuery: Encodable {
let cursorId: Int
let filterType: String
let organizationIds: [Int]
let status: String
var toParameters: [String: Any] {
["cursorId": cursorId, "filterType": filterType,
"organizationIds": organizationIds, "status": status]
}
}
// 2. Router는 DTO만 받아 위임
case .getNoticeReadStatusList(_, let query):
return .requestParameters(parameters: query.toParameters, encoding: URLEncoding.queryString)
case .addLink(_, let body):
return .requestJSONEncodable(body)- Body:
Features/{Feature}/Data/DTO/Request/{Name}RequestDTO.swift - Query:
Features/{Feature}/Data/DTO/Request/{Name}Query.swift
서버는 응답으로 내려주는 모든 정수 값을 String으로 직렬화합니다.
Int로 선언된 필드를 그대로 디코딩하면 런타임 DecodingError.typeMismatch가 발생합니다.
- Response DTO의 모든
Int필드는 String 폴백을 보장한다 -
synthesized
Codable금지 —Int필드가 있으면 반드시 custominit(from:)+encode(to:)작성 -
decode(Int.self)/decodeIfPresent(Int.self)직접 호출 금지 —decodeIntFlexibleIfPresent헬퍼 사용 -
폴백 순서:
Int→String("123")→Double(123.0)→ throw -
Request DTO(Encodable, 보내는 쪽)는 제외 —
Int그대로 OK
struct UserDTO: Codable {
let userId: Int
let name: String
private enum CodingKeys: String, CodingKey { case userId, name }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
userId = try container.decodeIntFlexibleIfPresent(forKey: .userId) ?? 0
name = try container.decodeIfPresent(String.self, forKey: .name) ?? ""
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(userId, forKey: .userId)
try container.encode(name, forKey: .name)
}
}
private extension KeyedDecodingContainer {
func decodeIntFlexible(forKey key: Key) throws -> Int {
if let value = try? decode(Int.self, forKey: key) { return value }
if let value = try? decode(String.self, forKey: key), let intValue = Int(value) { return intValue }
if let value = try? decode(Double.self, forKey: key) { return Int(value) }
throw DecodingError.typeMismatch(Int.self, DecodingError.Context(
codingPath: codingPath + [key],
debugDescription: "Expected Int/String-number/Double for key '\(key.stringValue)'"))
}
func decodeIntFlexibleIfPresent(forKey key: Key) throws -> Int? {
if (try? decodeNil(forKey: key)) == true { return nil }
return try? decodeIntFlexible(forKey: key)
}
}-
Int/Int?/[Int]/Int64등 정수 타입 필드가 있는가 - 정수 필드가 있다면 custom
init(from:)정의 (synthesized Codable 금지) -
init(from:)안에서decodeIntFlexibleIfPresent사용 —decode(Int.self)직접 호출 없음 - 헬퍼가 파일 내
private extension KeyedDecodingContainer로 정의되어 있는가 - Request/Encodable DTO는 적용 제외 확인
관련 문서: Coding Conventions · Error Handling