Skip to content

Instantly share code, notes, and snippets.

@kazzkiq
Created February 27, 2025 18:59
Show Gist options
  • Save kazzkiq/0dfa1c853ecc81b5058c5ffec37f764e to your computer and use it in GitHub Desktop.
Save kazzkiq/0dfa1c853ecc81b5058c5ffec37f764e to your computer and use it in GitHub Desktop.
iOS Background service

Conversation with Claude 3.5 Sonnet

Chat (user): I heard Apple iOS does not accept background tasks to keep running when an app is terminated. So if the user closes the app there is no way to keep running anything from that app in background, is that true?

While iOS is more restrictive with background processing compared to Android, there are several legitimate ways to perform background tasks in iOS, even when the app is not actively running:

  1. Background Modes (supported capabilities):
  • Audio playback
  • Location updates
  • VoIP
  • Background fetch
  • Remote notifications
  • Background processing
  • Background downloads/uploads (URLSession)
  1. Background App Refresh:
if UIApplication.shared.backgroundRefreshStatus == .available {
    // Configure background fetch
    UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)
}
  1. Push Notifications with background content:
// In payload
{
    "aps": {
        "content-available": 1
    }
}
  1. Background URLSession:
let config = URLSessionConfiguration.background(withIdentifier: "com.example.bgdownload")
let session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
  1. Silent Push Notifications (Remote Notifications)

However, there are limitations:

  • Background execution time is limited
  • System can terminate background tasks if resources are needed
  • Must declare background modes in Info.plist
  • Some features require user permission
  • App must be installed to use these features

The key is that while you can't keep an app permanently running in the background, you can schedule and execute specific background tasks within iOS's guidelines and restrictions.

Chat (user): Can you tell me more about Background fetch?

Background fetch is a feature in iOS that allows apps to periodically wake up in the background to fetch new content. Here's a detailed explanation:

  1. First, you need to enable Background Fetch capability in Xcode:
// Add in Info.plist
UIBackgroundModes: fetch
  1. Basic implementation in AppDelegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Request background fetch
    UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)
    // Or set custom interval (in seconds)
    // UIApplication.shared.setMinimumBackgroundFetchInterval(3600) // 1 hour
    return true
}

func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    // Perform your background fetch here
    
    // After fetch, call completion handler with one of these:
    completionHandler(.newData)     // If new data was downloaded
    completionHandler(.noData)      // If no new data was available
    completionHandler(.failed)      // If the fetch failed
}
  1. Modern implementation using BGTaskScheduler (iOS 13+):
import BackgroundTasks

class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Register background task
        BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.example.fetch", using: nil) { task in
            self.handleAppRefresh(task: task as! BGAppRefreshTask)
        }
        return true
    }
    
    func scheduleAppRefresh() {
        let request = BGAppRefreshTaskRequest(identifier: "com.example.fetch")
        request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) // 15 minutes
        
        do {
            try BGTaskScheduler.shared.submit(request)
        } catch {
            print("Could not schedule app refresh: \(error)")
        }
    }
    
    func handleAppRefresh(task: BGAppRefreshTask) {
        // Schedule the next fetch
        scheduleAppRefresh()
        
        // Create an operation that performs the main fetch work
        let operation = NetworkOperation()
        
        // Create a task cancellation handler that cancels the operation
        task.expirationHandler = {
            operation.cancel()
        }

        operation.completionBlock = {
            task.setTaskCompleted(success: !operation.isCancelled)
        }
        
        // Start the operation
        operationQueue.addOperation(operation)
    }
}

Important points about Background Fetch:

  1. Limitations:
  • The system decides when to wake up your app
  • Not guaranteed to run at specific times
  • Limited execution time (30 seconds typically)
  • System considers battery level and user patterns
  1. Best Practices:
  • Complete work quickly
  • Schedule the next fetch after current one completes
  • Handle task expiration appropriately
  • Use for important updates only
  • Consider battery impact
  1. Testing:
// In Debug scheme
Edit Scheme -> Run -> Options -> Launch due to background fetch event
  1. Monitoring:
// Check if background fetch is available
if UIApplication.shared.backgroundRefreshStatus == .available {
    // Background fetch is enabled
} else {
    // Background fetch is disabled or restricted
}

Background fetch is useful for apps that need to:

  • Update content periodically
  • Download new data
  • Sync with servers
  • Prepare content for the user before they open the app

Remember that users can disable Background App Refresh in Settings, so your app should handle cases where it's not available.

Chat (user): Is it possible to use Background fetch to get the user device location and send it to an API?

Yes, it's possible to combine Background Fetch with Location Services, but there are some important considerations and requirements:

  1. First, you need both capabilities in Info.plist:
<key>UIBackgroundModes</key>
<array>
    <string>fetch</string>
    <string>location</string>
</array>

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>We need your location to...</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>We need your location to...</string>
  1. Basic implementation example:
import CoreLocation
import BackgroundTasks

class LocationManager: NSObject, CLLocationManagerDelegate {
    private let locationManager = CLLocationManager()
    
    override init() {
        super.init()
        setupLocationManager()
    }
    
    private func setupLocationManager() {
        locationManager.delegate = self
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.allowsBackgroundLocationUpdates = true
        locationManager.pausesLocationUpdatesAutomatically = false
        
        // Request permissions
        locationManager.requestAlwaysAuthorization()
    }
    
    func startMonitoringLocation() {
        locationManager.startUpdatingLocation()
    }
    
    // Location delegate method
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.last else { return }
        sendLocationToAPI(location)
    }
    
    private func sendLocationToAPI(_ location: CLLocation) {
        let coordinates = [
            "latitude": location.coordinate.latitude,
            "longitude": location.coordinate.longitude
        ]
        
        // Your API call here
    }
}

class AppDelegate: UIResponder, UIApplicationDelegate {
    let locationManager = LocationManager()
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        registerBackgroundTask()
        return true
    }
    
    private func registerBackgroundTask() {
        BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.example.locationfetch", using: nil) { task in
            self.handleLocationFetch(task: task as! BGAppRefreshTask)
        }
    }
    
    private func scheduleLocationFetch() {
        let request = BGAppRefreshTaskRequest(identifier: "com.example.locationfetch")
        request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) // 15 minutes
        
        do {
            try BGTaskScheduler.shared.submit(request)
        } catch {
            print("Could not schedule location fetch: \(error)")
        }
    }
    
    private func handleLocationFetch(task: BGAppRefreshTask) {
        scheduleLocationFetch() // Schedule next fetch
        
        task.expirationHandler = {
            // Handle task expiration
        }
        
        locationManager.startMonitoringLocation()
        
        // Set a timeout to complete the task
        DispatchQueue.main.asyncAfter(deadline: .now() + 25) {
            task.setTaskCompleted(success: true)
        }
    }
}

Important Considerations:

  1. Legal and Privacy:
  • Must clearly inform users about background location tracking
  • Get explicit user consent
  • Follow privacy laws and App Store guidelines
  • Provide a clear way to opt out
  1. Technical Limitations:
  • Background fetch isn't guaranteed to run at specific intervals
  • System may limit frequency based on battery life and usage patterns
  • Location updates in background might be less frequent
  • Need to handle cases where location services are disabled
  1. Battery Impact:
  • Continuous location monitoring can significantly impact battery life
  • Consider using lower accuracy settings when appropriate
  • Implement intelligent polling intervals
  1. Better Alternatives: For location tracking, you might want to consider these alternatives instead:
// Significant Location Changes
locationManager.startMonitoringSignificantLocationChanges()

// Region Monitoring
let center = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
let region = CLCircularRegion(center: center, radius: 100, identifier: "identifier")
locationManager.startMonitoring(for: region)
  1. Best Practices:
// Check authorization status
if CLLocationManager.authorizationStatus() == .authorizedAlways {
    // Start location updates
} else {
    // Handle unauthorized state
}

// Check if background location is available
if CLLocationManager.significantLocationChangeMonitoringAvailable() {
    // Use significant location changes
}

// Battery-efficient location monitoring
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.distanceFilter = 100 // meters

Remember that constantly tracking location in the background:

  • Must provide clear user benefit
  • Should be implemented with battery efficiency in mind
  • Must respect user privacy
  • Should include proper error handling
  • Must comply with Apple's guidelines and policies

Consider if your use case really requires background location tracking, as it might affect App Store approval and user trust.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment