【问题标题】:Finish all asynchronous requests before loading data?在加载数据之前完成所有异步请求?
【发布时间】:2015-09-24 12:34:18
【问题描述】:

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

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 来帮助我处理这个问题:Checking for multiple asynchronous responses from Alamofire and Swift 但我的程序似乎只执行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)
            }
        }
    }
}

【问题讨论】:

    标签: ios swift firebase asynchronous firebase-realtime-database


    【解决方案1】:

    对于在标题中寻求答案的用户,请使用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 {
    
            }
        })
    
    }
    

    上面的代码只是一个大纲,但希望能给你指明正确的方向。

    【讨论】:

    • 嗨,戴夫,感谢您的回复。我想我理解你的逻辑,但不幸的是它似乎不起作用。我通过了第一个异步调用,但第二个异步调用 (getAttendeesPictures) 似乎没有执行,因为当我打印出来时,attendees.count 是 0。这与我上面遇到的问题相同。还有其他建议吗?
    • 嘿,getAttendees 函数返回 - (result, name, objectID) 但您似乎没有使用这些,除了打印到日志。参加者是在组外创建的,但永远不会改变然后你做'attendees.addEntriesFromDictionary(参加者作为[NSObject:AnyObject])',我认为这会添加这个空(新)参加者。您是否打算使用(结果、名称、objectID)信息生成一个与会者对象,然后将其添加到与会者?
    • 是的,没错!这就是我想做的。对不起,我不是很清楚。我想将参加者对象与(名称,objectID)信息一起使用,然后将其添加到参加者中。将所有与会者对象添加到与会者之后,我想将所有这些信息存储到我的事件对象中。关于如何解决这个问题的任何想法?
    • 我已经编辑了答案以尝试进一步帮助您,但我认为您提供的代码存在更多问题,而目前只有 GCD。不幸的是,我不知道您使用的代码库,但提供的函数似乎没有返回您认为应该返回的内容。
    • 嗨,戴夫,非常感谢您的详细回复。我使用 Swift 作为我的代码库,仅供参考。我了解您要传达的内容,但我只是在一方面苦苦挣扎:如何才能得到它,所以只有在填充了数组后我才调用完成处理程序?这是我正在努力的部分。我想这将取决于我使用Firebase 数据库做一些工作吗?
    【解决方案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

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

    希望对你有帮助!

    【讨论】:

    • 您好,感谢您的回复。这是有道理的……我明白你在说什么。我只是想知道,那么根据上面的代码,您是否将event对象的创建放在dispatch_group_notify块中?
    • 如果所有与会者只有一个活动,是的,活动的创建大部分发生在 dispatch_group_notify 闭包内。
    【解决方案3】:

    虽然使用 GCD 及其周围的东西肯定有解决方案,但同步通常很痛苦,而且代码越复杂,它就会开始显示越多的问题 - 但我认为有一个万能的解决方案: Bolts framework 来自 Facebook(均适用于 android na iOS)

    Bolts 框架使用

    那么它有什么神奇之处呢?好吧,它可以让您创建“任务”,然后将它们链接起来。您特别感兴趣的方法是 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 框架文档/自述文件基本上涵盖了有关它的所有知识,而且内容非常广泛,因此我建议您仔细阅读它 - 一旦您掌握了基础知识,它就非常易于使用。我个人正是为此使用它,它是一个爆炸。这个答案有望为您提供不同的解决方案和方法,可能是一种更清洁的方法。

    【讨论】:

    • 感谢您解释 Bolt API!完成所有任务后,如何访问各个任务的结果和错误?该信息是否包含在使用 forCompletionOfAllTask​​s 返回的 BFTask 中?
    【解决方案4】:

    这在概念上有些问题。听起来您想等到这两个功能都完成后再做其他事情,但您没有解释的是getAttendeesPictures 取决于 getAttendees 的结果。这意味着您真正想要执行的操作是执行一个异步块,然后使用第一个异步块的输出执行第二个异步块,然后在两者都完成后执行您的最终完成块。

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

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

    NSHipster 有一篇很棒的文章,我建议你去阅读。它主要是在抽象中讨论,但是您要做的是使用 NSBlockOperation 创建两个块操作,一个用于执行getAttendees,一个用于执行getAttendeesPictures,然后明确表示第二个块依赖于第一个块在将它们都添加到队列之前。然后它们都将执行,您可以在第二个操作上使用完成块来在两者都完成后执行某些操作。

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

    【讨论】:

      【解决方案5】:

      这不是我的想法。这个想法是只有在所有嵌套块都完成时才读取和处理新的异步数据。

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

      【讨论】:

      • 您好,杰伊,非常感谢您的帮助。不幸的是,我尝试了您的方法,但似乎没有用。无论出于何种原因,方法getAttendees 永远不会完成。事实上,似乎程序挂起是因为我有打印语句来表示它是否完成并且两者都没有打印。上面显示了我的尝试,我认为这与您在答案中概述您的想法的方式非常相似。我还包含了getAttendees 函数。还有其他想法吗?那很好啊。谢谢!
      • getAttendees 是否开始执行?此外,将在每个子节点启动时调用一次 ChildAdded 事件,然后在添加任何子节点时执行。在这种情况下,快照永远不会为零,因为它仅在有数据时才会触发,因此应该为每个参加者孩子打印一次“已完成的抓取参加者” - 说“参加者已加载”会更准确,因为它只有一个参加者。快照 != nil 可以被删除。
      • 嗯,有趣的是,即使我知道我添加了孩子,attendeesReference.observeEventType 函数似乎也没有执行。我知道这是因为println("AttendeesReference: \(attendeesReference)") 打印出来了。此外,这对快照很有意义。我也取出了if snapshot != nil 部分,但这似乎并没有太大变化。还有其他想法吗?非常感谢你顺便帮助我
      • 您的示例中有很多不必要的代码和一些令人困惑的项目。我会将整个事情分解成一个超级简单的循环,看看事情会走向何方。利用我发布的概念答案,将每次读取的数据隔离到单独的函数(方法)并保持循环超级干净。在每个子函数中添加打印语句,以便您知道它何时开始和结束。使用异步代码,每个函数都会在准备好后完成并继续执行它的子函数,这就是为什么外部循环需要等待 readyToReadNextAttendee = true 才能读取下一个参与者
      【解决方案6】:

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

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

      希望您(和其他人)发现这对您有所帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-01-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-03-14
        相关资源
        最近更新 更多