fatal error: Index out of range when refreshing ta

2020-07-22 19:32发布

问题:

I've this weird app crash when pulling to refresh occurs.

My code goes as it follows:

var posts: [Posts] = []

override func viewDidLoad() {
    super.viewDidLoad()

    // refreshControl -> pull to refresh handler
    let refreshControl = UIRefreshControl()
    refreshControl.addTarget(self,
                             action: #selector(Main_TVC.getData),
                             for: UIControlEvents.valueChanged)
    self.refreshControl = refreshControl

    getData()
}

override func numberOfSections(in tableView: UITableView) -> Int {
    return 1
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return posts.count
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell",
                                             for: indexPath) as! PostsTableViewCell

    cell.titleLabel.text = posts[indexPath.row].postTitle
    cell.bodyLabel.text = posts[indexPath.row].postBody

    return cell
}

func getData() {
    self.posts.removeAll()

    // queries the backend and fills the data - sorry for code omission

    refresh()

}


func refresh() {
    self.tableView.reloadData()
    self.refreshControl?.endRefreshing()
}

The app runs properly and even when I pull down to refresh, everything runs perfectly, but if do a long pull down to refresh, like pulling down almost hitting the bottom of the screen, the app crashes and prompts the following error:

fatal error: Index out of range

on the line

cell.titleLabel.text = posts[indexPath.row].postTitle

If I print the post count and the indexPath as follows:

print("posts.count = (posts.count)") print("indexPath.row = (indexPath.row)")

When I'm pulling down in the normal way, it prints the correct data, but if I pull down like a long pull, through the whole screen if prompts this when it crashes

posts.count = 0

indexPath.row = 2

This kind of thing has never happen to me using the refreshControl the way I'm using here.

Hope my information is understandable, mostly on the long pull to refresh issue.

回答1:

Your problem is that the first thing you do in getData is remove all of the posts from self.posts, but you do not (presumably since code is missing) reload the table view, so now the number of posts in the array (0) and the number of posts that the tableview *thinks is in the array (not zero) is different, so you get an array bounds crash.

Calling reloadData after self.posts.removeAll() would fix the issue but result in the tableview 'flashing' as it redrew empty and then redrew with the new data.

Since you haven't shown the full code for getData I can't provide the exact code you need, but it should be something like:

func getData() { 
     fetchPostsWithCompletion() {
         if let tempPosts = dataFromNetwork {
             self.posts = tempPosts
         } else {
             self.posts.removeAll()
         }
         self.refresh()        // Dispatch this on the main queue if your completion handler is not already on the main queue
    }
}

This way you don't manipulate the backing array until you have the new data.



回答2:

I just had the same issue. My finding is that delaying the execution of removeAll() on the posts array allows the table view to get up to date on its count.

func getData() {
   self.delayExecutionByMilliseconds(500) {
      self.posts.removeAll()
   }
   // queries the backend and fills the data - sorry for code omission

   refresh()
}

fileprivate func delayExecutionByMilliseconds(_ delay: Int, for anonFunc: @escaping () -> Void) {
    let when = DispatchTime.now() + .milliseconds(delay)
    DispatchQueue.main.asyncAfter(deadline: when, execute: anonFunc)
}