Youngminah/TIL

URLSession

Youngminah opened this issue · 2 comments

URL Loading System이란

  • 표준 Internet protocols를 사용해서 URL과 상호작용하고 서버와 통신하는 시스템.

  • URL Loading System은 표준 규약인 https나 커스텀된 규약을 사용해서

  • URLs에 의해서 identified되는 자원에 접근을 제공

  • Loading은 비동기적으로 수행되기 때문에 앱은 여전히 사용자에 대해 반응적으로 작동할 수 있고
    받아오는 데이터나 에러에 대해서 처리할 수 있다.

  • URLSession 객체를 사용해서 하나 이상의 URLSessionTask 객체를 생성할 수 있는데,
    이러한 URLSessionTask는 데이터를 fetch 하고 앱으로 데이터를 보내주거나, 파일을 다운받거나,
    원격 장소에 데이터나 파일을 업로드하는 일들을 의미한다.

  • 이러한 session을 구성하기 위해서 URLSessionConfiguration 객체를 사용하는데,
    이 객체는 caches나 cookies의 사용 방법이나 셀룰러 네트워크에서 연결을 허용할 지 여부와
    같은 동작을 제어한다.

  • 하나의 session을 사용해서 여러 개의 task를 생성할 수 있다.
    예를 들어 web browser가 일반 browsing 사용과
    data를 cache하지 않는 private browsing 사용에 대해 구별해서 session을 가져야 할 수 있다.

  • 이러한 경우에는 아래의 그림이 2개의 session이 어떻게 구성되고,
    여러 개의 task들을 생성하는지에 대해서 확인해 볼 수 있을 것이다.
    (그림에서 보면 private browsing에는 UploadTask가 없는 것을 확인해 볼 수 있다.)

image

  • 각각의 session은 주기적인 업데이트나 오류를 수신하기 위해서 delegate에 연관되어 있다.
    Default delegate는 작성한 completion handler block를 불러주게 된다.
    반면에 커스텀한 delegate의 경우에는 이 block을 호출되지 않는다.

  • session을 background에서 동작하도록 구성해서 앱이 중지되었을 때
    시스템이 데이터를 다운로드하고 결과를 앱에 전달해주기 위해서 깨우게 할 수 있다.


URLSession

image

  • URL Loading System은 Foundation Framework에 포함이 되어있다.
  • 참고
    Foundation Framework는 데이터의 저장 및 지속, 텍스트 처리, 날짜 및 시간 계산, 정렬 및 필터링,
    네트워킹을 포함한 애플리케이션과 프레임 워크의 기본 기능을 제공해준다.

URLSession의 Life Cycle

  1. Session configuration을 결정하고, Session을 생성.
  2. 통신할 URL과 Request 객체를 설정.
  3. 사용할 Task를 결정하고, 그에 맞는 Completion Handler나 Delegate 메소드들을 작성.
  4. 해당 Task를 실행.
  5. Task 완료 후 Completion Handler가 실행.

Session

지정된 URL session 내의 task는 단일 호스트에 대한 최대 동시 연결 수, 셀룰러 네트워크를 사용할 수 있는지 여부 등과 같은 연결 동작을 정의하는 common session 구성 객체를 공유한다.

  • URLSession에는 기본 요청에 대해 configuration 객체가 없는 singleton shared session이 있다.
  • 직접 만든 session보다 커스터마이즈는 할 수 없지만 요구사항이 매우 제한적인 경우에 사용하기에 좋다.
  • shared class method를 사용해서 이 session에 접근할 수 있다.
  • 다른 session의 경우 아래의 세 가지 구성 중 하나를 사용해서 URLSession을 생성한다.

그 외에 URLSession은 크게 3가지 종류의 Session을 지원.

  • Default Session: 기본적인 Session으로 디스크 기반 캐싱을 지원.
  • Ephemeral Session: 어떠한 데이터도 저장하지 않는 형태의 세션.
  • Background Session: 앱이 종료된 이후에도 통신이 이뤄지는 것을 지원하는 세션.

객체가 없는 Shared Session의 한계

  • 간단한 몇줄의 코드로 구현하여 URL의 contents에서 데이터를 받아오는 경우에는 shared를 사용하면 된다.
  • 이미 singleton으로 하나의 객체밖에 없으므로 직접 생성하는 것이 아니라 단지 접근해서 사용하면 된다.
  • 그 결과 delegate나 configuration 객체를 생성할 수 없게 된다.

delegate나 configuration 객체가 없으므로 당연히 다음과 같은 한계점이 발생하게 된다.

  • 서버로 부터 데이터를 받아올 때 data를 점차 증가시면서 받아올 수 없다.
  • connection behavior를 defualt에서 더 이상 커스터마이즈 할 수 없게 된다.
  • authentication(인증)을 수행하는 기능은 사용할 수 없다.
  • 앱이 실행되지 않을 때, background에서 download나 upload를 수행할 수 없다.
  • shared session은 URLCache, HTTPCookieStorage, URLCredentialStorage 객체들을 공유하게 된다.
  • 또한 registerClass(:), unregisterClass(:) 에 의해서 구성되는
  • shared custom networking protocol list를 공유하게 된다.
  • 그리고 이러한 것들은 모두 default configuration에 기반을 두고 있다.

따라서 shared session을 사용하는 경우에는 cache, cookie storage, credential storage를
커스트마이징 하는 것을 피해야한다. (만약 NSURLConnection을 사용하는 것이 아니라면!!)

결론은 만약 cache, cookies, authentication, custom networking protocols을 사용하는 것이라면
shared session이 아닌 default session을 사용해야 하는 것이다!!


Request

  • URLRequest를 통해서는 서버로 요청을 보낼 때 어떻게 데이터를 캐싱할 것인지, 어떤 HTTP 메소드를 사용할 것인지(Get, Post 등), 어떤 내용을 전송할 것인지 등을 설정

Task

Task 객체는 일반적으로 Session 객체가 서버로 요청을 보낸 후, 응답을 받을 때 URL 기반의 내용들을 받는(retrieve) 역할

  • Data Task - Data 객체를 통해 데이터 주고받는 Task입니다.
  • Download Task - data를 파일의 형태로 전환 후 다운 받는 Task입니다. 백그라운드 다운로드 지원
  • Upload Task - data를 파일의 형태로 전환 후 업로드하는 Task입니다.
  • Web Socket Task - RFC 6455에 정의된 웹 소켓 규약을 사용하여 TCP와 TLS 간의 메세지를 교환한다.

class APIService {
    
    let sourceURL = URL (string: "http://kobis.or.kr/kobisopenapi/webservice/rest/people/searchPeopleList.json?key=f5eef3421c602c6cb7ea224104795888")!
    
    //Escaping의 옵셔널 이유는 데이터가 안올 경우를 대비
    func requestCast(completion: @escaping (Cast?) -> Void) {
        
        URLSession.shared.dataTask(with: sourceURL) { data, response, error in
            
            if let error = error {
                print("ERROR", error)
                return
            }
            
            guard let response = response as? HTTPURLResponse, (200...299).contains(response.statusCode) else {
                print("ERROR")
                return
            }
            
            if let data = data, let castData = try? JSONDecoder().decode(Cast.self, from: data) {
                print("SUCCEED", castData)
                completion(castData)
                return
            }
            
            completion(nil)
            
        }.resume()
    }
}
apiService.requestCast { [weak self] cast in
   self?.castData = cast
   DispatchQueue.main.async {
      self?.tableView.reloadData()
   }
}
  • Alamofire는 자동으로 main 쓰레드에서 클로져를 처리해줌 그렇게 구현해놓은 라이브러리임
  • URLSession은 이러한 처리를 해주지 않음. 그래서 직접 main 스레드에서 동작하도록 해야함

Delegate로 통신

extension NASAViewController: URLSessionDataDelegate {
    
    //서버에서 최초로 응답 받은 경우 호출
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse) async -> URLSession.ResponseDisposition {
        if let response = response as? HTTPURLResponse, (200...299).contains(response.statusCode) {
            return .allow
        } else {
            return .cancel
        }
    }
    
    // 받아오는 과정 퍼센트 확인가능
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        print(data)
    }
    
    //응답이 완료되었다면 error -> nil
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        if error != nil {
            print("오류가 발생하였습니다.")
        } else {
            print("성공!")
        }
    }
}

주의 해야할점

  • 위에서도 설명하였지만,
  • shared에는 delegate나 configuration 객체가 없으므로
  • 그 결과 delegate나 configuration 객체를 생성할 수 없게 된다.
  • shared를 쓰면 delegate 객체를 생성할수 없는데, 그러면 받아오는 실시간 과정 같은 모습을 볼수 없다.