URLSessino을 사용하다 보면 boiler plate 코드가 너무 많다.
var urlRequest = URLRequest(url: URL(string: "http://localhost:8080/")!)
urlRequest.httpMethod = "GET"
urlRequest.timeoutInterval = TimeInterval(5)
urlRequest.httpBody = Data()
URLSession.shared.dataTask(with: urlRequest) { data, response, error in
if let error = error {
// error
}
if let response = response {
// response
}
}.resume()
일반적으로 URLSession을 통해 통신을 할 때 위와 같이 사용한다.
코드를 URLRequest를 설정하는 부분과 URLSession을 사용하는 부분으로 나누어서 생각해보자.
extension URLRequest {
init<Body: Encodable> (url: URL, method: HttpMethod<Body>) {
self.init(url: url)
self.timeoutInterval = TimeInterval(10)
switch method {
case .get:
self.httpMethod = "GET"
case .post(let body):
self.httpMethod = "POST"
self.httpBody = try? JSONEncoder().encode(body)
case .put(let body):
self.httpMethod = "PUT"
self.httpBody = try? JSONEncoder().encode(body)
case .patch(let body):
self.httpMethod = "PATCH"
self.httpBody = try? JSONEncoder().encode(body)
case .delete(let body):
self.httpMethod = "DELETE"
self.httpBody = try? JSONEncoder().encode(body)
}
}
}
GET 메소드는 body가 필요가 없지만 나머지 메소드들은 body를 필요로 하므로 메소드 설정을 해주었다.
또 body를 설정하는 과정에서 인코딩을 해야하므로 body의 제네릭 타입은 encodable이어야 한다.
추가로 헤더를 넣거나 하려면 해당 init코드 내부에서 설정하면 된다.
let getRequest = URLRequest(url: URL(string: "http://localhost:8080/")!, method: .get)
let postRequest = URLRequest(url: URL(string: "http://localhost:8080/")!, method: .post(body))
이후 위와같이 사용을 하면 자동으로 timeout, method, body가 설정이 된다. (body는 Encodable 채택)
extension URLSession
func request<T: Decodable>(_ urlRequest: URLRequest, completion: @escaping(T?, Error?) -> Void) {
dataTask(with: urlRequest) { data, response, error in
if let error = error {
print("error: \(error.localizedDescription)")
completion(nil, error)
}
if let response = response as? HTTPURLResponse,
(200..<300).contains(response.statusCode),
let data = data {
print("URLSession data: \(String(describing: data))")
let decodedData = try? JSONDecoder().decode(T.self, from: data)
completion(decodedData, nil)
}
}.resume()
}
}
URLSession으로 통신하는데 사용할 URLRequest, completion을 인자로 받는 request 함수를 만들고, 함수 내부에서 dataTask를 실행시킨다.
completion은 (T, Error)로 이루어진 튜플이므로 dataTask 내부의 error handling 부분과 response handling 부분에서 모두 (T, Error)의 튜플 형태로 이루어지도록 한다.
또한 response에서 T의 형태로 디코딩을 해야하므로 T는 Decodable 프로토콜을 준수해야 한다.
struct User: Codable {
let name: String
let age: Int
}
let user = User(name: "charlie", age: 20)
let req = URLRequest(url: URL(string: "")!, method: HttpMethod<User>.post(user))
URLSession.shared.request(req) { data, error in
// code
}
URLRequest를 만들어놓은 생성자를 이용하여 간편히 request를 만들고,
위에 만들어 놓은 request 함수를 사용하여 URLSession 통신 결과 데이터에 상관없이 parsing된 값을 얻을 수 있다.