[회고] 신입 iOS 개발자가 되기까지 feat. 카카오 자세히보기

🍎 Apple/Swift

[Swift] URL Loading System

inu 2022. 5. 12. 23:25
반응형

URL Loading System

  • URL을 통해 네트워크에 존재하는 서버와 통신하는 기술입니다.
  • 이에 포함된 프레임워크는 높은 수준의 API를 제공하기 때문에 이것만으로 서버연동, 다운로드, 업로드 기능을 구현할 수 있습니다.

URLSession

  • 이 시스템에서 가장 중요한 것이 URLSession입니다. 이를 통해 네트워크 연결을 설정하고 요청과 응답을 처리할 수 있습니다.
  • URLSession은 네가지 Session 타입을 가집니다.
    • Shared : 기본 설정을 기반으로 단순한 네트워크 요청을 수행할 수 있습니다.
    • Default : 세션을 직접 구성할 수 있습니다. Degation을 제공하기 때문에 세부적인 제어도 가능합니다. 해당 세션을 통해 전송된 응답은 Disk 및 Memory에 캐싱됩니다.
    • Ephemeral : Default와 유사하지만 캐싱처리가 Memory에만 수행됩니다. 주로 private한 처리를 하려할 때 사용됩니다.
    • Background : 백그라운드 전송을 구현할 때 사용합니다. 앱 실행상태과 관련없이 데이터를 다운로드하고 업로드할 수 있습니다.
  • Shared Session을 제외한 모든 세션은 Session Configuration 객체를 통해 생성합니다.

Task

  • URLSession에 전달하는 개별요청
  • 데이터 전달방식 및 목적에 따라 4가지 Task 타입을 사용합니다.
    • DataTask : API 서버와 통신하기 적합합니다. 가장 많이 사용되는 Task입니다.
    • DownloadTask / UploadTask : 파일 전송(다운로드, 업로드)에 적합합니다. (백그라운드 전송 지원)
    • StreamTask : 채팅과 같은 TCP 프로그램을 구현할 때 사용하는 Task입니다.
  • URLSession.shared.task()와 같은 형식으로 손쉽게 Task를 생성할 수 있습니다.
  • 하지만 이는 suspended 상태로 생성되기 때문에 resume 메서드를 호출해야 작업을 시작합니다.

Data Task 과정

  • 이제 이들을 활용한 대표적인 처리인 DataTask 과정을 살펴봅시다.
​​​​guard let url = URL(string: urlStr) else { fatalError("Invalid URL") }
  • 먼저 URL 인스턴스를 생성합니다.
​​​​let session = URLSession.shared
  • URLSession.shared를 좀 더 사용하기 좋도록 상수에 저장합니다.
    let task = session.dataTask(with: url) { (data, response, error) in
        if let error = error {
            // 통신 중 에러가 발생한 경우 에러 처리
            return
        }

        guard let httpResponse = response as? HTTPURLResponse else {
            // HTTPURLResponse로 변환할 수 없는 경우 에러 처리
            return
        }

        guard (200...299).contains(httpResponse.statusCode) else {
            // 상태코드가 성공이 아닐 경우 에러 처리
            return
        }

        guard let data = data else {
            // 데이터가 nil일 경우 에러 처리
            return
        }

        do {
            let decoder = JSONDecoder()
            // 디코딩 및 데이터 활용 처리 
        } catch {
            // 디코딩이 성공적으로 수행되지 않았을 경우 에러처리
        }
    }
  • URL을 전달하여 Task를 생성합니다.
  • URL이 아닌 URLRequest를 전달할 수도 있는데, 이는 Task 별로 네트워크 설정(캐시 정책 등)을 변경하고 싶을 때 사용합니다.
  • completion handler는 URL을 통해 네트워크 작업이 완료되면 호출됩니다. 이를 통해 data, response, error값을 전달받을 수 있습니다.
    • data : 서버에서 전달된 Binary Data
    • response : 서버 응답에 대한 정보
    • error : 에러 발생시 에러 정보
  • 이들을 활용해 data를 decoding하고 이를 활용합니다.
  • 아직은 task가 suspended 상태입니다.
​​​​task.resume()
  • task를 실행합니다. (끝)

Session Configuration

  • Session Configuration을 통해 네트워크 연결과 관련된 속성을 설정할 수 있습니다.
  • 셀룰러 연결 금지, 캐시 저장위치 설정, 쿠기 설정 변경, 타임아웃 값 설정 등을 수행할 수 있습니다.
  • URLSession을 생성하기 전에 미리 구성을 완료한 뒤, URLSession 생성자로 전달해 사용할 수 있습니다.
  • URLSession이 생성된 후에는 Session Configuration을 수정할 수 없습니다. (Read Only)
  • 기본적으로 제공하는 Session Configuration은 총 4가지입니다.
    • Shared : shared session에서 사용되는 Configuration으로, 일반적으로 직접 사용하는 경우는 없습니다.
    • Default : 기본 Configuration 설정을 기반으로 구성되어 있습니다. delegate를 통한 이벤트 처리 제공하며 disk 캐싱으로 서버 전달 데이터를 전달합니다.
    • Ephemeral : Default와 유사하지만 메모리에만 캐싱을 처리한다는 특징이 있습니다.
    • Background : Download 및 Upload Task에서 활용합니다. 앱 실행상태와 관계없이 데이터를 전송할 수 있도록 합니다.
let configurationDefault = URLSessionConfiguration.default
let configurationEphemeral = URLSessionConfiguration.ephemeral

let sessionWithDefault = URLSession(configuration: configurationDefault)
let sessionWithEphemeral = URLSession(configuration: configurationEphemeral)
  • 이와 같은 방식으로 Configuration을 생성하고 활용합니다.
let configurationBackground = URLSessionConfiguration.background(withIdentifier: "DownTask")
let sessionWithBackground = URLSession(configuration: configurationBackground)
  • background Configuration에서는 Identifier 및 Delegate를 필요로 합니다.
  • 이 부분은 나중에 다시 공부해서 포스팅하겠습니다!
let configurationCustom = URLSessionConfiguration.default
configurationCustom.timeoutIntervalForRequest = 30
configurationCustom.httpAdditionalHeaders = ["API-VERSION": "2.0.0"]

let sessionWithCustom = URLSession(configuration: configurationCustom)
  • 이렇게 생성된 URLSessionConfiguration은 자유롭게 수정이 가능합니다.
  • timeout 시간을 설정하는 timeoutIntervalForRequest,
  • 모든 task에 추가 헤더를 설정해주는 httpAdditionalHeaders 등 다양한 프로퍼티가 존재합니다.
  • 더 많은 프로퍼티는 공식문서를 참고합시다.

URL Session Deleagate

  • 서버에서 전달된 데이터는 Completion Handler 혹은 Session Delegate를 기반으로 처리합니다.
  • 일반적으로는 Completion Handler를 사용하지만, Session Task 처리 중 내부에서 발생하는 다양한 이벤트를 세부적으로 확인 및 처리하고 싶다면 Session Delegate를 사용해야 합니다.
  • Session Delegate를 사용하면 Completion Handler를 통한 처리는 동작하지 않습니다.
  • Session Delegate Protocol은 가장 최상위에 URLSessionDelegate가 존재하며
    • 그 아래에 이를 상속받는 URLSessionTaskDelegate가 존재하고
      • 그 아래에 이를 상속받는 URLSessionDataDelegate와
      • URLSessionDownloadDelegate가 존재합니다.
  • 각 Delegate는 이름에 걸맞는 Delegate Method들을 보유하고 있습니다.
    session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: OperationQueue.main)    
    let task = session.dataTask(with: url)
    task.resume()
  • Delegate를 사용하기 위해서는 처음 Session을 생성할 때 URLSession(configuration:, delegate:, delegateQueue:) 메서드를 사용해야합니다.
  • delegate를 self로 지정했습니다.
  • task 처리에는 completion handler가 없는 dataTask 메서드를 사용합니다.
class ViewController: UIViewController {
    var buffer: Data?
    ...
}

extension ViewController: URLSessionDataDelegate {
    // 서버로부터 최초로 응답을 받았을 때 호출
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
        guard let response = response as? HTTPURLResponse,
              (200...299).contains(response.statusCode) else {
            // 아닐 경우 cancel 처리
            completionHandler(.cancel)
            return
        }
        // 맞을 경우 allow 처리
        completionHandler(.allow)
    }

    // 전체 데이터가 아니기 때문에 이들을 누적하다가 완료되면 파싱처리
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
        buffer?.append(data)
    }

    // 데이터 전송이 완료되면 호출
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        if let error = error {
            // 에러 처리
        }
        // buffer data를 이용해 파싱처리
    }
}
  • delegate를 self로 지정했기 때문에 이를 구현해주어야합니다.
  • 각각 최초로 응답을 받았을 때, 각 데이터가 전송될 때 마다, 데이터 전송이 완료되면 호출되는 메서드입니다.
  • 각각 최초로 응답을 받았을 때 : 조건에 따라 작업을 계속할지 말지를 결정해 completionHandler에 전달합니다.
  • 각 데이터가 전송될 때 마다 : 버퍼에 데이터를 저장해놓습니다.
  • 데이터 전송이 완료되면 : 에러 처리 및 파싱 처리를 수행합니다.
    session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: OperationQueue.main)   
    buffer = Data()
    let task = session.dataTask(with: url)
    task.resume()
  • 버퍼는 멤버변수로 등록해 계속해서 활용합니다.
  • 따라서 task를 실행하기 전에 buffer를 초기화해야합니다.
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        session.finishTasksAndInvalidate() // Task가 모두 종료될 때까지 기다렸다가 리소스 정리
        session.invalidateAndCancel() // 모든 Task를 취소하고 바로 리소스 정리
    }
  • delegate를 사용하면서 ViewController와 URLSession이 서로 순환참조를 일으킬 수 있습니다.
  • 따라서 ViewWillDisapear에서 이를 메모리에서 해제해주는 작업이 필요합니다.
  • finishTasksAndInvalidate 혹은 invalidateAndCancel를 사용합니다.

POST Request

    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.addValue("application/json", forHTTPHeaderField: "Content-Type")
    let body = ["email": id, "password": password]
    let encoder = JSONEncoder()
    request.httpBody = try encoder.encode(body)
  • POST 작업에는 URLRequest 객체가 필요합니다.
  • httpMethod 속성을 통해 POST Method로 요청 메서드를 변경합니다.
  • addValue 속성을 통해 헤더를 추가합니다.
  • httpBody 속성을 통해 바디를 추가합니다.
    let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
        ...
    }
  • 이 후의 과정은 GET Requsest와 유사합니다.
  • 서버가 보내온 데이터를 필요에 따라 처리하면 됩니다.
반응형