Подтвердить что ты не робот

Завершить все асинхронные запросы перед загрузкой данных?

У меня возникла проблема, когда у меня есть несколько асинхронных запросов, которые захватывают изображения и информацию из API Facebook и моей базы данных 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)
    }
}

Я знаю, что мои обработчики завершения корректно установлены, как я тестировал в своей программе. Тем не менее, я хочу, чтобы только после завершения функций getAttendees и getAttendeesPictures, я хочу сохранить всю информацию, которую я захватил в функции snapshot, getAttendees и 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)
            }
        }
    }
}
4b9b3361

Ответ 1

Для пользователей, которые ищут ответ на вопрос в заголовке, используйте здесь dispatch_group и GCD: i. Встраивание одной группы внутри метода уведомления другого 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 принимает дочерний элемент события и возвращает objectID и Name, которые являются двумя строками? Должен ли этот метод возвращать массив участников? Если нет, то что возвращается objectID?

Как только массив участников будет возвращен, вы можете обработать их в группе, чтобы получить изображения.

getAttendeesPictures в конечном итоге возвращает 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 {

        }
    })

}

Вышеприведенный код является лишь контуром, но, надеюсь, укажет вам в правильном направлении.

Ответ 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 внутри закрытия завершения.

Этот код точно не использует те же самые переменные и методы исходного кода, он дает только идею.

Надеюсь, что это поможет!

Ответ 3

Несмотря на то, что существует определенная проблема с использованием GCD и всего этого, синхронизация в целом - это боль, и чем больше ваш код усложняется, тем больше проблем он начнет показывать, но я думаю, что есть одно решение для всего: Рамки болтов от Facebook (оба для android na iOS)

Использование рамок болтов

Так что же такое волшебство? Ну, это позволяет вам создавать "Задачи", а затем связывать их. Метод, в частности, который вас интересует, - taskForCompletionOfAllTasks:, который предназначен для параллельной обработки, именно то, что вам нужно. Я написал для вас небольшой пример, который вы можете приспособить к вашим потребностям:

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 охватывает в основном все, что нужно знать об этом, и она довольно обширна, поэтому я предлагаю вам пройти через нее - ее очень легко использовать, как только вы получите основы. Я лично использую его именно для этого, и это взрыв. Этот ответ, мы надеемся, предоставит вам различные решения и подход, возможно, более чистый.

Ответ 4

Что-то не так с этим концептуально. Похоже, вы хотите дождаться завершения обеих этих функций, прежде чем делать что-то еще, но то, что вы не объяснили, заключается в том, что getAttendeesPictures зависит от результата getAttendees. Это означает, что вы действительно хотите это сделать, выполнить один асинхронный блок, а затем выполнить второй асинхронный блок с выходом первого, а затем выполнить свой окончательный блок завершения, когда оба завершены.

GCD не подходит для этого; вам лучше использовать NSOperationQueue с NSBlockOperations. Для GCD есть два отличительных преимущества:

  • NSOperation использует знакомый объектно-ориентированный синтаксис по сравнению с функциями G-типа c-type, поэтому его довольно легко написать и понять.
  • Операции в очереди могут иметь явные зависимости друг от друга, поэтому вы можете сделать это ясно, например, операция B будет выполнена только после завершения операции A.

Существует большая запись этого NSHipster, который я бы рекомендовал вам прочитать. В основном речь шла о абстрактном, но то, что вы хотите сделать, это использовать NSBlockOperation для создания двух блочных операций, один для выполнения getAttendees и один для выполнения getAttendeesPictures, а затем сделать явным, что второй блок зависит от первого перед добавлением их в очередь. Затем они будут выполняться, и вы можете использовать блок завершения во второй операции, чтобы сделать что-то, как только они завершатся.

Дэйв Робертс прав в своем ответе: непосредственная проблема с кодом заключается в том, что вы не используете вывод функции getAttendees для фактического создания любых участников. Возможно, эта часть кода отсутствует, но из того, что я вижу, только что напечатаны name и objectID. Если вы хотите передать что-то полезное в функцию getAttendeesPictures, вам нужно будет сначала исправить эту часть.

Ответ 5

Это от головы. Идея состоит в том, чтобы читать и обрабатывать новые данные asyc только тогда, когда все вложенные блоки завершены.

Мы используем цикл while для обработки ожидающего сигнала для чтения следующего набора данных.

Внешний цикл while продолжается до тех пор, пока done равен false. И ничего не происходит, кроме потребления циклов процессора, пока он ждет. Если внутри цикла будет только триггер (установлено значение 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

Ответ 6

Одна идея, которую я использовал, - это установить проверку оператора if внутри оператора запроса запроса и поместить запрос оператора запроса обратно в цикл for (чтобы вы могли выполнить все ваши запросы), поэтому оператор if должен проверять если это ожидалось от последнего вызова, тогда вы должны выполнить оператор return или оператор отложенного .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);
       }
   })
}

Помещение этого кода в функцию и его асинхронное обращение даст вам возможность ждать результатов, прежде чем приступать к другим вещам.

Надеюсь, вы (и другие) найдете это полезным.