收到后台位置时应用程序崩溃
App Crashes When Background Location Received
我正在尝试在后台向服务器发送位置更新
应用程序有时在后台崩溃
这是我的 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
这是代码
代码为文本
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
}
}
}
我想您需要使用后台配置来配置 Alamofire 会话管理器,以便在后台执行请求。然后为了发出请求,请使用正确配置的会话管理器。
例如
let configuration = URLSessionConfiguration.background(withIdentifier: "com.example.app.background")
let sessionManager = Alamofire.SessionManager(configuration: configuration)
我高度怀疑您正在尝试 force-unwrap 此行中的 nil
值:
let userID = TegKeychain.get("userID")!
要弄清楚我是否正确,请尝试这是否修复了崩溃(但显然仍然没有按照您的意愿进行):
guard let userID = TegKeychain.get("userID") else {
return
}
假设 TegKeychain.get("userID")
正在尝试从系统 KeyChain 中获取密钥的值,该值很可能已被写入具有 non-suitable 可访问性的 KeyChain。因此,您不能在后台访问它并返回 nil
。
要解决此问题,请在将凭据保存到 KeyChain 时为密钥 kSecAttrAccessible
设置一个符合您需要的值。
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)
有关详细信息,请查看 Apples Documentation。
编辑:
根据您在评论中的陈述,我的猜测是错误的。
我的下一个猜测是 OS 在网络进程中杀死了你。您可以通过询问 OS 更多时间来避免这种情况。
为此,启动一个后台任务。
在看起来像这样的代码中:
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
}
}
}
}
另外不要忘记设置所需的功能,如其他答案中所述:
你还应该多做调试。阅读 this 了解如何在 Xcode 中模拟运动。
您是否启用了后台模式的定位服务?如果是,则在您进入后台时尝试重新初始化位置管理器,它实际上对我有用,但最佳做法是在用户激活时将坐标保存在 data-base 中,将未同步的记录同步到服务器。
您需要将后台位置调用包装在 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
}
}
}
}
}
我正在尝试在后台向服务器发送位置更新
应用程序有时在后台崩溃
这是我的 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
这是代码
代码为文本
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
}
}
}
我想您需要使用后台配置来配置 Alamofire 会话管理器,以便在后台执行请求。然后为了发出请求,请使用正确配置的会话管理器。
例如
let configuration = URLSessionConfiguration.background(withIdentifier: "com.example.app.background")
let sessionManager = Alamofire.SessionManager(configuration: configuration)
我高度怀疑您正在尝试 force-unwrap 此行中的 nil
值:
let userID = TegKeychain.get("userID")!
要弄清楚我是否正确,请尝试这是否修复了崩溃(但显然仍然没有按照您的意愿进行):
guard let userID = TegKeychain.get("userID") else {
return
}
假设 TegKeychain.get("userID")
正在尝试从系统 KeyChain 中获取密钥的值,该值很可能已被写入具有 non-suitable 可访问性的 KeyChain。因此,您不能在后台访问它并返回 nil
。
要解决此问题,请在将凭据保存到 KeyChain 时为密钥 kSecAttrAccessible
设置一个符合您需要的值。
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)
有关详细信息,请查看 Apples Documentation。
编辑:
根据您在评论中的陈述,我的猜测是错误的。
我的下一个猜测是 OS 在网络进程中杀死了你。您可以通过询问 OS 更多时间来避免这种情况。
为此,启动一个后台任务。
在看起来像这样的代码中:
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
}
}
}
}
另外不要忘记设置所需的功能,如其他答案中所述:
你还应该多做调试。阅读 this 了解如何在 Xcode 中模拟运动。
您是否启用了后台模式的定位服务?如果是,则在您进入后台时尝试重新初始化位置管理器,它实际上对我有用,但最佳做法是在用户激活时将坐标保存在 data-base 中,将未同步的记录同步到服务器。
您需要将后台位置调用包装在 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
}
}
}
}
}