Как вызвать функцию внутри замыкания

В классе модели Location я получаю название текущего города:

var currentLatitude: Double!
var currentLongitude: Double!
var currentLocation: String!
var currentCity: String!

func getLocationName() {

    let geoCoder = CLGeocoder()
    let location = CLLocation(latitude: currentLatitude, longitude: currentLongitude)

    geoCoder.reverseGeocodeLocation(location, completionHandler: { placemarks, error in
        guard let addressDict = placemarks?[0].addressDictionary else {
            return
        }
        if let city = addressDict["City"] as? String {
            self.currentCity = city
            print(city)
        }
        if let zip = addressDict["ZIP"] as? String {
            print(zip)
        }
        if let country = addressDict["Country"] as? String {
            print(country)
        }

        self.nowUpdateUI()
    })
}

В контроллере представления я хочу обновить пользовательский интерфейс и обновить свою метку, чтобы показать текущий город. Однако self.currentCity = city происходит внутри замыкания. Итак, если я просто запускаю func в контроллере представления:

func updateUI() {
        cityLbl.text = Location.sharedInstance.currentCity
}
  • Я никуда не денусь, потому что закрытие еще не закончилось. Мне посоветовали добавить обработчик завершения в getLocationName() и внутри него выполнить вызов функции, которая будет обновлять пользовательский интерфейс. Однако из всех руководств по замыканиям, обработчикам завершения мне не ясно, как этого добиться. Как создать обработчик завершения, передать его в качестве аргумента getLocationName() и как вызвать getLocationName из контроллера представления?

person Mr_Vlasov    schedule 15.02.2017    source источник


Ответы (2)


Чтобы справиться с этой ситуацией, у вас есть несколько вариантов.

  1. Создайте delegate/protocol со своим классом Location

    • Create one protocol and implement that protocol method with your ViewController and declare its instance in your Location class. After then in the completionHandler of reverseGeocodeLocation call this delegate method. Check Apple documentation on Protocol for more details.
  2. Вы можете создать completionHandler с помощью метода getLocationName класса Location.

    • Добавьте completionHandler к getLocationName и назовите это completionHandler внутри completionHandler из reverseGeocodeLocation вот так.

      func getLocationName(completionHandler: @escaping (_ success: Bool) -> Void) {
      
          let geoCoder = CLGeocoder()
          let location = CLLocation(latitude: currentLatitude, longitude: currentLongitude)
      
          geoCoder.reverseGeocodeLocation(location, completionHandler: { placemarks, error in
              guard let addressDict = placemarks?[0].addressDictionary else {
                  completionHandler(false)
                  return
              }
              if let city = addressDict["City"] as? String {
                  self.currentCity = city
                  print(city)
              }
              if let zip = addressDict["ZIP"] as? String {
                  print(zip)
              }
              if let country = addressDict["Country"] as? String {
                  print(country)
              }
              completionHandler(true)
              //self.nowUpdateUI()
          })
      }
      

      Теперь в ViewController, где вы вызываете эту функцию, вызовите свой метод updateUI внутри блока завершения.

      Location.sharedInstance.getLocationName { (success) in
          if success {//If successfully got response
              self.updateUI()
          }
      }
      
  3. Вы можете добавить наблюдателя для (NS)NotificationCenter.

    • Register the observer with (NS)NotificationCenter and then post the notification inside the completionHandler of reverseGeocodeLocation. You can get more detail on this with this StackOverflow Post.
person Nirav D    schedule 15.02.2017
comment
если у вас есть какие-либо сомнения, не стесняйтесь задавать вопросы. - person Nirav D; 15.02.2017
comment
ага, я пока не владею программированием, можете показать реализацию 2-го варианта, который вы предлагаете? (учитывая, что updateUI() является методом в контроллере представления, а не в Location, где реализовано getLocationName.) - person Mr_Vlasov; 15.02.2017
comment
@Mr_Vlasov Итак, вы хотите использовать completionHandler с методом. Вы работаете со Swift 3? - person Nirav D; 15.02.2017
comment
я изо всех сил пытаюсь найти хороший учебник или простое объяснение обработчиков завершения. Документация Apple содержит весь этот ненужный промежуточный персонал прямо в статье, где они вводят замыкания. Не могли бы вы предложить какие-нибудь хорошие источники по теории замыканий? Пытаюсь понять логику его использования, а не синтаксис. - person Mr_Vlasov; 15.02.2017

Весь этот кусок вашего кода:

completionHandler: { placemarks, error in
        guard let addressDict = placemarks?[0].addressDictionary else {
            return
        }
        if let city = addressDict["City"] as? String {
            self.currentCity = city
            print(city)
        }
        if let zip = addressDict["ZIP"] as? String {
            print(zip)
        }
        if let country = addressDict["Country"] as? String {
            print(country)
        }

        self.nowUpdateUI()
    }

)

уже происходит в завершенииHandler (что происходит после того, как все завершено). Просто также запустите updateUI() внутри завершенияHandler. Таким образом, ваш конечный код будет:

completionHandler: { placemarks, error in
        guard let addressDict = placemarks?[0].addressDictionary else {
            return
        }
        if let city = addressDict["City"] as? String {
            self.currentCity = city
            DispatchQueue.main.async {
                updateUI() 
        }
        }
        if let zip = addressDict["ZIP"] as? String {
            print(zip)
        }
        if let country = addressDict["Country"] as? String {
            print(country)
        }

        self.nowUpdateUI()
    }

)

Причина, по которой вы должны использовать DispatchQueue.main, заключается в том, что ваш завершениеHandler находится в фоновой очереди, но вы ДОЛЖНЫ всегда выполнять действия, связанные с пользовательским интерфейсом, из вашей основной очереди, чтобы пользователи получали самые быстрые изменения в своем пользовательском интерфейсе без каких-либо сбоев. Представьте, если бы вы работали в фоновом потоке, и это происходило бы медленно.

person Honey    schedule 15.02.2017
comment
но updateUI() - это функция контроллера представления, в которой объявлена ​​​​метка местоположения. Должен ли я создать экземпляр контроллера представления и вызвать функцию? - person Mr_Vlasov; 15.02.2017
comment
@Mr_Vlasov Понятно, нет, ты не должен этого делать. Вы должны сделать то, что предложил Nirav D - person Honey; 15.02.2017