"Create tasks that download files while your app is inactive."
앱이 비활성화 상태인 동안 파일을 다운로드하는 작업을 생성합니다.
긴 시간이 걸리고 급하지 않은 전송의 경우 백그라운드에서 실행되는 작업을 생성할 수 있습니다. 이와 같은 작업은 앱이 일시정지되어 있을 때에도 계속되며, 앱이 재개될 때 다운로드 파일에 접근할 수 있도록 해줍니다.
Note
이 글에서 설명하는 것처럼 백그라운드 세션에서 모든 백그라운드 네트워크 활동을 할 필요는 없습니다. 포어그라운드에서 하는 것처럼 적합한 백그라운드 모드를 선언한 앱은 기본값 URL 세션 및 데이터 작업을 사용할 수 있습니다.
백그라운드 다운로드를 수행하려면 백그라운드 작업을 위한 URLSession
을 설정해야 합니다. Listing 1이 이 과정을 설명합니다.
URLSession
의 background(withIdentifier:)
클래스 메소드를 사용해서 백그라운드 URLSessionConfiguration
객체를 생성합니다. 앱 내부에서 고유한 세션 아이덴티파이어를 제공하면서 생성해야 합니다. 대부분의 앱이 몇 가지 백그라운드 세션만 필요(보통 하나)하기 때문에 동적으로 생성되는 아이덴티파이어보다 대한 고정된 스트링 아이덴티파이어를 사용할 수 있습니다. 아이덴티파이어는 글로벌에서 고유할 필요는 없습니다.sessionSendsLaunchEvents
속성 true
(기본값)여야 합니다.isDiscretionary
속성을 활성화해서 시스템이 전송을 수행하는 데 최적의 조건이 되길 기다릴 수 있습니다. 기기가 플러그 인 되거나 와이파이에 연결되는 경우에 그렇습니다.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/
URL을 받는 downloadTask(with:)
혹은 URLRequest
인스턴스를 받는 downloadTask(with:)
를 사용해서 세션으로부터 다운로드 작업을 생성할 수 있습니다. 시스템이 동작을 최적화할 수 있도록 이 메소드에 속성을 설정할 수 있습니다.
downloadTask(with:)
를 사용해서 다운로드 작업을 생성합니다.earliestBeginDate
속성을 설정합니다. 다운로드는 정확히 이 시점에 시작한다고 보장할 수는 없지만 더 빨리 시작되지는 않습니다.countOfBytesClientExpectsToSend
와 countOfBytesClientExpectsToReceive
를 설정하시기 바랍니다. 이 값들은 예상 바이트의 상한선으로 가장 잘 예측되며, 헤더와 바디 데이터를 고려해야 합니다.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()
앱 상태마다 앱이 백그라운드 다운로드와 상호작용하는 방법에 영향을 미칠 수 있습니다. 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
}
모든 이벤트가 전달되면 시스템은 URLSessionDelegate
의 urlSessionDidFinishEvents(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()
}
}
재개된 앱이 컴플리션 핸들러를 호출하면 다운로드 작업은 작업을 마무리하고 딜리게이트의 urlSession(_:downloadTask:didFinishDownloadingTo:)
메소드를 호출합니다. 이 시점에 파일은 완전히 다운로드되고, 딜리게이트 메소드가 반환할 때까지 사용이 가능할 것입니다. 오직 한 번만 읽을 필요가 있다면, 임시 위치에서 즉시 파일에 접근할 수 있습니다. 이 파일을 보존하길 원한다면, 문서 디렉토리와 같은 영구적 위치에 옮겨야 합니다. Downloading Files from Websites에서 설명하고 있습니다.
Downloading Files from Websites
<>
시스템이 일시정지되었던 앱을 종료시키면 시스템은 백그라운드에서 앱을 다시 launch합니다. launch 타임 설정의 부분처럼 이전과 같은 세션 아이덴티파이어를 사용해 시스템이 백그라운드 다운로드 작업을 세션과 다시 연결할 수 있도록 백그라운드 세션을 재생성(Listing 1을 보시기 바랍니다)해야 합니다. 이렇게 하면 백그라운드 세션은 사용자 혹은 시스템에 의해 앱이 launch 되었을 때 준비가 됩니다. 앱이 다시 launch되면 이벤트의 연속은 앱이 실시정지와 재개되었던 경우와 같습니다. 앞서 Handle App Suspension에서 설명한 것과 같습니다.
Note
앱이 백그라운드에 있는 동안 전송이 초기화된 곳에서 세션 설정의isDiscretionary
속성은true
인 것처럼 처리됩니다.
백그라운드 세션에서 실제 전송은 앱의 프로세스로부터 분리되어 있는 프로세스에 의해 수행됩니다. 앱의 프로세스를 재시작하는 것은 비용이 높기 때문에 몇 가지 기능은 사용이 불가능하고 아래와 같은 제한사항이 생깁니다.
urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:)
를 구현했다고 하더라도 호출되지 않습니다.시스템이 앱을 재개하거나 다시 launch하면, 시스템은 남용을 방지하기 위해 속도 제한기를 사용합니다. 앱이 백그라운드에 있는 동안 새로운 다운로드 작업을 시작하면, 작업은 지연이 만료될 때까지 시작되지 않습니다. 지연은 시스템 재개 혹은 앱의 launch 각각에 시간을 증가시킵니다.
결과적으로 앱이 단일 백그라운드 다운로드를 시작하면 다운로드가 완료될 때 재개되고, 이후 새로운 다운로드를 시작하며, 지연시간을 크게 증가시킵니다. 대신 작은 수의 백그라운드 세션을 사용해야 하며(이상적으로 하나), 이 세션을 한 번에 많은 다운로드 작업을 시작할 수 있도록 만들어야 합니다. 이는 시스템이 한 번에 여러 다운로드를 수행할 수 있게 하고, 다운로드가 완료되면 앱을 재개하도록 합니다.
각 작업은 각각의 오버헤드를 갖고 있음을 명심해야 합니다. 수 천개의 다운로드 작업이 필요한 경우를 찾게 되면 그 수를 줄이고 더 큰 전송으로 디자인 설계를 바꿔야 합니다.
Note
지연은 사용자가 앱을 포어그라운드로 가져올 때마다 0으로 재설정합니다. 또한, 지연 시간이 경과되면 시스템 재개 혹은 다시 launch하는 것 없이도 재설정합니다.
파일시스템에 파일을 직접 다운로드합니다.
https://developer.apple.com/documentation/foundation/url_loading_system/downloading_files_from_websites
https://velog.io/@panther222128/Downloading-Files-from-Websites
사용자가 다시 시작하지 않고도 다운로드를 재개할 수 있도록 합니다.
https://developer.apple.com/documentation/foundation/url_loading_system/pausing_and_resuming_downloads
https://velog.io/@panther222128/Pausing-and-Resuming-Downloads