Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
dpearson2699 avatar

Background Processing

  • 2.7k installs
  • 944 repo stars
  • Updated July 15, 2026
  • dpearson2699/swift-ios-skills

background-processing is a skill for iOS BGTaskScheduler tasks, background URLSession, and silent push handling.

About

background-processing covers registering, scheduling, and executing background work on iOS with BackgroundTasks, background URLSession, and silent push. Every task identifier must appear in Info.plist BGTaskSchedulerPermittedIdentifiers or submit throws notPermitted. UIBackgroundModes fetch and processing enable BGAppRefreshTask and BGProcessingTask respectively. Handlers register before app launch completes in AppDelegate or SwiftUI App.init. BGAppRefreshTask suits short fetches with earliestBeginDate hints and mandatory expirationHandler calling setTaskCompleted. BGProcessingTask supports longer maintenance when idle and optionally on external power. BGContinuedProcessingTask on iOS 26+ continues foreground-started exports with ProgressReporting and Live Activity updates. Background URLSession downloads use delegate callbacks and store completion handlers for relaunch. Silent push requires content-available 1 and apns-push-type background with priority 5. Common mistakes include missing plist identifiers, skipping setTaskCompleted, scheduling refreshes too frequently, and a review checklist for simulated launches and incremental cancellation-safe work.

  • Info.plist BGTaskSchedulerPermittedIdentifiers required for every task.
  • Register handlers before app launch; always call setTaskCompleted.
  • expirationHandler must cancel work on BGAppRefresh and Processing tasks.
  • BGContinuedProcessingTask on iOS 26+ with progress reporting.
  • Background URLSession delegate and silent push apns-push-type background.

Background Processing by the numbers

  • 2,721 all-time installs (skills.sh)
  • +133 installs in the week ending Jul 29, 2026 (Skillselion tracking)
  • Ranked #70 of 1,039 Mobile Development skills by installs in the Skillselion catalog
  • Security screen: LOW risk (skills.sh audit)
  • Data as of Jul 31, 2026 (Skillselion catalog sync)
At a glance

background-processing capabilities & compatibility

Capabilities
info.plist bgtaskschedulerpermittedidentifiers s · bgapprefreshtask and bgprocessingtask scheduling · bgcontinuedprocessingtask progress reporting ios · background urlsession delegate download handling · silent push content available handling · expiration handler and settaskcompleted enforcem
Use cases
api development · orchestration
Platforms
macOS
IDEs
vscode
Pricing
Free
From the docs

What background-processing says it does

Every task identifier **must** be declared in `Info.plist`
SKILL.md
CRITICAL: Handle expiration -- system can revoke time at any moment
SKILL.md
earliestBeginDate is a lower-bound hint
SKILL.md
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill background-processing

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs2.7k
repo stars944
Security audit3 / 3 scanners passed
Last updatedJuly 15, 2026
Repositorydpearson2699/swift-ios-skills

How do I register, schedule, and complete iOS background refresh and processing tasks correctly?

Register and schedule iOS background tasks with BGTaskScheduler, URLSession downloads, and silent push triggers.

Who is it for?

iOS developers implementing BGAppRefreshTask, BGProcessingTask, or background downloads.

Skip if: Skip for Android background work or foreground-only SwiftUI UI tasks.

When should I use this skill?

User configures BGTaskScheduler, Info.plist background modes, URLSession background downloads, or silent push.

What you get

Permitted identifiers, registered handlers, expiration-safe tasks, and validated background modes.

  • BGTaskScheduler registration code
  • Info.plist background configuration
  • Expiration and completion handler patterns

By the numbers

  • Covers 3 BGTaskScheduler task types: BGAppRefreshTask, BGProcessingTask, and BGContinuedProcessingTask (iOS 26+)

Files

SKILL.mdMarkdownGitHub ↗

Background Processing

Register, schedule, and execute background work on iOS using the BackgroundTasks framework, background URLSession, and background push notifications.

Contents

Info.plist Configuration

Every task identifier must be declared in Info.plist under BGTaskSchedulerPermittedIdentifiers, or submit(_:) throws BGTaskScheduler.Error.Code.notPermitted.

<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
    <string>com.example.app.refresh</string>
    <string>com.example.app.db-cleanup</string>
    <string>com.example.app.export.*</string>
</array>

Also enable the required UIBackgroundModes:

<key>UIBackgroundModes</key>
<array>
    <string>fetch</string>       <!-- Required for BGAppRefreshTask -->
    <string>processing</string>  <!-- Required for BGProcessingTask -->
</array>

In Xcode: target > Signing & Capabilities > Background Modes > enable "Background fetch" and "Background processing".

BGTaskScheduler Registration

Register handlers before app launch completes. In UIKit, register in application(_:didFinishLaunchingWithOptions:); in SwiftUI, register in App.init().

UIKit Registration

import BackgroundTasks

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        BGTaskScheduler.shared.register(
            forTaskWithIdentifier: "com.example.app.refresh",
            using: nil  // nil = default background queue
        ) { task in
            self.handleAppRefresh(task: task as! BGAppRefreshTask)
        }

        BGTaskScheduler.shared.register(
            forTaskWithIdentifier: "com.example.app.db-cleanup",
            using: nil
        ) { task in
            self.handleDatabaseCleanup(task: task as! BGProcessingTask)
        }

        return true
    }
}

SwiftUI Registration

import SwiftUI
import BackgroundTasks

@main
struct MyApp: App {
    init() {
        BGTaskScheduler.shared.register(
            forTaskWithIdentifier: "com.example.app.refresh",
            using: nil
        ) { task in
            BackgroundTaskManager.shared.handleAppRefresh(
                task: task as! BGAppRefreshTask
            )
        }
    }

    var body: some Scene {
        WindowGroup { ContentView() }
    }
}

BGAppRefreshTask Patterns

Short-lived tasks (~30 seconds) for fetching small data updates. The system decides when to launch based on usage patterns. Review notes should say earliestBeginDate is a lower-bound hint and the system may run the task later.

func scheduleAppRefresh() {
    let request = BGAppRefreshTaskRequest(
        identifier: "com.example.app.refresh"
    )
    request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
    // earliestBeginDate is a lower-bound hint; the system may delay launch.
    do {
        try BGTaskScheduler.shared.submit(request)
    } catch {
        print("Could not schedule app refresh: \(error)")
    }
}

func handleAppRefresh(task: BGAppRefreshTask) {
    // Schedule the next refresh before doing work
    scheduleAppRefresh()

    let fetchTask = Task {
        do {
            let data = try await APIClient.shared.fetchLatestFeed()
            await FeedStore.shared.update(with: data)
            task.setTaskCompleted(success: true)
        } catch {
            task.setTaskCompleted(success: false)
        }
    }

    // CRITICAL: Handle expiration -- system can revoke time at any moment
    task.expirationHandler = {
        fetchTask.cancel()
        task.setTaskCompleted(success: false)
    }
}

BGProcessingTask Patterns

Long-running tasks (minutes) for maintenance, data processing, or cleanup. Runs only when device is idle and (optionally) charging. Review notes should say earliestBeginDate is a lower-bound hint and the system may run the task later.

func scheduleProcessingTask() {
    let request = BGProcessingTaskRequest(
        identifier: "com.example.app.db-cleanup"
    )
    request.requiresNetworkConnectivity = false
    request.requiresExternalPower = true
    request.earliestBeginDate = Date(timeIntervalSinceNow: 60 * 60)
    // earliestBeginDate is a lower-bound hint; the system may delay launch.
    do {
        try BGTaskScheduler.shared.submit(request)
    } catch {
        print("Could not schedule processing task: \(error)")
    }
}

func handleDatabaseCleanup(task: BGProcessingTask) {
    scheduleProcessingTask()

    let cleanupTask = Task {
        do {
            try await DatabaseManager.shared.purgeExpiredRecords()
            try await DatabaseManager.shared.rebuildIndexes()
            task.setTaskCompleted(success: true)
        } catch {
            task.setTaskCompleted(success: false)
        }
    }

    task.expirationHandler = {
        cleanupTask.cancel()
        task.setTaskCompleted(success: false)
    }
}

BGContinuedProcessingTask (iOS 26+)

A task initiated in the foreground by a user action that continues running in the background. The system displays progress via a Live Activity. Conforms to ProgressReporting.

Availability: iOS 26.0+, iPadOS 26.0+

Unlike BGAppRefreshTask and BGProcessingTask, this task starts immediately from the foreground. The system can terminate it under resource pressure, prioritizing tasks that report minimal progress first. Set expirationHandler for user or system cancellation, cancel in-flight work, and clean up partial output before reporting completion.

import BackgroundTasks

func startExport() {
    // Register the task handler at app launch, not here.
    // BGTaskScheduler requires registration before app launch completes.
    let jobID = UUID().uuidString
    let request = BGContinuedProcessingTaskRequest(
        identifier: "com.example.app.export.\(jobID)",
        title: "Exporting Photos",
        subtitle: "Processing 247 items"
    )
    // Use a permitted base wildcard identifier: com.example.app.export.*
    // earliestBeginDate is ignored for continued processing requests.
    // .queue: begin as soon as possible if can't run immediately
    // .fail: fail submission if can't run immediately
    request.strategy = .queue

    do {
        try BGTaskScheduler.shared.submit(request)
    } catch {
        print("Could not submit continued processing task: \(error)")
    }
}

func performExport(task: BGContinuedProcessingTask) async {
    let items = await PhotoLibrary.shared.itemsToExport()
    let progress = task.progress
    progress.totalUnitCount = Int64(items.count)

    for (index, item) in items.enumerated() {
        if Task.isCancelled { break }

        await PhotoExporter.shared.export(item)
        progress.completedUnitCount = Int64(index + 1)

        // Update the user-facing title/subtitle
        task.updateTitle(
            "Exporting Photos",
            subtitle: "\(index + 1) of \(items.count) complete"
        )
    }

    task.setTaskCompleted(success: !Task.isCancelled)
}

For GPU work, check support and enable Background GPU Access (com.apple.developer.background-tasks.continued-processing.gpu):

let supported = BGTaskScheduler.supportedResources
if supported.contains(.gpu) {
    request.requiredResources = .gpu
}

Background URLSession Downloads

Use URLSessionConfiguration.background for downloads that continue even after the app is suspended or terminated. The system handles the transfer out of process.

class DownloadManager: NSObject, URLSessionDownloadDelegate {
    static let shared = DownloadManager()

    private lazy var session: URLSession = {
        let config = URLSessionConfiguration.background(
            withIdentifier: "com.example.app.background-download"
        )
        config.isDiscretionary = true
        config.sessionSendsLaunchEvents = true
        return URLSession(configuration: config, delegate: self, delegateQueue: nil)
    }()

    func startDownload(from url: URL) {
        let task = session.downloadTask(with: url)
        task.earliestBeginDate = Date(timeIntervalSinceNow: 60)
        task.resume()
    }

    func urlSession(
        _ session: URLSession,
        downloadTask: URLSessionDownloadTask,
        didFinishDownloadingTo location: URL
    ) {
        // Move file from tmp before this method returns
        let dest = FileManager.default.urls(
            for: .documentDirectory, in: .userDomainMask
        )[0].appendingPathComponent("download.dat")
        try? FileManager.default.moveItem(at: location, to: dest)
    }

    func urlSession(
        _ session: URLSession,
        task: URLSessionTask,
        didCompleteWithError error: (any Error)?
    ) {
        if let error { print("Download failed: \(error)") }
    }
}

Handle app relaunch — store and invoke the system completion handler:

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

// In URLSessionDelegate — call stored handler when events finish:
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
    Task { @MainActor in
        self.backgroundSessionCompletionHandler?()
        self.backgroundSessionCompletionHandler = nil
    }
}

Background Push Triggers

Silent push notifications wake your app briefly to fetch new content. Set content-available: 1 in the push payload.

{ "aps": { "content-available": 1 }, "custom-data": "new-messages" }

Send the APNs request with apns-push-type: background and apns-priority: 5. Background push delivery is low priority and not guaranteed; keep sends infrequent, generally no more than two or three per hour.

Handle in AppDelegate:

func application(
    _ application: UIApplication,
    didReceiveRemoteNotification userInfo: [AnyHashable: Any],
    fetchCompletionHandler completionHandler:
        @escaping (UIBackgroundFetchResult) -> Void
) {
    Task {
        do {
            let hasNew = try await MessageStore.shared.fetchNewMessages()
            completionHandler(hasNew ? .newData : .noData)
        } catch {
            completionHandler(.failed)
        }
    }
}

Enable "Remote notifications" in Background Modes and register:

UIApplication.shared.registerForRemoteNotifications()

Common Mistakes

1. Missing Info.plist identifiers

// DON'T: Submit a task whose identifier isn't in BGTaskSchedulerPermittedIdentifiers
let request = BGAppRefreshTaskRequest(identifier: "com.example.app.refresh")
try BGTaskScheduler.shared.submit(request)  // Throws .notPermitted

// DO: Add every identifier to Info.plist BGTaskSchedulerPermittedIdentifiers
// <string>com.example.app.refresh</string>

2. Not calling setTaskCompleted(success:)

// DON'T: Return without marking completion -- system penalizes future scheduling
func handleRefresh(task: BGAppRefreshTask) {
    Task {
        let data = try await fetchData()
        await store.update(data)
        // Missing: task.setTaskCompleted(success:)
    }
}

// DO: Always call setTaskCompleted on every code path
func handleRefresh(task: BGAppRefreshTask) {
    let work = Task {
        do {
            let data = try await fetchData()
            await store.update(data)
            task.setTaskCompleted(success: true)
        } catch {
            task.setTaskCompleted(success: false)
        }
    }
    task.expirationHandler = {
        work.cancel()
        task.setTaskCompleted(success: false)
    }
}

3. Ignoring the expiration handler

// DON'T: Assume your task will run to completion
func handleCleanup(task: BGProcessingTask) {
    Task { await heavyWork() }
    // No expirationHandler -- system terminates ungracefully
}

// DO: Set expirationHandler to cancel work and mark completed
func handleCleanup(task: BGProcessingTask) {
    let work = Task { await heavyWork() }
    task.expirationHandler = {
        work.cancel()
        task.setTaskCompleted(success: false)
    }
}

4. Scheduling too frequently

// DON'T: Request refresh every minute -- system throttles aggressively
request.earliestBeginDate = Date(timeIntervalSinceNow: 60)

// DO: Use reasonable intervals (15+ minutes for refresh)
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
// earliestBeginDate is a hint -- the system chooses actual launch time

5. Over-relying on background time

// DON'T: Start a 10-minute operation assuming it will finish
func handleRefresh(task: BGAppRefreshTask) {
    Task { await tenMinuteSync() }
}

// DO: Design work to be incremental and cancellable
func handleRefresh(task: BGAppRefreshTask) {
    let work = Task {
        for batch in batches {
            try Task.checkCancellation()
            await processBatch(batch)
            await saveBatchProgress(batch)
        }
        task.setTaskCompleted(success: true)
    }
    task.expirationHandler = {
        work.cancel()
        task.setTaskCompleted(success: false)
    }
}

Review Checklist

  • [ ] All task identifiers listed in BGTaskSchedulerPermittedIdentifiers
  • [ ] Required UIBackgroundModes enabled (fetch, processing)
  • [ ] Tasks registered before app launch completes
  • [ ] setTaskCompleted(success:) called on every code path
  • [ ] expirationHandler set and cancels in-flight work
  • [ ] Next task scheduled inside the handler (re-schedule pattern)
  • [ ] earliestBeginDate uses reasonable intervals and is treated as a hint
  • [ ] Background URLSession uses delegate (not async/closures)
  • [ ] Background URLSession file moved in didFinishDownloadingTo before return
  • [ ] handleEventsForBackgroundURLSession stores and calls completion handler
  • [ ] Background push payload includes content-available: 1
  • [ ] Background push APNs request uses apns-push-type: background and apns-priority: 5
  • [ ] fetchCompletionHandler called promptly with correct result
  • [ ] BGContinuedProcessingTask reports progress via ProgressReporting
  • [ ] Work is incremental and cancellation-safe (Task.checkCancellation())
  • [ ] No blocking synchronous work in task handlers

References

  • See references/background-task-patterns.md for extended patterns, background

URLSession edge cases, debugging with simulated launches, and background push best practices.

Related skills

How it compares

Use background-processing for BGTaskScheduler and plist-compliant setup; use push notification skills alone when you only need foreground APNs handling.

FAQ

Why does submit throw notPermitted?

The task identifier is missing from BGTaskSchedulerPermittedIdentifiers in Info.plist.

When must setTaskCompleted be called?

On every code path including expirationHandler; missing completion penalizes future scheduling.

What is earliestBeginDate?

A lower-bound hint; the system may delay launch beyond that date.

Is Background Processing safe to install?

skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.