1

My iOS app receives notification to refresh its state from our main service. Right now, we are using Alamofire to fetch the latest state and we play a continues sound when there is updated data. Our devices are locked in guided mode to stop them from turning off and provide kiosk experience.

We are making changes such that the device can go to sleep after xx minutes of inactivity. However, we ran into a problem where the device was not getting results back from Alamofire even though the request was sent successfully (based on our logs on the api side).

As I am using Alamofire 4, I have setup a singleton with backgroundsessionmanager which is how AF requests are sent now. But the challenge is that requests are sent intermittently and fail most of the time when the device is sleeping with this error:

Domain=NSURLErrorDomain Code=-997 "Lost connection to background transfer service"

Here is my code for singleton (and I have associated code in AppDelegate):

class Networking {
    static let sharedInstance = Networking()
    public var sessionManager: Alamofire.SessionManager // most of your web service clients will call through sessionManager
    public var backgroundSessionManager: Alamofire.SessionManager // your web services you intend to keep running when the system backgrounds your app will use this
    private init() {
        self.sessionManager = Alamofire.SessionManager(configuration: URLSessionConfiguration.default)
        self.backgroundSessionManager = Alamofire.SessionManager(configuration: URLSessionConfiguration.background(withIdentifier: "com.test.app"))
    }
}

Here is my code for sending the request:

let NetworkManager = Networking.sharedInstance.backgroundSessionManager

        
        DispatchQueue.main.async(execute: {
            NetworkManager.request(self.api_url, method: .post, parameters: params, encoding: JSONEncoding.default, headers: headers).responseJSON{ response in
            
                switch (response.result){
                    case .success:
                        if let jsonResp = response.result.value{
                            print("got the response")
                            print(jsonResp)
                            // parse results
                        }
                    
                    case .failure:
                        print("Network Error: \(response.error)")
                    
                }
            }
        })

I am hoping to get some help to resolve this issue as I am unable to rootcause the inconsistent behavior. I have read in some places that Apple/iOS will only allow upload/download when the app is in background mode instead of requests.

whawhat
  • 175
  • 2
  • 7
  • Unrelated to your question, the `DispatchQueue.main.async` is likely not needed. Alamofire requests already run asynchronously. And if your app is running in the background (as result of background fetch), that doesn’t mean it’s running on a background thread. There are three completely different uses of the word “background”: background queues/threads (as opposed to main queue/thread); background mode (as opposed to running in the foreground); and background, out of process, URL session (as opposed to default, in process, sessions). But these are three completely different concepts. – Rob Jun 26 '20 at 18:37
  • BTW, as a matter of convention, variable and property names generally should start with a lowercase letter. Thus, you might use `let networkManager = ...` instead of `NetworkManager`. It means that, at a glance, you can easily distinguish a variable name from a type name. – Rob Jun 26 '20 at 18:51
  • Thank you for pointing out the improvements. I will make adjustments. – whawhat Jun 26 '20 at 19:51

1 Answers1

2

Yes, background sessions only permit upload and download tasks, not data tasks. They also only permit delegate-based requests, not completion handler-based requests. This answer outlines many of the considerations when doing this in conjunction with Alamofire.

But this begs the question as to whether you really want to use a background session at all. When your app is awaken for background fetch, if you’re able to finish you request within a reasonable amount of time (e.g. 30 seconds), you should probably consider a standard session, not a background session. It’s a lot simpler.

Do not conflate an app running in the “background” with a “background” URLSessionConfiguration: They’re completely different patterns. Just because your app is running in the background, that doesn’t mean you have to use background URLSessionConfiguration. If your app is running (whether in foreground or in the background), then a standard session is fine. You only need background session if you want it to continue after the app is suspended (or is terminated) and you’re willing to encumber yourself with all the extra overhead that background sessions entail.

Background sessions are not intended for requests performed while the app is running in the background. They’re intended for requests that will continue after your app is suspended (and even if it eventually is terminated in the course of its natural lifecycle). That means that background sessions are ideal for slow requests that cannot be completed in a reasonable amount of time, e.g., downloading video asset, downloading many large image assets or documents, etc.

But if you’re just performing a routine GET/POST request that will complete in a reasonable amount of time, consider not using background URLSessionConfiguration, but just do normal request and call the background fetch completion handler when your request is done (i.e., in your network request’s completion handler).

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Thank you for the detailed explanation, Rob. My request/response is taking normally taking less than 5 seconds so it is good to know that I can use the standard session. Would you mind helping me understand why Alamofire requests would not complete with standard session as well. For example, I see the request make it to our API but the response is not processed. – whawhat Jun 26 '20 at 19:49
  • @whawhat - When the system wakes our app to run in the background, it generally gives us a completion handler to call to let it know when it’s done. That should go inside this completion handler. Are you possibly calling that before the request had a chance to finish? – Rob Jun 26 '20 at 19:54
  • Appreciate the quick response. Right now, when the notification arrives, I am pushing into notificationCenter and there is an observer which grabs in the class from where the request is fired. Do you mean that I should add the completion handler from the AF request into the completion handler of didReceiveRemoteNotification:fetchCompletionHandler? Also I have set the ```UIApplicationExitsOnSuspend``` key in my plist file. Could this be causing some issue? – whawhat Jun 26 '20 at 20:02
  • @whawhat - Yep, I’m saying that you want to call the supplied `fetchCompletionHandler` from inside your network request completion handler, and not before. The whole purpose of that supplied completion handler is to let the OS know when you’re done with everything and the app can be suspended/terminated again. But you don’t want that to happen until the network request is done. – Rob Jun 26 '20 at 20:27
  • Thanks. I think I have enough to go from here. I will post back if I have more questions. Again, thank you for all your help! – whawhat Jun 26 '20 at 20:29
  • @whawhat - Re `UIApplicationExitsOnSuspend`, [the documentation](https://developer.apple.com/documentation/bundleresources/information_property_list/uiapplicationexitsonsuspend) suggests that’s deprecated, so I would personally not use that, but I don’t think it’s the problem here. – Rob Jun 26 '20 at 20:36