Код SVProgressHUD выполняется не по порядку

Я создаю приложение прогнозирования шины с использованием API NextBus, которое поможет пользователям получить время прогнозирования и информацию об автобусе. Я реализовал функцию, которая берет текущее местоположение пользователя и выбранный адрес и возвращает список из 10 автобусных маршрутов, которые минимизируют расстояние и время в пути.

Вот @IBAction это вызывает вышеупомянутую функцию:

@IBAction func findAWayPressed(_ sender: UIButton) {
    // Hide confirm button.
    confirmButton.isHidden = true

    // Setup loading HUD.
    let blue = UIColor(red: 153/255, green: 186/255, blue: 221/255, alpha: 1.0)
    SVProgressHUD.setBackgroundColor(blue)
    SVProgressHUD.setStatus("Finding a way for you...")
    SVProgressHUD.setBorderColor(UIColor.black)
    SVProgressHUD.show()

    // Finds a list of ten bus routes that minimizes the distance from the user and their destination.
    WayFinder.shared.findAWay(startCoordinate: origin!, endCoordinate: destination!)

    SVProgressHUD.dismiss()
}

Проблема в том, что confirmButton.isHidden = true и линии SVProgressHUD, кажется, делают что-нибудь только после WayFinder.shared.findAWay() выполняет. HUD отображается на короткое время, а затем сразу же SVProgressHUD.dismiss(),

Вот findAWay функция:

func findAWay(startCoordinate: CLLocationCoordinate2D, endCoordinate: CLLocationCoordinate2D) {
    // Get list of bus routes from NextBus API.
    getRoutes()

    guard !self.routes.isEmpty else {return}

    // Initialize the the lists of destination and origin stops.
    closestDestinations = DistanceData(shortestDistance: 1000000, stops: [])
    closestOrigins = DistanceData(shortestDistance: 1000000, stops: [])

    // Fetch route info for every route in NextBus API.
    var routeConfigsDownloaded: Int = 0
    for route in routes {
        // Counter is always one whether the request fails
        // or succeeds to prevent app crash.
        getRouteInfo(route: route) { (counter) in
            routeConfigsDownloaded += counter
        }
    }

    while routeConfigsDownloaded != routes.count {}

    // Iterate through every stop and retrieve a list
    // of 10 possible destination stops sorted by distance.
    getClosestDestinations(endCoordinate: endCoordinate)
    // Use destination stop routes to find stops near
    // user's current location that end at destination stops.
    getOriginStops(startCoordinate: startCoordinate)

    // Sort routes by adding their orign distance and destination
    // distance and sorting by total distance.
    getFoundWays()
}

private func getRouteInfo(route: Route, completion: @escaping (Int) -> Void) {
    APIWrapper.routeFetcher.fetchRouteInfo(routeTag: route.tag) { (config) in
        if let config = config {
            self.routeConfigs[route.tag] = config
        } else {
            print("Error retrieving route config for Route \(route.tag).")
        }
        completion(1)
    }
}

Зачем код в @IBAction не выполнить в порядке? Как мог HUD не показывать на экране раньше findAWay назывался? Есть идеи?

1 ответ

Решение

Итак, вы захотите немного почитать о "главном потоке" и о том, как он работает. Возможно ПОНИМАНИЕ ОСНОВНОЙ РЕЗЬБЫ IOS

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

Для системы невозможно отобразить HUD, пока метод не существует, так как он будет частью следующего цикла (рисование / макет / другие важные вещи). В таких случаях я бы склонялся к какому-то "обещающему" API, такому как PromiseKit или Hydra, так как это будет очень просто надеяться на поток.

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

Который может выглядеть примерно так...

SVProgressHUD.show()
DispatchQueue.global(qos: .userInitiated).async {
    WayFinder.shared.findAWay(startCoordinate: origin!, endCoordinate: destination!)
    DispatchQueue.main.async {
        SVProgressHUD.dismiss()
    }
}

Помните, что никогда не изменяйте пользовательский интерфейс вне контекста основного потока, если ОС обнаружит это, это приведет к сбою вашего приложения!

Я мог бы также рассмотреть возможность использования DispatchSemaphore вместо "дикого бега" while-loop так что вместо..

// Fetch route info for every route in NextBus API.
var routeConfigsDownloaded: Int = 0
for route in routes {
    // Counter is always one whether the request fails
    // or succeeds to prevent app crash.
    getRouteInfo(route: route) { (counter) in
        routeConfigsDownloaded += counter
    }
}

while routeConfigsDownloaded != routes.count {}

Вы можете использовать что-то вроде...

let semaphore = DispatchSemaphore(value: routes.count)
// Fetch route info for every route in NextBus API.
var routeConfigsDownloaded: Int = 0
for route in routes {
    // Counter is always one whether the request fails
    // or succeeds to prevent app crash.
    getRouteInfo(route: route) { (counter) in
        semaphore.signal()
    }
}

semaphore.wait()

который будет делать то же самое, но более эффективно

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