Swift: получить значение с помощью асинхронного метода

Я ищу хорошую идиому или две для этой ситуации:

Я хочу преобразовать CLLocationCoordinate2D в CLPlacemark с помощью асинхронного обратного вызова геолокации, как часть последовательности других операций.

Этап преобразования в значительной степени является "служебным" шагом, поэтому помещение большого количества кода в обработчик для выполнения "других операций" выглядит как плохая структура.

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

Есть ли стандартный подход к этому? Распространено просто поместить код в обработчик?

Спасибо!

Вот конкретный код для моего контекста, FWIW.

func getPlaceFromCoordinate(coordinate: CLLocationCoordinate2D) -> CLPlacemark? {

    var loc = CLLocation(
        latitude: coordinate.latitude,
        longitude: coordinate.longitude
    )

    var mightBeAPlace: CLPlacemark? = nil

    CLGeocoder().reverseGeocodeLocation(loc, completionHandler: {(placemarks, error) -> Void in
        if(error != nil) {
            println("Reverse geocoding error.")
        }
        else if (placemarks.count == 0) {
            println("no placemarks")
        }
        else { // if (placemarks.count > 0)
            println("we have placemarks")
            mightBeAPlace = CLPlacemark(placemark: placemarks[0] as! CLPlacemark)
            println("Inside closure place: \(mightBeAPlace?.locality)")
            lastUserSelectedPlace = mightBeAPlace // This stores it in a class variable.
        }
    })
    println("Outside closure place: \(mightBeAPlace?.locality)")
    return mightBeAPlace // This of course fails because the async task is running separately.
}

2 ответа

Решение

Типичный подход заключается в принятии completionHandler подойти к себе, например:

lazy var geocoder = CLGeocoder()

func getPlaceFromCoordinate(coordinate: CLLocationCoordinate2D, completionHandler: (CLPlacemark!, NSError?) -> ()) {
    let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)

    geocoder.reverseGeocodeLocation(location) { placemarks, error in
        if error != nil {
            println("Reverse geocoding error: \(error)")
        } else if placemarks.count == 0 {
            println("no placemarks")
        }

        completionHandler(placemarks.first as? CLPlacemark, error)
    }
}

И вы бы назвали это так:

getPlaceFromCoordinate(coordinate) { placemark, error in 
    if placemark != nil {
        // use placemark here
    }
}

// but do not use it here, because the above runs asynchronously (i.e. later)

С точки зрения того, сколько кода вы положили в этом completionHandler закрытие, и сколько вы положили в getPlaceFromCoordinateэто полностью зависит от того, что влечет за собой этот код. Но столько же обычного кода, который повторяется (например, регистрация ошибок, что у вас есть) внутри getPlaceFromCoordinateи, надеюсь, закрытие будет ограничено принятием CLPlacemark и обновление объектов модели и / или пользовательского интерфейса.

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

Если вы обнаружите, что код внутри замыкания становится громоздким, то включите функциональную декомпозицию и переместите этот код в свою собственную функцию, и обработчик завершения просто вызовет это. Или есть и другие асинхронные шаблоны (например, асинхронные NSOperation подклассы с зависимостями между ними, обещаниями / фьючерсами и т. д.). Но используйте асинхронный шаблон.

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

func getPlaceFromCoordinate(
        coordinate: CLLocationCoordinate2D,
        placeAction: ((CLPlacemark) -> Void)?
    ) {

        :
    // Reverse geocode.
        :

    //  If we get a good placemark:
    if (placeAction != nil) {
        placeAction!(placemark)
    }

    }

Это кажется достаточно простым для контекста, гибким и возвращает код вызова "на место водителя". Не уверен, какие еще плюсы или минусы могут быть.

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