Важная заметка: Я знаю, что могу просто вызвать свою callback функцию внутри didUpdateLocations и добиться того, чего хочу. К сожалению, этот маршрут не может быть выбран из-за некоторых ранее существовавших дизайнерских решений, которые были приняты в этом проекте (на которые я не имею никакого влияния).
Мне нужно написать функцию, которая срабатывает при первом обновлении координат пользователя, а затем передает эти координаты обработчику завершения. В моем случае этот обработчик завершения представляет собой функцию с именем fetchCountry(fromLocation: CLLocation), которая возвращает страну, соответствующую заданному 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, но я действительно не уверен, как мне написать эту функцию, чтобы она достигала того, что я хочу.
Моя большая цель здесь — показать пользователю локальное уведомление только если о том, что он находится в определенной стране (например, США) при запуске приложения. Моему приложению сложно принять решение о планировании/непланировании этого уведомления внутри AppDelegate.swift, но это решение не может быть принято до тех пор, пока не будет получено местоположение пользователя. Я планирую использовать getUserLocation внутри appDelegate следующим образом:
Я надеюсь, что ясно объяснил, что хочу добиться этого с помощью функции с обработчик завершения. Вот что я хотел бы, чтобы мой код делал (то есть мой вариант использования) внутри 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)
}
})
})
}
}
К сожалению, это не сработает. Более общая картина, которую я не рассматривал, чтобы сохранить разумную область, заключается в том, что я хочу запланировать локальное уведомление, которое будет запускаться ТОЛЬКО после определения страны пользователя (у меня есть функция с именем fetchCountry(fromLocation: CLLocation, completion: @escaping (_ country: String) -> ()), которую я бы назвал как callback, а затем запланировать уведомление только после того, как у меня будет страна пользователя). Размещение уведомления в didUpdateLocations, я думаю, не поможет
Я «думаю», что есть способ определить, когда с помощью собственного API была достигнута определенная точность определения местоположения. Как только точность будет точной, скажем, до 100 м, запустите обратный вызов. И к тому времени должны быть в состоянии определить страну.
Не могли бы вы немного уточнить @arvidurs?
взгляните на этот developer.apple.com/documentation/corelocation/…, вы можете проверить, достигнута ли определенная точность. Как только это произошло, вы знаете, что это достаточно точно, чтобы вызвать в страну доставки.





Из вашего кода AppDelegate я могу предположить, что вы определяете страну только в классе LocationManager. Я бы предложил удалить обратный вызов из функции getUserLocation() и создать другую функцию с именем postLocalNotification() в AppDelegate, чтобы просто опубликовать локальное уведомление.
Когда вы начнете получать местоположение пользователя, будет вызван didUpdateLocation, в котором вы должны вызвать fetchCountry() с последним местоположением. Если выбранная страна правильная, и вы хотите опубликовать локальное уведомление, получите объект апелляции и вызовите функцию, которая опубликует уведомление, как показано ниже.
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.postLocalNotification()
Надеюсь это поможет.
Эй, спасибо за ответ. К сожалению, я специально пытаюсь добиться этого с помощью функции обратного вызова!
Привет, я написал ответ. Я надеюсь, что это соответствует вашим требованиям к дизайну
Хотя я присудил награду другому ответу, я все равно ценю, что вы поделились своими знаниями. Проголосовал.
Отредактировал весь ответ. Вам нужно будет использовать синхронизирующий API (OperationQueue, DispatchQueue и т. д.), потому что ваш 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
}
}
Я также согласен на использование сторонней библиотеки, если это возможно. Этот код будет гарантированно протестирован и будет обрабатывать пограничные случаи. Принимая во внимание, что мой приведенный выше код может иметь ошибку (кстати, я не знаю ни одной)
Привет @Joshua Francis Roman Большое спасибо за ваш подробный ответ. Я ценю, что вы внимательно рассмотрели вопрос и разработали ответ, соответствующий моим конкретным требованиям. Я сейчас просматриваю то, что вы написали, и посмотрю, решит ли это проблему.
Несколько обновлений: похоже, что метод очереди не будет работать, потому что didUpdateLocations еще не был вызван, когда я на самом деле вызываю getUserLocation в AppDelegate (и, таким образом, я разворачиваю location до нуля). Первый метод сейчас тоже не работает, хотя я подозреваю, что это может быть из-за некоторых деталей, которые мне еще нужно настроить.
Я понял, что не так с первым подходом! К сожалению, в didUpdateLocation происходит скрытая гонка. Прямо там, где вы проверяете, равно ли getLocationCallback нулю. Состояние гонки возникает из-за того, что getUserLocation несколько раз не назначает self.getLocationCallback = callback достаточно быстро, прежде чем didUpdateLocations достигнет нулевой проверки. Не знаю, как обойти эту проблему
Привет, я предполагаю, что местоположение уже ищет до вызова getUserLocation, верно?
Кроме того, «похоже, что метод очереди не будет работать, потому что didUpdateLocations еще не был вызван, когда я на самом деле вызываю getUserLocation». Хм, это странно. Цель очереди — гарантировать, что ваш обратный вызов внутри getUserLocation не будет вызван до тех пор, пока didUpdateLocations не сработает. Не понимаю, почему он прошел ноль. Обновил ответ, чтобы использовать кэшированное местоположение пользователя и решить проблемы гонки, которые я заметил.
В итоге я решил проблему, используя кешированное значение, которое было предоставлено в проекте. Однако ваш обновленный ответ выглядит очень законным, и вы помогли мне придумать различные жизнеспособные способы решения этой проблемы. Я уверен, что это поможет многим другим разработчикам в подобных ситуациях, как я. Заслуженная награда!
Вы пытались использовать PromiseKit+CoreLocation?
Он обеспечивает CLLocationManager.requestLocation(authorizationType:satisfying:). Если вы не хотите импортировать все Фреймворк PromiseKit (это здорово и позволяет избежать такой цепочки завершения), вы можете скопировать его код. Они сделали именно то, что вы хотели: обернули запрос CoreLocation в функцию.
Эй, спасибо за ответ. Будучи новой конструкцией, представленной в Swift5, обещания несовместимы со Swift 3/4. Как указано в моем сообщении, я ищу ответ, в котором специально используется цепочка обработчиков завершения.
Promise 6.0 совместим со Swift 4. И, как я уже сказал, вы можете использовать почти такой же код. Сделать CoreLocation совместимым с PromiseKit — это то же самое, что сделать CoreLocation совместимым с вашим шаблоном обработчика завершения.
Хотя я присудил награду другому ответу, я все равно ценю, что вы поделились своими знаниями. Проголосовал.
Я не знаю, каковы ваши особые требования, так что это общий совет.
Похоже, вам следует настроить второй менеджер местоположений для собственного использования. Настройте его в делегате приложения и поместите туда его обратные вызовы делегата, отдельно от основного менеджера местоположений.
Не пытайтесь отсрочить willFinishLaunchingWithOptions окончание. В зависимости от ваших требований вам, возможно, придется переместить любой код настройки пользовательского интерфейса в свой собственный обратный вызов, чтобы настроить интерфейс после определения страны. Я бы даже подумал о том, чтобы показать другой пользовательский интерфейс, пока вы выполняете настройку этого местоположения и уведомлений, а затем замените его на основной пользовательский интерфейс, когда у вас есть разрешение на уведомление, разрешение на местоположение и страну.
Поймите, что первое обновление местоположения, которое вы получите didUpdateLocations, может быть неточным. Проверьте его точность расстояния, но также проверьте его timestamp и удалите все старые обновления. Для ваших целей (точность страны) это, вероятно, означает, что прошло более часа. На самом деле вы рассматриваете только вариант использования, когда ваше приложение открывается впервые после того, как пользователь выходит из самолета, прилетающего из другой страны. Для точности, если отметка времени недавняя, для этого уровня подойдет что-нибудь меньше 3000 м или 5000 м.
Поскольку требуемая точность очень низкая, местоположение будет получено с помощью триангуляции вышек сотовой связи. Это должно быть быстро (возможно, в течение 2-5 секунд).
Единственное, о чем я должен быть осторожен, так это о том, что ваш менеджер по местоположению должен будет запрашивать разрешения на определение местоположения, в то время как основной менеджер по местоположению делает то же самое. Я не знаю, как работает двойной запрос разрешений.
Я бы также отделил получение страны и получение местоположения от разрешений на уведомления.
Еще несколько общих советов: ваш вариант использования выглядит так, как будто он обрабатывается в сеансе WWDC для Расширенные операции NSO. Динамик обрабатывает случаи, когда вам нужно настроить несколько вещей, прежде чем можно будет перейти к следующей части. Там также есть вариант использования местоположения и вариант использования разрешений, один зависит от другого.
Привет, @nevan king. Спасибо за ваш проницательный ответ. Я принимаю ваш совет во внимание и вижу, как это изменит мою архитектуру. Я вернусь к тебе.
Хотя я присудил награду другому ответу, я все равно ценю, что вы поделились своими знаниями. Проголосовал.
не могли бы вы просто опубликовать уведомление в didUpdateLocations?