Функция, которая срабатывает после обновления местоположения пользователя

Важная заметка: Я знаю, что могу просто вызвать свою 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)
                    }
                })
            })
        }
    }

не могли бы вы просто опубликовать уведомление в didUpdateLocations?

arvidurs 19.05.2019 04:03

К сожалению, это не сработает. Более общая картина, которую я не рассматривал, чтобы сохранить разумную область, заключается в том, что я хочу запланировать локальное уведомление, которое будет запускаться ТОЛЬКО после определения страны пользователя (у меня есть функция с именем fetchCountry(fromLocation: CLLocation, completion: @escaping (_ country: String) -> ()), которую я бы назвал как callback, а затем запланировать уведомление только после того, как у меня будет страна пользователя). Размещение уведомления в didUpdateLocations, я думаю, не поможет

iOShit I Screwed Up 19.05.2019 04:41

Я «думаю», что есть способ определить, когда с помощью собственного API была достигнута определенная точность определения местоположения. Как только точность будет точной, скажем, до 100 м, запустите обратный вызов. И к тому времени должны быть в состоянии определить страну.

arvidurs 19.05.2019 06:37

Не могли бы вы немного уточнить @arvidurs?

iOShit I Screwed Up 19.05.2019 06:58

взгляните на этот developer.apple.com/documentation/corelocation/…, вы можете проверить, достигнута ли определенная точность. Как только это произошло, вы знаете, что это достаточно точно, чтобы вызвать в страну доставки.

arvidurs 19.05.2019 07:08
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
5
519
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Из вашего кода AppDelegate я могу предположить, что вы определяете страну только в классе LocationManager. Я бы предложил удалить обратный вызов из функции getUserLocation() и создать другую функцию с именем postLocalNotification() в AppDelegate, чтобы просто опубликовать локальное уведомление.

Когда вы начнете получать местоположение пользователя, будет вызван didUpdateLocation, в котором вы должны вызвать fetchCountry() с последним местоположением. Если выбранная страна правильная, и вы хотите опубликовать локальное уведомление, получите объект апелляции и вызовите функцию, которая опубликует уведомление, как показано ниже.

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

Надеюсь это поможет.

Эй, спасибо за ответ. К сожалению, я специально пытаюсь добиться этого с помощью функции обратного вызова!

iOShit I Screwed Up 21.05.2019 03:36

Привет, я написал ответ. Я надеюсь, что это соответствует вашим требованиям к дизайну

Joshua Francis Roman 21.05.2019 07:15

Хотя я присудил награду другому ответу, я все равно ценю, что вы поделились своими знаниями. Проголосовал.

iOShit I Screwed Up 23.05.2019 02:19
Ответ принят как подходящий

Отредактировал весь ответ. Вам нужно будет использовать синхронизирующий 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 Большое спасибо за ваш подробный ответ. Я ценю, что вы внимательно рассмотрели вопрос и разработали ответ, соответствующий моим конкретным требованиям. Я сейчас просматриваю то, что вы написали, и посмотрю, решит ли это проблему.

iOShit I Screwed Up 21.05.2019 15:22

Несколько обновлений: похоже, что метод очереди не будет работать, потому что didUpdateLocations еще не был вызван, когда я на самом деле вызываю getUserLocation в AppDelegate (и, таким образом, я разворачиваю location до нуля). Первый метод сейчас тоже не работает, хотя я подозреваю, что это может быть из-за некоторых деталей, которые мне еще нужно настроить.

iOShit I Screwed Up 21.05.2019 16:11

Я понял, что не так с первым подходом! К сожалению, в didUpdateLocation происходит скрытая гонка. Прямо там, где вы проверяете, равно ли getLocationCallback нулю. Состояние гонки возникает из-за того, что getUserLocation несколько раз не назначает self.getLocationCallback = callback достаточно быстро, прежде чем didUpdateLocations достигнет нулевой проверки. Не знаю, как обойти эту проблему

iOShit I Screwed Up 21.05.2019 16:44

Привет, я предполагаю, что местоположение уже ищет до вызова getUserLocation, верно?

Joshua Francis Roman 22.05.2019 04:09

Кроме того, «похоже, что метод очереди не будет работать, потому что didUpdateLocations еще не был вызван, когда я на самом деле вызываю getUserLocation». Хм, это странно. Цель очереди — гарантировать, что ваш обратный вызов внутри getUserLocation не будет вызван до тех пор, пока didUpdateLocations не сработает. Не понимаю, почему он прошел ноль. Обновил ответ, чтобы использовать кэшированное местоположение пользователя и решить проблемы гонки, которые я заметил.

Joshua Francis Roman 22.05.2019 04:37

В итоге я решил проблему, используя кешированное значение, которое было предоставлено в проекте. Однако ваш обновленный ответ выглядит очень законным, и вы помогли мне придумать различные жизнеспособные способы решения этой проблемы. Я уверен, что это поможет многим другим разработчикам в подобных ситуациях, как я. Заслуженная награда!

iOShit I Screwed Up 22.05.2019 16:03

Вы пытались использовать PromiseKit+CoreLocation?

Он обеспечивает CLLocationManager.requestLocation(authorizationType:satisfying:). Если вы не хотите импортировать все Фреймворк PromiseKit (это здорово и позволяет избежать такой цепочки завершения), вы можете скопировать его код. Они сделали именно то, что вы хотели: обернули запрос CoreLocation в функцию.

Эй, спасибо за ответ. Будучи новой конструкцией, представленной в Swift5, обещания несовместимы со Swift 3/4. Как указано в моем сообщении, я ищу ответ, в котором специально используется цепочка обработчиков завершения.

iOShit I Screwed Up 21.05.2019 13:11

Promise 6.0 совместим со Swift 4. И, как я уже сказал, вы можете использовать почти такой же код. Сделать CoreLocation совместимым с PromiseKit — это то же самое, что сделать CoreLocation совместимым с вашим шаблоном обработчика завершения.

GaétanZ 21.05.2019 14:02

Хотя я присудил награду другому ответу, я все равно ценю, что вы поделились своими знаниями. Проголосовал.

iOShit I Screwed Up 23.05.2019 02:18

Я не знаю, каковы ваши особые требования, так что это общий совет.

Похоже, вам следует настроить второй менеджер местоположений для собственного использования. Настройте его в делегате приложения и поместите туда его обратные вызовы делегата, отдельно от основного менеджера местоположений.

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

Поймите, что первое обновление местоположения, которое вы получите didUpdateLocations, может быть неточным. Проверьте его точность расстояния, но также проверьте его timestamp и удалите все старые обновления. Для ваших целей (точность страны) это, вероятно, означает, что прошло более часа. На самом деле вы рассматриваете только вариант использования, когда ваше приложение открывается впервые после того, как пользователь выходит из самолета, прилетающего из другой страны. Для точности, если отметка времени недавняя, для этого уровня подойдет что-нибудь меньше 3000 м или 5000 м.

Поскольку требуемая точность очень низкая, местоположение будет получено с помощью триангуляции вышек сотовой связи. Это должно быть быстро (возможно, в течение 2-5 секунд).

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

Я бы также отделил получение страны и получение местоположения от разрешений на уведомления.

Еще несколько общих советов: ваш вариант использования выглядит так, как будто он обрабатывается в сеансе WWDC для Расширенные операции NSO. Динамик обрабатывает случаи, когда вам нужно настроить несколько вещей, прежде чем можно будет перейти к следующей части. Там также есть вариант использования местоположения и вариант использования разрешений, один зависит от другого.

Привет, @nevan king. Спасибо за ваш проницательный ответ. Я принимаю ваш совет во внимание и вижу, как это изменит мою архитектуру. Я вернусь к тебе.

iOShit I Screwed Up 21.05.2019 15:25

Хотя я присудил награду другому ответу, я все равно ценю, что вы поделились своими знаниями. Проголосовал.

iOShit I Screwed Up 23.05.2019 02:18

Другие вопросы по теме