Я пытаюсь отправить обновление местоположения на сервер в фоновом режиме

Приложение иногда вылетает в фоновом режиме

Вот мой 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()
                    }
                }
            }
            }

Это функция updatebacklog

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 часть

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

Функция startSignificationLocation запущена didFinishLaunchingWithOptions

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

Вот журнал сбоя

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

Вот код

enter image description here

Код как текст

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
            }
        }
    }
7
Utku Dalmaz 2 Мар 2018 в 12:50

4 ответа

Лучший ответ

Вы должны обернуть свои фоновые вызовы местоположения в 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
        }
    })
}

Затем при вызове функции updatebackloc используйте подходящую фоновую очередь и завершите фоновую задачу.

Что-то вроде:

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
                }
            }
        }
    }
}
1
ahbou 5 Апр 2018 в 20:56

Я очень подозреваю, что вы пытаетесь принудительно развернуть значение nil в этой строке:

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

Чтобы выяснить, если я прав, попробуйте, если это исправит сбой (но все равно не делает то, что вы хотите, очевидно):

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

Предполагая, что TegKeychain.get("userID") пытается получить значение для ключа из системы KeyChain, это значение, скорее всего, было записано в KeyChain с неподходящей доступностью. Таким образом, вам не разрешен доступ к нему в фоновом режиме, и nil возвращается.

Чтобы это исправить, установите значение, соответствующее вашим потребностям, для ключа kSecAttrAccessible при сохранении учетных данных в KeyChain.

kSecAttrAccessibleAfterFirstUnlock рекомендуется Apple и, вероятно, соответствует вашим потребностям.

В коде:

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)

Подробнее см. В документации по яблокам.


< Сильный > Edit:

По вашим высказываниям в комментариях мое предположение оказалось неверным.

Следующее мое предположение заключается в том, что ОС убивает вас в середине сетевого процесса. Вы можете избежать этого, попросив у ОС больше времени.

Для этого запустите backgroundTask.

В коде это будет выглядеть примерно так:

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
            }
        }
    }
}

Также не забудьте настроить требуемые способности, как упоминается в других ответах:

enter image description here

Вы также должны сделать больше отладки. Прочитайте этот о том, как имитировать движение в Xcode.

10
shallowThought 7 Апр 2018 в 23:59

Вы включили службы определения местоположения для фоновых режимов? Если да, то попробуйте повторно инициализировать диспетчер местоположений при вводе фона, это на самом деле работает для меня, но лучше всего сохранять координаты в базе данных всякий раз, когда пользователь стал активным, синхронизировать несинхронизированные записи с сервером. введите описание изображения здесь

1
navroz 13 Мар 2018 в 07:31

Я полагаю, вам нужно настроить менеджер сеанса Alamofire с фоновой конфигурацией, чтобы выполнить запрос в фоновом режиме. Затем, чтобы сделать запрос, используйте правильно настроенный менеджер сеансов.

Например

let configuration = URLSessionConfiguration.background(withIdentifier: "com.example.app.background")
let sessionManager = Alamofire.SessionManager(configuration: configuration)
2
riik 2 Мар 2018 в 10:26