Downloading Files in the Background

https://developer.apple.com/documentation/foundation/url_loading_system/downloading_files_in_the_background

"Create tasks that download files while your app is inactive."

앱이 비활성화 상태인 동안 파일을 다운로드하는 작업을 생성합니다.

Overview

긴 시간이 걸리고 급하지 않은 전송의 경우 백그라운드에서 실행되는 작업을 생성할 수 있습니다. 이와 같은 작업은 앱이 일시정지되어 있을 때에도 계속되며, 앱이 재개될 때 다운로드 파일에 접근할 수 있도록 해줍니다.

Note
이 글에서 설명하는 것처럼 백그라운드 세션에서 모든 백그라운드 네트워크 활동을 할 필요는 없습니다. 포어그라운드에서 하는 것처럼 적합한 백그라운드 모드를 선언한 앱은 기본값 URL 세션 및 데이터 작업을 사용할 수 있습니다.

Configure the Background Session

백그라운드 다운로드를 수행하려면 백그라운드 작업을 위한 URLSession을 설정해야 합니다. Listing 1이 이 과정을 설명합니다.

  1. URLSessionbackground(withIdentifier:) 클래스 메소드를 사용해서 백그라운드 URLSessionConfiguration 객체를 생성합니다. 앱 내부에서 고유한 세션 아이덴티파이어를 제공하면서 생성해야 합니다. 대부분의 앱이 몇 가지 백그라운드 세션만 필요(보통 하나)하기 때문에 동적으로 생성되는 아이덴티파이어보다 대한 고정된 스트링 아이덴티파이어를 사용할 수 있습니다. 아이덴티파이어는 글로벌에서 고유할 필요는 없습니다.
  2. 작업이 완료되고 앱이 백그라운드에 있을 때 시스템이 앱을 깨우려면, sessionSendsLaunchEvents 속성 true(기본값)여야 합니다.
  3. 시간이 많이 걸리는 작업의 경우 isDiscretionary 속성을 활성화해서 시스템이 전송을 수행하는 데 최적의 조건이 되길 기다릴 수 있습니다. 기기가 플러그 인 되거나 와이파이에 연결되는 경우에 그렇습니다.
  4. URLSession 인스턴스 생성을 위해 URLSessionConfiguration 인스턴스를 사용합니다. 백그라운드 전송으로부터 이벤트를 받기 위해 딜리게이트를 제공해야 합니다.

Listing 1 Creating a background URL session

private lazy var urlSession: URLSession = {
    let config = URLSessionConfiguration.background(withIdentifier: "MySession")
    config.isDiscretionary = true
    config.sessionSendsLaunchEvents = true
    return URLSession(configuration: config, delegate: self, delegateQueue: nil)
}()

Note
시스템이 어떻게 스케줄링 하고 백그라운드 작업을 어떻게 수행하는지 더 높은 수준의 시각화를 원한다면 Profiles and Logs에 있는 Background Networking Profile onto your iOS device를 다운로드하시기 바랍니다.

Profiles and Logs
https://developer.apple.com/bug-reporting/profiles-and-logs/

Create and Schedule the Download Task

URL을 받는 downloadTask(with:) 혹은 URLRequest 인스턴스를 받는 downloadTask(with:)를 사용해서 세션으로부터 다운로드 작업을 생성할 수 있습니다. 시스템이 동작을 최적화할 수 있도록 이 메소드에 속성을 설정할 수 있습니다.

  1. Listing 2에서 보이는 것처럼 downloadTask(with:)를 사용해서 다운로드 작업을 생성합니다.
  2. 선택적으로 나중의 특정 시점에 다운로드를 시작할 것을 스케줄링하려면 earliestBeginDate 속성을 설정합니다. 다운로드는 정확히 이 시점에 시작한다고 보장할 수는 없지만 더 빨리 시작되지는 않습니다.
  3. 시스템이 네트워크 활동을 효율적으로 스케줄링할 수 있도록 하려면 countOfBytesClientExpectsToSendcountOfBytesClientExpectsToReceive를 설정하시기 바랍니다. 이 값들은 예상 바이트의 상한선으로 가장 잘 예측되며, 헤더와 바디 데이터를 고려해야 합니다.
  4. 작업 시작을 위해 resume()을 호출합니다.

Listing 2에서 작업은 한 시간 이내에 시작하도록 설정되었고, 약 200 바이트의 데이터를 전송하고 약 500 킬로바이트를 받도록 설정되어 있습니다.

Listing 2 Creating a download task from a URL session

let backgroundTask = urlSession.downloadTask(with: url)
backgroundTask.earliestBeginDate = Date().addingTimeInterval(60 * 60)
backgroundTask.countOfBytesClientExpectsToSend = 200
backgroundTask.countOfBytesClientExpectsToReceive = 500 * 1024
backgroundTask.resume()

Handle App Suspension

앱 상태마다 앱이 백그라운드 다운로드와 상호작용하는 방법에 영향을 미칠 수 있습니다. iOS에서 앱은 포어그라운드, 일시정지, 시스템에 의한 종료되는 상태가 될 수 있습니다. 이와 같은 상태에 대한 더 많은 정보는 Managing Your App's Life Cycle을 보시기 바랍니다.

Managing Your App's Life Cycle
https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle
https://velog.io/@panther222128/App-Life-Cycle

만약 앱이 백그라운드에 있으면 시스템은 다운로드가 다른 프로세스에서 수행되는 동안 앱을 일시정지시킵니다. 이 경우 다운로드가 마무리되면 시스템은 앱을 재개하고 UIApplicationDelegate 메소드인 application(_:handleEventsForBackgroundURLSession:completionHandler:)를 호출합니다. 이 메소드는 두 번째 파라미터로써 Listing 1에서 생성한 세션 아이덴티파이어를 받습니다.

이 딜리게이트 메소드는 마지막 파라미터로써 컴플리션 핸들러도 받습니다. 앱에서 합리적이라고 판단되는 곳에 이 핸들러를 즉시 저장하시기 바랍니다. 아마 앱 딜리게이트의 속성으로써 혹은 URLSessionDownloadDelegate를 구현하는 클래스의 속성으로써 저장하게 될 것입니다. Listing 3에서 이 컴플리션 핸들러는 backgroundCompletionHandler라고 부르는 앱 딜리게이트 속성에 저장됩니다.

Listing 3 Storing the background download completion handler sent to the application delegate

func application(_ application: UIApplication,
                 handleEventsForBackgroundURLSession identifier: String,
                 completionHandler: @escaping () -> Void) {
        backgroundCompletionHandler = completionHandler
}

모든 이벤트가 전달되면 시스템은 URLSessionDelegateurlSessionDidFinishEvents(forBackgroundURLSession:) 메소드를 호출합니다. 이 시점에 Listing 3에서 앱 딜리게이트에 의해 저장된 backgroundCompletionHandler를 가져오고 실행해야 합니다. Listing 4는 이 과정을 보여줍니다.

urlSessionDidFinishEvents(forBackgroundURLSession:)는 두 번째 큐에서 호출될 것이기 때문에 명시적으로 핸들러(UIKit 메소드로부터 받았었던)를 메인 큐에서 실행할 필요가 있다는 것을 기억해야 합니다.

Listing 4 Executing the background URL session completion handler on the main queue

func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
    DispatchQueue.main.async {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate,
            let backgroundCompletionHandler =
            appDelegate.backgroundCompletionHandler else {
                return
        }
        backgroundCompletionHandler()
    }
}

Access the File, or Move It to a Permanent Location

재개된 앱이 컴플리션 핸들러를 호출하면 다운로드 작업은 작업을 마무리하고 딜리게이트의 urlSession(_:downloadTask:didFinishDownloadingTo:) 메소드를 호출합니다. 이 시점에 파일은 완전히 다운로드되고, 딜리게이트 메소드가 반환할 때까지 사용이 가능할 것입니다. 오직 한 번만 읽을 필요가 있다면, 임시 위치에서 즉시 파일에 접근할 수 있습니다. 이 파일을 보존하길 원한다면, 문서 디렉토리와 같은 영구적 위치에 옮겨야 합니다. Downloading Files from Websites에서 설명하고 있습니다.

Downloading Files from Websites
<>

Recreate the Session If the App Was Terminated

시스템이 일시정지되었던 앱을 종료시키면 시스템은 백그라운드에서 앱을 다시 launch합니다. launch 타임 설정의 부분처럼 이전과 같은 세션 아이덴티파이어를 사용해 시스템이 백그라운드 다운로드 작업을 세션과 다시 연결할 수 있도록 백그라운드 세션을 재생성(Listing 1을 보시기 바랍니다)해야 합니다. 이렇게 하면 백그라운드 세션은 사용자 혹은 시스템에 의해 앱이 launch 되었을 때 준비가 됩니다. 앱이 다시 launch되면 이벤트의 연속은 앱이 실시정지와 재개되었던 경우와 같습니다. 앞서 Handle App Suspension에서 설명한 것과 같습니다.

Note
앱이 백그라운드에 있는 동안 전송이 초기화된 곳에서 세션 설정의 isDiscretionary 속성은 true인 것처럼 처리됩니다.

Comply with Background Transfer Limitations

백그라운드 세션에서 실제 전송은 앱의 프로세스로부터 분리되어 있는 프로세스에 의해 수행됩니다. 앱의 프로세스를 재시작하는 것은 비용이 높기 때문에 몇 가지 기능은 사용이 불가능하고 아래와 같은 제한사항이 생깁니다.

  • 세션은 이벤트 전송을 위한 딜리게이트를 제공해야 합니다. (업로드와 다운로드의 경우 딜리게이트는 프로세스 중인 전송과 동일하게 동작합니다.)
  • HTTP 및 HTTPS 프로토콜만 지원됩니다(커스텀 프로토콜은 지원하지 않습니다.)
  • 재연결은 항상 따라옵니다. 결과적으로 urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:)를 구현했다고 하더라도 호출되지 않습니다.
  • 파일로부터의 업로드 작업만 지원됩니다(데이터 인스턴스로부터의 업로드 혹은 앱 종료 후 스트림 실패).

Use Background Sessions Efficiently

시스템이 앱을 재개하거나 다시 launch하면, 시스템은 남용을 방지하기 위해 속도 제한기를 사용합니다. 앱이 백그라운드에 있는 동안 새로운 다운로드 작업을 시작하면, 작업은 지연이 만료될 때까지 시작되지 않습니다. 지연은 시스템 재개 혹은 앱의 launch 각각에 시간을 증가시킵니다.

결과적으로 앱이 단일 백그라운드 다운로드를 시작하면 다운로드가 완료될 때 재개되고, 이후 새로운 다운로드를 시작하며, 지연시간을 크게 증가시킵니다. 대신 작은 수의 백그라운드 세션을 사용해야 하며(이상적으로 하나), 이 세션을 한 번에 많은 다운로드 작업을 시작할 수 있도록 만들어야 합니다. 이는 시스템이 한 번에 여러 다운로드를 수행할 수 있게 하고, 다운로드가 완료되면 앱을 재개하도록 합니다.

각 작업은 각각의 오버헤드를 갖고 있음을 명심해야 합니다. 수 천개의 다운로드 작업이 필요한 경우를 찾게 되면 그 수를 줄이고 더 큰 전송으로 디자인 설계를 바꿔야 합니다.

Note
지연은 사용자가 앱을 포어그라운드로 가져올 때마다 0으로 재설정합니다. 또한, 지연 시간이 경과되면 시스템 재개 혹은 다시 launch하는 것 없이도 재설정합니다.

See Also


Downloading

Downloading Files from Websites

파일시스템에 파일을 직접 다운로드합니다.

https://developer.apple.com/documentation/foundation/url_loading_system/downloading_files_from_websites
https://velog.io/@panther222128/Downloading-Files-from-Websites

Pausing and Resuming Downloads

사용자가 다시 시작하지 않고도 다운로드를 재개할 수 있도록 합니다.

https://developer.apple.com/documentation/foundation/url_loading_system/pausing_and_resuming_downloads
https://velog.io/@panther222128/Pausing-and-Resuming-Downloads


좋은 웹페이지 즐겨찾기