프로젝트 진행 중 URLSession을 이용한 REST API 연동 작업을 수행하게 되었다.
전체적인 네트워크 통신 구현은 다음과 같이 진행된다.
protocol Endpoint {
var scheme: String { get }
var host: String { get }
var path: String { get }
var method: RequestMethod { get }
var query: [String: String]? { get }
var header: [String: String]? { get }
var body: [String: String]? { get }
}
extension Endpoint {
var scheme: String {
return "http"
}
}
enum RequestMethod: String {
case get = "GET"
case post = "POST"
}
enum RequestError: Error {
case decode
case invalidURL
case noResponse
case unauthorized
case unexpectedStatusCode
case unknown
var message: String {
switch self {
case .decode:
return "Decode Error"
case .invalidURL:
return "Session expired"
case .noResponse:
return "noResponse"
case .unauthorized:
return "unauthorized"
case .unexpectedStatusCode:
return "unexpected"
case .unknown:
return "unknown"
}
}
}
enum AppEndPoint {
case kakaoLogin(code: String)
case appleLogin(identityToken: String)
case test
}
extension AppEndPoint: Endpoint {
var host: String {
switch self {
case .kakaoLogin, .appleLogin, .test:
return "app.shop"
}
}
var path: String {
switch self {
case .kakaoLogin:
return "/auth/kakao"
case .appleLogin:
return "/auth/apple/"
case .test:
return "/health/"
}
}
var query: [String : String]? {
switch self {
case .kakaoLogin(let code):
return ["code": code]
default:
return nil
}
}
var method: RequestMethod {
switch self {
case .appleLogin:
return .post
case .kakaoLogin, .test:
return .get
}
}
var header: [String : String]? {
switch self {
case .kakaoLogin, .appleLogin, .test:
return [
"accept": "application/json",
"Content-Type": "application/json"
]
}
}
var body: [String : String]? {
switch self {
case .appleLogin(let identityToken):
return [
"identityToken": identityToken,
]
default:
return nil
}
}
}
import Foundation
protocol HTTPClient {
func request<T: Decodable>(endpoint: Endpoint, responseModel: T.Type) async -> Result<T, RequestError>
}
extension HTTPClient {
func request<T:Decodable>(
endpoint: Endpoint,
responseModel: T.Type
) async -> Result<T, RequestError> {
var urlComponents = URLComponents()
urlComponents.scheme = endpoint.scheme
urlComponents.host = endpoint.host
urlComponents.path = endpoint.path
if let query = endpoint.query {
urlComponents.queryItems = dictToQueryItems(dict: query)
}
guard let url = urlComponents.url else {
return .failure(.invalidURL)
}
var request = URLRequest(url: url)
request.httpMethod = endpoint.method.rawValue
request.allHTTPHeaderFields = endpoint.header
if let body = endpoint.body {
request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: [])
}
do {
let (data, response) = try await URLSession.shared.data(for: request, delegate: nil)
guard let response = response as? HTTPURLResponse else {
return .failure(.decode)
}
switch response.statusCode {
case 200...299:
guard let decodeResponse = try? JSONDecoder().decode(responseModel, from: data) else {
return .failure(.decode)
}
return .success(decodeResponse)
case 401:
return .failure(.unauthorized)
default:
return .failure(.unexpectedStatusCode)
}
} catch(let error) {
print(error)
return .failure(.unknown)
}
}
func dictToQueryItems(dict: [String: String]?) -> [URLQueryItem]? {
var queryItems: [URLQueryItem] = []
for (key, value) in dict ?? [:] {
queryItems.append(URLQueryItem(name: key, value: value))
}
return queryItems
}
}
import Foundation
protocol AppServicable {
func postAppleLogin(identityToken: String) async -> Result<AppleLoginResModel, RequestError>
func test() async -> Result<String, RequestError>
}
class AppService: AppServicable, HTTPClient {
static let shared = AppService()
private init() {}
func postAppleLogin(identityToken: String) async -> Result<AppleLoginResModel, RequestError> {
return await request(endpoint: AppEndPoint.appleLogin(identityToken: identityToken), responseModel: AppleLoginResModel.self)
}
func getKakaoUserToken(code: String) async -> Result<KakaoLoginResModel, RequestError> {
return await request(endpoint: AppEndPoint.kakaoLogin(code: code), responseModel: KakaoLoginResModel.self)
}
func test() async -> Result<String, RequestError> {
return await request(endpoint: AppEndPoint.test, responseModel: String.self)
}
}