用户位置更新后触发的函数

Function that fires after user's location has updated

重要说明: 我知道我可以简单地在 didUpdateLocations 中调用我的 callback 函数并实现我想要的。不幸的是,由于在该项目中做出了一些预先存在的设计决策(我对此没有影响),因此无法采用这条路线。

我需要编写一个在用户坐标第一次更新时触发的函数,然后将这些坐标传递给完成处理程序。在我的例子中,完成处理程序是一个名为 fetchCountry(fromLocation: CLLocation) 的函数,其中 returns 对应于给定 CLLocation 的国家/地区。

换句话说,我想编写一个类似于 didUpdateLocations 的函数,能够在收到这些更新后调用完成处理程序:

func getUserLocation(callback: @escaping (CLLocation?) -> Void) {

    // wait until user's location has been retrieved by location manager, somehow

    // prepare output for callback function and pass it as its argument
    let latitude = manager.location!.coordinate.latitude
    let longitude = manager.location!.coordinate.longitude
    let location = CLLocation(latitude: latitude, longitude: longitude)

    callback(location)
}

简而言之,getUserLocation 只是 didUpdateLocations 的包装器,但我真的不确定我将如何编写此函数以实现我想要的。

我的更大目标是仅当用户在启动应用程序时位于特定国家/地区(例如美国)时,才向用户显示本地通知。我的应用程序很难决定 scheduling/not 在 AppDelegate.swift 内安排此通知,但在用户位置确定之前无法做出此决定检索。我打算像这样在 appDelegate 中使用 getUserLocation

我希望我已经清楚地表达了我希望使用带有 完成处理程序 的函数来实现此目的。这是我希望我的代码在 AppDelegate.swift:

中执行的操作(即我的用例)
// inside didFinishLaunchingWithOptions
UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert, .sound]) { (granted, error) in
    if granted {

            // this is the use case of the function I am trying to write                         
            LocationManager.shared.getLocation(completion: { location in

                // fetches the country from given location using reverse geo-coding
                LocationManager.shared.fetchCountry(from: location, completion: { country in

                    if country == "United States" {                  
                        let notification = LocalNotification()
                        notificationManager.schedule(notification: notification)
                    }
                })
            })
        }
    }

根据您的 AppDelegate 代码,我可以假设您仅在 LocationManager class 中确定国家/地区。我建议从 getUserLocation() 函数中删除回调,并在 AppDelegate 中创建一个名为 postLocalNotification() 的不同函数,仅 post 本地通知。

当您开始获取用户位置时,将调用 didUpdateLocation,您应在其中调用 fetchCountry() 和最新位置。如果获取的国家是正确的并且你想要 post 本地通知获取 appelegate 对象并调用将 post 通知如下的函数

let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.postLocalNotification()

希望对您有所帮助。

编辑了整个答案。您需要使用同步 api(OperationQueueDispatchQueue 等),因为您的 CLLocationManager 甚至在调用 getUserLocation 之前就已经在获取数据。单独的回调无法解决这个问题,这就是我已经删除该选项的原因。对于这种情况,我使用 DispatchQueue 因为我更喜欢使用它,而不是各自使用它。

class LocationManager: NSObject, CLLocationManagerDelegate{

    static let shared: LocationManager()
    private let privateQueue = DispatchQueue.init("somePrivateQueue")
    private var latestLocation: CLLocation!{
        didSet{
            privateQueue.resume()
        }
    }

    func getUserLocation(queue: DispatchQueue, callback: @escaping (CLLocation?) -> Void) {
        if latestLocation == nil{
            privateQueue.suspend() //pause queue. wait until got a location
        }

        privateQueue.async{ //enqueue work. should run when latestLocation != nil

            queue.async{ //use a defined queue. most likely mainQueue

              callback(self.latestLocation)
              //optionally clear self.latestLocation to ensure next call to this method will wait for new user location. But if you are okay with a cached userLocation, then no need to clear.
            }
        }

    }

    func fetchCountry(from currentLocation: CLLocation, completion: ) //you already have this right?

    @objc func locationManager(_ manager: CLLocationManager, 
           didUpdateLocations locations: [CLLocation]){
         latestLocation = locations.last! //API states that it is guaranteed to always have a value
    }

}


我也同意尽可能使用第三方库。这些代码将保证 unit-tested 并处理边缘情况。然而,我上面的代码可能有一个错误(none 我知道顺便说一句)

您尝试使用 PromiseKit+CoreLocation 了吗?

提供CLLocationManager.requestLocation(authorizationType:satisfying:)。如果您不想导入所有 PromiseKit framework (which is great & avoid such completion chain), you can copy its code。他们做的正是你想要的:将 CoreLocation 请求包装在一个函数中。

我不知道您的特殊要求是什么,所以这是一般性建议。

听起来您应该设置第二个位置管理器供您自己使用。在应用程序委托中设置它并将其委托回调放在那里,与主要位置管理器分开。

不要试图延迟 willFinishLaunchingWithOptions 完成。根据您的要求,您可能必须将任何 UI 设置代码移动到您自己的回调中,以便在确定国家/地区后设置界面。我什至会考虑在您设置位置和通知时显示不同的 UI,然后在您拥有通知权限、位置权限和国家/地区时将其换成主要 UI。

请注意,您通过 didUpdateLocations 获得的第一个位置更新可能不准确。检查它的距离精度,但也要检查它的 timestamp 并丢弃任何旧的更新。出于您的目的(国家/地区准确性),这可能意味着超过一个小时。您实际上只是在考虑用户从另一个国家/地区下飞机后首次打开您的应用程序的用例。为了准确起见,如果时间戳是最近的,则 3000m 或 5000m 以下的任何高度都可以达到该级别。

由于所需的精度非常低,因此该位置将来自手机信号塔三角测量。它应该很快(可能在 2-5 秒内)。

我要注意的一件事是,您的位置管理员将必须请求位置权限,而主要位置管理员会做同样的事情。我不知道这样两次请求权限是如何工作的。

我还将获取国家/地区和获取位置信息与通知权限分开。

一些更一般的建议:您的用例看起来像是在 Advanced NSOperations 的 WWDC session 中处理的。演讲者处理在下一部分继续之前需要设置几件事的情况。那里也有一个位置用例和一个权限用例,一个依赖另一个。