오늘은 BackgroundMode와 BackgroundTask에 대해 정리해보겠습니다.
iOS앱은 기본적으로 포그라운드, 즉 사용자가 앱을 열어 활성화한 경우에만 작동합니다.
하지만 사용자가 홈으로 나가거나 App Switcher로 앱을 전환하면 앱은 백그라운드 상태로 실행됩니다.
iOS는 퍼포먼스의 이슈로 백그라운드에서 실행되는 앱의 시간을 제한합니다.
이 제한된 시간안에 작업을 수행하려면 BackgroundTask API를 사용하여 시간 제한을 늘릴 수 있습니다.
예를 들어 파일 다운로드, 데이터 동기화, 알림 처리, 위치 추적, 오디오 재생 등을 백그라운드에서 처리할 수 있습니다.
(그래도 제한 시간이 지나면 강제로 종료 시킵니다.)
그리고! BackgroundTask를 구현하려면 XCode에서 Background Mode도 설정해주어야 합니다.
Background Mode는 iOS 앱이 백그라운드에서 실행 가능한 기능을 설정하는 옵션입니다.
정리하자면, Background Mode로 부터 승인된 기능을 Background Task API로 구현하는거네요~
백그라운드 모드는 iOS앱이 백그라운드에서 실행 가능한 기능을 설정하는 옵션 입니다.
XCode -> 타겟 -> Capabilities -> Background Mode
에서 설정할 수 있습니다.
많은 설정 값들이 있는데,
이 글에서는 BackgroundTask와 관련되어 보이는
백그라운드에서 실행되는 Task를 스케쥴링하기 위해, Background Mode를 설정하고 Task를 BGTaskScheduler
객체에 등록해야 합니다.
Background Task엔 2가지 유형이 있습니다.
1. BGAppRefeshTask
2.BGProcessingTask
Background Fetch는 BGAppRefresh Task를 사용합니다.
Background Processing은 BGProcessing Task를 사용합니다.
Permitted background task scheduler identifiers
에 추가한다! (BGTaskSchedulerPermittedIdentifiers 배열인)
여기에 Task의 공인된 식별자 String을 넣어준다.
👩💻 iOS13부터 여기다 key를 추가하면 application(_:performFetchWithCompletionHandler:)
and setMinimumBackgroundFetchInterval(_:)
이 함수가 안불린다고 합니다.
Background Task의 실제 동작은 1. 등록 2. 스케줄링 3. 실행 4. 완료 입니다.
각 Task는 launch Handler를 갖는 BGTaskScheduler
객체, 고유한 식별자를 갖습니다.
모든 Task를 app launch sequence가 끝나기 전에 BGTaskScheduler
에 등록 해야 합니다.
👩💻 그럼 launch 가 끝나는 시점인 appDelegate의 didFinishLaunchingWithOptions 함수에서 호출하면 되겠네요.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
registerBackgroundTasks()
return true
}
/// 앱의 launch sequence가 끝나기 전에 Background Task를 Scheduler에 "등록"해야 합니다.
/// Info.plist에 등록한 키 값으로 등록해야 합니다.
private func registerBackgroundTasks() {
print("Background Task 등록!")
// RefreshTask
// 1. Refresh Task 등록
let taskIdentifier = ["com.example.apple-samplecode.ColorFeed.refresh"]
BGTaskScheduler.shared.register(forTaskWithIdentifier: taskIdentifier[0], using: nil, launchHandler: { task in
// 2. 실제로 수행할 Background 동작 구현
self.handleBackgroundTask(task: task as! BGAppRefreshTask)
})
}
예시에서는 RefreshTask를 등록합니다.
등록 할 때 정의하는 클로저에서는 Task가 시작되면 실제 수행 할 함수를 구현합니다.
앱이 Background 상태에 들어갈 때 BGTaskScheduler
에 Task를 submit
해줍니다.
이 때, TaskRequest를 만들어서 submit할 수 있는데
을 설정할 수 있습니다.
Background 상태에 돌입할 때이니 AppDelegate와 SceneDelegate에 알맞은 함수에 넣어줘야겠죠?
func applicationDidEnterBackground(_ application: UIApplication) {
scheduleBackgroundTask()
print(#function)
}
private func scheduleBackgroundTask() {
let task = BGAppRefreshTaskRequest(identifier: "com.example.apple-samplecode.ColorFeed.refresh")
/// (Processing Task 였다면)
/*
task.requiresExternalPower = false // 배터리를 사용할 것인지 여부
task.requiresNetworkConnectivity = false // 네트워크를 사용할 것인지 여부
*/
// 백그라운드 작업을 실행할 때까지의 최소 대기 시간
task.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
do {
print("Background Task submit!")
// Background Task 등록!!
try BGTaskScheduler.shared.submit(task)
} catch {
print("Could not schedule app refesh")
}
}
BGTaskScheduler
에 submit하면 Task가 스케줄러에 등록되고 시스템이 판단했을 때 earliestBeginDate가 지나지 않은 적절한 시기에 실행됩니다. (안..할지도?)
BackgroundTask를 실제 구현하는 곳에서는 2가지만 기억합니다.
👩💻 예시1) 파일 다운로드
private func handleBackgroundTask(task: BGAppRefreshTask) {
task.expirationHandler = {
task.setTaskCompleted(success: false)
}
let config = URLSessionConfiguration.background(withIdentifier: "com.example.apple-samplecode.ColorFeed.refresh")
let session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
let downloadTask = session.downloadTask(with:URL(string: "https://example.com/my-file.zip")!)
downloadTask.resume()
}
BackgroundTask에서 URlSession을 이용해서 파일을 다운로드 한다면, urlSessionDelegate를 이용해서 완료되거나/에러 등으로 인해 종료될 때 task를 명시적으로 Complete 시켜줘야 합니다.
private func endBackgroundTask() {
if let backgroundTask = self.backgroundTask {
backgroundTask.setTaskCompleted(success: true)
self.backgroundTask = nil
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
// 파일 다운로드 완료
print("Background Task 완료!")
// BackgroundTask 종료
endBackgroundTask()
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error {
// 오류 처리..
print("Background Task 에러!")
print(error)
}
// BackgroundTask 종료
endBackgroundTask()
}
이런식으로요!
👩💻 예시2) async/await 사용
private func handleBackgroundTask(task: BGAppRefreshTask) {
task.expirationHandler = {
task.setTaskCompleted(success: false)
}
Task {
do {
let url = URL(string: "https://baconipsum.com/api/?type=all-meat¶s=1&start-with-lorem=1")!
let request = URLRequest(url: url)
async let (data, response) = URLSession.shared.data(for: request)
guard try await (response as? HTTPURLResponse)?.statusCode == 200 else {
throw HyunndyError.badNetwork
}
let paragraph = try JSONDecoder().decode([String].self, from: try await data)
print("BackgroundTask 성공!! \n\(paragraph[0])")
task.setTaskCompleted(success: true)
} catch {
task.setTaskCompleted(success: false)
}
}
}
한 곳에서 관리하니 확실히 편하네요.
위에 Background Task 관련 Background Mode가 Remote Notifications가 있다고 했는데요.
iOS 13 이상에서는 UNUserNotificationCenterDelegate 프로토콜을 통해 원격 알림을 처리할 수 있으나, iOS 12 이하 버전에서는 Background Task를 사용해야 한다고 합니다.
Background Task를 아주 간소하게 알아보았습니다!
Advanced in Background Task
요게 심화 과정 WWDC 이네요.
요건 천천히 보는걸로 하고~
Background Task를 학습해보니 티맵이나 이런 네비게이션 어플의 Background Task 구현이 궁금해지네요.