3

my swift app needs to run a piece of code periodically even while being in the background. What is the best way to accomplish that? i tried DispatchQueue.global(qos: .background).async but that didnt work

new try:

i added this to my ViewController:

private var time: Date?
private lazy var dateFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateStyle = .short
    formatter.timeStyle = .long
    return formatter
}()

func fetch(_ completion: () -> Void) {
    time = Date()
    completion()
}

and this to my AppDelegate

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)
    // Override point for customization after application launch.
    return true
}

func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    if let tabBarController = window?.rootViewController as? UITabBarController,
        let viewControllers = tabBarController.viewControllers
    {
        for viewController in viewControllers {
            if let fetchViewController = viewController as? ViewController {
                fetchViewController.fetch {
                    completionHandler(.newData)
                    print("fired")
                }
            }
        }
    }
}

and put a breakpoint on completionHandler(.newData). But when i run the app, the breakpoint never gets triggered. and the print statement never executed.

Ginso
  • 459
  • 17
  • 33
  • https://www.raywenderlich.com/143128/background-modes-tutorial-getting-started – Sahil Manchanda Jul 02 '18 at 07:37
  • `DispatchQueue` is a thread handling process which is not actual implementations for background process while the app itself is in background. This are two different thing. Please try to understand that first. In your case `DispatchQueue.global(qos: .background).async` will execute some code in background thread when the app is in foreground. Please look for background modes in iOS to achieve your requirements. – onCompletion Jul 02 '18 at 07:44

1 Answers1

4

iOS does not allow your app to keep running processes as and when you like whilst the app is backgrounded. When it is first backgrounded you will have a short amount of time to perform some tasks (around a couple of minutes) before it will stop.

Then, you need to use background fetch to periodically fetch new information for your app.

There is a good tutorial on the various background modes supported by iOS on teh Ray Wenderlich website: RayWenderlich - Background modes.

There is also a guide on Apples developer portal that explains what you can and cannot do while the app is backgrounded.

Implementing Long-Running Tasks

For tasks that require more execution time to implement, you must request specific permissions to run them in the background without their being suspended. In iOS, only specific app types are allowed to run in the background:

  • Apps that play audible content to the user while in the background, such as a music player app
  • Apps that record audio content while in the background
  • Apps that keep users informed of their location at all times, such as a navigation app
  • Apps that support Voice over Internet Protocol (VoIP)
  • Apps that need to download and process new content regularly
  • Apps that receive regular updates from external accessories
  • Apps that implement these services must declare the services they support and use system frameworks to implement the relevant aspects of those services.

Declaring the services lets the system know which services you use, but in some cases it is the system frameworks that actually prevent your application from being suspended.

UPDATE:

Firstly, I wouldn't put my refresh code in a ViewController, the VC may not be in memory during a background fetch operation. Create a separate class or something that deals with fetching and storing the data and then on your ViewControllers viewWillAppear function, load the latest data available.

For testing background fetch operations, in xcode, select Debug from the menu and then select Simulate Background Fetch. It will start the app and call the performFetchWithCompletionHandler function.

Scriptable
  • 19,402
  • 5
  • 56
  • 72
  • Well i want it to check a page in our intranet for new posts and notify the user if there are any. Can this be done? – Ginso Jul 02 '18 at 08:01
  • You should be able to do that in the background fetch mode. a better option would be to have some external script check it often and send a push notification to the user if there is new information – Scriptable Jul 02 '18 at 08:04
  • can u give me an example? i dont find anything in your links about scheduling repeating tasks – Ginso Jul 02 '18 at 08:10
  • Background fetch is what you need. it is in the background modes in both of the links, or here is a more [specific link](https://developer.apple.com/documentation/uikit/core_app/managing_your_app_s_life_cycle/preparing_your_app_to_run_in_the_background/updating_your_app_with_background_app_refresh) – Scriptable Jul 02 '18 at 08:13
  • i tried to follow one of your links, see my edited question – Ginso Jul 02 '18 at 11:12
  • I added some further information to my answer above. – Scriptable Jul 02 '18 at 11:35
  • the menu entry is disabled – Ginso Jul 02 '18 at 11:54
  • See: https://stackoverflow.com/questions/29596618/ios-background-fetch-not-working-even-though-correct-background-mode-configured – Scriptable Jul 02 '18 at 12:02
  • it gets called now(once), but it doesnt enter the first if-statement – Ginso Jul 02 '18 at 12:42
  • Then your view controller isn't loaded into memory yet, dont forget... this happens in the background. it wont start loading up all your UI if it doesn't need to – Scriptable Jul 02 '18 at 12:43
  • so how would i do it without the VC? if i just call the completionHandler in this function i get `+[CATransaction synchronize] called within transaction` and the function is still only called once – Ginso Jul 02 '18 at 13:17
  • I explained in the first paragraph in my update. just do your fetch in the performFetchWithCompletion handler and store the data for later. It should only run once during testing. iOS will periodically call this on each app that is registered for background fetch when it feels its suitable – Scriptable Jul 02 '18 at 13:24
  • so there is no way of to test the periodic call? How do i get it on my device s.t. it works properly? – Ginso Jul 02 '18 at 13:39
  • You need to look at those articles I linked. iOS decides when and how often to call the background fetch, you have no control over it. the only way is to simulate that a single fetch works, then if it does and you usually/always return .newData in the completion then it should do it more often as long as the device has enough battery. – Scriptable Jul 02 '18 at 13:45