2016-08-25 144 views
2

在Swift中,我打电话给Web Serice(Google Places)并成功获取Google Place ID。Swift:同步Web服务调用

因为我迭代通过JSON响应并获得谷歌广场的ID,我想调用另一个Web服务(谷歌Place详细)

与下面的代码,我得到这个反应我得到的是:

estPlace_ID_1 
Return Number Is: Nothing 
estPlace_ID 
Return Number Is: Nothing 
..... 
Function Phone Number is: 867-5309 
Function Phone Number is: 867-5309 

看起来好像该函数得到详细信息直到结果循环结束时才执行。

如何更改代码,使其在继续迭代之前等待getDetails被执行?

class func getDetails(id: String) -> String { 
    <Setup the request> 
    let session = NSURLSession.sharedSession() 

    //Second Request 
    let task = session.dataTaskWithRequest(request) { data, response, error in 
     do { 
      //Parse Result 
      print("Function Phone Number is" + phoneNumber) 
     } 
     catch { 
     } 
    } 
    task.resume() 

    return phoneNumber 
} 

//First request 
<Setup the request> 
let task = session.dataTaskWithRequest(request) { data, response, error in 
    //a few checks with guard statements 

    do {   
     //Extract results from JSON response 
     results = <FROM_JSON> 

     for result in results { 
      estPlace_ID = result["value"] 

      print(estPlace_ID) 
      print("return number is" + getDetails(estPlace_ID))    
     } 
     catch {  
     } 
    } 
    task.resume() 
} 
+1

你不能在任务本身内调用'task.resume()';那不应该编译。 – jtbandes

+0

@jtbandes我认为他的答案有一个错字;提交了一个编辑来移动task.resume –

+0

你会付出巨大的性能损失来按顺序执行它们。允许并发请求并使用调度组在完成后通知自己会更好。或者,更根本地说,通过更显着的性能改进,重构您的Web服务,以允许您向其发送一个您需要电话号码的多个身份号码的请求。 – Rob

回答

0

我建议你采用异步模式。例如,有一个检索电话号码异步,报告与完成处理成功或失败的方法:

let session = NSURLSession.sharedSession() 

func requestPhoneNumber(id: String, completionHandler: (String?) -> Void) { 
    let request = ... 

    let task = session.dataTaskWithRequest(request) { data, response, error in 
     do { 
      let phoneNumber = ... 
      completionHandler(phoneNumber) 
     } 
     catch { 
      completionHandler(nil) 
     } 
    } 
    task.resume() 
} 

那么你的第一个请求,检索所有的地方,都将使用这个异步requestDetails

// I don't know what your place structure would look like, but let's imagine an `id`, 
// some `info`, and a `phoneNumber` (that we'll retrieve asynchronously). 

struct Place { 
    var id: String 
    var placeInfo: String 
    var phoneNumber: String? 

    init(id: String, placeInfo: String) { 
     self.id = id 
     self.placeInfo = placeInfo 
    } 
} 

func retrievePlaces(completionHandler: ([Place]?) -> Void) { 
    let request = ... 

    let task = session.dataTaskWithRequest(request) { data, response, error in 
     // your guard statements 

     do { 
      // Extract results from JSON response (without `phoneNumber`, though 

      var places: [Place] = ... 

      let group = dispatch_group_create() 

      // now let's iterate through, asynchronously updating phone numbers 

      for (index, place) in places.enumerate() { 
       dispatch_group_enter(group) 

       self.requestPhoneNumber(place.id) { phone in 
        if let phone = phone { 
         dispatch_async(dispatch_get_main_queue()) { 
          places[index].phoneNumber = phone 
         } 
        } 
        dispatch_group_leave(group) 
       } 
      } 

      dispatch_group_notify(group, dispatch_get_main_queue()) { 
       completionHandler(places) 
      } 
     } 
    } 
    task.resume() 
} 

这也采用异步模式,这次使用调度组来确定请求何时完成。

retrievePlaces { phoneNumberDictionary in 
    guard phoneNumberDictionary != nil else { ... } 

    // update your model/UI here 
} 

// but not here 

注意,retrievePlaces会发出同时相对于彼此(由于性能原因)这些请求:当你把这个你应该使用完成处理模式。如果你想限制这个,你可以使用一个信号来做到这一点(只要确保在后台队列上,而不是在会话队列上)。其基本模式是:

dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)) { 
    let semaphore = dispatch_semaphore_create(4) // set this to however many you want to run concurrently 

    for request in requests { 
     dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER) 

     performAsynchronousRequest(...) { 
      dispatch_semaphore_signal(semaphore) 
     } 
    } 
} 

所以这可能看起来像:

func retrievePlaces(completionHandler: ([Place]?) -> Void) { 
    let request = ... 

    let task = session.dataTaskWithRequest(request) { data, response, error in 
     // your guard statements 

     do { 
      dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)) { 
       // Extract results from JSON response 
       var places: [Place] = ... 

       let semaphore = dispatch_semaphore_create(4) // use whatever limit you want here; this does max four requests at a time 

       let group = dispatch_group_create() 

       for (index, place) in places.enumerate() { 
        dispatch_group_enter(group) 
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER) 

        self.requestPhoneNumber(place.id) { phone in 
         if let phone = phone { 
          dispatch_async(dispatch_get_main_queue()) { 
           places[index].phoneNumber = phone 
          } 
         } 
         dispatch_semaphore_signal(semaphore) 
         dispatch_group_leave(group) 
        } 
       } 

       dispatch_group_notify(group, dispatch_get_main_queue()) { 
        completionHandler(places) 
       } 
      } 
     } 
    } 
    task.resume() 
} 

坦率地说,当它的这个复杂的,我会经常使用异步NSOperation子类,并使用maxConcurrentOperationCount队列的约束并发,但似乎这超出了这个问题的范围。但是你也可以像上面那样使用信号量来约束并发。但底线是,不是试图弄清楚如何使请求同步运行,如果你遵循异步模式,你将获得最好的UX和性能。

+0

感谢您的回答;有效。我用了一个稍微不同的实现,但如果没有你的帮助,这是不可能的。谢谢! –

1

制作函数调用块直到异步调用的结果可以通过调度信号量来实现。该模式是:

create_semaphore() 
someAyncCall() { 
    signal_semaphore() 
} 
wait_for_semaphore() 
rest_of_the_code() 

在你的情况,你可以修改你getDetails方法如下:

class func getDetails(id: String) -> String { 
    <Setup the request> 
    let session = NSURLSession.sharedSession() 
    let sem = dispatch_semaphore_create(0) 

    //Second Request 
    let task = session.dataTaskWithRequest(request) { data, response, error in 
     do { 
      //Parse Result 
      print("Function Phone Number is" + phoneNumber) 

     } catch { 
     } 
     // the task has completed, signal this 
     dispatch_semaphore_signal(sem) 
    } 
    task.resume() 

    // wait until the semaphore is signaled 
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER) 

    // we won't get here until dispatch_semaphore_signal() is called 
    return phoneNumber 
} 

一名要牢记(感谢罗布指出了这一点),重要的是你需要调用getDetails在不同的队列,否则你会得到一个僵局:

dispatch_async(dispatch_get_global_queue(0, 0)){ 
    for result in results { 
     let estPlace_ID = result["value"] 

     print(estPlace_ID) 
     print("return number is" + getDetails(estPlace_ID)) 
    } 
} 

注意,在上面的例子中的第二个参数dispatch_semaphore_waitDISPATCH_TIME_FOREVER这意味着调用代码将无限期地等待异步调用完成。如果你想设置一些超时您可以创建一个dispatch_time_t值,并将它传递:

// want to wait at most 30 seconds 
let timeout = 30 
let dispatchTimeout = dispatch_time(DISPATCH_TIME_NOW, timeout * Int64(NSEC_PER_SEC)) 
dispatch_semaphore_wait(sem, dispatchTimeout) 
+0

感谢@Rob指出了这一点,我已经更新了答案以避免死锁。 – Cristik

+1

感谢您的回复;我会使用派遣组(而不是信号量),但是在你的答案中不能这样做。你指出了导致我派遣小组的信号量。谢谢 –