【问题标题】:Load local JSON file async异步加载本地 JSON 文件
【发布时间】:2018-05-08 14:02:29
【问题描述】:

我不熟悉 swift 和同步/异步加载文件的方式。 我在本地有一个很大的 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)

            }

由于我在ViewDidLoad 中使用了loadJSON(),因此当我转到此视图时,应用程序会冻结几秒钟并占用大量内存。

在数据库中异步处理/实现类似搜索的正确方法是什么?

编辑: 我已经尝试使用 dispatch DispatchQueue.global(qos: .background).async 但我收到错误:indexPath.row out of range on 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

}

【问题讨论】:

  • 尝试使用DispatchQueue.global().async { }

标签: ios swift dispatch


【解决方案1】:

你需要在后台使用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)

                }
            }
        }
    }
}

【讨论】:

  • 我尝试使用它,但是当我在 tableView 中显示该数据时,在 [indexPath.row] 上出现超出范围错误,我是否也应该更改 tableView 以异步显示数据?
  • 你如何声明你的数组 players ? @丹
  • 我在问题中添加了额外的代码。我有两个数组,一个用于过滤玩家,当用户开始输入 searchBar 时,另一个用于通用玩家列表。
  • @dan 在您将播放器添加到播放器数组后直接过滤它。
  • 能详细点吗?我不明白你说的直接过滤是什么意思。谢谢:)
【解决方案2】:

您需要将冗长的任务分派到后台队列并将结果分派回主队列。

我的简化 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
    }
}

【讨论】:

    【解决方案3】:

    在后台线程中执行loadJSON(),然后通过回调或属性赋值将结果推送回main。代码:

    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
        }
    }
    

    【讨论】:

      【解决方案4】:
      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.
      
              }
          }
      }
      

      永远不要对 background thread 中的 UI 元素做任何事情

      【讨论】:

      • 请注意,因为完成块不是从主线程执行的。
      【解决方案5】:

      移动到后台线程做一些长时间运行的工作

      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)
                  }
              }
      

      【讨论】:

        猜你喜欢
        • 2018-10-17
        • 1970-01-01
        • 2011-11-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-09-12
        • 2012-05-16
        相关资源
        最近更新 更多