Background Tasks

RSS for tag

Request the system to launch your app in the background to run tasks using Background Tasks.

Background Tasks Documentation

Pinned Posts

Posts under Background Tasks tag

136 Posts
Sort by:
Post not yet marked as solved
1 Replies
307 Views
In the WWDC 2020 session "Background execution demystified", it was mentioned that the runtime of non-discretionary Background URL Sessions (using URLSession background transfer) is influenced mainly by factors such as the App Switcher and rate limits. Based on this information, I'm curious to know if there are specific factors that similarly affect the runtime of UIApplication.shared.beginBackgroundTask(). Are there any documented constraints or system behaviors that impact how long a background task initiated by this method can run? I would appreciate any documentation or insights that could help clarify this aspect of background task management in iOS.
Posted
by Seisyu.
Last updated
.
Post marked as solved
1 Replies
521 Views
Hello community, I am looking for information about how BGAppRefreshTasks behave in case a user has disabled the Background App Refresh for my app in the Settings app. Does disabling Background App Refresh for my app means, that my registered and schedules tasks will never run or will the system execute scheduled tasks when the app is in the foreground and has priority on system resources? My app is dependent on the data downloaded with an BGAppRefreshTask and I want to know if I have to implement alternative ways to download data, for example when the scene phase changes, if users restrict my app. Thanks for any information about this topic in advance!
Posted Last updated
.
Post not yet marked as solved
1 Replies
310 Views
Hello All, I have been having issues scheduling background tasks in iOS and was wondering if there is a repository of information for that type of task. Essentially, I just need to run a function (lets call it customFunction(), and it currently is a blank function that doesn't do anything, for testing purposes) once in a while (e.g., every 5min or so... it doesn't have to be precisely at 5min. The system can decide when to run it.) I am using the BackgroundTasks framework. The background task is being scheduled without a problem, but it's being denied execution by the system. bgRefresh-com.testing.scheduler:EECE08:[ {name: ApplicationPolicy, policyWeight: 50.000, response: {Decision: Can Proceed, Score: 0.35}} {name: DeviceActivityPolicy, policyWeight: 5.000, response: {Decision: Can Proceed, Score: 0.33}} ] sumScores:61.706667, denominator:97.540000, FinalDecision: Can Proceed FinalScore: 0.632629} 'bgRefresh-com.testing.scheduler:EECE08' CurrentScore: 0.632629, ThresholdScore: 0.805854 DecisionToRun:0 Does anyone have any feedback on the most important items to check when scheduling a background task? I can share code, if necessary.
Posted
by marcelops.
Last updated
.
Post not yet marked as solved
1 Replies
591 Views
I have a project that must upload data in background and keep data going even when the app is closed/terminated. So i have a background upload of data that should start/continue when the app is unloaded from memory (user force exit). In most of the cases after killing the app background upload resumes after some time (from few seconds to 10-20 mins) and data is successfully sent. However this does not always work even as expected. When the user kills the application, background uploading may terminate too with an error «Error Domain=NSURLErrorDomain Code=-999 "(null)" UserInfo={NSURLErrorBackgroundTaskCancelledReasonKey=0» After killing the app background upload may stop without finishing/throwing error and never produce any event, and when I open the application background upload does not resume. There is no error events and there is no pending/active background tasks either on my URLSession. Can they really vanish this way? In very rare cases I have encountered the problem that when background uploading in proccess (app is fully closed and when I launch the application a white screen appears (i see no splash screen, no main view), but I still can see logs that the background tasks is working actively. After the completion of data uploading the white screen does not go away, it is necessary to re-launcth the application to solve this problem. I note that these cases are not always encountered and most of the time background upload works correctly. But I'd would like to understand how to handle these problems and what can cause this weirdness. Do I get that right, so that after the user has force exited the application and it is unloaded from memory, even if a background upload task is launhed - we have no guarantee that the task will complete and the system won't kill the background task? I guess i have to act on my own, keep an eye on my tasks and restart them if they're got vanished/terminated? Is there any advices or good practices how to handle this best? And are there any strict limits on the amount of data that can be sent in background per task? P.S I do have these capabilities enabled: Background fetch & Background processing.
Posted
by ElissP.
Last updated
.
Post not yet marked as solved
1 Replies
348 Views
I'm building a BGProcessingTask that may take more then 5 minutes to complete. The processing task runs on a Singleton object that can be accessed (or even started) when the app is on foreground. When the task expirationHandler is called, do i must finish my background work before calling task.setTaskCompleted(success: false)? I would like to have the background work suspended not cancelled. So when i reschedule another background processing task later or the app moves to foreground the same background task will continue. Is it ok practice? Will iOS might kill the background process (and the app) if not finished work before calling task.setTaskCompleted()? Or can i trust it will only get suspended and be able to resume later? Kind Regards
Posted
by Patz267.
Last updated
.
Post not yet marked as solved
1 Replies
671 Views
Hi. Didn't find the answer in the documentation. Are CloudKit procedures executables in background tasks ? I'm trying to do : "[privateDatabase fetchRecordWithID:myMasterRecordID completionHandler:^(CKRecord *myMasterRecord, NSError *error) { ... }]" but it returns a record only when the app is active. In background task, it does nothing and waits until app is active. Thanks for you answer. Best Claude
Posted
by tybutin.
Last updated
.
Post marked as solved
1 Replies
1k Views
Hello there, So I'm currently building a small Ble Device that can be controlled via an app. For ease of use I want to give the users the ability to turn on a setting to receive a notification when the the device is turned on or in range. In other words if the phone can discover it. The difficult part is that I want this behaviour to also work if the app is terminated. I already tried using iBeacons by adding the iBeacon capability to my Ble Device and waiting for it to be discovered so the app launches in background mode. This technically works but I'm looking for a more elegant solution to bring the app from terminated to background if a Ble Device is scanned. Is there any way to accomplish that? Thanks for your help.
Posted Last updated
.
Post not yet marked as solved
1 Replies
416 Views
Hi, I am having some trouble with uploading HealthKit data to AWS S3 in the background. As of now, when I click on the button to beginBackgroundUpdates the data is uploaded as expected. When I go off the app and add data to HealthKit nothing happens. When I go back to the app, the new data is uploaded. I am not sure why this is not happening in the background. More specifically, I am not sure if this is allowed, and if so what I am doing wrong. ContentView: import SwiftUI import HealthKit struct ContentView: View { @StateObject var healthKitManager = HealthKitManager() var body: some View { VStack { Button("Enable Background Step Delivery") { healthKitManager.beginBackgroundUpdates() } } .padding() .onAppear { healthKitManager.requestAuthorization { success in print("Configured HealthKit with Return: \(success)") } } } } #Preview { ContentView() } BackgroundDeliveryApp: import SwiftUI import HealthKit import Amplify import AWSS3StoragePlugin import AWSCognitoAuthPlugin @main struct HKBackgruondDeliveryApp: App { private func configureAmplify() { do { try Amplify.add(plugin: AWSCognitoAuthPlugin()) try Amplify.add(plugin: AWSS3StoragePlugin()) try Amplify.configure() print("Succesfully configured Amplify with S3 Storage") } catch { print("Could not configure Amplify") } } init() { configureAmplify() } var body: some Scene { WindowGroup { ContentView() } } } HealthKitManager import Foundation import HealthKit import Amplify struct TestStep: Encodable, Decodable { let count: Double let startDate: Date let endDate: Date let device: String } class HealthKitManager: ObservableObject { var healthStore: HKHealthStore? let stepType = HKObjectType.quantityType(forIdentifier: .stepCount) let heartRateType = HKQuantityType(.heartRate) let sleepType = HKObjectType.categoryType(forIdentifier: .sleepAnalysis) init() { if HKHealthStore.isHealthDataAvailable() { healthStore = HKHealthStore() } else { print("There is no health data available") healthStore = nil } } func encodeStepList(stepList: [TestStep]) -> Data{ let encoder = JSONEncoder() encoder.dateEncodingStrategy = .iso8601 do { return try encoder.encode(stepList) } catch { return Data() } } func uploadStepData(stepList: [TestStep]) async { let stepData = self.encodeStepList(stepList: stepList) let uploadTask = Amplify.Storage.uploadData( key: "ExampleKey", data: stepData ) Task { for await progress in await uploadTask.progress { print("Progress: \(progress)") } } do { let value = try await uploadTask.value print("Completed: \(value)") } catch { print("Could not upload step data") } } func requestAuthorization(completion: @escaping (Bool) -> Void) { guard let stepType = stepType, let sleepType = sleepType else { return completion(false) } guard let healthStore = self.healthStore else { return completion(false) } healthStore.requestAuthorization(toShare: [], read: [stepType, heartRateType, sleepType]) { success, error in if let error = error { print("Some error has occoured during authorization of healthKit") print(error) } return completion(success) } } func beginBackgroundUpdates() { guard let healthStore = healthStore, let stepType = stepType else { print("Cannot begin background updates because HealthStore is nil") return } healthStore.enableBackgroundDelivery(for: stepType, frequency: .immediate) { success, error in print("Background update of health data") if let error = error { print("Some error has occoured during the set up of the background observer query for steps") print(error) return } guard let query = self.createObserverQuery() else { print("Could not create a query for steps") return } healthStore.execute(query) } } func stepCountDeviceRecordsQuery(stepCountObjects: @escaping ([TestStep]) -> Void) { guard let stepType = stepType else { print("Nil step type") return } let stepCountUnit = HKUnit.count() let endDate = Date() let startDate = Calendar.current.date(byAdding: .day, value: -7, to: endDate) let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate) let sortDescriptors = [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: true)] let stepCountQuery = HKSampleQuery(sampleType: stepType, predicate: predicate, limit: 10000, sortDescriptors: sortDescriptors) { query, results, error in if let error = error { print("Error in getStepCount") print(error) return } guard let results = results else { print("Empty results in getStepCount") return } var stepCounts: [TestStep] = [] for (_, record) in results.enumerated() { guard let record: HKQuantitySample = record as? HKQuantitySample else {return} let step = TestStep(count: record.quantity.doubleValue(for: stepCountUnit), startDate: record.startDate, endDate: record.endDate, device: record.device?.model ?? "") stepCounts.append(step) } print("\(stepCounts.count) records at \(Date())") print(stepCounts[stepCounts.count - 1]) stepCountObjects(stepCounts) } healthStore?.execute(stepCountQuery) } private func createObserverQuery() -> HKQuery? { guard let stepType = stepType else { return nil } let query = HKObserverQuery(sampleType: stepType, predicate: nil) { query, completionHandler, error in self.stepCountDeviceRecordsQuery { stepList in Task { await self.uploadStepData(stepList: stepList) } } completionHandler() } return query } }
Posted
by VMoorjani.
Last updated
.
Post not yet marked as solved
1 Replies
769 Views
I'm facing an issue with background scanning of Bluetooth on devices with iOS 15.7.3. I'm developing an application that needs to discover BLE devices even in the background mode. However, the didDiscover method is not getting called during background scanning. Here's what I've already checked and configured in my project: Appropriate permissions for Bluetooth usage and background processing are in place. Flags in the project settings for background processing are configured correctly. I've verified that BLE devices are set up for advertising in the background. There are no physical obstacles or interference; devices and BLE devices are in an open environment. Additional details and conditions: The application initiates background scanning in the applicationDidEnterBackground method. At the beginning of the test, the BLE device is not accessible. I bring it near the iPhone after approximately 5 minutes of the app being in the background. If I don't move the BLE device away from the iPhone, it is detected almost immediately after the app goes into the background, but only once. The phone screen doesn't lock during the test. The CBCentralManagerOptionRestoreIdentifierKey option is used in the CBCentralManager. On an iPhone 12 mini with iOS 16.3.1, background scanning works, but the device is found only once for call scanForPeripherals method. The following filters are used: CBUUID(string: "330C5AD1-7261-4F06-B87C-0F6342365C2E") and CBUUID(string: "4c6607e0-2c3d-4fca-b201-0246773d6e9c"). If during the test you go to the Bluetooth settings of the iPhone (where there is a search for devices), the didDiscover method begins to report found devices Advertisement data for BLE looks like this 7 elements 0 : 2 elements key : "kCBAdvDataServiceUUIDs" value : 1 element 0 : 4C6607E0-2C3D-4FCA-B201-0246773D6E9C 1 : 2 elements key : "kCBAdvDataRxSecondaryPHY" value : 0 2 : 2 elements key : "kCBAdvDataTimestamp" value : 719149435.0168051 3 : 2 elements key : "kCBAdvDataLocalName" value : GB3_0CCE 4 : 2 elements key : "kCBAdvDataRxPrimaryPHY" value : 0 5 : 2 elements key : "kCBAdvDataServiceData" value : 1 element 0 : 2 elements key : 330C5AD1-7261-4F06-B87C-0F6342365C2E value : \<01020304\> 6 : 2 elements key : "kCBAdvDataIsConnectable" value : 1 Here is a link to the repository with the test application. This is a test application that I use for testing. The repository contains code that is not needed for testing. The required classes for testing are AppConfiguration, BackgroundScanManager, and BackgroundBLEManager. After bringing the BLE device near the iPhone, I wait for approximately 15-20 minutes. Please, help me understand why the didDiscover method is not being called in the background mode on iOS 15.7.3. Perhaps someone has encountered a similar problem and can provide advice or recommendations? Thank you very much!
Posted
by nruzumak.
Last updated
.
Post marked as solved
1 Replies
794 Views
Hello everyone, I'm using Flutter and the just_audio package. When a user receives a push notification, the app plays audio in the background. I've tested this functionality on iPhone 6s and iPhone 13. It works correctly on iPhone 6s and the app plays the sound on push notification received. However on iPhone 13 the app receives the notification, starts the background process but fails to play the sound with these errors: mediaserverd(MediaExperience)[17680] &lt;Notice&gt;: -CMSUtilities- CMSUtility_IsAllowedToStartPlaying: Client sid:0x45107e5, Runner(28933), 'prim' with category MediaPlayback and mode Default and mixable does not have assertions to start mixable playback mediaserverd(MediaExperience)[17680] &lt;Notice&gt;: -CMSessionMgr- MXCoreSessionBeginInterruption_WithSecTaskAndFlags: CMSessionBeginInterruption failed as client 'sid:0x45107e5, Runner(28933), 'prim'' has insufficient privileges to take control mediaserverd(AudioSessionServer)[17680] &lt;Error&gt;: AudioSessionServerImp.mm:405 { "action":"cm_session_begin_interruption", "error":"translating CM session error", "session":{"ID":"0x45107e5","name":"Runner(28933)"}, "details":{"calling_line":879,"error_code":-16980,"error_string":"Operation denied. Cannot start playing"} } From what I understand of these errors is that on the newer iPhones, there must be additional permissions. Does anyone have any idea on how I can fix this?
Posted
by gotiobg.
Last updated
.
Post not yet marked as solved
1 Replies
651 Views
We are building an App that collects Products from an online database. During setup of the App the initial products data set is loaded and saved into the SwiftData Database. Over time the product information will change in the online environment and we need to update this information accordingly in the App. The idea is that based on the product update date (timestamp) we will collect all the products that have been updated since the last create/update sync. Because it can be a large amount of products that need to be updated we would like to execute this update task in the background preferably during the night when the App/Device is not used. We found the following from Apple and tried to implement this, https://developer.apple.com/wwdc22/10142 We would like to use this SwiftUI background tasks feature to accommodate this but are running in some questions: Would the Backgroundtasks be the right approach to implement this feature? Can we trust that this update will be executed on the defined schedule Can we trust that this update will be completed once started?
Posted Last updated
.
Post not yet marked as solved
4 Replies
872 Views
Hello everyone! I'm currently working on an iOS app developed with Swift that involves connecting to a specific ble (Bluetooth Low Energy) device and exchanging data even when the app is terminated or running in the background. I'm trying to figure out a way to wake up my application when a specific Bluetooth device(uuid is known) is visible and then connect to it and exchange data. Is this functionality achievable? Thank you in advance for your help!
Posted
by vofr.
Last updated
.
Post marked as solved
2 Replies
407 Views
background push or background refresh can wake app in the background. according douctument About the background execution sequence, if app is not runing, system launch app an move it directy to background. however if user tap app icon when system launch app and calling didFinishLaunchingWithOptions method, then what is execution order of app lifecycle
Posted
by Sodasi.
Last updated
.
Post not yet marked as solved
1 Replies
361 Views
I am aiming to create a feature within an iOS application to backup all photos and videos from the device to a server, similar to Dropbox’s camera upload feature. Upon initiating the upload, I want to ensure the process continues even when the app moves to the background. Would utilizing Background Task Completion be the appropriate approach to maintain the upload process in the background? Furthermore, in scenarios where the background process gets interrupted and the app transitions back to the foreground, I want to resume the upload. What would be the suitable implementation to ensure the upload resumes seamlessly? Moreover, I have observed that an app named TeraBox continues its background processes for over 10 minutes after transitioning to the background. How might this prolonged background process be achieved?
Posted
by Seisyu.
Last updated
.
Post not yet marked as solved
2 Replies
662 Views
My background task, implemented as per Apple's documentation, runs only once or twice instead of running continuously one after another. Seeking assistance to resolve this problem. What I want to achieve I want to hit the jsonbin API continuously in regular intervals is there anything wrong in code? AppDelegate import UIKit import BackgroundTasks @main class AppDelegate: UIResponder, UIApplicationDelegate { let apiKey = "xxxxxxx" // jsonbin x-master-key let timestampUserDefaultsKey = "Timestamps" static let backgroundAppRefreshTaskSchedulerIdentifier = "com.process" func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Register a background task if #available(iOS 13.0, *) { let status = BGTaskScheduler.shared.register( forTaskWithIdentifier: AppDelegate.backgroundAppRefreshTaskSchedulerIdentifier, using: .global()) { task in self.handleBackgroundTask(task: task as! BGProcessingTask) } print(status) } return true } func handleBackgroundTask(task: BGProcessingTask) { self.scheduleAppRefresh() task.expirationHandler = { task.setTaskCompleted(success: false) self.scheduleAppRefresh() } let backgroundQueue = DispatchQueue.global(qos: .background) backgroundQueue.async { self.postToJsonBin(prefix:"P-Background") { result in switch result { case .success(let responseJSON): // Handle the success and responseJSON print("Response JSON: \(responseJSON)") task.setTaskCompleted(success: true) case .failure(let error): task.setTaskCompleted(success: false) // Handle the error print("Error: \(error.localizedDescription)") } } } } func scheduleAppRefresh() { if #available(iOS 13.0, *) { let request = BGProcessingTaskRequest(identifier: AppDelegate.backgroundAppRefreshTaskSchedulerIdentifier) // Fetch no earlier than 15 seconds from now. request.earliestBeginDate = Date(timeIntervalSinceNow: 60 * 5) // 60 seconds // request.requiresNetworkConnectivity = true do { try BGTaskScheduler.shared.submit(request) print("bg App Refresh requested") } catch { print("Could not schedule app refresh: \(error)") } } } func postToJsonBin(prefix:String, completion: @escaping (Result<Any, Error>) -> Void) { // Define the URL for jsonbin.io with the collection ID let apiUrl = URL(string: "https://api.jsonbin.io/v3/b")! // Define your JSON data including the timestamp parameter let jsonData: [String: Any] = ["timestamp": Date().currentTimestampInIST(), "prefix":prefix] do { // Serialize the JSON data let requestData = try JSONSerialization.data(withJSONObject: jsonData) // Create the URLRequest var request = URLRequest(url: apiUrl) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue(apiKey, forHTTPHeaderField: "X-Master-Key") // Replace with your API key request.setValue(prefix, forHTTPHeaderField: "X-Bin-Name") // Set the HTTP body with the serialized JSON data request.httpBody = requestData // Create a URLSession task let task = URLSession.shared.dataTask(with: request) { (data, response, error) in if let error = error { completion(.failure(error)) return } if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 { if let responseData = data { do { // Parse the response data and call the completion handler let responseJSON = try JSONSerialization.jsonObject(with: responseData, options: []) completion(.success(responseJSON)) } catch { completion(.failure(error)) } } } else { completion(.failure(NSError(domain: "JsonBinErrorDomain", code: 0, userInfo: nil))) // Replace with appropriate error handling } } // Start the URLSession task task.resume() } catch { completion(.failure(error)) } } } extension Date { func currentTimestampInIST() -> String { let dateFormatter = DateFormatter() dateFormatter.timeZone = TimeZone(identifier: "Asia/Kolkata") // Set the time zone to IST // Define your desired date format dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" // Get the current date and time let currentDate = Date() // Format the date as a string in IST let istTimestamp = dateFormatter.string(from: currentDate) return istTimestamp } } SceneDelegate func sceneDidEnterBackground(_ scene: UIScene) { if #available(iOS 13.0, *) { let request = BGProcessingTaskRequest(identifier: AppDelegate.backgroundAppRefreshTaskSchedulerIdentifier) request.earliestBeginDate = Date(timeIntervalSinceNow: 60 * 5 ) do { try BGTaskScheduler.shared.submit(request) print("bg App Refresh requested") } catch { print("Could not schedule app refresh: \(error)") } } }
Posted Last updated
.
Post not yet marked as solved
7 Replies
2.1k Views
I setup a BGProcessingTask that do some work with the db and at the end sends an email. I've notice that, even if I set requiresExternalPower to false the task runs only when the device is connected to the power cable. I've tested setting the repeating time every 10 minutes. If the power cable is disconnected the task isn't launhed anuymore. After I attach the cable, waiting some minutes it restarts.
Posted Last updated
.
Post not yet marked as solved
1 Replies
436 Views
From a strategy perspective I use BGAppRefreshTask and BGProcessingTasks, successfully implementing them for events triggered within the client application. But there are certain events that are best triggered from the host, such as when we deploy a new set of vendor or price lists. We are trying to implement this background process using Push Notifications. We have successfully implemented sending APNS notifications from the host and receive this .json message in. application: didReceiveRemoteNotification: fetchCompletionHandler: using this code: - (void)application:(UIApplication *)application didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(nonnull void (^)(UIBackgroundFetchResult))completionHandler { NSLog(@"userInfo - %@", userInfo); switch (application.applicationState) { case UIApplicationStateBackground: { //Check to see if this is a configs file update background notification NSArray *dictKeys = [userInfo allKeys]; BOOL foundConfigs = NO; for (NSString *dk in dictKeys) if ([dk isEqualToString:kCONFIGSVERSION]) foundConfigs = YES; if (foundConfigs) { id configVersion = [userInfo objectForKey:kCONFIGSVERSION]; NSLog(@"AAAppDelegate configVersion - %@", configVersion); if ([ServerUtilities deviceAssociatedWithAccount]) [self.bkSessionManager retrieveAndInstallUpdatedConfigFilesWithVersion:[configVersion integerValue]]; } NSLog(@"AAAppDelegate remote notification handler"); completionHandler(UIBackgroundFetchResultNewData); } break; case UIApplicationStateActive: NSLog(@"AAAppDelegate application is active"); completionHandler(UIBackgroundFetchResultNoData); break; case UIApplicationStateInactive: NSLog(@"AAAppDelegate application is inactive"); completionHandler(UIBackgroundFetchResultNoData); break; default: break; } In the Xcode simulator running on an iPhone XS, we place the application in background and then send the APNS message from the host, receiving this .json message in the userInfo dictionary: userInfo - { "app_version" = "0.1"; aps = { "content-available" = 1; }; "configs_version" = 1; "dev_os" = 13; "os_type" = ios; } Receipt of this notification then triggers the client to initiate URLSession and retrieve the appropriate files from the host in order to update values on the client. As we are seeing inconsistent behavior we suspect that we are not implementing the URLSession properly. When setting up the URLSession should it be configured using: NSURLSessionConfiguration defaultSessionConfiguration or NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier: ? This is important to know since using backgroundSession requires us to set up the sessions as download tasks and the defaultSession as data tasks. I have tried various combinations so far without success. Any leads here will help...
Posted
by eric_s.
Last updated
.
Post not yet marked as solved
0 Replies
5.2k Views
Note Much of this content has been rolled into URL Loading System documentation, but I’m leaving this doc here for my own reference. URLSession background sessions are optimised for transferring a small number of large resources. Moreover, it’s best if the transfer is resumable. This design makes the best use of client device resources and the available network bandwidth. If your app runs a lot of tasks in a background session, you should rethink its design. Below you’ll find a number of options you might consider. Most of these options require server-side support. If your server does not have this support, and you can’t add it — perhaps you’re writing a client app for a server you don’t control — you won’t be able to implement these options directly. In that case consider creating your own server that sits between your app and the final server and implements the necessary smarts required to optimise your app’s network usage. If that’s not possible, a final option is to not use a background session but instead take advantage of the BackgroundTasks framework. See BackgroundTasks Framework, below. Basics The basic strategy here is to have the sender (the server for a download, your app for an upload) pack the data into some sort of archive, transfer that archive over the network, and then have the receiver unpack it. There are, however, a number of complications, as described in the subsequent sections. Archive Format The obvious choices for the archive format are zip and tar. macOS has lots of options for handling these formats but none of that support is present on iOS (r. 22151959). OTOH, it’s easy to find third-party libraries to fill in this gap. Incremental Transfers It’s common to have a corpus of data at one end of the connection that you need to replicate at the other. If the data is large, you don’t want to transfer the whole thing every time there’s an update. Consider using the following strategies to deal with this: Catalogue diff — In this approach the receiver first downloads a catalogue from the sender, then diffs its current state against that catalogue, then requests all the things that are missing. Alternatively, the receiver passes a catalogue of what it has to the sender, at which point the sender does the diff and returns the things that are missing. The critical part is that, once the diff has been done, all of the missing resources are transferred in a single archive. The biggest drawback here is resume. If the sender is working with lots of different receivers, each of which has their own unique needs, the sender must keep a lot of unique archives around so it can resume a failed transfer. This can be a serious headache. Versions — In this approach you manage changes to the data as separate versions. The receiver passes the version number it has to the sender, at which point the sender knows exactly what data the receiver needs. This approach requires a bit more structure but it does avoid the above-mentioned problem with resume. The sender only needs to maintain a limited number of version diffs. In fact, you can balance the number of diffs against your desire to reduce network usage: Maintaining a lot of diffs means that you only have to transfer exactly what the receiver needs, while maintaining fewer diffs makes for a simpler server at the cost of a less efficient use of the network. Download versus Upload The discussion so far has applied equally to both downloads and uploads. Historically, however, there was one key difference: URLSession did not support resumable uploads. IMPORTANT Starting with iOS 17, URLSession supports resumable uploads. See WWDC 2023 Session 10006 Build robust and resumable file transfers for the details. The rest of this section assumes that you don’t have access to that support, either because you’re working on an older system or because the server you’re uploading to doesn’t support this feature. When doing a non-resumable upload you have to balance the number of tasks you submit to the session against the negative effects of a transfer failing. For example, if you do a single large upload then it’s annoying if the transfer fails when it’s 99% complete. On the other hand, if you do lots of tiny uploads, you’re working against the URLSession background session design. It is possible to support resumable uploads with sufficient server-side support. For example, you could implement an algorithm like this: Run an initial request to allocate an upload ID. Start the upload with that upload ID. If it completes successfully, you’re done. If it fails, make a request with the upload ID to find out how much the server received. Start a new upload for the remaining data. Indeed, this is kinda how the built-in resumable upload support works. If you’re going to implement something like this, it’s best to implement that protocol. (r. 22323347) BackgroundTasks Framework If you’re unable to use an URLSession background session effectively, you do have an alternative, namely, combining a standard session with the BackgroundTasks framework, and specifically the BGProcessingTaskRequest. This allows you to request extended processing time from the system. Once you’ve been granted that time, use it to run your many small network requests in a standard session. The main drawback to this approach is latency: The system may not grant your request for many hours. Indeed, it’s common for these requests to run overnight, once the user has connected their device to a power source. Background Assets Framework If you’re using URLSession to download assets for your app or game, check out the Background Assets framework. Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com" Revision History 2023-09-27 Added information about the new resumable upload support. Added the Background Assets Framework section. Made significant editorial changes. 2022-01-31 Fixed the formatting and tags. Added a link to the official docs. 2018-03-24 Added the BackgroundTasks Framework section. Other editorial changes. 2015-08-18 First written.
Posted
by eskimo.
Last updated
.