且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何让函数A等待一个被调用的函数B完成

更新时间:2023-11-24 11:42:04

您正确地指出它的工作原理当你把这三行放在地理编码器的封闭内。所以,你应该做到这一点:利用这个异步模式,并把所有依赖geocode进程的东西放在闭包中。



回答你的问题,是的,这可以使异步任务以同步方式运行。例如,您可以使用分派组或调度信号量。但是你从 IBAction 中调用它,它在主线程上运行。你永远不想阻止主线程。因此,任何尝试使这个地理编码请求同步从主线程运行是不明智的。



此外,上述过程由 reverseGeocodeLocation :具体来说,闭包自身在主线程上运行,所以如果阻塞主线程等待闭包完成,应用程序将会死锁。因此,上述模式甚至不能与 reverseGeocodeLocation 一起工作(如果从主线程完成)。

底线,您应该拥抱异步模式,并在关闭中保留相关代码。






另外,您的示例很简单一个,你只要把你想要执行的代码放在地理编码器的关闭中。但是如果您想要有两个单独的函数(例如,一个用于返回注释的地理编码)以及另一个用于将注释添加到地图的函数,该怎么办?也许你正在预期类似于:

  func handleLongPress(gesture:UILongPressGestureRecognizer){
if gesture.state ==。开始{
let location = gesture.locationInView(self.mapView)
let annotation = geocodeLocationInMapView(self.mapView,location:location)
addAnnotationToMapView(self.mapView,annotation:annotation)


$ / code>

然后问题是你将如何获得第二个函数, addAnnotationToMapView ,等待第一个, geocodeLocationInMapView 完成。答案再次是你不知道。相反,就像Apple使用它们的异步方法一样,你可以使用完成块模式:

  func handleLongPress(gesture:UILongPressGestureRecognizer) {
如果gesture.state == .Began {
let location = gesture.locationInView(self.mapView)

geocodeLocationInMapView(self.mapView,location:location){annotation,
后卫注释错误!= nil&&错误== nil else {
print(error = \(error))
return
}

self.addAnnotationToMapview(self.mapView,annotation: )
}
}
}

在这种情况下, geocodeLocationInMapView 可能如下所示:

  func geocodeLocationInMapView(mapView:MKMapView ,location:CGPoint,completion:(MKAnnotation ?, NSError?) - >()){
let coordinate = mapView.convertPoint(location,toCoordinateFromView:mapView)
let location = CLLocation(latitude:coordinate .latitude,longitude:coordinate.longitude)

CLGeocoder()。reverseGeocodeLocation(location){地标,地点标记,错误
警戒地标!= nil&& error == nil else {
completion(nil,error)
return
}

如果让placemark = placemarks!.first {
let annotation = MKPointAnnotation()
annotation.coordinate = coordinate
let throughfare = placemark.thoroughfare
let subThoroughfare = placemark.subThoroughfare

switch(thoroughfare,subThoroughfare){
case(nil,nil):
annotation.title =添加\(NSDate())
大小写(_,nil):
annotation.title =大道
默认值:
annotation.title =通途! ++ subThoroughfare!
}

完成(注解,无)
}
}
}

addAnnotationToMapview 可能如下所示:

  func addAnnotationToMapview(mapView:MKMapView,annotation:MKAnnotation){
mapView.addAnnotation(annotation)
mapView.selectAnnotation(annotation,animated:true)

places.append([name:\(annotation.title),lat:\(annotation.coordinate.latitude),lon:\(annotation.coordinate.longitude) ])
}

无可否认,这是一个人为的例子,建议您处理您的 IBAction ,就像我在本答案开始时所描述的那样。但是为了回答更广泛的问题:如何让函数等待,直到函数B完成,您可以使用如此处所述的完成块模式。


The User is on a map view. When doing a long press somewhere on the map the following function gets triggered to set a new annotation inclusively a proper title.

func action(gestureRecognizer: UIGestureRecognizer) {

    if gestureRecognizer.state == UIGestureRecognizerState.Began {

        var newTouch: CGPoint = gestureRecognizer.locationInView(self.mapView)

        var newCoordinate: CLLocationCoordinate2D = mapView.convertPoint(newTouch, toCoordinateFromView: self.mapView)

        var newLocation = CLLocation(latitude: newCoordinate.latitude, longitude: newCoordinate.longitude)

        var newAnnotation = MKPointAnnotation()

        newAnnotation.coordinate = newCoordinate

        CLGeocoder().reverseGeocodeLocation(newLocation, completionHandler: {(placemarks, error) in

            if error != nil { println(error) }

            let p: CLPlacemark = placemarks[0] as CLPlacemark

            var thoroughfare: String? = p.thoroughfare

            var subThoroughfare: String? = p.subThoroughfare

            if p.thoroughfare == nil || p.subThoroughfare == nil {

                var date = NSDate()

                newAnnotation.title = "Added \(date)"

            } else {

                newAnnotation.title = thoroughfare! + " " + subThoroughfare!

            }

        })

        self.mapView.addAnnotation(newAnnotation)

        self.mapView.selectAnnotation(newAnnotation, animated: true)

        places.append(["name":"\(newAnnotation.title)", "lat":"\(newCoordinate.latitude)", "lon":"\(newCoordinate.longitude)"])

    }

}

I know it is working fine when keeping the last three lines of code within the CLGeocoder block (closure?). But if I separate those and list them after the }) (or put some of the code to another thread) I'm facing the problem that its running asynchronous (as I don't understand how to control async vs sync) and by the time the annotation is added to the map and saved to places its title is not set yet by the CLGeocoder. A beginner to programming is asking: What would be necessary to be implemented (disptach_sync...-something) so the last lines of code wait for the CLGeocoder block to finish? I haven't managed to implement this command in the right way yet...

You correctly point out that that it works when you put those three lines within the geocoder's closure. So, you should do precisely that: Leverage this asynchronous pattern and put everything dependent upon the geocode process inside the closure.

In answer to your question, yes, there are patterns which can make an asynchronous task behave in a synchronous manner. For example, you can use dispatch groups or dispatch semaphores. But you are calling this from an IBAction, which runs on the main thread. And you never want to block the main thread. So any attempt to make this geocode request run synchronously from the main thread is ill-advised.

Furthermore, the above process is complicated by a wrinkle of reverseGeocodeLocation: Specifically, the closure, itself, runs on the main thread, so if you block the main thread waiting for the closure to finish, the app will deadlock. So the above patterns won't even work with reverseGeocodeLocation (if done from the main thread).

Bottom line, you should embrace the asynchronous patterns, keeping dependent code inside the closure.


As an aside, your example was a simple one, where you'd just put the code you want to perform inside the geocoder's closure. But what if you wanted to have two separate functions, for example, one for geocoding which returned an annotation, and another function for adding the annotation to the map. Perhaps you were anticipating something like:

func handleLongPress(gesture: UILongPressGestureRecognizer) {
    if gesture.state == .Began {
        let location = gesture.locationInView(self.mapView)
        let annotation = geocodeLocationInMapView(self.mapView, location: location)
        addAnnotationToMapView(self.mapView, annotation: annotation)
    }
}

And then the question would be how would you have the second function, addAnnotationToMapView, wait for the first, geocodeLocationInMapView, to complete. Again, the answer is "you don't." Instead, just like Apple does with their asynchronous methods, you would employ a completion block pattern:

func handleLongPress(gesture: UILongPressGestureRecognizer) {
    if gesture.state == .Began {
        let location = gesture.locationInView(self.mapView)

        geocodeLocationInMapView(self.mapView, location: location) { annotation, error in
            guard annotation != nil && error == nil else {
                print("error = \(error)")
                return
            }

            self.addAnnotationToMapview(self.mapView, annotation: annotation!)
        }
    }
}

In that case, the geocodeLocationInMapView might look like:

func geocodeLocationInMapView(mapView: MKMapView, location: CGPoint, completion: (MKAnnotation?, NSError?) -> ()) {
    let coordinate = mapView.convertPoint(location, toCoordinateFromView: mapView)
    let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)

    CLGeocoder().reverseGeocodeLocation(location) { placemarks, error in
        guard placemarks != nil && error == nil else {
            completion(nil, error)
            return
        }

        if let placemark = placemarks!.first {
            let annotation = MKPointAnnotation()
            annotation.coordinate = coordinate
            let thoroughfare = placemark.thoroughfare
            let subThoroughfare = placemark.subThoroughfare

            switch (thoroughfare, subThoroughfare) {
            case (nil, nil):
                annotation.title = "Added \(NSDate())"
            case (_, nil):
                annotation.title = thoroughfare
            default:
                annotation.title = thoroughfare! + " " + subThoroughfare!
            }

            completion(annotation, nil)
        }
    }
}

And the addAnnotationToMapview might look like:

func addAnnotationToMapview(mapView: MKMapView, annotation: MKAnnotation) {
    mapView.addAnnotation(annotation)
    mapView.selectAnnotation(annotation, animated: true)

    places.append(["name":"\(annotation.title)", "lat":"\(annotation.coordinate.latitude)", "lon":"\(annotation.coordinate.longitude)"])
}

This is, admittedly, a contrived example, and I'd suggest you handle your IBAction like I described at the start of this answer. But in answer to the broader question of "how do I have function A wait until function B is finished", you might employ a completion block pattern as outlined here.