iOS

iOS) URLSession + NSCache 실습(1) - URLSession

snowe 2021. 9. 14. 23:21

Kingfisher, Alamofire, Moya 많이들 사용하시나요? 저도 사용하고 있습니다만..
얘네를 사용하는 이유가 URLSession, NSCache등을 사용할 때 번거로움이 많아서잖아요? 근데 제가 그 번거로움을 못 느껴본 것 같아서 이참에 직접 구현하며 경험해보고 정말 어떤 점이 좋은 건지 공부해보려고 해요.

 

URLSession을 먼저 알아볼게요

URLSession?

HTTP/HTTPS를 통해 콘텐츠 및 데이터를 주고받기 위해 애플에서 만든 서버 통신 API 클래스.

HTTP/HTTPS외에도 인증, 쿠키 관리, 캐시 등도 지원한대요(사실 메서드 되게 많으니까 definition 참고하는게 더 좋을듯).

 

URLSession은 기본적으로 비동기로 구현이 되어있어요. 그래서 구현 할 때 별도의 비동기 처리는 필요하지 않습니다!

하지만 통신시 일어나는 UI처리가 있을 수 있겠죠?! UI 관련 처리는 모두 Main Thread에서 이루어져야하기 때문에 DispatchQueue.main.sync 내부에서 처리해야 됨을 유의해야합니다!(사실 어차피 Xcode가 보라색 알림문구로 알려주긴함)

 

URLSession을 사용하는 순서?

  1. URLSessionConfiguration을 설정하고, Session을 생성합니다.
  2. 통신할 URL과 Request 객체를 준비합니다.
  3. 사용할 Task를 결정하고, Completion Handler / Delegate 메소드를 작성합니다.
    • 간단한 통신: Completion Handler , Background 상태일때에도 동작: Delegate 패턴을 주로 사용
  4. Task를 실행합니다.
  5. 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를 가져왔기 때문에 다음엔 이걸로 이미지 캐싱을 연습해볼게요! 

 

감사합니다 :)