8

I need to make an API call when the user terminates the app (force close). The straight forward implementation I did is as below.

In the app delegate, I added the following code.

func applicationWillTerminate(_ application: UIApplication) {
    print("________TERMINATED___________")
    testAPICall()
}

func testAPICall(){
    let url = getURL()
    let contentHeader = ["Content-Type": "application/json"]
    Alamofire.request(url,
                  method: .put,
                  parameters: ["username": "abc@xyz.com"],
                  encoding: JSONEncoding.default,
                  headers: contentHeader).responseJSON { (response) -> Void in
                    print("-----")
                  }
}

However, the call is not being made. And on going through the documentation, I have found that I get only 5 seconds for completing the task in this method and above all, making api call is not a task to be done here. So I wonder, what would be the way to do this.

Sujal
  • 1,447
  • 19
  • 34
  • check this- https://www.hackingwithswift.com/example-code/system/how-to-run-code-when-your-app-is-terminated – shivi_shub Mar 19 '19 at 06:37
  • Do you need to wait for the reply, can't you just "fire and forget"? – Joakim Danielson Mar 19 '19 at 07:28
  • You can keep a flag in user-defaults when application terminates and call the API in `didBecomeActive` when user comes back by checking the flag. – Ankit Jayaswal Mar 19 '19 at 08:44
  • @Joakim Danielson I do not need to wait for response but the api call was not being fired. I have got the solution. Thanks. – Sujal Mar 20 '19 at 16:02
  • @Ankit Jayaswal I need to make the call at the moment user terminates. I am able to do this using the solution below. Thanks. – Sujal Mar 20 '19 at 16:05

3 Answers3

9

This is a two fold question

Phase 1: Ensuring API Call starts every time user terminates the app/ before it turns in active

You can always make use of expiration handler background mode of iOS application In your appdelegate

declare var bgTask: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier(rawValue: 0);

and in your appdelegate

 func applicationDidEnterBackground(_ application: UIApplication) {

    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.

    bgTask = application.beginBackgroundTask(withName:"MyBackgroundTask", expirationHandler: {() -> Void in
        // Do something to stop our background task or the app will be killed
        application.endBackgroundTask(self.bgTask)
        self.bgTask = UIBackgroundTaskIdentifier.invalid
    })

    DispatchQueue.global(qos: .background).async {
        //make your API call here
    }
    // Perform your background task here
    print("The task has started")
}

Background expiration handler will ensure you will get enough time to start your API call every time you put your application turns inactive or gets terminated

Phase 2: Ensuring API call started finishes successfully

Though expiration handler might ensure that you get enough time to start your API call it can't ensure the successful completion of API call. What if API call takes longer and while the request is in flight and time runs out??

The only way you to ensure that API call gets successful once started is to make sure to use proper configuration for URLSession

As per docs

Background sessions let you perform uploads and downloads of content in the background while your app isn't running.

link: https://developer.apple.com/documentation/foundation/nsurlsession?language=objc

So make use of Background session and use upload task. Rather than having a plain get/post API which you will hit with some parameter, ask your backend developer to accept a file and put all your param data in that file (if you have any) and start an upload task with background session.

Once the upload task starts with background session iOS will take care of its completion (unless u end up in a authentication challenge obviously) even after your app is killed.

This I believe is the closest you can get to ensure starting a API call and ensuring it finishes once app gets inactive/terminated. I kind a had a discussion with a apple developer regarding the same, and they agreed that this can be a probable solution :)

hope it helps

Sandeep Bhandari
  • 19,999
  • 5
  • 45
  • 78
  • @sujal: Hey sujal sorry I saw your comment late night n din had bandwidth enough to go through your issue of file upload. Thanks for accepting the answer :) Appreciate it :) I don't see your comment anymore here, I believe your issue is resolved by now, lemme know if you are still facing issue :) cheers happy coding – Sandeep Bhandari Mar 21 '19 at 07:00
  • Hi @Sujal and sandeep, i also want to call api on app termination but can't send file. what will be the best solution in this case ? – Taimoor Suleman Nov 29 '19 at 17:21
  • Hi @SandeepBhandari In latest Xcode we have scene delegate so how we can handle this thing in sceneDidEnterBackground method? – Rahul Gupta Feb 08 '20 at 02:59
1

The main idea here is to make a sync call before app terminate

func applicationWillTerminate(_ application: UIApplication) {
        
     let semaphore: dispatch_semaphore_t = dispatch_semaphore_create(0)
     let request = NSMutableURLRequest(URL:url)
     let task = NSURLSession.sharedSession().dataTaskWithRequest(request, 
                     completionHandler: {
            taskData, _, error -> () in
            
            dispatch_semaphore_signal(semaphore);
      })
      task.resume()
      dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
}

Tips:

A dispatch semaphore is an efficient implementation of a traditional counting semaphore. Dispatch semaphores call down to the kernel only when the calling thread needs to be blocked. If the calling semaphore does not need to block, no kernel call is made.

You increment a semaphore count by calling the signal() method, and decrement a semaphore count by calling wait() or one of its variants that specifies a timeout.

G Wesley
  • 21
  • 5
  • 2
    While this code may answer the question, providing additional context regarding why and/or how this code answers the question improves its long-term value. – xiawi Dec 19 '19 at 16:07
1

here is simple way to achieve this task-

func applicationWillTerminate(_ application: UIApplication) {
    
let sem = DispatchSemaphore(value: 0)
startSomethingAsync(completionHandler: {

sem.signal()//When task complete then signal will call
 })
sem.wait()//waiting until task complete
}
Rohit Nishad
  • 428
  • 4
  • 7