2016-08-25 18 views
2

Swiftでは、Web Serice(Googleプレイス)を呼び、GoogleプレイスIDを取得しています。Swift:同期Webサービスコール

私はJSONレスポンスを反復し、Googleの場所のIDを取得していますように、私は別のWebサービス(Googleの場所詳細)以下のコードで

を呼び出すしたいと思い、私が手に私が手にこの応答は次のとおりです。

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 

結果ループのforの結果が終了するまで、関数の詳細が実行されていないようです。

コードを変更して、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サービスをリファクタリングして、電話番号を必要とする複数のID番号のリクエストを送信できるようにします。 – Rob

答えて

0

私はあなたが非同期パターンを採用してお勧めしたいです。例えば、完了ハンドラの成功または失敗の報告、電話番号を非同期に取得する方法があります:

// 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() 
} 

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を使用します。

これは非同期パターンを採用しています。今回は、ディスパッチグループを使用してリクエストがいつ終了するかを識別します。あなたがこの呼び出すとき、あなたは完了ハンドラパターンを使用したい:

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) 
     } 
    } 
} 

だから、次のようになります。それは、この複雑だとき、私は多くの場合、非同期NSOperationサブクラスを使用して、同時実行を制限するキューのmaxConcurrentOperationCountを使用します、

率直に言って
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() 
} 

、しかし、それはこの質問の範囲を超えているようだった。しかし、上記のようなセマフォを使用して並行性を制限することもできます。しかし、結論は、要求を同期的に動作させる方法を理解するのではなく、非同期パターンに従う場合に最高のUXとパフォーマンスを達成することです。

+0

;出来た。私はわずかに異なる実装を使用しましたが、それはあなたの助けなしには可能ではありませんでした。ありがとう! –

1

非同期呼び出しの結果が到着するまで関数呼び出しブロックを作成することは、ディスパッチセマフォを介して達成できます。パターンは次のとおりです。心の中で(このことを指摘してくれてありがとうロブ)を維持するために

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 
} 

一つ重要なことは、あなたが必要だということです。あなたの場合は

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

、次のようにあなたのgetDetails方法を変更することができますに、上記の例の2番目のパラメータという

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)) 
    } 
} 

注:別のキューにgetDetailsを呼び出し、そうでなければ、デッドロックを取得しますするには10はDISPATCH_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

お返事ありがとうございます。私はディスパッチグループ(セマフォではない)を使用することになりますが、あなたの答えでそうすることはできませんでした。あなたは私にグループをディスパッチさせたセマフォを指摘しました。お返事ありがとうございます –

関連する問題