네트워크 레이어 진화 과정

1단계: Requestable 프로토콜 설계

팀원과 페어 프로그래밍으로 네트워크 레이어의 기반을 설계했습니다.

HttpMethod 열거형

enum HttpMethod: String {
    case get = "GET"
    case post = "POST"
    case put = "PUT"
    case delete = "DELETE"
}

Requestable 프로토콜

protocol Requestable {
    associatedtype Response: Decodable

    var baseURL: String { get }
    var httpMethod: HttpMethod { get }
    var path: String { get }
    var queryItems: [URLQueryItem] { get }
    var headers: [String: String] { get }

    func makeURLRequest() -> URLRequest?
}

NetworkError 열거형

enum NetworkError: Error {
    case invalidURL
    case urlSessionFailed(Error)
    case invalidResponse
    case serverError(code: ServerErrorCode, message: String?)
    case emptyData
    case encodingFailed(Error)
    case decodingFailed(Error)
    case unknown(Error?)
}

2단계: 하드코딩 방식의 API 연동

빠른 기능 구현을 위해 URLSession을 직접 사용하는 방식을 선택했습니다.

LoginManager 구현

final class LoginManager {
    static let shared = LoginManager()
    private init() {}

    func login(username: String, password: String, completion: @escaping (Result<Token, Error>) -> Void) {
        // 1. URLRequest 생성 (하드코딩)
        let url = URL(string: "<https://popcorn.store/login>")!
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")

        // 2. JSON 요청 바디 생성
        let requestBody: [String: String] = [
            "username": username,
            "password": password
        ]
        request.httpBody = try? JSONSerialization.data(withJSONObject: requestBody)

        // 3. URLSession 요청
        let task = URLSession.shared.dataTask(with: request) { data, response, error in
            // 4. 에러 처리
            if let error = error {
                completion(.failure(error))
                return
            }

            // 5. HTTP 응답 검증
            guard let httpResponse = response as? HTTPURLResponse,
                  let data = data else {
                completion(.failure(NSError(domain: "InvalidResponse", code: -1)))
                return
            }

            // 6. 상태 코드 검증
            if httpResponse.statusCode == 200 {
                // 7. JSON 디코딩
                do {
                    let loginResponse = try JSONDecoder().decode(LoginResponse.self, from: data)
                    completion(.success(loginResponse.data))
                } catch {
                    completion(.failure(error))
                }
            } else {
                completion(.failure(NSError(domain: "LoginFailed", code: httpResponse.statusCode)))
            }
        }

        task.resume()
    }
}

하드코딩 방식의 문제점