2016-05-29 8 views
2

私は現在の非同期ネットワーク要求数を把握し、進行中の要求が1つ以上ある間だけアクティビティインジケータを表示しようとしています。GCD - 総数または非同期タスクのトラッキング

fatal error: unexpectedly found nil while unwrapping an Optional value 

私が原因ときそこにこの問題が発生したことを考える:私は時折dispatch_group_leave(taskGroup)ライン上でクラッシュを取得するので、私は派遣・グループを使用していますが、私はdispatch_group_notifyのブロックと、私のclosureブロック間の競合状態を持っていると思いますディスパッチグループ内の項目がなくなったため、新しいグループが作成されるのではなく、それ以降のリクエストで使用される前に時間外に解放されないことがあります(nilに設定)。その後、グループはすぐに空であることを通知し、コールバッククロージャが呼び出され、nilに設定されますが、今すぐnilグループから離れることを試みる余分な項目があります。


私は解決策が最後dispatch_group_leaveがすなわちcallback閉鎖する前に、triggedされた直後にdispatch_group_leaveはそのブロックを発射を確認することにあると思います。

別のdispatch_syncクロージャでdispatch_group_leavecallbackのコードをラップし、カスタムシリアルキューに追加しようとしましたが、問題はすべての実行の50%以上にとどまりました。

メインキューのdispatch_async(後述のコードを参照)のクロージャーコールをラップすると役立ちますが、この問題はすべての実行の約10%に残ります。

Creating new dispatch group 
Using existing dispatch group 
Task fired with [2] second delay. 
Task fired with [2] second delay. 
Task with [2] second delay finished! 
All done! 
Task with [2] second delay finished! 
Creating new dispatch group 
Task fired with [2] second delay. 
All done! 
Task with [2] second delay finished! 

そして、私はクラッシュを取得し、他の回:

Creating new dispatch group 
Using existing dispatch group 
Task fired with [2] second delay. 
Task fired with [2] second delay. 
Task with [2] second delay finished! 
Using existing dispatch group 
Task fired with [2] second delay. 
All done! 
Task with [2] second delay finished! 
fatal error: unexpectedly found nil while unwrapping an Optional value 
私は時々、良い結果を得る

import UIKit 
import XCPlayground 

// Allow for asynchronous execution to take as long as it likes 
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true 

// Background container view 
let view = UIView(frame: CGRectMake(0, 0, 100, 100)) 
view.backgroundColor = UIColor.blackColor() 
XCPlaygroundPage.currentPage.liveView = view 

// Our activity indicator 
let activityIndicator = UIActivityIndicatorView() 
view.addSubview(activityIndicator) 
activityIndicator.center = view.center 

// Used to keep track of the number of current tasks 
var taskGroup: dispatch_group_t! 

// An async task that calls its callback after 2 to 5 seconds 
func fireATask(callback: String -> Void) { 

    if taskGroup == nil { 
     print("Creating new dispatch group") 
     taskGroup = dispatch_group_create() 
     dispatch_group_enter(taskGroup) 
     activityIndicator.startAnimating() 
     dispatch_group_notify(taskGroup, dispatch_get_main_queue()) { 
      activityIndicator.stopAnimating() 
      taskGroup = nil 
      print("All done!") 
     } 
    } else { 
     print("Using existing dispatch group") 
     dispatch_group_enter(taskGroup) 
    } 

    dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) { 

     let delay = arc4random_uniform(1) + 2 
     print("Task fired with [\(delay)] second delay.") 
     let delayNanoseconds = Int64(UInt64(delay) * NSEC_PER_SEC) 
     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delayNanoseconds), dispatch_get_main_queue()) { 

      dispatch_group_leave(taskGroup) // Sometimes crashing here because taskGroup is nil 

      dispatch_async(dispatch_get_main_queue()) { // My attempt to make sure dispatch_group_notify is called before the callback 
       callback("Task with [\(delay)] second delay finished!") 
      } 
     } 
    } 
} 

:ここ

は私のコード(テストするための遊び場にコピーペースト)です

+0

1.なぜあなたはdispatch_group_notifyは、コールバックの前に呼び出されているかどうかを気にしていますか? 2. fireATask関数がdispatch_groupの作成を担当するのはなぜですか?永続グループを1つだけ使用できますか? – jtbandes

+0

2)ディスパッチグループはAFAIKが一度しか使えないので、一度起動するとnilに設定して再作成します。 1)dispatch_group_notifyは最初に呼び出す必要があります。なぜなら、前回グループを離れるときには、グループを再作成してから、他のfireATask呼び出しを呼び出すコールバックを呼び出す必要があるからです(現在のグループに入ります) 。 – paulvs

+0

2)に追加するには、このコードを使用して、アプリ内のどこからでも通話できるように、ネットワークアクティビティインジケータを一度に中央の場所で処理します。fireTaskリクエストを起動している間は、インジケータが表示されるはずですが、すべてのリクエストが終了し、それ以上は追加されていない場合、アクティビティインジケータは消えてしまいます。 – paulvs

答えて

2

dispatch_group_notifyは、グループが空のときにキューに送信されるブロックオブジェクトをスケジュールします。したがって、2番目の例ではクラッシュします。異なるスレッドからの印刷を非同期に呼び出すため、ログ内のメッセージが順序どおりに表示されません。ここでは実際の状況です:

Creating new dispatch group 
Using existing dispatch group 
Task fired with [2] second delay. 
Task fired with [2] second delay. 
Task with [2] second delay finished! 
Task with [2] second delay finished! 
All done! 

Using existing dispatch group 
Task fired with [2] second delay. 
Task with [2] second delay finished! 
fatal error: unexpectedly found nil while unwrapping an Optional value 

だけdispatch_sync(dispatch_get_main_queue())ですべてprintの呼び出しをラップし、あなたが似た何かを得るでしょう。

ここで私は問題を解決したい方法:

// The execution queue 
var tasksQueue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0) 
var nTasks = 0 

// An async task that calls its callback after 2 to 5 seconds 
func fireATask(callback: String -> Void) { 
    dispatch_async(tasksQueue) { 

     dispatch_sync(dispatch_get_main_queue()) { 
      nTasks += 1 
      activityIndicator.startAnimating() 
     } 

     let delay = arc4random_uniform(1) + 2 
     print("Task fired with [\(delay)] second delay.") 
     let delayNanoseconds = Int64(UInt64(delay) * NSEC_PER_SEC) 
     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delayNanoseconds), dispatch_get_main_queue()) { 
      callback("Task with [\(delay)] second delay finished!") 
      nTasks -= 1 
      if nTasks == 0 { 
       activityIndicator.stopAnimating() 
       print("All done!") 
      } 
     } 
    } 
} 
+0

私はこれが問題の素晴らしい解決策だと思っています、ありがとう!私はまだ、しかし、私のコード(ディスパッチグループを使用して)を修正する方法を理解していない。ところで、印刷メッセージの 'dispatch_sync'ではなく' dispatch_async'を意味しましたか? – paulvs

+0

ここにGCDグループは必要ありません。 'dispatch_group_notify'は一度呼び出されるコールバックを設定します。あなたのケースでは、タスク間にギャップがある場合、時々空のキューがあるかもしれません。 'dispatch_group_notify'、' dispatch_async'& 'dispatch_after'の非同期性のために、ログメッセージが順不同で表示されることを考慮してください。 – sgl0v

+0

ここでは、ディスパッチグループが最後の 'dispatch_group_leave'を受け取った後(すなわち、' dispatch_group_leave'の数が 'dispatch_group_enter'sの数と等しい)、' dispatch_group_notify'はそのハンドラを同期的に呼び出さないということです。直ちに)、それは任意の時間の後に非同期的にそれを呼び出します。そして、私はこの振る舞いを変える方法がないことを発見しました。 – paulvs