Как вы запускаете код после завершения асинхронного вызова, когда код находится за пределами асинхронной функции в swift3

У меня есть массив, содержащий идентификаторы сообщений, затем я выполняю цикл for, извлекаю содержимое каждого сообщения и добавляю сообщение в массив сообщений. После завершения цикла мне нужно вернуть массив сообщений в обработчике завершения.

Однако .observeSingleEvent является асинхронным методом, поэтому содержимое сообщений извлекается из Firebase, обработчик завершения (сообщений) уже выполняется, что возвращает пустой массив. Я хочу вернуть массив после того, как каждое сообщение будет добавлено к массиву, подобно тому, как эти методы синхронизируются.

static func getPost(postKeys:[String], completionHandler: @escaping ([Post])->()){
    var posts = [Post]()
    for postKey in postKeys{
        let postRef = DataService.ds.REF_POSTS.child(postKey)
        postRef.observeSingleEvent(of: .value, with: { (snapshot) in
            if let postDict = snapshot.value as? Dictionary<String, Any>{
                //create a post and append it to the post array
                let key = snapshot.key
                let post = Post(postKey: key, postData: postDict)
                posts.append(post)
            }
        })
    }
    completionHandler(posts)
}

person Kai    schedule 11.04.2017    source источник
comment
Ах... не уверен, что мне нравится дизайн. В любом случае, для простого исправления, можете ли вы добавить завершениеHandler(posts) внутри цикла for (после posts.append(post)) и запустить его, когда posts.count == postKeys.count??   -  person HMHero    schedule 11.04.2017
comment
Спасибо за ответ, я не уверен, что это правильный дизайн... Я также подумал о создании posts.count, но что, если последний элемент выполняется быстрее, чем второй предыдущий элемент (потому что asyn), тогда предпоследний пост не будет включен в массив   -  person Kai    schedule 11.04.2017


Ответы (1)


Вы можете использовать DispatchGroup для выполнения некоторого кода после выполнения ряда задач.

Вызовите функцию enter перед запуском задачи и функцию leave после завершения задачи. Вы можете использовать notify, чтобы обеспечить закрытие, которое будет выполнено, как только все задачи «покинут» DispatchGroup.

Вы также должны быть осторожны, чтобы избежать проблем параллелизма при обновлении массива; Массивы Swift не являются потокобезопасными. Это можно исправить, выполнив обновление массива в очереди последовательной отправки.

static func getPost(postKeys:[String], completionHandler: @escaping ([Post])->()){
    var posts = [Post]()
    let dispatchGroup = DispatchGroup()
    let arrayQueue = DispatchQueue(label: "arrayQueue")
    for postKey in postKeys{
        dispatchGroup.enter()
        let postRef = DataService.ds.REF_POSTS.child(postKey)
        postRef.observeSingleEvent(of: .value, with: { (snapshot) in
            if let postDict = snapshot.value as? Dictionary<String, Any>{
                //create a post and append it to the post array
                let key = snapshot.key
                let post = Post(postKey: key, postData: postDict)
                arrayQueue.sync {
                    posts.append(post)
                }
            }
            dispatchGroup.leave()
        })
    }

    dispatchGroup.notify(queue: DispatchQueue.main) { 
        completionHandler(posts)
    }
}
person Paulw11    schedule 11.04.2017
comment
Вау, @Paulw11, большое спасибо, я сейчас на улице, попробую, когда вернусь домой, БОЛЬШОЕ СПАСИБО!!! - person Kai; 11.04.2017