быстро: асинхронный + JSON + завершение + DispatchGroup

Код в моем классе viewcontroller выполняется до того, как будет готов процесс загрузки JSON, даже если в функции есть обработчик завершения для загрузки JSON и DispatchGroup (). Я храню данные JSON в массиве под названием «fetchedModules», и в данном случае он заполняется 11 элементами. Почему это происходит?

result in console:
---> in Class PostCell - func numberOfSections: 0
JSON call finished 

// ViewController
override func viewDidLoad() {
    super.viewDidLoad()

    let group = DispatchGroup()

    group.enter()

    self.fetchJSON()
    // here calling downloadJSONasync

    group.leave()

    group.notify(queue: .main)  {
        print("JSON call finished")
    }

    ...

    // networkService with completion
    func downloadJSONasync(searchItem: String, completion: @escaping ([NSDictionary]) -> Void) {

    //request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringLocalCacheData

    request.httpMethod = "GET"

    let configuration = URLSessionConfiguration.default
    //let session = URLSession(configuration: configuration, delegate: nil)

    let session = URLSession(configuration: configuration)

    let task = session.dataTask(with: request, completionHandler: {(data, response, error) in

        guard let data = data, error == nil else { return }

        if (error != nil) {
            print("error!")
        }

        else{

            do {

                let fetchedData = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as! [NSDictionary]

                completion(fetchedData)

            }

            catch {
                print("error")
            }

        }

    })

    task.resume()

}

// call in viewController
override func numberOfSections(in tableView: UITableView) -> Int {

    print("---> in Class PostCell - func numberOfSections: \(String(describing: fetchedModules.count))")
    return fetchedModules.count

// code of fetchJSON
func fetchJSON()
{

    let baseurl = AppConstants.Domains.baseurl // https://m.myapp2go.de

    let compositURL = baseurl + "getmodules_noItems.php?id=\(AppConstants.appString.startString)"

    let encodedUrl : String! = compositURL.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) // remove the spaces in the url string for safty reason

    let JSONurl = URL(string: encodedUrl)! // convert the string into url

    var JSONrequest = URLRequest(url: JSONurl) // make request

    JSONrequest.httpMethod = "GET"

    //JSONrequest.cachePolicy = .reloadIgnoringCacheData

    let networkService = NetworkService(request: JSONrequest)

    networkService.downloadJSONasync(searchItem: AppConstants.appString.startString, completion: { (fetchedData) in

        fetchedModules.removeAll()

        DispatchQueue.main.async {

            for eachFetchedModul in fetchedData {

                let eachModul = eachFetchedModul

                if

                    let custid    = eachModul["custid"]    as? String,
                    let modulcat  = eachModul["modulcat"]  as? String,
                    let modulname = eachModul["modulname"] as? String,
                    let comment   = eachModul["comment"]   as? String

                {

                    fetchedModules.append(CModules(custid: custid, modulcat: modulcat, modulname: modulname, comment: comment))

                    print(custid)
                    print(modulcat)
                    print(modulname)
                    print(comment)

                    print("---------------------")
                }

            }// for end

            // ... somehow set data source array for your table view
            self.tableView.reloadData()
        }// dispatch



    }


)} // func end

person Ralf Bordé    schedule 12.02.2019    source источник


Ответы (2)


Поскольку fetchJSON возвращается немедленно, до загрузки JSON. Эффект состоит в том, что DispatchGroup вводится и сразу же уходит, не дожидаясь JSON:

group.enter()
self.fetchJSON() // returns immediately
group.leave()    // the JSON has yet to be downloaded

Чтобы дождаться прибытия JSON, добавьте обработчик завершения в fetchJSON:

override func viewDidLoad() {
    group.enter()
    self.fetchJSON { 
        group.notify(queue: .main)  {
            print("JSON call finished")
        }
        group.leave()
    }
}

// Change the data type of the completion handler accordingly
func fetchJSON(completionHandler: @escaping (Data?) -> Void) {
    // ...
    networkService.downloadJSONasync(searchItem: AppConstants.appString.startString) { fetchedData in
        defer { completionHandler(fetchedData) }
        // ...
    }
)

Использование defer гарантирует, что обработчик завершения всегда будет вызываться, независимо от того, как возвращается внешнее замыкание. Я не понимаю, почему вы используете здесь DispatchGroup, поскольку здесь нет ожидания, но я оставил его на месте, чтобы ответить на ваш вопрос.

person Code Different    schedule 12.02.2019
comment
Спасибо за ответы. При построении с использованием вашего кода я получаю ошибку в func fetchJSON: Невозможно преобразовать значение типа «[NSDictionary]» в ожидаемый тип аргумента «Данные»? Должен ли обработчик завершения иметь тип NSDictionary, а не Data? Я вообще не должен использовать DispatchGroup в этом месте? - person Ralf Bordé; 12.02.2019
comment
Измените подпись completionHandler на [NSDictionary]. я не знал, что это за тип данных fetchedData. И нет, я не видел причин, по которым вам нужна группа диспетчеризации в коде. - person Code Different; 12.02.2019
comment
Окей, извините, вы написали это в своем комментарии. Значит, я могу полностью опустить DispatchGroup (ввести, уведомить, уйти)? - person Ralf Bordé; 12.02.2019
comment
Увы, я изменил тип данных на свой тип NSDictionary с сообщением: Невозможно преобразовать значение типа «[NSDictionary]» в ожидаемый тип аргумента «NSDictionary» Что теперь делать? - person Ralf Bordé; 12.02.2019
comment
Вы забыли скобки - person Code Different; 12.02.2019
comment
К сожалению, в NSDictionary отсутствовали []. - person Ralf Bordé; 12.02.2019
comment
да, спасибо :) Последний вопрос: в func downloadJSONasync есть код ** DispatchQueue.main.async {Завершение (fetchedData)} **. Тогда это нормально? - person Ralf Bordé; 12.02.2019
comment
Я не вижу вас downloadJSONasync, но вы запускаете этот обработчик завершения в основной очереди. Обычно я избегаю касаться основной очереди, за исключением обновления графического интерфейса. - person Code Different; 12.02.2019
comment
Позвольте нам продолжить это обсуждение в чате. - person Ralf Bordé; 12.02.2019

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


Пожалуйста, не используйте для этого DispatchGroup, просто используйте параметр completion вашего метода и внутри completion закрытия после получения данных установите массив источников данных для представления таблицы, а затем ... перезагрузите данные представления таблицы

downloadJSONasync(searchItem: "someString") { dictionary in
    DispatchQueue.main.async { // don't forget that your code doesn't run on the main thread
        // ... somehow set data source array for your table view
        self.tableView.reloadData()
    }
}

Обратите внимание, что вам следует избегать использования NSDictonary, а лучше использовать Dictionary. Также из Swift 4+ вы можете использовать Codable вместо JSONSerialization.

person Robert Dresler    schedule 12.02.2019
comment
Привет, Роберт, спасибо за ваше предложение. Я не могу выполнить self.tableView.reloadData (), потому что я нахожусь в другом классе networkService. Ошибка: использование неразрешенного идентификатора tableView. Что вы имеете в виду ... как-то установить массив источников данных для представления таблицы? Я заполняю свой массив данных (JSON-массив) вне этого класса netwokService И я попробую использовать Dictionary inst. NSDictionary. - person Ralf Bordé; 12.02.2019
comment
@ RalfBordé хорошо, затем добавляем завершение для fetchJSON и вызывается вызов завершения после завершения downloadJSONasync - person Robert Dresler; 12.02.2019
comment
вы имеете в виду, что я должен вызвать DispatchQueue.main.async ... в моей функции fetchJSON? Как я могу вставить код в свой комментарий? возможный? - person Ralf Bordé; 12.02.2019
comment
@ RalfBordé, нет. Вы также должны добавить параметр completion для fetchJSON, и в этом закрытии вы должны установить и перезагрузить данные представления таблицы - person Robert Dresler; 12.02.2019
comment
Хорошо, я попытаюсь. теперь все становится сложнее ... пух - person Ralf Bordé; 12.02.2019
comment
@ RalfBordé, если вы добавили код fetchJSON в свой вопрос, возможно, я смогу вам помочь - person Robert Dresler; 12.02.2019
comment
я пытался добавить код. Это слишком долго, и я не знаю, какую уценку использовать в комментарии? извиняюсь. - person Ralf Bordé; 12.02.2019
comment
@ RalfBordé `` `` - person Robert Dresler; 12.02.2019
comment
Я добавил это в исходный вопрос. - person Ralf Bordé; 12.02.2019
comment
@ RalfBordé man, вам не нужно устанавливать массив источника данных, если вы добавляете элементы. Итак ... просто используйте self.tableView.reloadData(), это должно быть все - person Robert Dresler; 12.02.2019
comment
что вы имеете в виду под массивом источников данных? Мой массив fetchedModules? Но я должен заполнить его своими данными fetchedData. - person Ralf Bordé; 12.02.2019
comment
@ РальфБордэ, да! И это то, что вы делаете. Вы добавляете элементы. Значит, ты не хочешь этого делать. Просто перезагрузите данные - person Robert Dresler; 12.02.2019
comment
хм ... в моем контроллере (prototypCell) я заполняю метки данными из fetchedModules. Если это пусто, все пусто. Может ли это быть правильным путем? Если у вас есть время, присмотритесь к коду поближе. Большое спасибо. - person Ralf Bordé; 12.02.2019
comment
OK. Спасибо. Я проверю. Да, я приму ответ. - person Ralf Bordé; 12.02.2019