Swift

Swift URLSession 공통화 하기

hyeonii_12 2021. 4. 18. 15:10
반응형

Swift URLSession 공통화 하기

오늘은 제가 지난 글에서 말씀드렸던 네트워크 통신 부분 공통화에 대해 말씀드리려고 합니다.
이번 글은 iOS의 기본 통신 모듈인 URLSession을 이용한 예시입니다.


일반적으로 우리가 URLSession을 쓰는 방법은 아래와 같습니다.

var urlRequest = URLRequest(url: URL(string: "http://localhost:8080/")!)
urlRequest.httpMethod = "GET"
urlRequest.timeoutInterval = TimeInterval(10)
urlRequest.httpBody = Data()
        
URLSession.shared.dataTask(with: urlRequest) { data, response, error in
    if let error = error {
        // error handling
    }
            
    if let response = response {
        // response handling
    }
}.resume()

그리고 이러한 코드들이 거의 모든 통신 부분에서 반복되기 때문에 공통화를 하고자 했습니다.

URLRequest를 설정하는 부분과 URLSession을 이용해서 통신하는 부분을 나눠서 진행해보도록 하겠습니다.

 

#1. URLRequest 공통화하는 방법

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)
        }
    }
}

새로운 initializer를 만들어서 타임아웃 시간을 설정했습니다.
그 밑에는 이전 글에서 만들었던 HttpMethod enum을 활용해서 각 method를 설정했습니다.
GET은 body가 없지만 나머지 method는 body가 있기 때문에 해당 부분에 대한 설정도 해야합니다.
또한 바디를 설정하는 과정에서 인코딩을 해야하기 때문에 Body라는 Generic type은 Encodable이어야 합니다.

(추가적으로 헤더 필드를 넣는다거나 하려면 해당 initializer 내부에서 설정하시면 됩니다.)

따라서

let getRequest = URLRequest(url: URL(string: "http://localhost:8080/")!, method: .get)
let postRequest = URLRequest(url: URL(string: "http://localhost:8080/")!, method: .post(body))

이렇게 리퀘스트를 설정하고 나면 자동으로 타임아웃과 메소드, 바디까지 설정이 되는 것입니다.
물론 post에서 설정된 body는 Encodable을 준수하는 데이터여야 합니다.

 

#2. URLSession 공통화하는 방법

이번에는 URLSession 부분을 공통화 해 보도록 하겠습니다.

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을 공통화하는 함수 이름은 request로 설정했습니다. 통신하는데에 사용할 URLRequest와 completion 부분을 인자로 받도록 하겠습니다.

우리가 공통화할 함수 dataTask를 실행시킵니다.
completion이 (T, Error)로 이루어진 튜플이기 때문에 모든 에러 처리와 완료 처리를 해당 튜플의 형태로 이루어지도록 합니다.
완료 처리 부분에서 T의 형태로 디코딩을 해야하기 때문에 T는 Decodable의 형태가 되어야 합니다.

 

#3. 사용 방법

만들어 놓은 공통 함수들을 사용하는 방법은 간단합니다.

struct User: Codable {
	let name: String
	let age: Int
}

let user = User(name: "hyeoni", age: 0)
let req = URLRequest(url: URL(string: "")!, method: HttpMethod<User>.post(user))
URLSession.shared.request(req) { data, error in
    //
}

먼저 URLRequest를 우리가 만들어놓은 생성자를 이용하여 생성합니다.

여기서 중요한 점은 method입니다.
우리가 지난번에 HttpMethod의 Body를 Generic으로 선언하였기 때문에 저희가 직접 만든 User라는 구조체를 post의 body로 사용하겠다고 할 수 있는 것입니다.

두 번째로 URLSession 부분을 보자면 data와 error 처리를 각각 if-let으로 처리해야하기 때문에 기존의 dataTask 부분과 별로 다를 것이 없어 보입니다.
하지만 여기서 리턴되는 data는 원하는 구조체로 파싱이 되어있는 형태이기 때문에 바로 data.name 등의 형태로 사용할 수 있다는 장점을 가집니다. 또한 Error의 형태가 Swift에서 제공하는 형태로 그대로 가져가지 않고 자체 Error 구조체를 만들어서 사용하는 경우 등에는 해당 부분들도 한 번 더 감싸서 보여줄 수 있기 때문에 더 편리하다고 볼 수 있습니다.


이렇게 통신 모듈을 공통화 함에 따라 우리는

1. HttpMethod에 상관없이 어떠한 형태의 Body가 들어가도 URLRequest를 만들 수 있음.
	+) 타임아웃 설정 등도 공통적으로 처리할 수 있음.

2. URLSession 통신의 결과 데이터에 상관없이 parsing 된 값을 받을 수 있음.

이런 효과를 얻을 수 있게 되었습니다.

반응형