iosswiftbackground-processbackground-taskbackgroundtaskidentifier

How to perform local backup if iOS app is closed?


My goal is this: perform local backup / copy of some files/folders.

My app is an open source Signal app fork from GitHub. a Messaging app. Messages are store in a local database.

I only found references to background activity, with a schema about background task (not refresh) that I report in this question, but it will work up to OS needs and wills, even if a preferred hour was set. I understand this. I give my code for example and possible help.

But is it possible to perform intensive operation such a backup while app is closed? Is there a way to perform my local backup at least once a day even if app is closed? how do other apps such Calendar, Alarm wok?

so, no shortcuts, I need to understand if there is some way to book a task while app is closed, or at least wile suspended. If the answer is "not possible" it is ok, I just need an explanation reference for my company.

This is my code for background task, if not possible doing what I ask, is this code right? Tried to test on console, seems to work

Targeting at least iOS 13, using UIKit.

I enabled background modes, background fetch and background processing in settings and capabilities, then added a string for the BGProcessingTaskRequest.

UPADTED from BGAppRefreshTask to BGProcessingTaskRequest (does not have 30 second of activity limit).

import UIKit
import BackgroundTasks

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.example.mytask", using: nil) { task in
            self.handleAppRefreshTask(task: task as! BGProcessingTaskRequest)
        }
        
        scheduleAppRefresh()

        return true
    }

    func scheduleAppRefresh() {
        print("called: scheduleAppRefresh")
        
        let request = BGProcessingTaskRequest(identifier: "com.example.mytask")
        request.earliestBeginDate = Date(timeIntervalSinceNow: 5 * 60

        do {
            try BGTaskScheduler.shared.submit(request)
        } catch {
            print("not possible scheduling background app activity: \(error.localizedDescription)")
        }
        
    }

    func handleAppRefreshTask(task: BGProcessingTaskRequest) {
        let startDate = Date()
        print("starting backup date: \(startDate)")

        DispatchQueue.global().async {
            let backupDuration: TimeInterval = 180
            let endTime = Date(timeIntervalSinceNow: backupDuration)

            while Date() < endTime {
                print("performing backup...")
                Thread.sleep(forTimeInterval: 1)
            }

            let endDate = Date()
            print("end of backup: \(endDate)")

            task.setTaskCompleted(success: true)
        }

        scheduleAppRefresh()
    }



}

my info.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>UIApplicationSceneManifest</key>
    <dict>
        <key>UIApplicationSupportsMultipleScenes</key>
        <false/>
        <key>UISceneConfigurations</key>
        <dict>
            <key>UIWindowSceneSessionRoleApplication</key>
            <array>
                <dict>
                    <key>UISceneConfigurationName</key>
                    <string>Default Configuration</string>
                    <key>UISceneDelegateClassName</key>
                    <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
                    <key>UISceneStoryboardFile</key>
                    <string>Main</string>
                </dict>
            </array>
        </dict>
    </dict>
    <key>BGTaskSchedulerPermittedIdentifiers</key>
    <array>
        <string>com.example.mytask</string>
    </array>
    <key>UIBackgroundModes</key>
    <array>
        <string>fetch</string>
        <string>processing</string>
    </array>
</dict>
</plist>

Solution

  • The tool you want is BGProcessingTaskRequest, and this is exactly the use case it's designed for, but it is not promised to run once a day. There is no mechanism to run something on a specific schedule other than sending silent push notifications, which have various limits of their own (the user can disable them, for example, and the phone needs a network or cell connection). There is no guaranteed way to run code on a fixed schedule. This is by design. Trying to work around it with tricks like background audio are, as you suggest, bad and can cause you to fail app review and be removed from the store.

    You should step back and think through the actual problem you're trying to solve. Since there's no server, it sounds like this isn't actually a "backup," so much as perhaps moving/copying something to per-day files. If that's what you're doing, you can do that when the app launches; you don't have to do it exactly when the day changes. And you can use a BGProcessingTaskRequest to schedule that "occasionally" in the background so that it doesn't impact launch time as much.