백그라운드에서 작업 완료하기 (Finish tasks in the background) -wwdc25


사용자가 앱을 나가고도 프로세스가 살아 있으면 앱은 백그라운드로 전환.
기본적으로 백그라운드 앱은 Suspended 되어서 CPU를 사용하지 않게 됨.
이는 배터리를 보호하고 개인 정보를 지키며 foreground 앱에 더 많은 리소스를 확보해 줌.
경우에 따라서는 앱은 suspened 전 작업을 마무리 할 수 있도록 백그라운드에 실행을 요청할 수 있음.

백그라운드 런타임을 사용하기 전에 시스템이 리소스를 어떻게 우선순위화 하고 관리하는지 그리고 앱 안에서 무엇을 할 수 있을지 이해하는 것이 좋음.
시스템의 목표는 배터리를 보호하고 개인정보를 지키며 반응성 좋은 앱을 만드는 것임.
즉, 백그라운드 실행은 보장되지 않으며, 기회가 있을 때만 허용되고 재량에 따라 엄격히 제한됨.
작업을 올바르게 수행하려면 이런 시스템 맥락을 이해하고 시스템과 협력하는 방향으로 설계해야 함.

가장 기본적인 제약은 에너지(battery life)로 모든 작업, CPU 사이클, GPU 렌더링, 네트워크 요청, Neural Engine까지 모두 에너지를 소모함.
배터리 수명은 한정적인 리소스로 이를 지키기 위해 기기가 깨어날 때 작업을 묶어서 처리하고 불필요한 백그라운드 활동을 줄임

백그라운드 런타임은 제한적이므로 앱이 백그라운드에서 할 일은 작고 명확한 개별 작업으로 구성해야 함.
각 시스템은 우선순위를 정해 효율적으로 하나의 일을 처리해야 함.
백그라운드 작업은 사용자 앱에 표시되어서 어떤 앱이 배터리에 영향을 주는지 알 수 있음.
효율적으로 작업하기 위해 작업이 즉시 실행될 필요가 없다면 기기가 충전 중일 때 지연 실행하는 것도 고려할 수 있음.
즉시 실행이 필요해도 가볍고 목적 중심적으로 구성해야 함.

메모리, CPU, 네트워크 대역폭 등 공유되는 자원도 관리함.
사용자의 기기를 사용할 때는 포그라운드 앱이 우선임.
백그라운드 앱이 과도한 메모리나 CPU를 사용하면 비효율적일 뿐만 아니라 포그라운드 경험과도 충돌하며 이 경우 시스템을 스로틀링 일시 중단 등 심하면 프로세스 종료까지 진행할 수 있음.
즉, 백그라운드 작업을 최소화해야 함. 작업을 나눠서 일괄 처리하고 메모리 사용량을 줄여야 함.

작업이 잘 설계 되었어도 항상 실행되는 것도 아님.
백그라운드 작업 큐는 비어있는 적이 없기 때문에 시스템은 다른 작업에 우선순위를 줄 수 있음.
작업 부하는 탄력적이어야 함. 중간 결과를 자주 저장하고 시스템이 나중에 작업을 재개할 것을 믿어야 함.


최종 결정권은 기기 사용자에게 있음.
저전력 모드 및 백그라운드 앱 새로고침 등 스케줄링에 영향을 줌.


모든 프로세스가 이 원칙을 지키더라도 시스템은 매우 복잡하게 동작함.
여러가지 맥락들이 스케줄 결정에 반영하며,시스템은 최적의 결정을 하고자 함.
따라서 적응력이 중요하며 작업은 작고 가볍게 유지하고, 필요 사항은 명확히 전달해야 함.
작업을 이어받아서 재개할 수 있다면 런타임 기회가 생길 때마다 점진적으로 진행이 가능함.

iOS에는 백그라운드 런타임을 요청하는 다양한 API가 존재하고 각 API는 작업의 특성에 따라 다른 유형과 조건을 가정함
이를 통해 시스템은 앱의 런타임을 앞서 설명한 제약과 조건에 맞게 조정할 수 있음
- BGAppRefresh
- 사용 직전 서버에서 조용히 콘텐츠를 불러올 수 있음.
- 시스템은 해당 작업을 새 사용과 이력에 따라 스케줄링 하며, 자주 사용하는 앱일수록 예약될 확률이 높아짐.
- 그 결과 실행 때마다 컨텐츠가 최신으로 유지됨.

SwiftUI에서 해당 기능을 사용한다면 Scene에서 backgroundTask(.appRefresh)를 추가하면 됨.
앱이 백그라운드에서 실행될 때 시스템은 해당 클로저를 호출하고 종료 시 앱을 일시 중단 함.

앱이 서버 원격 콘텐츠를 사용하지만 자주 변경되지 않는 문서라면 Background Push Notifications가 더 적합함
- Background Push Notifications
- 서버에서 새 콘텐츠에 대한 알림을 보내면 시스템이 적절한 시점에 앱을 깨줘 콘텐츠를 가져오게 함.
- 이 방식은 앱을 새로고침 하는 방식과 달라 데이터를 직접 요청하는게 아니라 기기로 업데이트가 푸시됨.
- 이는 새로운 원격 콘텐츠가 있다는 신호로 쓰이므로 항상 선택적으로 취급되며 낮은 우선순위로 전송되고 병합되어서 과부하 및 배터리 사용을 최소화 함.
- 사용자가 앱을 앱 전환기(App Switcher를 통해 종료하면 시스템은 그 의도를 존중해 앱이 다시 실행되기 전까지 알림을 받지 않음


때로는 생성된 데이터를 바탕으로 ML 모델을 실행하거나 단순히 데이터베이스 정리를 하고 싶을 수도 있음.
이 경우에는 BGProcessingTask API가 이를 가능하게 함.
- BGProcessingTask
- forTaskWithIdentifier: 작업 식별 아이디
- using: 콜백에서 사용할 큐
- closure: 런타임에 호출될 클로저
- 앱 실행 직후 즉시 BackgroundTasks를 등록해야 하며, 앱이 백그라운드에서 실행될 경우 해당 작업을 바로 인식하고 즉시 호출할 수 있음.
- 처리 작업은 앞서 설명한 원칙을 반영할 수 있도록 추가 설정도 지원함.
- 위의 우측 사진의 예제는 충전중이고 네트워크가 연결되었을 때실행하도록 설정

때로는 백그라운드로 전환되는 동안에 조금 더 시간을 확보하고 싶을 수 있음.
beginBackgroundTask는 중단되면 복구가 어려운 작업을 마무리할 수 있게 도와줌
- beginBackgroundTask
- 단순한 상태 저장도 작업에 따라 중단 시 앱 경험을 해칠 수 있음
- 이런 코드를 API로 감싸면 시스템에 중요한 작업이 진행중임을 알릴 수 있고 중단을 피할 수 있음.
- 파일 핸들링을 종료하거나 데이터베이스 연결을 닫을 때 유용


사용자 주도 작업의 완료를 보장하는건 BGContinuedProcessingTask로 좋은 앱 경험의 핵심임.
이를 통해 앱은 백그라운드 전환 이후에도 작업을 수행하고 시스템은 진행상황을 계속 표시함.
이를 사용하면 사용자가 언제든 작업을 수행하고 작업을 취소할 수 있어서 복잡한 기능도 제어가 가능함.

BGContinuedProcessingTask는 언제나 명확한 사용자의 행동으로 시작됨 (버튼 클릭 혹은 제스처 등)
각 작업은 즉각적이고 명확한 목표를 가지고 있음. (파일 내보내기, SNS 컨텐츠 게시 연결된 액세서리 업데이트 등)
이런 작업은 측정 가능한 진행률을 가지고 있으며 작업이 완료되었을 때 무엇을 의미하는지 알 수 있음.

사용자는 작업이 자동으로 시작되리라고 기대하지 않음.
유지관리, 백업, 사진 동기화처럼 자동으로 실행되는 작업은 피해야 함.
작업이 명확한 동작 없이 자동으로 시작되면 사용자는 그 목적이나 진행 상태를 이해하지 못할 수도 있음.
예상치 못한 작업은 취소로 이어질 수 있음.

먼저 Info.plist에 작업 식별자를 추가함.
해당 식별자는 상태와 진행 상황을 관리하는 핸들러를 등록할 때 사용함.
그 다음 요청일 필요할 때 작업 요청을 제출하면 됨.


Info.plist에 새 값을 축하고 앱 번들 ID로 시작되도록 함.
정적인 식별자 외에도 지속적 처리 작업은 동적 접미사를 지원하는 새로운 와일드카드 형식을 지원함.
와일드 카드 식별자는 {번들 아이디}.{의미 있는 맥락}.{*}로 구성됨.


지속적으로 작업을 수행할 때 어떤 코드를 실행할 지 알아야 함.
앱 실행이 끝나기 전에 등록할 필요가 없고 해당 기능이 필요할 때 등록하면 됨.
또한 진행 상황을 제때 보고하는 것도 중요한데, 생각보다 오래 걸릴 때 사용자에게 의사를 물어서 리소스를 회수할 수도 있음.



프로세스는 이런 코드들을 사용해 진행상황을 보고하고 시스템은 이를 실시간을 감지하고 UI에 반영함.
상황에 따라 시스템이 일찍 중단해야 한다는 사실을 인지해야 함.
이를 처리하기 위해서는 expirationHandler를 제공해야 하며 이를 받았을 때 변수를 바꿔 작업을 종료할 수 있음
무엇보다도 작업이 끝났을 때는 setTaskCompleted를 반드시 호출해 시스템에 작업이 끝났음을 알려야 함.

먼저 작업을 요청할 객체를 초기화 함.
이후 strategy을 선택하는데 기본적으로 지속적 처리 작업을 즉시 실행할 수 없을 경우 큐에 추가함.
어떤 경우에는 큐를 사용하는게 적절하지 않은데 해당 작업이 즉시 실행되어야 유용하다면 strategy에 .fail로 설정하여 실패하게 할 수 있음.
strategy을 정하고 요청 구성을 마치면 스케줄러에 submit 함.


백그라운드에서 GPU를 활용하려면 Xcode 프로젝트 설정에서 백그라운드 GPU 기능을 추가해야 함.

추가한 후에는 속성을 동적으로 쿼리하는 것이 좋음.
이를 통해 런타임에서 어떤걸 지원하는지 명확하게 인지하고 사용함.
사용할 수 없는 리소스를 제출하면 거부되며 시스템과 작업 모두 안정적인 상태로 유지됨.
마지막으로 시스템의 우선순위 전체 맥락을 이해해야 하는데, iOS는 언제나 foreground 경험을 최우선으로 하여 background 작업은 활성 상태보다 낮은 퀄리티로 처리할 수 있음.
(참고)