swift ios 10 execute code asynchronously or in the background - ios

When user launch the app or finish editing the data I need to update local notifications, basically it takes around 2-3 seconds in async way. I need to make sure that this code executes even if app leave foreground. What I have now:
func buildLocalNotifications()
let dq = DispatchQueue.global(qos: .userInteractive)
dq.async {
//recreate the notifications
}
}
And I can call this method from didFinishLaunchingWithOptions or when user save the form and everything works like a charm while app stays active for more then 3-4 seconds and its not blocking UI of course.. but if user lock the screen or terminate the app - this code won;t finished and notifications won't be created. How to safely execute sensitive code?
What is coming on my mind - show up a loader while performing this action - but it will block the user interaction

Ok I found the solution for the task which requires some time and should not be interrupted when app leaves foreground.
So we need beginBackgroundTask and endBackgroundTask
Small manager which you can use to execute code even when app is not in foreground
class BackgroundTaskManager {
let backgroundDQ = DispatchQueue.global(qos: .background)
var backgroundUpdateTask: UIBackgroundTaskIdentifier!
init(withName: String) {
self.backgroundUpdateTask = UIApplication.shared.beginBackgroundTask(withName: withName) {}
}
/* Using completion handler to know when code is done*/
func runBackgroundTask(withCode: #escaping (_ cH: #escaping () -> Void) -> Void)
{
backgroundDQ.async {
withCode() {
self.endBackgroungTask()
}
}
}
func endBackgroungTask() {
if backgroundUpdateTask != nil && backgroundUpdateTask != UIBackgroundTaskInvalid {
UIApplication.shared.endBackgroundTask(backgroundUpdateTask)
backgroundUpdateTask = UIBackgroundTaskInvalid
}
}
}
And you can use it like
let taskManager = BackgroundTaskManager(withName: "LocalNotifications")
taskManager.doBackgroundTask() { (cH) in
//Your code goes here
//Send back completion handler so system knows when to finish background task
cH()
}
More information you can find on the Medium

If you want to make sure your code gets executed even if the user closes your app, you need to call your function in applicationWillTerminate. However, you only have ~5 seconds to execute code, before the system closes your app, so asynchronous execution is not encouraged here. It also doesn't matter if you execute code synchronously, since the user already quit your app, so you won't be blocking any UI updates.

Try to excute your code in background
DispatchQueue.global(qos: .background).async {
// your code here
}

Related

iOS long task in background

After reading several similar questions I don't find anything that fits into my problem.
I have an app that analyzes CSV files (some kind of big data analyzer app). So far CSV files were small and the analysis didn't take too long.
But now we need to support very large CSV files and the analysis takes 5-10 minutes.
If the user closes the app, this process is paused and when the users opens the app again the process is resumed. We need that the analysis continues while the app is in the background.
After reading about different possibilities of running in background I have this:
Use BGTaskScheduler
I'm not sure how to do it. I've followed this official tutorial https://developer.apple.com/documentation/uikit/app_and_environment/scenes/preparing_your_ui_to_run_in_the_background/using_background_tasks_to_update_your_app?language=objc and I've implemented this in my AppDelegate method:
didFinishLaunchingWithOptions {
...
BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.mydomain.myapp.myproccess", using: nil) { task in
self.scheduleAppRefresh()
self.handleAppRefresh(task: task as! BGAppRefreshTask)
}
...
}
func scheduleAppRefresh() {
let request = BGAppRefreshTaskRequest(identifier: "com.mydomain.myapp.myproccess")
request.earliestBeginDate = Date(timeIntervalSinceNow: 1)
do {
try BGTaskScheduler.shared.submit(request)
} catch {
print("Could not schedule app refresh: \(error)")
}
}
func handleAppRefresh(task: BGAppRefreshTask) {
scheduleAppRefresh()
// Run my task, but how??
}
I don't know how to call the code inside one of my views (in fact one of my presenter cause I'm using VIPER) from appDelegate.
I'm not sure if this is how it works. If I'm not wrong, this BGTaskScheduler is to schedule a task when app goes to background.
Use beginBackgroundTask.
My code was this:
func csvAnalysis() {
DispatchQueue.global(qos: .userInitiated).async {
// Task to analyze CSV
}
}
and now is this:
func csvAnalysis() {
DispatchQueue.global(qos: .userInitiated).async {
self.backgroundTaskID = UIApplication.shared.beginBackgroundTask (withName: "mytask") {
UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
self.backgroundTaskID = UIBackgroundTaskIdentifier.invalid
}
// Task to analyze CSV
UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
self.backgroundTaskID = UIBackgroundTaskIdentifier.invalid
}
}
But this way, the process keeps pausing after entering background and resuming after opening the app again.
So, is it possible to execute this long task while the app is in background?

How to use beginBackgroundTask and endBackgroundTask in Swift

I am developing an iOS application on Swift. I am on simulator and I would like to update the data of a user when he leaves the application.
Here is the code:
func applicationWillTerminate(_ application : UIApplication){
//We update user Data
let taskId = application.beginBackgroundTask {}
Users.updateUsersData {
application.endBackgroundTask(taskId)
}
}
static func updateUsersData(completion : #escaping () -> Void) {
let update : [String : Any] = [
"Users/\(userId!)/numberOfGame" : (Users.numberOfGame)!,
"Users/\(userId!)/numberOfWin" : (Users.numberOfWin)!
]
dbRef.updateChildValues(update){
(error,dbRef) in
completion()
}
}
Only when I close the application from the simulator, the applicationWillTerminate function is called but it finishes before the writing in updateUserData finishes. It is normal because updateChildValues is asynchronous but I wait, at no time the writing occurs. This may be because I'm doing my update in applicationWillTerminate and not in applicationDidEnterBackground, which would give the OS more time to accept the timeout request, but the problem is that applicationDidEnterBackground is never called (I don't know why).
Does anyone have a solution?
Thanks !

In Swift what is purpose of using UIBackgroundTaskIdentifier. how below mentioned code will execute

self.backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
print("animateRightToLeft: went here")
if let indentifier = self.backgroundTaskIdentifier {
print("animateRightToLeft: stop here")
UIApplication.shared.endBackgroundTask(indentifier)
}
})
My App auto killed after some time if App goes background.
Can some one advice is it because of the above code?
It would be much easier to help you if you explain what you are trying to do? The code you provided will only allow your app to execute code in background for limited amount of time (currently 180 seconds on my iPhone 7).
Detailed:
Once you call beginBackgroundTask, you are given a timer which starts running after your app goes to background. While that timer is running, your app will be executing code even in background. When this timer runs out, or you call endBackgroundTask, your code will stop executing in background. Also if that timer runs out before you called endBackgroundTask, your expiration handler will be called and you should call endBackgroundTask there.
Please note that the code you wrote in the expirationHandler will be called only if you don't call endBackgroundTask before timer runs out.
You can use this code to test how it all behaves, e.g. if you run it as is, app will print backgroundTimeRemaining in the console even when in background. If you comment beginBackgroundTask your app will not print anything after it goes to background.
private var backgroundTaskIdentifier: UIBackgroundTaskIdentifier?
var timer: Timer?
#IBAction func buttontapped(_ sender: Any)
{
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block:
{
(timer) in
NSLog("$$$$$ Time remaining: \(UIApplication.shared.backgroundTimeRemaining)")
})
self.backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler:
{
NSLog("$$$$$ Timer expired: Your app will not be executing code in background anymore.")
if let indentifier = self.backgroundTaskIdentifier
{
UIApplication.shared.endBackgroundTask(indentifier)
}
})
NSLog("$$$$$ start")
DispatchQueue.main.asyncAfter(deadline:.now() + 30)
{
NSLog("$$$$$ end")
if let indentifier = self.backgroundTaskIdentifier
{
UIApplication.shared.endBackgroundTask(indentifier)
}
}
}
From Docs beginBackgroundTask(expirationHandler:)
This method requests additional background execution time for your app. Call this method when leaving a task unfinished might be detrimental to your app’s user experience. For example, call this method before writing data to a file to prevent the system from suspending your app while the operation is in progress. Do not use this method simply to keep your app running after it moves to the background.
Each call to this method must be balanced by a matching call to the endBackgroundTask(_:) method.
My App auto killed after some time if App goes background , is it because of the above code?
no it isn't the above snippet only asks for additional time until task is finished , your app will be terminated anyway

How to use background task using Swift 3?

I am new in background tasks. I have a small work that I am fetching tweets and If my app is in background mode then also it should fetch tweets, but I don't know how.
I am using simply Timer in Appdelegate didFinishLaunchOption Method. When I will close the app then it's not working. I am new in that so please any suggestion. Here below is my code:
Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(getTweets), userInfo: nil, repeats: true).
func getTweets() {
let locationName = Helper.sharedInstance.userDefault.value(forKey: ModelKey.currentLocation) as? String
let accessToken = Helper.sharedInstance.userDefault.value(forKey: ModelKey.twitterAccessToken) as? String
if (locationName == "Bengaluru" && nil != accessToken) || (locationName == "Bangalore" && nil != accessToken){
tweetModel.getTweets(accessToken: accessToken!, city: ModelKey.blrcitytraffic, cityName: "Bengaluru")
}
}
Text to speech is also there but when I will close the app then it stops speaking. If I am not using app then also it can fetch tweets and text to speech should work using a background mode. How long does that work?
A background task means you need to use background threads. Threads in iOS are too many, but if you want to make only background task, you should use two threads; the main and background thread that their structure is:
DispatchQueue.global(qos: .background).async {
//background code
DispatchQueue.main.async {
//your main thread
}
}
So, you firstly initialize the global queue with background mode. This thread can be used for background task and then you must use main thread (only if you want) for doing something when the background task is finished. This can be an option. Another option should be applicationDidEnterBackground in appDelegate and you can only must put your code in that method.
You need to do three things:
In your Info.plist add the following entry for key Required background modes to allow background network access:
Required background modes: App downloads content from the network
In your AppDelegate add to your applicationDidEnterBackground():
func applicationDidEnterBackground(_ application: UIApplication) {
// Fetch no sooner than every (60) seconds which is thrillingly short actually.
// Defaults to Infinite if not set.
UIApplication.shared.setMinimumBackgroundFetchInterval( 60 ) )
}
Also in AppDelegate implement
func application(application: UIApplication, performFetchWithCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
var fetchResult: UIBackgroundFetchResult!
if doingYourStuffActuallyCreatesNetworkTraffic() {
fetchResult = UIBackgroundFetchResult.newData
} else if thereWasAnError() {
fetchResult = UIBackgroundFetchResult.failed
} else {
fetchResult = UIBackgroundFetchResult.noData
}
completionHandler( fetchResult )
return
}
There are still some pitfalls, e.g. there is no guaranteed maximum fetch interval, and background execution might behave substantially different in XCode/Simulator than on real devices.
You could take a look at this pretty similiar topic:
performFetchWithCompletionHandler never gets fired
and of course
https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html

iOS best way to download bulk images

In my app, I have to download nearly 500 images as I open a ViewController. Downloading all 500 images at a time is not a right idea. I'd like to keep 5 active asynchronous downloads at a time. When any one in five is completed, it should start the next.
I also have a refresh control which will restart downloading all the images from first.
Which technique I could go for to implement this modal?
Here is what I tried so far,
Semaphore is created in property declaration
private var semaphore = dispatch_semaphore_create(5)
After getting web service response,
private func startDownloadingImages() {
for place in places {
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER)
self.downloadImageForPlace(place)
}
}
private func downloadImageForPlace(place: Place) {
ApplicationControls.getImageForPlace(place, withCompletion: { (image, error) -> () in
// error checks
dispatch_async(dispatch_get_main_queue(), {
// UI update
dispatch_semaphore_signal(self.semaphore)
})
})
}
But when I tap refresh control, app locks at dispatch_semaphore_wait and I could able to find a way to reset semaphore.
I would use an OperationQueue like this
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 5;
for url in imageUrls {
queue.addOperationWithBlock { () -> Void in
let img1 = Downloader.downloadImageWithURL(url)
NSOperationQueue.mainQueue().addOperationWithBlock({
//display the image or whatever
})
}
}
you can stop your operations with this
queue.cancelAllOperations();
and then just restart the whole thing.
The only thing that you have to change is that your requests have to be synchronous then. Because this approach wont work with callbacks.

Resources