web-dev-qa-db-ja.com

データをロードする前にすべての非同期リクエストを終了しますか?

FacebookAPIとFirebaseデータベースから画像と情報を取得する複数の非同期リクエストが発生するという問題が発生しました。すべての非同期リクエストを実行してから、Facebook API/Firebaseデータベースから取得したすべてのデータを1つのオブジェクト全体に保存して、すばやく読み込むことができます。非同期リクエストごとに完了ハンドラーを設定しました。リクエストが完了するまでプログラムを「待機」させてからプログラムを続行するように強制しましたが、それはうまくいかないようです。以下は私の試みです:

func setupEvents(completion: (result: Bool, Event: Event) -> Void){
    // Get a reference to Events
    eventsReference = Firebase(url:"<DB Name>")
    eventAttendeesRef = Firebase(url:"<DB Name>")

    //Read the data at our posts reference
    println("Event References: \(eventsReference)")
    eventsReference.observeEventType(FEventType.ChildAdded, withBlock: { (snapshot) -> Void in

        let eventName = snapshot.value["eventName"] as? String
        let eventLocation = snapshot.value["eventLocation"] as? String
        let eventCreator = snapshot.value["eventCreator"] as? String

        var attendees: NSMutableDictionary = [:]
        var attendeesImages = [UIImage]()
        let attendee: NSMutableDictionary = [:]

        let group = dispatch_group_create()

        //Get attendees first
        dispatch_group_enter(group)
        self.getAttendees(snapshot.key as String, completion:{ (result, name, objectID) -> Void in
            if(result == true){
                println("Finished grabbing \(name!) \(objectID!)")
                attendees.addEntriesFromDictionary(attendee as [NSObject : AnyObject])
            }
            else {
                println("False")
            }
            dispatch_group_leave(group)
        })

        //Get attendees photos
        dispatch_group_enter(group)
        self.getAttendeesPictures(attendee, completion: { (result, image) -> Void in
            if result == true {
                println("Finished getting attendee photos. Now to store into Event object.")
                attendeesImages.append(image!)
            }
            else{
                println("false")
            }
            dispatch_group_leave(group)
        })

        dispatch_group_notify(group, dispatch_get_main_queue()) {
            println("both requests done")
            //Maintain array snapshot keys
            self.eventIDs.append(snapshot.key)

            if snapshot != nil {
                let event = Event(eventName: eventName, eventLocation:eventLocation, eventPhoto:eventPhoto, fromDate:fromDate, fromTime:fromTime, toDate:toDate, toTime:toTime, attendees: attendees, attendeesImages:attendeesImages, attendeesImagesTest: attendeesImagesTest, privacy:privacy, eventCreator: eventCreator, eventCreatorID: eventCreatorID)
                println("Event: \(event)")
                completion(result: true, Event: event)
            }
        }

        }) { (error) -> Void in
            println(error.description)
    }
}

プログラムでテストしたので、完了ハンドラーが正しく設定されていることはわかっています。ただし、必要なのは、getAttendees関数とgetAttendeesPictures関数の両方が完了した後でのみ、snapshotgetAttendees、およびgetAttendeesPictures関数を取得したすべての情報を格納し、それらをeventオブジェクトに格納することです。これを達成する方法についてのアイデアはありますか?このリンクを介してこれを処理するためにdispatch_groupsを調べようとしました: AlamofireとSwiftからの複数の非同期応答をチェックしています しかし、私のプログラムはgetAttendees関数のみを実行しているようですが、 getAttendeesPictures関数。以下は、getAttendees関数とgetAttendeesPictures関数も示しています。

func getAttendees(child: String, completion: (result: Bool, name: String?, objectID: String?) -> Void){
    //Get event attendees of particular event
    var attendeesReference = self.eventAttendeesRef.childByAppendingPath(child)
    println("Loading event attendees")
    //Get all event attendees
    attendeesReference.observeEventType(FEventType.ChildAdded, withBlock: { (snapshot) -> Void in
        let name = snapshot.value.objectForKey("name") as? String
        let objectID = snapshot.value.objectForKey("objectID") as? String
        println("Name: \(name) Object ID: \(objectID)")
        completion(result: true, name: name, objectID: objectID)
        }) { (error) -> Void in
            println(error.description)
    }

 func getAttendeesPictures(attendees: NSMutableDictionary, completion: (result: Bool, image: UIImage?)-> Void){
    println("Attendees Count: \(attendees.count)")
    for (key, value) in attendees{
        let url = NSURL(string: "https://graph.facebook.com/\(key)/picture?type=large")
        println("URL: \(url)")
        let urlRequest = NSURLRequest(URL: url!)
        //Asynchronous request to display image
        NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue()) { (response:NSURLResponse!, data:NSData!, error:NSError!) -> Void in
            if error != nil{
                println("Error: \(error)")
            }
            // Display the image
            let image = UIImage(data: data)
            if(image != nil){
                completion(result: true, image: image)
            }
        }
    }
}
18
user1871869

タイトルで質問への回答を求めているユーザーの場合、ここで概説されているdispatch_groupとGCDを使用します。つまり、あるグループを別のdispatch_groupの通知メソッドに埋め込むことが有効です。より高いレベルに進む別の方法は、NSOperationsと依存関係であり、これにより、操作のキャンセルなどのさらなる制御も可能になります。

概要

func doStuffonObjectsProcessAndComplete(arrayOfObjectsToProcess: Array) -> Void){

    let firstGroup = dispatch_group_create()

    for object in arrayOfObjectsToProcess {

        dispatch_group_enter(firstGroup)

        doStuffToObject(object, completion:{ (success) in
            if(success){
                // doing stuff success
            }
            else {
                // doing stuff fail
            }
            // regardless, we leave the group letting GCD know we finished this bit of work
            dispatch_group_leave(firstGroup)
        })
    }

    // called once all code blocks entered into group have left
    dispatch_group_notify(firstGroup, dispatch_get_main_queue()) {

        let processGroup = dispatch_group_create()

        for object in arrayOfObjectsToProcess {

            dispatch_group_enter(processGroup)

            processObject(object, completion:{ (success) in
                if(success){
                    // processing stuff success
                }
                else {
                    // processing stuff fail
                }
                // regardless, we leave the group letting GCD know we finished this bit of work
                dispatch_group_leave(processGroup)
            })
        }

        dispatch_group_notify(processGroup, dispatch_get_main_queue()) {
            print("All Done and Processed, so load data now")
        }
    }
}

この回答の残りの部分はこのコードベースに固有です。

ここにはいくつかの問題があるようです。getAttendees関数はイベントの子を受け取り、両方とも文字列であるobjectIDNameを返しますか?このメソッドは参加者の配列を返すべきではありませんか?そうでない場合、返されるobjectIDは何ですか?

一連の参加者が戻ってきたら、グループで処理して写真を取得できます。

getAttendeesPicturesは最終的にFacebookからUIImagesを返します。これらをディスクにキャッシュしてpath refを渡すのがおそらく最善です。これらのフェッチされたすべての画像を保持することはメモリに悪影響を及ぼし、サイズと数によってはすぐに問題が発生する可能性があります。

いくつかの例:

func getAttendees(child: String, completion: (result: Bool, attendees: Array?) -> Void){

    let newArrayOfAttendees = []()

    // Get event attendees of particular event

    // process attendees and package into an Array (or Dictionary)

    // completion
    completion(true, attendees: newArrayOfAttendees)
}

func getAttendeesPictures(attendees: Array, completion: (result: Bool, attendees: Array)-> Void){

    println("Attendees Count: \(attendees.count)")

    let picturesGroup = dispatch_group_create()

    for attendee in attendees{

       // for each attendee enter group
       dispatch_group_enter(picturesGroup)

       let key = attendee.objectID

       let url = NSURL(string: "https://graph.facebook.com/\(key)/picture?type=large")

        let urlRequest = NSURLRequest(URL: url!)

        //Asynchronous request to display image
        NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue()) { (response:NSURLResponse!, data:NSData!, error:NSError!) -> Void in
            if error != nil{
                println("Error: \(error)")
            }

            // Display the image
            let image = UIImage(data: data)
            if(image != nil){
               attendee.image = image
            }

            dispatch_group_leave(picturesGroup)
        }
    }

    dispatch_group_notify(picturesGroup, dispatch_get_main_queue()) {
         completion(true, attendees: attendees)
    }
}

func setupEvents(completion: (result: Bool, Event: Event) -> Void){

    // get event info and then for each event...

    getAttendees(child:snapshot.key, completion: { (result, attendeesReturned) in
        if result {
            self.getAttendeesPictures(attendees: attendeesReturned,         completion: { (result, attendees) in

              // do something with completed array and attendees


            }
        }
        else {

        }
    })

}

上記のコードは単なる概要ですが、うまくいけば正しい方向を示しています。

15
Dave Roberts

GCDとその周辺のものを使用することで確実に解決策がありますが、一般的に同期は苦痛であり、コードが複雑になるほど、より多くの問題が表示され始めます-しかし、それに対する1対1の解決策があると思います:- ボルトフレームワーク Facebookから(両方ともAndroid na iOS)

ボルトフレームワークの使用法

それで、それについてとても魔法のようなものは何ですか?それでは、「タスク」を作成して、それらをチェーンすることができます。特に関心のあるメソッドはtaskForCompletionOfAllTask​​s:です。これは、必要なものだけを並列処理するために作成されています。私はあなたがあなたのニーズに合わせることができるあなたのために小さな例を書きました:

func fetchAllInformation() -> BFTask {

    // First, create all tasks (if you need more, than just create more, it is as easy as that
    var task1 = BFTaskCompletionSource()
    var task2 = BFTaskCompletionSource()
    var tasks = [task1, task2]

    // What you do, is you set result / error to tasks and the propagate in the chain upwards (it is either result, or error)
    // You run task 1 in background
    API.instance.fetchFirstDetailsInBackgroundWithBlock {
        (object: AnyObject!, error: NSError!) -> Void in

        // On error or on success, you assign result to task (whatever you want)
        if error == nil {
            task1.setResult(object)
        } else {
            task1.setError(error)
        }
    }

    // You run task 2 in background
    API.instance.fetchSecondDetailsInBackgroundWithBlock {
        (object: AnyObject!, error: NSError!) -> Void in

        // On error or on success, you assign result to task (whatever you want)
        if error == nil {
            task2.setResult(object)
        } else {
            task2.setError(error)
        }
    }

    // Now you return new task, which will continue ONLY if all the tasks ended
    return BFTask(forCompletionOfAllTasks: tasks)
}

Mainメソッドが完了したら、ボルトチェーンマジックを使用できます。

func processFullObject() {

    // Once you have main method done, you can use bolts chaining magic
    self.fetchAllInformation().continueWithBlock { (task : BFTask!) -> AnyObject! in

        // All the information fetched, do something with result and probably with information along the way
        self.updateObject()
    }
}

Boltsフレームワークのドキュメント/ READMEは、基本的にそれについて知っておくべきことすべてをカバーしており、非常に広範囲にわたるので、それを確認することをお勧めします-一度取得すると非常に使いやすいです基本。私は個人的にこれを正確に使用していますが、これはすばらしいことです。この回答が、さまざまな解決策とアプローチ、おそらくよりクリーンなものを提供することを願っています。

4
Jiri Trecak

2つのリクエストは同時に実行されているため、2番目のリクエストが実行されたときから写真を取得する参加者はありません。getAttendees完了クロージャが複数回呼び出される場合は、次のように実行できます。

let group = dispatch_group_create()

for key in keys {
   dispatch_group_enter(group)
   self.getAttendee(key as String, completion:{ (result, attendee) in
      if(result == true){
         attendees.addEntriesFromDictionary(attendee)
         self.getAttendeesPictures(attendee, completion: { (result, image) in
           if result == true {
              attendeesImages.append(image!)
           }
           dispatch_group_leave(group)
         })
      } else {
         dispatch_group_leave(group)
      }            
   })
}

dispatch_group_notify(group, dispatch_get_main_queue()) {}

最初のリクエストの結果が、GCDを使用する必要さえない参加者の完全なセットである場合は、完了クロージャー内でgetAttendeesPicturesを呼び出すだけです。

このコードは、元のコードとまったく同じ変数とメソッドを使用しているわけではなく、アイデアを提供するだけです。

それが役に立てば幸い!

4
juanjo

これには概念的に何か問題があります。これらの関数の両方が完了するまで待ってから何か他のことをしたいようですが、説明していないのは、getAttendeesPicturesdependsgetAttendeesの結果について。つまり、本当にやりたいことは、1つの非同期ブロックを実行し、最初の出力で2番目の非同期ブロックを実行し、両方が終了したら最後の完了ブロックを実行することを意味します。

GCDはこれには特に適していません。 NSBlockOperationsでNSOperationQueueを使用することをお勧めします。これには、GCDに比べて2つの明確な利点があります。

  1. NSOperationは、GCDのc型関数と比較して、使い慣れたオブジェクト指向構文を使用しているため、記述と理解が非常に簡単です。
  2. キュー内の操作は相互に明示的な依存関係を持つ可能性があるため、次のことを明確にすることができます。操作Bは、操作Aが完了した後にのみ実行されます。

NSHipster によるこれの素晴らしい記事があります。これを読んでおくことをお勧めします。これは主に要約で説明されていますが、NSBlockOperationを使用して2つのブロック操作を作成します。1つはgetAttendeesを実行するためのもので、もう1つはgetAttendeesPicturesを実行するためのものです。 2番目のブロックは、両方をキューに追加する前に、最初のブロックに依存します。その後、両方が実行され、2番目の操作で完了ブロックを使用して、両方が完了したら何かを実行できます。

ただし、Dave Robertsは彼の応答に正解です。コードの当面の問題は、getAttendees関数の出力を使用して実際に参加者を作成しないことです。コードのこの部分が欠落している可能性がありますが、私が見ることができるものから、nameobjectIDが出力されているだけです。何か便利なものをgetAttendeesPictures関数に渡したい場合は、最初にこの部分を修正する必要があります。

3
quantumkid

私が使用したアイデアの1つは、クエリステートメントのコールバック内にifステートメントチェックを配置し、クエリステートメントのコールバックをforループに配置することです(すべてのクエリをループできるようにするため)。したがって、ifステートメントはこれが最後のコールバックが必要な場合は、returnステートメントまたはdeferred.resolveステートメントを実行する必要があります。以下はコンセプトコードです。

var list=fooKeys //list of keys (requests) i want to fetch form firebase
var array=[]  // This is the array that will hold the result of all requests 
for(i=xyz;loop breaking condition; i++){
    Ref = new Firebase("https://yourlink.firebaseio.com/foo/" + fooKeys[i]);
   Ref.once("value", function (data) {
       array.Push(data.val());
       if(loop breaking condition == true){
           //This mean that we looped over all items
           return array;  //or deferred.resolve(array);
       }
   })
}

このコードを関数に入れて非同期で呼び出すと、結果全体を待ってから他の作業に進むことができます。

あなた(そして他の人たち)がこれが有益だと思うことを願っています。

1
Khaldoun Khaled

これは私の頭から離れています。ネストされたブロックがすべて完了した場合にのみ、新しいasycデータを読み取って処理するという考え方です。

Whileループを利用して、シグナルが次のデータセットを読み取るのを待機します。

Doneがfalseに等しい限り、外側のwhileループが続きます。そして、待機中にCPUサイクルを消費する以外は、実際には何も起こっていません。ループ内のifは、すべての参加者が読み取られたときにのみトリガーされます(trueに設定されます)。

一方、ループ内では、ネストされたブロックを処理し、出席者を読み取り、それが完了したら画像を読み取り、完了したらFirebaseデータを読み取ります。最後に、前のブロックのすべてのデータを取得したら、データをオブジェクトに詰め込み、それを辞書に追加します。その時点で、出席者の読み終えたかどうかが判断され、終わった場合は完全に保釈されます。そうでない場合は、次の参加者を読みます。

(これは概念的なものです)

done = false
readyToReadNextAttendee = true
      while ( done == false )
      {
        if (readyToReadNextAttendee == true ) {
          readyToReadNextAttendee = false
          readAttendee
           readPicture
            readFirebase {
              putDataIntoObject
              addObjectToDictionary
              if finishedReadingAttendees {
                 done = true
              } else {
                 readyToReadNextAttendee = true
              }
            }
        }
      }

最初にすべての参加者を読み込むオプションがある場合は、readyToReadNextAttendee = trueになるまで次のインデックスを読み込まずに、繰り返して配列することもできます。

1
Jay