URLSession
요청을 하는 하나하나가 Task라고 해서 URLSessionTask
CompletionHandler를 통해서 클로저 구문에서 네트워크 통신이 일어나게 됨
[1] URLSession
일반적으로는 shared를 가장 많이 쓰는데 default 값이라 응답은 클로저로 받고, 커스텀이 불가능해서 백그라운드 다운로드가 불가하다.
만약 커스텀을 하고 싶다면, 초기화 구문의 configuration을 통해서 가능하다.
< configuration 종류 >
1). 기본값은 default인데 URLSession.shared과 설정이 유사하지만
- 커스텀이 가능하고(셀룰러 연결 여부를 캐치해서 사용자한테 "너 LTE 쓸 건데 괜찮아?"라고 물어볼 수 있음,
- timeout 간격(ex. 서버에게 요청하고 5초가 지났는데 응답이 오지 않으면 사용자에게 "다시 시도해달라"라는 메시지를 보내는 것) 등을 설정할 수 있음),
- 응답은 클로저와 딜리게이트 둘 다에서 받을 수 있다.
2). 보안과 관련해서 정보를 휘발시킬 수 있는 ephemeral
3). 사용자가 앱을 사용하고 있지 않더라도 백그라운드에서 작업이 가능한 background
[2] Task
어떻게 request를 할 거냐에 따라 URLSessionTask 부분에서 결정
1). 목적에 따라 종류가 달라지는데, 가장 많이 사용하는 게 URLSessionDataTask
2). 넷플릭스에서 오프라인으로 저장하는 기능 같은 백그라운드 다운로드를 할 건지 URLSessionBackgroundTask
크롬의 secret mode 같은 정보가 남지 않는 경우에서 할 건지
3). 구글드라이브에 기획서와 같은 파일을 업로드하고 싶을 때는 URLSessionUploadTask
dataTask 부분이 알라모에서 request 메소드 부분과 같음
[3] Response
1). handler - 클로저를 통해서
응답이 빨라서 간단한 처리인 경우
2). URLSessionDelegate / URLSessionTaskDelegate / URLSessionDataDelegate
10초가 걸리는 네트워크 통신이 있는데, 요청하고 응답하는 시간 동안 10초 내내 기다려야 했는데
이걸 통해서 프로그래스바나 그 사이에 발생하는 에러 등을 추가로 덧붙이기 위해서 사용할 때 사용하는 것이 Delegate
왜 클로저로 2개의 값을 보내는 걸까?
통신이 성공해서 데이터를 받아올 수도 있지만, 실패할 가능성도 있기 때문에 APIError?를 보내는 것이다.
스위프트에서 말하는 에러 핸들링 방법에서의 그 Error 프로토콜...!
URLSession에서 네트워크 통신을 하는 순간 background thread로 동작하도록 만든다.
main이 아니라 global로 작업을 하는 것임
따라서 뷰컨에서 UI 관련 작업은 main thread에서 하라는 경고창이 뜰 거다.
그래서 해당 API 코드를 필요할 때마다 호출하면 DispatchQueue.main.async { ~ } 의 반복이 나타날 건데...
애초에 통신 할 때 클로저 구문에서 main 스레드로 비동기처리를 해주면 된다.
URLComponent
url을 URLComponent라는 구조체를 통해서 다른 형식으로 써줄 수 있다.
이건 또 제법 신기하다. 네트워크 공부하면서 나온 개념들 scheme, host, path, query,,
반복되는 APIManager 코드를 개선해보기
매번 APIManager를 만들 때마다 같은 코드가 반복되는 것을 개선시켜주기 위해서 아래와 같이 함수를 만들어준다.
extension URLSession {
typealias completionHandler = (Data?, URLResponse?, Error?) -> Void
@discardableResult
func customDataTask(_ endpoint: URLRequest,
completionHandler: @escaping completionHandler) -> URLSessionDataTask {
let task = dataTask(with: endpoint, completionHandler: completionHandler)
task.resume()
return task
}
// T에는 string, int 이런 것들이 들어가는 게 아니라 Codable을 채택한 아이들만 들어갈 수 있음
static func request<T: Codable>(_ session: URLSession = .shared,
endpoint: URLRequest,
completion: @escaping (T?, APIError?) -> Void) {
session.customDataTask(endpoint) { data, response, error in
DispatchQueue.main.async {
guard error == nil else {
print("Failed Request")
completion(nil, .failedRequest)
return
}
guard let data = data else {
print("No Data Returned")
completion(nil, .noData)
return
}
guard let response = response as? HTTPURLResponse else {
print("Unable Response")
completion(nil, .invalidResponse)
return
}
guard response.statusCode == 200 else {
print("Failed Response")
completion(nil, .failedRequest)
return
}
do {
let result = try JSONDecoder().decode(T.self, from: data)
print(result)
completion(result, nil)
} catch {
print(error)
completion(nil, .invalidData)
}
}
}
}
}
하나하나씩 먼저 이해해보자면,
✔️1단계)
기존 URLSession 코드는 아래와 같다.
- URLSession.shared를 받는 매개변수로 session이 들어가고
✔️2단계)
그 뒷부분의 dataTask(with: ~, completionHandler: ~)을 대신해서 아래 customDataTask 함수를 만들어준다.
customDataTask 함수는
- URLReqeust를 받는 매개변수로 endpoint
- completionHandler를 받는 매개변수로 completionHandler (= typealias로 (Data?, URLResponse?, Error?) -> Void 임)
반환값은 URLSessionDataTask이기 때문에 1단계에서 session 뒤에 붙을 수 있는 거다.
근데 왜 customDataTask를 안써주냐고 경고창이 뜨는데
반환값을 안쓰겠다는 의미에서 @discardableResult를 달아주면 된다.
✔️3단계)
다시 request 함수로 돌아와서, 기존 APIManager에서 작성해준 DispatchQueue.main.async { ~ } 부분을 그대로 긁어다가
붙여넣어주면 된다.
근데 고쳐줘야 하는 지점은 모델과 completionHandler
LottoAPIManager라면 (Lotto?, APIError?) -> Void
PersonAPIManager라면 (Person?, APIError?) -> Void
모델 부분을 제네릭하게 만들어줘야 함
1). 모델은 Codable을 채택한 타입들만 들어갈 수 있기 때문에 Codable을 채택시킨 제네릭 타입을 넣어주고 (T.self)
2). completionHandler 부분에 알맞게 (T?, APIError?) -> Void 를 넣어준다.
'⭐️ 개발 > iOS & Swift' 카테고리의 다른 글
[iOS] 다국어 지원 i18n, l10n (1) | 2022.09.06 |
---|---|
[iOS] 안간단한 MVVM 안간단하게 톺아보기 (3) | 2022.08.31 |
[Swift] Codable (2) | 2022.08.29 |
[Swift] Error Handling (4) | 2022.08.26 |
[Swift] WMO - Swift 성능 최적화 (1) | 2022.08.26 |