AndyIbanez / andyibanez-com

Static website.
1 stars 0 forks source link

posts/modern-background-tasks-ios13/ #6

Open utterances-bot opened 4 years ago

utterances-bot commented 4 years ago

Modern Backgrounds Tasks in iOS 13 • Andy Ibanez

https://www.andyibanez.com/posts/modern-background-tasks-ios13/

Pratik948 commented 4 years ago

I have a doubt, let's say user has forced killed our app (by swiping up from app switcher), our app will not be launched in background right? if yes, we can say its the same as background fetch (prior to iOS 13) but this supports other platforms also(iOS, macOs, iPadOS, etc).

AndyIbanez commented 4 years ago

Yes. Background Tasks require the app to be running in the background.

phranck commented 4 years ago

Thanks for that explanation. So, how to handle a case when I develop my application with the latest iOS SDK but I need to give support for iOS 12, too? The new background task API is only available from iOS 13 and above. As the result I have to implement both the old fashioned way and the new API, haven't I?

AndyIbanez commented 4 years ago

@phranck yes. You can use the #available API to conditionally compile the method for your platform.

snout-o commented 4 years ago

Would love to see an Objective-C implementation of this code somewhere - anywhere - because I'm having a hell of a time getting the scheduled task to actually run once the app is in the background.

simon-jonghun-song commented 4 years ago

Can I have a question? I have executed your code in Xcode11.3/iOS13.2(with iPhone7). I see the bulbasaur screen but there's no anything action occured. After I have press home button, the task scheduled but there's no action also.

In the log, I can see belows.

2020-04-06 08:49:32.961483+0900 bgTasks[968:230666] [LayoutConstraints] Unable to simultaneously satisfy constraints. Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. ( "<NSLayoutConstraint:0x2830474d0 UILabel:0x101410800'Label'.width == 374 (active)>", "<NSLayoutConstraint:0x2830477f0 UILayoutGuide:0x282a609a0'UIViewSafeAreaLayoutGuide'.trailing == UILabel:0x101410800'Label'.trailing + 20 (active)>", "<NSLayoutConstraint:0x283047890 UILabel:0x101410800'Label'.leading == UILayoutGuide:0x282a609a0'UIViewSafeAreaLayoutGuide'.leading + 20 (active)>", "<NSLayoutConstraint:0x28307b200 'UIView-Encapsulated-Layout-Width' UIView:0x1014090f0.width == 375 (active)>", "<NSLayoutConstraint:0x283047700 'UIViewSafeAreaLayoutGuide-left' H:|-(0)-UILayoutGuide:0x282a609a0'UIViewSafeAreaLayoutGuide' (active, names: '|':UIView:0x1014090f0 )>", "<NSLayoutConstraint:0x2830477a0 'UIViewSafeAreaLayoutGuide-right' H:[UILayoutGuide:0x282a609a0'UIViewSafeAreaLayoutGuide']-(0)-|(LTR) (active, names: '|':UIView:0x1014090f0 )>" )

Will attempt to recover by breaking constraint <NSLayoutConstraint:0x2830474d0 UILabel:0x101410800'Label'.width == 374 (active)>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger. The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful. 2020-04-06 08:49:32.968608+0900 bgTasks[968:230666] [LayoutConstraints] Unable to simultaneously satisfy constraints. Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. ( "<NSLayoutConstraint:0x2830473e0 UIImageView:0x10140fed0.width == 100 (active)>", "<NSLayoutConstraint:0x283047660 UILayoutGuide:0x282a609a0'UIViewSafeAreaLayoutGuide'.trailing == UIImageView:0x10140fed0.trailing + 157 (active)>", "<NSLayoutConstraint:0x2830478e0 UIImageView:0x10140fed0.leading == UILayoutGuide:0x282a609a0'UIViewSafeAreaLayoutGuide'.leading + 157 (active)>", "<NSLayoutConstraint:0x28307b200 'UIView-Encapsulated-Layout-Width' UIView:0x1014090f0.width == 375 (active)>", "<NSLayoutConstraint:0x283047700 'UIViewSafeAreaLayoutGuide-left' H:|-(0)-UILayoutGuide:0x282a609a0'UIViewSafeAreaLayoutGuide' (active, names: '|':UIView:0x1014090f0 )>", "<NSLayoutConstraint:0x2830477a0 'UIViewSafeAreaLayoutGuide-right' H:[UILayoutGuide:0x282a609a0'UIViewSafeAreaLayoutGuide']-(0)-|(LTR) (active, names: '|':UIView:0x1014090f0 )>" )

Will attempt to recover by breaking constraint <NSLayoutConstraint:0x2830473e0 UIImageView:0x10140fed0.width == 100 (active)>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger. The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful. Backgrounded task scheduled 2020-04-06 08:51:25.874216+0900 bgTasks[968:230666] Can't end BackgroundTask: no background task exists with identifier 1 (0x1), or it may have already been ended. Break in UIApplicationEndBackgroundTaskError() to debug.

AndyIbanez commented 4 years ago

Like I mention in the article, it's not really possible to test much in real life due to the limitations of the system and how the scheduling works. The system will choose whether your task will run and the best time approximated to the time of your choosing.

In other words, you can execute the code that would be executed if you received a background event, but you cannot test the reception of the events themselves beyond what the simulation APIs provide.

charlesofkastrup commented 4 years ago

This a a great article thanks.

I have implemented a refresh task, which issues a local notification just writing the time stamp. I can invoke this using the debugger command and it all works. However, nothing is happening while letting the phone try to call the background task by itself.

I am currently uncertain if it is:

I have remove the notification and now trying to log a background call a different way to see if that is the issue.

Question to you: are you aware of being able to issue location notifications (that is using UNUserNotificationCenter etc) from a background app refresh task?

AndyIbanez commented 4 years ago

@charlesofkastrup you shouldn't use these APIs to attempt to trigger notifications. I don't really know what your end goal is, but you could use a server to push a silent notification just to trigger a task (old but still fully functioning APIs).

charlesofkastrup commented 4 years ago

Thanks for the reply Andy.

First, can you state why should should not use local notifications from a background task? I would think this is a pretty common scenario and usage of them. What is the matter with doing that?

Using a server to do push notifications is a big infrastructure investment, particularly for a lone developer. I am primarily a backend developer, and I know what it takes to develop, maintain and pay for that kind of service - which these days needs to be behind a professional cloud such as Azure, AWS etc. I would think very few Swift developers woking on their own would be able to manage this.

Conversely - why should they? The background task app refresh running on the client appears to be quite enough to achieve some simple web service calls and display the result - whether that result is in the primary UI of the app or in a local notification does not seems to make any difference in my eyes - the former being the promoted usage of BG tasks, anyway.

Local notifications is a very simple API - so I see no reason why they cannot be called from a BG task. My problem appears to be that the task was not called at all, but oddly after a few days it is starting to be called - sporadically. :-)

AndyIbanez commented 4 years ago

@charlesofkastrup exactly because of the sporadically-ness of the API. Like I mention in the article, there's really never guarantee your task is going to run at all. You can program one and assign parameters to tell it more or less when you want it to be executed, but it's up to the system if it will execute your tasks at all. If it's acceptable for your local notification to be triggered this way, then you may continue doing that.

Also a server infrastructure doesn't have to be that complicated and expensive. I'd write something really quick and host it in a $5 DigitalOcean instance just to periodically send silent push notifications to a bunch of devices 😄

charlesofkastrup commented 4 years ago

OK, so what it comes down to is the choice Apple have made to avoid calling the task on a fairly regular basis (an odd choice for sure, and a shame), which definitely reduces their usefulness and, as you say, points to a back-end instead. Thanks a lot for your replies!

AndyIbanez commented 4 years ago

@charlesofkastrup correct. Many people want to use them as cron-like tasks. Unfortunately it’s not really their purpose. They are used more for stuff like data backup at night, and things like that - things you don’t require to be ran at an specific point in time.

charlesofkastrup commented 4 years ago

@AndyIbanez Aside from the frequency of calling the task, which now seems to be more frequent in my testing than it was, I can confirm that displaying a local notification is working fine too, just so you are aware.

zhenqiu1101 commented 4 years ago

@AndyIbanez Hi Andy, thank you for the post! I have a question. When I try to schedule two task requests, I keep getting "Error submitting task request: <BGAppRefreshTaskRequest: task-1, earliestBeginDate: 2020-06-01 20:50:19 +0000>, error: Error Domain=_DASActivitySchedulerErrorDomain Code=4 "(null)"" for one of them (or both sometimes). I have no clue of what is the problem. Do you have any insights or suggestions? Thank you!

zhenqiu1101 commented 4 years ago

Never mind. I figured it out. The real error is " BGTaskSchedulerErrorCodeTooManyPendingTaskRequests".

Disco2000 commented 4 years ago

Hi @AndyIbanez, great article which is certainly different to the dozens of other articles that I have read on the older UIApplication.shared.setMinimumBackgroundFetchInterval . I have already implemented running a task on silent push notification in my app which works really well however it randomly seems to stop working after a few days so I am trying to implement background tasks as an alternative. I have been unable to get even one background fetch to happen with the old code, your new code, or even the sample project you have provided. Other than the code itself, is there something I have to set in the xcode options as I cannot understand why it never fires?

AndyIbanez commented 4 years ago

Hi @AndyIbanez, great article which is certainly different to the dozens of other articles that I have read on the older UIApplication.shared.setMinimumBackgroundFetchInterval . I have already implemented running a task on silent push notification in my app which works really well however it randomly seems to stop working after a few days so I am trying to implement background tasks as an alternative. I have been unable to get even one background fetch to happen with the old code, your new code, or even the sample project you have provided. Other than the code itself, is there something I have to set in the xcode options as I cannot understand why it never fires?

Ultimately, the system will decide if it will wake up your app or not. If you don’t use your app often, the system allocated less time for it. The only thing I can think of in your case is that it is running with extremely low priority assigned by the system.

Disco2000 commented 4 years ago

Hi @AndyIbanez, great article which is certainly different to the dozens of other articles that I have read on the older UIApplication.shared.setMinimumBackgroundFetchInterval . I have already implemented running a task on silent push notification in my app which works really well however it randomly seems to stop working after a few days so I am trying to implement background tasks as an alternative. I have been unable to get even one background fetch to happen with the old code, your new code, or even the sample project you have provided. Other than the code itself, is there something I have to set in the xcode options as I cannot understand why it never fires?

Ultimately, the system will decide if it will wake up your app or not. If you don’t use your app often, the system allocated less time for it. The only thing I can think of in your case is that it is running with extremely low priority assigned by the system.

Thank you for your response. I think I have fixed a few bugs in my code that were preventing the Push Notification. I have also now seen Fetch working. It seems to do nothing for several days then perform the task several times in a short period of time. I think it was possibly always working but that it is more erratic than I had expected.

MuhammadAjmalZia commented 3 years ago

Hey @AndyIbanez, Great article!! I have few questions.

i) In a comment above you said "Yes. Background Tasks require the app to be running in the background." Does that mean the app needs to be in the "Recent apps" tray , in order for the system to run the background task ? Otherwise it won't run the task ?

ii) Do you think it's normal behavior that the BGProcessingTask is NOT called even once 24 hours day on some devices. Despite that earliestBeginDate is set to 1 minute.

iii) I have a photo uploading app. That needs to upload photos in the background at few intervals in a day. But my BGProcessingTask is getting called only once in 24 hours, and on some devices it's not even called once in 24 hours. What alternative would you suggest for using in such scenarios. We can't afford push notifications since they are costly operations in terms of having a backend server.

Many Thanks !!

AndyIbanez commented 3 years ago

@imAjji

i) Yes.

ii) Based on what I have seen (and others have commented here), I'd say that yes, it is unfortunately common. Remember you have no control over when the system will trigger your tasks, if at all. What you really do is a request, and it's up to the system to honor it or not - and to honor it at the requested time or deferred for later if it does.

iii) Remember that this framework is linked to how much users use your app. If your app is not used enough, the system simply will not allocate the same resources that most used apps get. If silent push notifications are not an option, then you need to promote more foreground use of your app to your users. This is why you are seeing different behavior on different devices. In short, if the task works (and you can test it with the debugging commands in the article), then it works fine - it's just not getting the expected attention by the system that you would expect.

asifayub2010 commented 3 years ago

@AndyIbanez Aside from the frequency of calling the task, which now seems to be more frequent in my testing than it was, I can confirm that displaying a local notification is working fine too, just so you are aware.

I am facing the same problem but did not find any way to create/remove local notification in BGAppRefreshTask. Logs are being printing inside of BGAppRefresh but only local notifications are not creating/removing. If you did it please share it.

sumitkale-zymr commented 3 years ago

Can we check battery charging state in background in iOS 13 and bellow iOS 13 till user open app, It may take 1 day to 2 day or more day If yes how?

asifayub2010 commented 3 years ago

Yes we can get battery level while app is in background by using silent push notifications.

On Sep 10, 2020, at 11:40 AM, sumitkale-zymr notifications@github.com wrote:

Can we check battery charging state in background in iOS 13 and bellow iOS 13 till user open app, It may take 1 day to 2 day or more day If yes how?

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/AndyIbanez/andyibanez-com/issues/6#issuecomment-690023701, or unsubscribe https://github.com/notifications/unsubscribe-auth/AF4GASO7VZYDE5PX7FPR3H3SFBYG3ANCNFSM4KJCHLWQ.

sumitkale-zymr commented 3 years ago

Thanks for your answer. Actually When app goes to background I Need to to check every-time that when user is plugged in for charging and when user unplugged.but I am unable to do that. how we can active this via silent push notification or any other method. can you share demo code.

asifayub2010 commented 3 years ago

Yes we can get battery level and charging state while app is in background by using silent push notifications.

On Sep 10, 2020, at 11:40 AM, sumitkale-zymr notifications@github.com wrote:

Can we check battery charging state in background in iOS 13 and bellow iOS 13 till user open app, It may take 1 day to 2 day or more day If yes how?

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/AndyIbanez/andyibanez-com/issues/6#issuecomment-690023701, or unsubscribe https://github.com/notifications/unsubscribe-auth/AF4GASO7VZYDE5PX7FPR3H3SFBYG3ANCNFSM4KJCHLWQ.

sumitkale-zymr commented 3 years ago

but in silent push notification we need to server to send notification every time.

asifayub2010 commented 3 years ago

That is how WhatsApp get battery status and level. If you discuss the case in which you need battery charging status while app is in background then I may give a complete solution.

On Sep 11, 2020, at 10:55 AM, sumitkale-zymr notifications@github.com wrote:

but in silent push notification we need to server to send notification every time.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/AndyIbanez/andyibanez-com/issues/6#issuecomment-690892231, or unsubscribe https://github.com/notifications/unsubscribe-auth/AF4GASIGEPEGOKXWWJ5JFIDSFG3WVANCNFSM4KJCHLWQ.

sumitkale-zymr commented 3 years ago

my case is when app goes to background I need to to check at what time user is plugged in for charging and at what time user unplugged. user can do this several time throughout day. one cycle of charging and discharging. there will be several cycle of charging and discharging I need all.

AndyIbanez commented 3 years ago

A big takeaway from these APIs is that you cannot use them to execute code periodically reliably. They are all "best effort" and they all depend on factors such as overall system usage and how well your app plays with the system. Even silent notifications are prone to fail.

If you need a way to constantly execute code without fail, there is no way to do that on iOS at this time.

sumitkale-zymr commented 3 years ago

I have find one solution. I used one hack that playing silent file in background for infinite time so every time it called I am calling phone charing and discharging status. It is providing me proper data but I have one query that can this provide any issue while uploading app to app store?

cdellinger commented 3 years ago

Thanks for this article, it is very helpful! Two questions for you

  1. When you downloaded the image, was there a reason you used a dataTask instead of a downloadTask?
  2. Your URLSessions are using the default configuration, is there a reason you wouldn't want to use background type of URLSession or does that conflict with or duplicate what the BackgroundTasks framework provides?
AndyIbanez commented 3 years ago

@cdellinger because in this specific example it did what I wanted it to do, which was to download simple images. I use downloadTask when downloading bigger things due to the delegate methods and all those methods. It wasn't necessary to populate the code with unnecessary code.

As for the second question, it's similar to my previous answer. I'd use a background URLSession if I was downloading something much bigger in the background.

You should definitely configure your sessions and use the right kind of URLSessionTask when using this (or any other) networking API.

Zukeh commented 3 years ago

Hey

great article! Any plans on updating it for SwiftUI ? I'm trying to port it, but I'm getting several errors when compiling. Plus, I'm not sure how the content would be updated from the background task?

AndyIbanez commented 3 years ago

I cannot help without seeing any code, but generally people who tell me about problems with SwiftUI and the sample code in my article are trying to call it directly from an App, Scene, or a View.

If you are using this code directly in an App, Scene, or View, then create a view model instead and move your task scheduling codethere. Updating content and keeping it up to date is going to be much easier if you use a view model as well.

Zukeh commented 3 years ago

Thanks for the prompt reply, here's my code (the pokemon specific code is the same):

//
//  bk_testApp.swift
//  bk-test
//

import SwiftUI
import BackgroundTasks

let appId: String = "com.bk-test"
extension Notification.Name {
    static let newPokemonFetched = Notification.Name(appId + ".newPokemonFetched")
}

@main
struct agora_appApp: App {
    var appRefreshId = appId + ".backgroundAppRefreshId"

    init() {
        BGTaskScheduler.shared.register(forTaskWithIdentifier:  self.appRefreshId, using: nil) { (task) in
            print("BackgroundAppRefreshTaskScheduler is executed NOW!")
            print("Background time remaining: \(UIApplication.shared.backgroundTimeRemaining)s")
            task.expirationHandler = {
                task.setTaskCompleted(success: false)
                PokeManager.urlSession.invalidateAndCancel()
            }

            let randomPoke = (1...151).randomElement() ?? 1
            PokeManager.pokemon(id: randomPoke) { (pokemon) in
                NotificationCenter.default.post(name: .newPokemonFetched,
                                                object: self,
                                                userInfo: ["pokemon": pokemon])

                task.setTaskCompleted(success: true)
            }

            self.scheduleBackgroundPokemonFetch()
        }
    }

    var body: some Scene {
        WindowGroup {
            ContentView().onReceive(NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)) { _ in
                print("Moving to the background!")
                scheduleBackgroundPokemonFetch()
            }
        }

    }
    func scheduleBackgroundPokemonFetch() {
        let pokemonFetchTask = BGAppRefreshTaskRequest(identifier: self.appRefreshId)
        pokemonFetchTask.earliestBeginDate = Date(timeIntervalSinceNow: 0)
        do {
            try BGTaskScheduler.shared.submit(pokemonFetchTask)
        } catch {
            print("Unable to submit task: \(error.localizedDescription)")
        }
    }
}

and my content.view:


import SwiftUI

struct ContentView: View {
    @State var name = "init"
    @State var image: UIImage = UIImage()

    init() {

    }

    var body: some View {
        VStack {
            Text(name)
            Image(uiImage: image)
        }.onAppear(perform: {
            fetchPokemon(id: 1)
        })

    }

    func fetchPokemon(id: Int) {
        PokeManager.pokemon(id: id) { (pokemon) in
            name = pokemon.species.name
            print("ku", pokemon.species.name)

            PokeManager.downloadImage(url: pokemon.sprites.backDefault!) { (_image) in
                image = _image
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

The error I get when compiling is this:

Escaping closure captures mutating 'self' parameter

then create a view model instead and move your task scheduling codethere

The background task can update the view model the view will use?

LolaBrigada commented 2 years ago

Hi Mr. Ibanez, I'm not a developer, but just was curious after reading some of your posts on iOS and had some questions for you given your impressive background with iOS, if you don't mind me asking.

When an iPhone or maybe an older model like iphone 4 is running iOS 7, and it has no way to connect to a GSM due to lack of cell towers (so no phone connection or internet), has no apps launched by the user and is not turned off, are there are any processes that continue to run in the background?

If so what are some of these processes? Are they based on the iOS core functions?

Does the activity of these processes get logged somewhere? Does the date and time stamp get logged as well for these processes?

And is this log file accessible by the phone user or does it need specialized software and knowledge like xcode to look at it?

Thank you Mr. Ibanez

LuanCMartins commented 2 years ago

Hi @AndyIbanez, great article.

I have a question, if the app is being used at the moment, does that means that the task won't be executed? I need it be constantly executed, even if the time when it will be executed depends on the system deciding when to trigger it. If I change the placement of the function (UIApplication.shared.delegate as! AppDelegate).scheduleBackgroundPokemonFetch() from the method func sceneWillEnterBackground(_ scene: UIScene to the method func sceneWillEnterForeground(_ scene: UIScene, will this make the scheduling of the task be called when the app is being used? If so, may I use the scheduling in both methods, so the task may be registered either when the app is being used or not?

Thank you very much, your article helped me a lot.

patilmahesh-logitech commented 2 years ago

Hi @andylbanez, I have simple question i tried above source code, but my requirement is like, I want to execute "xyz function" periodically in the background. I have in house application so not have to worry about app store. In above code you said this line "If you want to keep executing it throughout the day, you simply need to call scheduleBackgroundPokemonFetch again inside handleAppRefreshTask." i didn't get it exactly. Is it mean that scheduleBackgroundPokemonFetch keep calling after specific time interval without limit per day.

erichorlait commented 1 year ago

Hi @AndyIbanez. This post is very interesting. Is there a mean for background tasks being schedules even if the App is terminated? I would like to implement a system of notifications based on a private server being accessed periodically by my App

AndyIbanez commented 1 year ago

The app has to be running in the background and these APIs cannot be used to periodically run tasks like a cronjob.

erichorlait commented 1 year ago

Thanks. Is there any mean for writing cron jobs like with iOS as there is for Android...

Le 3 mai 2023 à 03:43, Andy Ibanez @.***> a écrit :

The app has to be running in the background and these APIs cannot be used to periodically run tasks like a cronjob.

— Reply to this email directly, view it on GitHubhttps://github.com/AndyIbanez/andyibanez-com/issues/6#issuecomment-1532354626, or unsubscribehttps://github.com/notifications/unsubscribe-auth/A6VIVBKUXF6OTUWJKZ4EVKTXEGZ5BANCNFSM4KJCHLWQ. You are receiving this because you commented.Message ID: @.***>