Загрузить локальный файл JSON асинхронно

Я новичок в быстром и синхронном / асинхронном способе загрузки файла. У меня есть большой локальный файл JSON для iPad приложения о футболе со списком и статистикой футболистов.

На данный момент я загружаю весь список игроков в массив словарей и позволяю пользователю искать конкретного игрока.

func loadJSON() {
    /// Load Json File
    if let path = Bundle.main.path(forResource: "players", ofType: "json") {
        do {
            let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .alwaysMapped)
            let jsonObj = try JSON(data: data)

            /// For Player in JSON Serialize values
            for (_,subJson):(String, JSON) in jsonObj["PackData"]["PlayerData"]["P"] {

                let firstName = subJson["_f"].stringValue
                let lastName = subJson["_s"].stringValue
                let id = subJson["_id"].stringValue
                let dateOfBirth = subJson["_d"].stringValue
                let height = subJson["_h"].stringValue
                let weight = subJson["_w"].stringValue
                let image = subJson["_i"].stringValue


                let player = Player(id: id, firstName: firstName, lastName: lastName, dateOfBirth: dateOfBirth, height: height, weight: weight, image: image)

                /// Append Player in players Array
                players.append(player)

            }

Поскольку я использую loadJSON() в ViewDidLoad, приложение зависает на несколько секунд и использует много памяти, когда я перехожу к этому представлению.

Как правильно обрабатывать / реализовывать что-то вроде поиска в БД в асинхронном режиме?

РЕДАКТИРОВАТЬ: Я уже пытался использовать отправку DispatchQueue.global(qos: .background).async, но получаю сообщение об ошибке: indexPath.row out of range на player = filteredPlayers[indexPath.row]

 // create a cell for each table view row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    // create a new cell if needed or reuse an old one

    let cell:UITableViewCell = self.searchTableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as UITableViewCell!
    let player: Player

    /// Return a different table if is searching or not
    if isFiltering() {
        player = filteredPlayers[indexPath.row]
    } else {
        player = players[indexPath.row]
    }
    cell.textLabel?.text = player.firstName! + " " + player.lastName!

    cell.textLabel?.textColor = UIColor.white

    return cell

}

person dan    schedule 08.05.2018    source источник
comment
попробуйте использовать DispatchQueue.global().async { }   -  person a.masri    schedule 08.05.2018


Ответы (5)


Вам необходимо использовать DispatchQueue в фоновом режиме,

func loadJSON() {
    /// Load Json File
    DispatchQueue.global(qos: .background).async{
        if let path = Bundle.main.path(forResource: "players", ofType: "json") {
            do {
                let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .alwaysMapped)
                let jsonObj = try JSON(data: data)

                /// For Player in JSON Serialize values
                for (_,subJson):(String, JSON) in jsonObj["PackData"]["PlayerData"]["P"] {

                    let firstName = subJson["_f"].stringValue
                    let lastName = subJson["_s"].stringValue
                    let id = subJson["_id"].stringValue
                    let dateOfBirth = subJson["_d"].stringValue
                    let height = subJson["_h"].stringValue
                    let weight = subJson["_w"].stringValue
                    let image = subJson["_i"].stringValue


                    let player = Player(id: id, firstName: firstName, lastName: lastName, dateOfBirth: dateOfBirth, height: height, weight: weight, image: image)

                    /// Append Player in players Array
                    players.append(player)

                }
            }
        }
    }
}
person A.Munzer    schedule 08.05.2018
comment
Я попытался использовать его, но при отображении этих данных в tableView я получаю сообщение об ошибке вне диапазона [indexPath.row]. Должен ли я также изменить tableView для отображения данных в асинхронном режиме? - person dan; 08.05.2018
comment
как вы объявляете свой массив player? @dan - person A.Munzer; 08.05.2018
comment
Я добавил к вопросу дополнительный код. У меня есть два массива, один для отфильтрованных игроков, когда пользователь начинает вводить searchBar, другой для общего списка игроков. - person dan; 08.05.2018
comment
@dan после того, как вы добавили игрока в массив игроков, отфильтруйте его напрямую. - person A.Munzer; 09.05.2018
comment
не могли бы вы рассказать поподробнее? Я не понимаю, что вы подразумеваете под прямым фильтром. Спасибо :) - person dan; 09.05.2018
comment
Вы должны отфильтровать свой массив после добавления всех своих игроков, а также убедитесь, что эта функция func tableView (_ tableView: UITableView, numberOfRowsInSection section: Int) - ›Int {if isFilterd {return filterPlayers.count} else {return Players.count}} используйте filterPlayers, пожалуйста, нажмите на текущий ответ, если он вам поможет - person A.Munzer; 09.05.2018

Вам нужно отправить свою длительную задачу в фоновую очередь и отправить результат обратно в основную.

Мой упрощенный пример json:

{
    "person": "Bob"
}

Создать метод загрузки json

func loadJSON(completion: @escaping (_ data: String?, _ error: Error?) -> ())  {
    var person: String?
    var receivedError: Error?

    /// Load json file and parse in background queue
    DispatchQueue.global(qos: .background).async {
        let path = Bundle.main.path(forResource: "myJSON", ofType: "json")!
        do {
            let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .alwaysMapped)
            let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
            let jsonDictionary =  json as! Dictionary<String, Any>

            person = jsonDictionary["person"] as? String
        } catch {
            receivedError = error
        }

        // Dispatch the found value to main queue
        DispatchQueue.main.async {
            completion(person, receivedError)
        }
    }
}

И назовите это в своем коде:

loadJSON { (data, error) in
    if let retrievedData = data {
        print(retrievedData)
        // It is safe to assign the value to UI objects 
        // because the callback is on the main thread
    }
}
person Au Ris    schedule 08.05.2018

Выполните loadJSON() в фоновом потоке, затем отправьте результат обратно в основной с помощью обратного вызова или присвоения свойств. Код:

DispatchQueue(label: "jsonLoading", qos: .background).async {
    let players = self.loadJSON()
    DispatchQueue.main.async {
        // Handle data in the main thread in whatever way you need, eg:
        self.players = players
    }
}
person Vadim Popov    schedule 08.05.2018

func loadJSON(completion: @escaping ()->()) {
    /// Above Parameter is a completion handler which informs user that some task have been done.
    //Do such heavy operations like json loading from local file in a back ground thread so your main thread doesn't get affected.
    DispatchQueue.global(qos: .background).async {
        if let path = Bundle.main.path(forResource: "players", ofType: "json") {
            do {
                let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .alwaysMapped)
                let jsonObj = try JSON(data: data)

                /// For Player in JSON Serialize values
                for (_,subJson):(String, JSON) in jsonObj["PackData"]["PlayerData"]["P"] {

                    let firstName = subJson["_f"].stringValue
                    let lastName = subJson["_s"].stringValue
                    let id = subJson["_id"].stringValue
                    let dateOfBirth = subJson["_d"].stringValue
                    let height = subJson["_h"].stringValue
                    let weight = subJson["_w"].stringValue
                    let image = subJson["_i"].stringValue


                    let player = Player(id: id, firstName: firstName, lastName: lastName, dateOfBirth: dateOfBirth, height: height, weight: weight, image: image)

                    /// Append Player in players Array
                    players.append(player)
                }
                completion()
            }
            catch _ {

            }
        }
    }
}

Теперь вызовите указанный выше метод в вашем viewController классе или любом другом, который вам нужен.

func yourViewControllerMethod() {
    loadJSON {
        // This Block will execute when your json is loaded and parsed completely.
        DispatchQueue.main.async{
             // This is the main thread now you are again here on your main thread after your journey from background thread.

        }
    }
}

Никогда ничего не делайте с вашими UI элементами в background thread

person Syed Qamar Abbas    schedule 08.05.2018
comment
Имейте в виду, потому что блок завершения выполняется не из основного потока. - person Leo; 08.05.2018

Перейдите в фоновый поток, чтобы выполнить длительную работу

func loadJSON(_ completion:@escaping (_ jsonObj:JSON?) -> Void){
        DispatchQueue.global(qos: .background).async {
            if let path = Bundle.main.path(forResource: "players", ofType: "json") {
               if  let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .alwaysMapped), let jsonObj = try? JSON.init(data: data){
                    completion(jsonObj)
               }else{
                 completion(nil)
                }
            }else{
                 completion(nil)
            }
        }
    }

тогда позвони вот так

   self.loadJSON { (jsonObj) in

            DispatchQueue.main.async {
                guard let jsonObj = jsonObj else {return}
                /// For Player in JSON Serialize values
                for (_,subJson):(String, JSON) in jsonObj["PackData"]["PlayerData"]["P"] {

                    let firstName = subJson["_f"].stringValue
                    let lastName = subJson["_s"].stringValue
                    let id = subJson["_id"].stringValue
                    let dateOfBirth = subJson["_d"].stringValue
                    let height = subJson["_h"].stringValue
                    let weight = subJson["_w"].stringValue
                    let image = subJson["_i"].stringValue


                    let player = Player(id: id, firstName: firstName, lastName: lastName, dateOfBirth: dateOfBirth, height: height, weight: weight, image: image)

                    /// Append Player in players Array
                    players.append(player)
            }
        }
person Abdelahad Darwish    schedule 08.05.2018