Search code examples
iosswiftappdelegate

Requests run when my app will enter foreground fail with "The network connection was lost."


My app is getting a fair number of network errors after I start making requests while transitioning from the background to the foreground.

The error looks like this:

Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost." UserInfo={NSUnderlyingError=0x2808e85a0 {Error Domain=kCFErrorDomainCFNetwork Code=-1005 "(null)" UserInfo={NSErrorPeerAddressKey=<CFData 0x282550410 [0x1f80cb728]>{length = 16, capacity = 16, bytes = 0x10021068c0a8010a0000000000000000}, _kCFStreamErrorCodeKey=-4, _kCFStreamErrorDomainKey=4}}, NSErrorFailingURLStringKey=http://192.168.1.10:4200/api/users/sessions, NSErrorFailingURLKey=http://192.168.1.10:4200/api/users/sessions, _kCFStreamErrorDomainKey=4, _kCFStreamErrorCodeKey=-4, NSLocalizedDescription=The network connection was lost.}

The code is triggered from my app's delegate:

    func applicationWillEnterForeground(_: UIApplication) {
        coordinator?.handleWillEnterForeground()
    }

And from this documentation, network requests should be allowed at this stage:

At launch time, the system starts your app in the inactive state before transitioning it to the foreground. Use your app’s launch-time methods to perform any work needed at that time. For an app that is in the background, UIKit moves your app to the inactive state by calling one of the following methods:

  • For apps that support scenes — The sceneWillEnterForeground(_:) method of the appropriate scene delegate object.
  • For all other apps — The applicationWillEnterForeground(_:) method.

When transitioning from the background to the foreground, use these methods to load resources from disk and fetch data from the network.

Moreover:

  • if I enter background for just 1 second, the network request will be successful (and the app state will be active when the completion handler gets called)
  • if I wait more than 5 seconds, the network request will error (and the app state will be inactive when the completion handler gets called)

From my understanding, the inactive state means you're not able to interact with the user and events are not being delivered. But network requests should work fine, right? Any idea why my app can't run requests while being inactive?

(I also considered using didBecomeActive instead of willEnterForeground, but it gets called when the app launches as well, so it is not really what I need.)

EDIT: I also happened to get Receive failed with error "Software caused connection abort" errors that are unrelated to the issue that is discussed here (ie multiple versions of Xcode): Xcode error connecting to simulator "Software caused connection abort"


Solution

  • I don't know the root cause of the bug you're describing, but I've run into it myself. It appears, as you alluded to, that Apple tells apps that they're in the foreground before their own system has marked them as such, and therefore requests made immediately upon becoming active may be blocked (it happens frequently but not 100% of the time in my experience).

    The solution that worked for me is to simply wait a tenth of a second.

    You could do this by wrapping your call in an asyncAfter call

    func applicationWillEnterForeground(_: UIApplication) {
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { [weak self]
            self?.coordinator?.handleWillEnterForeground()
        }
    }
    

    but more preferably if all your requests go through a single coordinator, leave your AppDelegate code the same and instead have the coordinator look out for requests that return with error code -1005 or 53 and have it wait a tenth of a second and then retry the request (be sure to have a retry limit).