Kingfisher, Alamofire, Moya 많이들 사용하시나요? 저도 사용하고 있습니다만..
얘네를 사용하는 이유가 URLSession, NSCache등을 사용할 때 번거로움이 많아서잖아요? 근데 제가 그 번거로움을 못 느껴본 것 같아서 이참에 직접 구현하며 경험해보고 정말 어떤 점이 좋은 건지 공부해보려고 해요.
URLSession을 먼저 알아볼게요
URLSession?
HTTP/HTTPS를 통해 콘텐츠 및 데이터를 주고받기 위해 애플에서 만든 서버 통신 API 클래스.
HTTP/HTTPS외에도 인증, 쿠키 관리, 캐시 등도 지원한대요(사실 메서드 되게 많으니까 definition 참고하는게 더 좋을듯).
URLSession은 기본적으로 비동기로 구현이 되어있어요. 그래서 구현 할 때 별도의 비동기 처리는 필요하지 않습니다!
하지만 통신시 일어나는 UI처리가 있을 수 있겠죠?! UI 관련 처리는 모두 Main Thread에서 이루어져야하기 때문에 DispatchQueue.main.sync 내부에서 처리해야 됨을 유의해야합니다!(사실 어차피 Xcode가 보라색 알림문구로 알려주긴함)
URLSession을 사용하는 순서?
- URLSessionConfiguration을 설정하고, Session을 생성합니다.
- 통신할 URL과 Request 객체를 준비합니다.
- 사용할 Task를 결정하고, Completion Handler / Delegate 메소드를 작성합니다.
- 간단한 통신: Completion Handler , Background 상태일때에도 동작: Delegate 패턴을 주로 사용
- Task를 실행합니다.
- Task 완료 후 Completion Handler를 실행합니다.
제가 연습한 코드로 살펴보면..!
import Foundation
struct TestService {
static let shared = CAFETIService()
// MARK: - baseURL + path
let baseURL = "http://유.알.엘/"
var urlString: String { get { baseURL + "패.쓰" } }
let token = "토.큰"
func fetchTestData(answers: [Int], completion: @escaping (NetworkResult<모델>) -> ()) {
if let url = URL(string: urlString) {
// MARK: 1.URLSessionConfiguration을 설정하고, Session을 생성합니다.
let session = URLSession(configuration: .default)
// MARK: 2.통신할 URL과 Request 객체를 준비합니다.
var requestURL = URLRequest(url: url)
requestURL.setValue("Application/json", forHTTPHeaderField: "Content-Type")
requestURL.setValue(token, forHTTPHeaderField: "token")
requestURL.httpMethod = "POST"
let body = ["answers": answers]
guard let bodyData = try? JSONSerialization.data(withJSONObject: body, options: []) else { return }
requestURL.httpBody = bodyData
// MARK: 3.사용할 Task 를 결정하고, 그에 맞는 Completion Handler, 혹은 Delegate 메소드를 작성합니다.
let dataTask = session.dataTask(with: requestURL) { (data, response, error) in
if error != nil {
print(error!)
return
}
if let bindingData = data {
do {
let decodedData = try JSONDecoder().decode(모델.self, from: bindingData)
// MARK: 5.Task 완료 후 Completion Handler를 실행합니다.
completion(.success(decodedData))
} catch {
print(error.localizedDescription)
}
}
}
// MARK: 4.Task를 실행합니다.
dataTask.resume()
}
}
}
순서는 이렇고 이제 1번부터 생소한 용어를 짚어가면서 살펴볼게요!
1. URLSessionConfiguration을 설정하고, Session을 생성한다.
우리는 URL과 Request객체를 가지고 통신을 할거에요
이때, 통신을 위해서는 URLSession 세션 객체를 생성해야합니다. 이는 URLSessionConfiguration을 통해 생성할 수 있어요.
URLSessionConfiguration이 제공하는 세션 객체의 유형은 아래의 "3가지"입니다.
아 그 전에 하나 유의해야 할 점이 한 가지 있어요. URLSession의 definition에 보면 configuration이 @NSCopying을 준수하고 있는 모습을 볼 수 있는데, 이는 기능적으로 자신의 복사본을 통해 동작하는 느낌인것 같더라구요?! 그래서 한번 선언된 Session 객체를 추후에다른 유형으로 변경하고자 할 때에는 새롭게 객체를 만들어주어야한다고 합니다!
Default Session(기본)
: 디스크가 기반 캐싱, 자격 증명 및 쿠키 저장소 개체를 사용하는 세션
: delegate 지정이 가능하다
URLSessionConfiguration(.default)
Ephemeral Session (임시/하루살이 세션)
: Default Session에서 설명한 저장소 개체를 사용하지 않는 세션, Private하다.
URLSessionConfiguration(.ephemeral)
Background Session(백그라운드)
: 백그라운드에서도 통신을 수행할 수 있는 세션
URLSessionConfiguration(.background)
2. 통신할 URL과 Request 객체를 준비합니다.
우리가 통신을 할 때 필요한 baseURL+path, 그리고 통신을 위해 넣어주어야 할 Request 객체를 준비해주어야겠죠?
..code..
// 통신을 주고 받을 URL
let baseURL = "http://~~~~/"
var urlString: String { get { baseURL + "PATH자리" } }
if let url = URL(string: urlString) {
// MARK: 1.URLSessionConfiguration을 설정하고, Session을 생성합니다.
let session = URLSession(configuration: .default)
// MARK: 2.통신할 URL과 Request 객체를 준비합니다.
// Request 객체 생성
var requestURL = URLRequest(url: url)
// Request Header 구성
requestURL.setValue("Application/json", forHTTPHeaderField: "Content-Type")
requestURL.setValue(token, forHTTPHeaderField: "token")
// HTTP Method는 기본이 "GET", 그 외에는 아래처럼 별도 설정 필요
requestURL.httpMethod = "POST"
// Request Body
let body = ["answers": answers]
guard let bodyData = try? JSONSerialization.data(withJSONObject: body, options: []) else { return }
// Request 객체에 할당
requestURL.httpBody = bodyData
// ...
// MARK: 3.사용할 Task 를 결정하고, 그에 맞는 Completion Handler, 혹은 Delegate 메소드를 작성합니다.
}
3. 사용할 Task를 결정하고, Completion Handler / Delegate 메소드를 작성합니다.
- 간단한 통신: Completion Handler , Background 상태일때에도 동작: Delegate 패턴을 주로 사용
4. Task를 실행합니다.
5. Task 완료 후 Completion Handler를 실행합니다.
Task객체란?
Session객체를 통해 서버로 요청을 보내고 난 뒤, 응답을 받을 때 필요한 객체. 3가지 유형이 있다.
URLSessionDataTask
: Data객체를 통해 데이터를 주고받은 Task 수행
URLSessionUploadTask
: Data를 파일로 변환 후 업로드하는 Task 수행
URLSessionDownloadTask
: Data를 파일로 변환 후 다운로드하는 Task 수행
: 백그라운드 다운로드도 지원하며 일시정지 또한 가능하다.
// MARK: 3.사용할 Task를 결정합니다.고, 그에 맞는 Completion Handler를 작성합니다.
let dataTask = session.dataTask(with: requestURL) { (data, response, error) in
if error != nil {
print(error!)
return
}
// 데이터 바인딩
if let bindingData = data {
do {
// 통신을 성공하여 데이터를 받았다면 JSON 타입 데이터를 사용 가능하도록 디코딩
let decodedData = try JSONDecoder().decode(CAFETI.self, from: bindingData)
// MARK: 5.Task 완료 후 Completion Handler를 실행합니다.
completion(.success(decodedData))
} catch {
print(error.localizedDescription)
}
}
}
// MARK: 4.Task를 실행합니다.
dataTask.resume()
}
Final. 서비스 메서드 호출
@objc
private func tappedLoad() {
TestService.shared.fetchTestData(answers: 데이터 넣기) { [self] response in
switch response {
case .success(let data):
case .requestErr(let err):
case .pathErr:
case .serverErr:
case .networkFail:
}
}
}
오늘은 URLSession에 대한 전반적인 부분들을 직접 사용하면서 익혀보았습니다.
사실 오늘 호출한 API가 제가 만든 API인데 response로 이미지가 오는 API를 가져왔기 때문에 다음엔 이걸로 이미지 캐싱을 연습해볼게요!
감사합니다 :)