7

I am trying to send the location update to the server in the background

App sometimes crashes in the background

Here is my locationmanager delegate

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

            if let lat = manager.location?.coordinate.latitude,
                let long = manager.location?.coordinate.longitude {
                glat = String(lat)
                glong = String(long)

                if UIApplication.shared.applicationState == .background {
                self.updatebackloc(lat, long: long)
                }

                if UIApplication.shared.applicationState == .active {
                self.updateloc(lat, long: long)
                    locationManager.stopUpdatingLocation()
                    let status = CLLocationManager.authorizationStatus()
                    if (status == CLAuthorizationStatus.authorizedAlways) {
                    locationManager.startMonitoringSignificantLocationChanges()
                    }
                }
            }
            }

This is the updatebacklog function

func updatebackloc(_ lat: CLLocationDegrees, long: CLLocationDegrees) {
        let userID = TegKeychain.get("userID")!
        let parameters: Parameters = ["userID": userID, "lat": lat, "long":long]
        Alamofire.request("https://xxxxx.com/ios/updatebackloc.php", method: .post, parameters: parameters).validate().responseJSON { response in
            switch response.result {
            case .success:
                if let json = response.result.value {
                    var success = 0
                    if let dictJSON = json as? [String: AnyObject] {
                        if let successInteger = dictJSON["success"] as? Int {
                            success = successInteger
                            if success == 1
                            {

                            }
                        }
                    }
                }
            case .failure(_):
                return
            }
        }
    }

didFinishLaunchingWithOptions part

if let _ = launchOptions?[UIApplicationLaunchOptionsKey.location] {
                    startSignificationLocation()
                }

startSignificationLocation function triggered on didFinishLaunchingWithOptions

func startSignificationLocation() {
        let locationManager = CLLocationManager()
        locationManager.delegate = self
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.requestAlwaysAuthorization()
        locationManager.allowsBackgroundLocationUpdates = true
        locationManager.startMonitoringSignificantLocationChanges()
    }

Here is the crash log

Crashed: com.apple.main-thread
0                          0x10285b798 specialized AppDelegate.updatebackloc(Double, long : Double) -> () + 4339644312
1                          0x10285b8e4 specialized AppDelegate.locationManager(CLLocationManager, didUpdateLocations : [CLLocation]) -> () (AppDelegate.swift:396)
2                          0x1028540c0 @objc AppDelegate.locationManager(CLLocationManager, didUpdateLocations : [CLLocation]) -> () (AppDelegate.swift)
3  CoreLocation                   0x1874f97bc (null) + 77412
4  CoreLocation                   0x1874f901c (null) + 75460
5  CoreLocation                   0x1874e16b4 (null) + 1004
6  CoreFoundation                 0x180ea3590 __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 20
7  CoreFoundation                 0x180ea2e60 __CFRunLoopDoBlocks + 288
8  CoreFoundation                 0x180ea10c8 __CFRunLoopRun + 2436
9  CoreFoundation                 0x180dc0c58 CFRunLoopRunSpecific + 436
10 GraphicsServices               0x182c6cf84 GSEventRunModal + 100
11 UIKit                          0x18a5195c4 UIApplicationMain + 236
12                         0x1027fe524 main (InboxInterests.swift:30)
13 libdyld.dylib                  0x1808e056c start + 4

enter image description here

Here is the code

enter image description here

code as text

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

        if let lat = manager.location?.coordinate.latitude,
            let long = manager.location?.coordinate.longitude {
            glat = String(lat)
            glong = String(long)

            if UIApplication.shared.applicationState == .background {
            self.updatebackloc(lat, long: long)
            }

            if UIApplication.shared.applicationState == .active {
            self.updateloc(lat, long: long)
                locationManager.stopUpdatingLocation()
                let status = CLLocationManager.authorizationStatus()
                if (status == CLAuthorizationStatus.authorizedAlways) {
                locationManager.startMonitoringSignificantLocationChanges()
                }
            }
        }
        }

        func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
            print(error)
        }


        func updateloc(_ lat: CLLocationDegrees, long: CLLocationDegrees) {
            let userID = TegKeychain.get("userID")!
            let parameters: Parameters = ["userID": userID, "lat": lat, "long":long]
            Alamofire.request("https://xxxxx.com/ios/updateloc.php", method: .post, parameters: parameters).validate().responseJSON { response in
                switch response.result {
                case .success:
                    if let json = response.result.value {
                        var success = 0
                        if let dictJSON = json as? [String: AnyObject] {
                            if let successInteger = dictJSON["success"] as? Int {
                                success = successInteger
                                if success == 1
                                {

                                }
                            }
                        }
                    }
                case .failure(_):
                    return
                }
            }
        }
    func updatebackloc(_ lat: CLLocationDegrees, long: CLLocationDegrees) {
        guard let userID = TegKeychain.get("userID") else {
            return
        }
        let parameters: Parameters = ["userID": userID, "lat": lat, "long":long]
        Alamofire.request("https://xxxxx.com/ios/updatebackloc.php", method: .post, parameters: parameters).validate().responseJSON { response in
            switch response.result {
            case .success:
                if let json = response.result.value {
                    var success = 0
                    if let dictJSON = json as? [String: AnyObject] {
                        if let successInteger = dictJSON["success"] as? Int {
                            success = successInteger
                            if success == 1
                            {

                            }
                        }
                    }
                }
            case .failure(_):
                return
            }
        }
    }
Utku Dalmaz
  • 9,780
  • 28
  • 90
  • 130

4 Answers4

10

I highly suspect you are trying to force-unwrap a nil value in this line:

let userID = TegKeychain.get("userID")!

To figure out if I am correct, try if this fixes the crash (but still does not do what you want obviously):

guard let userID = TegKeychain.get("userID") else {
    return
}

Assuming TegKeychain.get("userID") is trying to get a value for a key from the system KeyChain, this value has most probably been written to the KeyChain with a non-suitable accessibility. Thus you are not allowed to access it in background and nil is returned.

To fix this, set a value that fits your needs for key kSecAttrAccessible when saving the credential to the KeyChain.

kSecAttrAccessibleAfterFirstUnlock is recommended by Apple and probably fits your needs.

In code:

let userIdToSave = "secretString"
guard let secretAsData = userIdToSave.data(using: String.Encoding.utf8) else {
    return
}
let keyChainKey = "userID"
let query = [
    kSecClass as String: kSecClassGenericPassword as String,
    kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock as String,
    kSecAttrService as String: yourService,
    kSecAttrAccount as String: keyChainKey,
    kSecValueData as String: secretAsData] as [String : Any]

SecItemDelete(query as CFDictionary)

let status = SecItemAdd(query as CFDictionary, nil)

For details check Apples Documentation.


Edit:

According to your statements in the comments my guess was wrong.

My next guess is that the OS is killing you in the middle of the network process. You can avoid that by asking the OS for more time.

To do so, start a backgroundTask.

In code that would look something like this:

class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    var bgTask = UIBackgroundTaskInvalid

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        if let _ = launchOptions?[UIApplicationLaunchOptionsKey.location] {
            // app is launched in backgrond due to location change
            bgTask = UIApplication.shared.beginBackgroundTask(expirationHandler: { // Start background task
                UIApplication.shared.endBackgroundTask(bgTask)
                bgTask = UIBackgroundTaskInvalid
            })
            startSignificationLocation()
        }
        return true
    }

    // ... your other methods

    func updatebackloc(_ lat: CLLocationDegrees, long: CLLocationDegrees) {
        guard let userID = TegKeychain.get("userID") else {
            return
        }
        let parameters: Parameters = ["userID": userID, "lat": lat, "long":long]
        Alamofire.request("https://xxxxx.com/ios/updatebackloc.php", method: .post, parameters: parameters).validate().responseJSON { response in
            defer {
                // Tell the OS that you are done
                UIApplication.shared.endBackgroundTask(bgTask)
                bgTask = UIBackgroundTaskInvalid
            }
            switch response.result {
            case .success:
                if let json = response.result.value {
                    var success = 0
                    if let dictJSON = json as? [String: AnyObject] {
                        if let successInteger = dictJSON["success"] as? Int {
                            success = successInteger
                            if success == 1
                            {

                            }
                        }
                    }
                }
            case .failure(_):
                return
            }
        }
    }
}

Also do not forget to setup required Capatibilities as mentions in other answers:

enter image description here

You also should do more debugging. Read this about how to simulate movement in Xcode.

shallowThought
  • 19,212
  • 9
  • 65
  • 112
2

I guess you need to configure an Alamofire Session Manager with the background configuration in order to perform a request in the background. Then in order to make the request use the properly configured session manager.

e.g.

let configuration = URLSessionConfiguration.background(withIdentifier: "com.example.app.background")
let sessionManager = Alamofire.SessionManager(configuration: configuration)
riik
  • 4,448
  • 1
  • 12
  • 16
1

Have you enabled the location services for background modes? If yes then try to reinitialise location manager when you enter background it actually worked for me but the best practise is to save the coordinates in data-base whenever user became active sync unsynced records to the server. enter image description here

navroz
  • 382
  • 2
  • 13
1

You need to wrap your background location calls inside a UIBackgroundTask.

var bgTask: UIBackgroundTaskIdentifier?
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: "bgLocation", expirationHandler: {
        if let task = self.bgTask {
            application.endBackgroundTask(task)
            self.bgTask = UIBackgroundTaskInvalid
        }
    })
}

Then when calling your updatebackloc function use the proper background queue and end the background task.

Something like:

func updatebackloc(_ lat: CLLocationDegrees, long: CLLocationDegrees) {
    let userID = TegKeychain.get("userID")!
    let parameters: Parameters = ["userID": userID, "lat": lat, "long":long]
    DispatchQueue.global().async {
        Alamofire.request("https://xxxxx.com/ios/updatebackloc.php", method: .post, parameters: parameters).validate().responseJSON { response in

            //response handling here...
            //....
            if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
                if let task = appDelegate.bgTask {
                    UIApplication.shared.endBackgroundTask(task)
                    appDelegate.bgTask = UIBackgroundTaskInvalid
                }
            }
        }
    }
}
ahbou
  • 4,710
  • 23
  • 36
  • I am getting Ambiguous reference to member 'application(_:didFinishLaunchingWithOptions:)' error when I try to end the bgtask with application.endBackgroundTask(task) code – Utku Dalmaz Apr 05 '18 at 17:48
  • @UtkuDalmaz you're calling this from outside AppDelegate right? see my updated code – ahbou Apr 05 '18 at 20:56
  • applicationDidEnterBackground is the wrong place to start the task. It will be killed latestly after 3 minutes. SignificantLocationChages can wakeup your app at any time later. – shallowThought Apr 07 '18 at 18:48