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

时间:2015-07-07 06:42:26

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

我遇到了一个问题,我发现了多个异步请求,这些请求从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函数完成后,我才会想要存储我抓取snapshotgetAttendees和{的所有信息。 {1}}函数并将它们存储到getAttendeesPictures对象中。有关如何实现这一目标的任何想法?我试图通过以下链接查看event以帮助我处理此问题:Checking for multiple asynchronous responses from Alamofire and Swift但我的程序似乎只执行dispatch_groups函数而不执行getAttendees函数。以下是getAttendeesPicturesgetAttendees函数:

getAttendeesPictures

6 个答案:

答案 0 :(得分:15)

对于在标题中寻求问题答案的用户,请使用此处概述的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 {

        }
    })

}

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

答案 1 :(得分:4)

这两个请求同时执行,因此没有与会者从第二个请求执行时获取图片,如果要多次调用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

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

希望它有所帮助!

答案 2 :(得分:4)

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

螺栓框架使用

那么它的神奇之处是什么?好吧,它可以让你创建&#34;任务&#34;,然后链接它们。您感兴趣的方法特别是taskForCompletionOfAllTask​​s:,它是为了并行处理而提供的,正是您所需要的。我为你写了一个小例子,你可以根据自己的需要进行调整:

private class MyPagerAdapter extends FragmentPagerAdapter {

    private final FragmentManager mFragmentManager;

    public MyPagerAdapter(android.support.v4.app.FragmentManager fm) {
        super(fm);
        mFragmentManager = fm;
    }
    @Override
    public Fragment getItem(int position) {
        if(position == 0){
            Log.d("check","111");
            return mFragmentAtPos0;
        }
        else if(position == 1){
            return(new InvolveFragment());
        }
        else {
            return(new MeFragment());
        }

    }
    @Override
    public int getCount() {
        return 3;
    }
    @Override
    public int getItemPosition(Object object)
    {
    if (object instanceof NewFragment && mFragmentAtPos0 == hotfrag)
        return POSITION_NONE;
    return POSITION_UNCHANGED;
    }
}

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

HotFrag

Bolts框架文档/ README基本上涵盖了所有关于它的知识,而且它非常广泛,所以我建议你去完成它 - 一旦掌握了基础知识就很容易使用。我个人正是这样使用它,这是一个爆炸。这个答案有望为您提供不同的解决方案和方法,可能更清晰。

答案 3 :(得分:3)

这在概念上存在问题。听起来你想要等到这两个函数完成之后再做其他事情,但你没有解释的是getAttendeesPictures 依赖于getAttendees的结果。这意味着你真正想要它执行一个异步块,然后用第一个异步块的输出执行第二个异步块,然后在两个完成时执行你的最终完成块。

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

  1. 与GCD的c-type函数相比,NSOperation使用熟悉的面向对象语法,因此编写和理解起来非常简单。
  2. 队列中的操作可以彼此明确相关,因此您可以明确说明例如操作B仅在操作A完成后执行。
  3. NSHipster有一篇很好的文章,我建议你去阅读。它主要是在摘要中讨论,但你想要做的是使用NSBlockOperation创建两个块操作,一个用于执行getAttendees,一个用于执行getAttendeesPictures,然后明确表示第二个块在将它们都添加到队列之前依赖于第一个。然后它们都会执行,你可以在第二次操作时使用完成块,一旦完成,就可以做一些事情。

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

答案 4 :(得分:1)

这是我的头脑。我们的想法是只在所有嵌套块完成时才读取和处理新的asyc数据。

我们利用while循环处理等待信号读取下一组数据。

只要完成等于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

答案 5 :(得分: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);
       }
   })
}

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

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

相关问题