Swift/Как использовать dispatch_group с несколькими вызываемыми веб-службами?

Я использую dispatch_group для вызова функций запроса Firebase и уведомляется после завершения запроса, чтобы иметь возможность работать с результатом. В этом случае я просто поставил оператор печати. ​​

func loadStuff() {
    dispatch_group_enter(group)
        myFirebaseFunction() {
             dispatch_group_leave(group)
        }

    dispatch_group_notify(group, dispatch_get_main_queue()) {
        print("done")
    }
}

func myFirebaseFunction(completionHandler: () -> ()) {

    let usersRef = firebase.child("likes")
    usersRef.observeEventType(.Value, withBlock: { snapshot in

             if snapshot.exists() {
                   let sorted = (snapshot.value!.allValues as NSArray).sortedArrayUsingDescriptors([NSSortDescriptor(key: "date",ascending: false)])

                   for item in sorted {

                       dict.append(item as! NSDictionary)
                   }
            }
            completionHandler()
   })   
}

Этот код работает нормально. Проблема заключается в том, что во время выполнения данные будут добавлены в базу данных Firebase. Вот почему я должен использовать observeEventType вместо observeSingleEventOfType.

Это означает, что во время выполнения есть наблюдатель, и в случае добавления данных в базу данных снова будет вызван блок внутри myFirebaseFunction.

Как только это произойдет, приложение выйдет из строя, потому что dispatch_group_leave(group) был вызван без dispatch_group_enter(group). Пока я прав.

dispatch_group_enter(group)
    myFirebaseFunction() {
         dispatch_group_leave(group)      // crash here
    }

Если я изменю его на observeSingleEventOfType, авария не произойдет, но новые добавленные данные в Firebase не будут соблюдаться.

Какая наилучшая практика использования dispatch_group с несколькими веб-службами запуска? Или что мне нужно сделать, чтобы исправить мою проблему? Помощь очень ценится.

PS В настоящее время я использую Swift 2.3, но обновление до Swift 3 запланировано, поэтому было бы очень здорово получить ответ, способный для обоих.

Ответ 1

Проблема

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

Способ 1 - все вызовы в группе

Если вы не беспокоитесь о том, что myFirebaseFunction всегда выполняет свою работу над этой группой отправки, тогда вы можете поместить оба входа и выйти туда, возможно, с помощью beginHandler и completeHandler:

func loadStuff() {
    myFirebaseFunction(beginHandler: {
        dispatch_group_enter(group)
        dispatch_group_notify(group, dispatch_get_main_queue()) {
            print("done")
        }
    }, completionHandler: { dispatch_group_leave(group) })

}

func myFirebaseFunction(beginHandler: () -> (), completionHandler: () -> ()) {        

    let usersRef = firebase.child("likes")
    usersRef.observeEventType(.Value, withBlock: { snapshot in

        beginHandler()
        if snapshot.exists() {
            let sorted = (snapshot.value!.allValues as NSArray).sortedArrayUsingDescriptors([NSSortDescriptor(key: "date",ascending: false)])

            for item in sorted {

                dict.append(item as! NSDictionary)
            }
        }
        completionHandler()
   })   
}

Здесь обработчик завершения по-прежнему будет установлен на dispatch_group_leave на loadStuff, но есть также обработчик начала, который вызовет dispatch_group_enter, а также dispatch_group_notify. Причина, по которой нужно уведомлять, должна быть вызвана в начале, так это то, что мы должны убедиться, что мы уже вошли в группу, прежде чем мы вызываем уведомление, или блок уведомления будет выполняться немедленно, если группа пуста.

Блок, который вы передаете dispatch_group_notify, вызывается только один раз, даже если блоки выполняются в группе после вызова уведомления. Из-за этого может быть безопасным, чтобы каждый автоматический вызов observeEventType выполнялся в группе. Затем в любое время вне этих функций вам нужно дождаться завершения загрузки, вы можете просто вызвать уведомление.

Изменить:. Поскольку уведомление вызывается каждый раз, когда вызывается beginHandler, этот метод фактически приведет к тому, что каждый раз будет вызываться блок уведомлений, поэтому он может быть не идеальным выбором.

Метод 2 - Только первый вызов группы, несколько методов

Если вам действительно нужен только первый вызов observeEventType для использования этой группы, тогда один из вариантов должен иметь две версии myFirebaseFunction: один, как тот, который у вас уже есть, и один с помощью observeSingleEventOfType, Тогда загрузочный материал мог вызывать обе эти функции, передавая dispatch_group_leave как завершение одному из них:

func loadStuff() {
    dispatch_group_enter(group)
        myInitialFirebaseFunction() {
            dispatch_group_leave(group)
        }

    dispatch_group_notify(group, dispatch_get_main_queue()) {
        print("done")
    }

    myFirebaseFunction({})
}

func myInitialFirebaseFunction(completionHandler: () -> ()) {

    let usersRef = firebase.child("likes")
    usersRef.observeSingleEventOfType(.Value, withBlock: { snapshot in
        processSnapshot(snapshot)
        completionHandler()
    })   
}

func myFirebaseFunction(completionHandler: () -> ()) {

    let usersRef = firebase.child("likes")
    usersRef.observeSingleEventOfType(.Value, withBlock: { snapshot in
        processSnapshot(snapshot)
        completionHandler()
    })   
}

func processSnapshot(snapshot: FDataSnapshot) {

    if snapshot.exists() {
        let sorted = (snapshot.value!.allValues as NSArray).sortedArrayUsingDescriptors([NSSortDescriptor(key: "date",ascending: false)])

        for item in sorted {
            dict.append(item as! NSDictionary)
        }
    }
}

Метод 3 - Только первый вызов по группе, без дополнительных методов

Обратите внимание, что поскольку loadStuff в "Методе 2" в основном загружает вещи из Firebase дважды, это может быть не так эффективно, как вам хотелось бы. В этом случае вместо этого вы можете использовать Bool, чтобы определить, должен ли вызываться вызов:

var shouldLeaveGroupOnProcess = false

func loadStuff() {
    dispatch_group_enter(group)
        shouldLeaveGroupOnProcess = true
        myFirebaseFunction() {
            if shouldLeaveGroupOnProcess {
                shouldLeaveGroupOnProcess = false
                dispatch_group_leave(group)
            }
        }

    dispatch_group_notify(group, dispatch_get_main_queue()) {
        print("done")
    }
}

func myFirebaseFunction(completionHandler: () -> ()) {

    let usersRef = firebase.child("likes")
    usersRef.observeEventType(.Value, withBlock: { snapshot in

        if snapshot.exists() {
            let sorted = (snapshot.value!.allValues as NSArray).sortedArrayUsingDescriptors([NSSortDescriptor(key: "date",ascending: false)])

            for item in sorted {
                dict.append(item as! NSDictionary)
            }
        }
        completionHandler()
    })   
}

Здесь, даже если во время начальной загрузки выполняются несколько вызовов observeEventType, leave гарантируется, что он будет вызван только один раз, и не произойдет сбой.

Swift 3

PS В настоящее время я использую Swift 2.3, но обновление до Swift 3 запланировано, поэтому было бы очень здорово получить ответ, способный для обоих.

Диспетчер получил полный пересмотр в Swift 3 (он объектно-ориентированный), поэтому код, который хорошо работает на обоих, на самом деле не вещь:)

Но понятия каждого из трех вышеперечисленных методов одинаковы. В Swift 3:

  • Создайте свою группу с одним из элементов DispatchGroup
  • dispatch_group_enter теперь является методом экземпляра enter в группе
  • dispatch_group_leave теперь является методом экземпляра leave в группе
  • dispatch_group_notify теперь является методом экземпляра notify в группе

Ответ 2

var group : dispatch_group_t?

//в вашей функции init.. ИЛИ... в вашем представленииDidLoad

group = dispatch_group_create()

//добавить функции

func leaveDispatchGroup(){
     dispatch_group_leave(group!)
}

func joinDispatchGroup(){
     dispatch_group_enter(group!)
}

//выполняем операции

func makeAllAPICalls() {

    self.joinDispatchGroup()
    //API Operation

    self.myFirebaseFunction() {
        //leave group on completion
        self.leaveDispatchGroup()
    }

    self.joinDispatchGroup()
    //API Operation
    self.newFirebasedFunction() {

        //leave group on completion
        self.leaveDispatchGroup()
    }


    //this will call once your all operation will leave the group, so add this after join all operations
    dispatch_group_notify(group!, dispatch_get_main_queue()) { () -> Void in
        //dispatch group notify...
        print("done")
    }
}

//ваши функции

func myFirebaseFunction(completionHandler: () -> ()) {
     completionHandler()  
}

func newFirebasedFunction(completionHandler: () -> ()){
     completionHandler()
}