在加载数据之前完成所有异步请求?

Finish all asynchronous requests before loading data?

我 运行 遇到一个问题,我有多个异步请求从 Facebook API 和我的 Firebase 数据库中获取图像和信息。我想执行我所有的异步请求,然后将我从 Facebook API/Firebase 数据库中获取的所有数据存储到一个我可以快速加载的完整对象中。我已经为每个异步请求设置了完成处理程序,我认为这会强制程序 "wait" 直到请求完成,然后让程序继续,但这似乎对我不起作用。以下是我的尝试:

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

我知道我已经正确设置了我的完成处理程序,因为我已经在我的程序中进行了测试。但是,我想要的是,只有在 getAttendeesgetAttendeesPictures 函数都完成后,我才想存储我抓取的所有信息 snapshotgetAttendeesgetAttendeesPictures 函数并将它们存储到 event 对象中。关于如何实现这一目标的任何想法?我试图研究 dispatch_groups 来帮助我通过这个 link 来处理这个问题: 但我的程序似乎只执行 getAttendees 函数而不是 getAttendeesPictures 功能。下面还有 getAttendeesgetAttendeesPictures 函数:

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

这超出了我的想象。这个想法是仅当所有嵌套块完成时才读取和处理新的异步数据。

我们利用 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 才读取下一个索引

对于寻求标题中问题答案的用户,请使用 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 函数接受一个事件 child 和 returns 一个 objectIDName 这两个都是字符串?这种方法 return 不应该是一组与会者吗?如果不是,那么 returned 的 objectID 是什么?

return编辑一组与会者后,您可以将他们分组处理以获取照片。

getAttendeesPictures 最终 returns UIImages 来自 Facebook。最好将这些缓存到磁盘并传递 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 {

        }
    })

}

以上代码只是一个大纲,但希望能为您指明正确的方向。

这两个请求是同时执行的,所以第二个请求执行的时候没有参与者可以获取图片,如果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

这段代码并没有完全使用与原始代码相同的变量和方法,它只是给出了思路。

希望对您有所帮助!

这在概念上有问题。听起来您想等到这两个函数都完成后再做其他事情,但是您没有解释的是 getAttendeesPictures 取决于 [=11] 的结果=].这意味着你真正想要做的是执行一个异步块,然后使用第一个异步块的输出执行第二个异步块,然后在两个都完成时执行最后一个完成块。

GCD 不是特别适合这个;你最好将 NSOperationQueue 与 NSBlockOperations 一起使用。与 GCD 相比,它有两个明显的优势:

  1. 与 GCD 的 c 类型函数相比,NSOperation 使用熟悉的面向对象语法,因此非常易于编写和理解。
  2. 队列中的操作之间可以有明确的依赖关系,因此您可以明确表示,例如操作B只会在操作A完成后执行。

NSHipster 写了一篇很棒的文章,我建议您去读一读。讲的大多是抽象的,但是你要做的是用NSBlockOperation创建两个块操作,一个执行getAttendees,一个执行getAttendeesPictures,然后明确表示第二个块在将它们都添加到队列之前取决于第一个。然后它们都将执行,您可以在第二个操作上使用完成块来在两者都完成后做一些事情。

Dave Roberts 的回答是正确的:代码的一个直接问题是您没有使用 getAttendees 函数的输出来实际创建任何与会者。也许这部分代码丢失了,但据我所见,nameobjectID 只是打印出来了。如果您想将有用的东西传递给 getAttendeesPictures 函数,您需要先修复这部分。

虽然使用 GCD 和相关的东西肯定有解决方案,但同步通常是痛苦的,你的代码越复杂,它开始显示的问题就越多 - 但我认为有一个一劳永逸的解决方案为此:来自 Facebook 的 Bolts framework(均用于 android na iOS)

Bolts 框架使用

那么它有什么神奇之处呢?好吧,它可以让您创建 "Tasks",然后将它们链接起来。您特别感兴趣的方法是 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)
}

完成主要方法后,您可以使用螺栓链接魔法:

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 基本上涵盖了所有关于它的知识,而且内容非常广泛,所以我建议你通读一遍——一旦你掌握了基础知识,它就非常容易使用。我个人正是为此而使用它,这真是太棒了。这个答案有望为您提供不同的解决方案和方法,可能是更清洁的解决方案和方法。

我使用的一个想法是在查询语句回调中放置一个 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);
       }
   })
}

将此代码放入一个函数中并异步调用它将使您能够在继续执行其他操作之前等待整个结果。

希望您(和其他人)发现这对您有益。