简体   繁体   中英

Closure does not run in the main thread in swift

I want to show some images on UITableViewCell. However I got an error below fatal error: Index out of range . The problem is that closure does not run in the main thread probably. How can I solve this issue?

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

    APIManager.getAnotherArticle{ (articles: Array<Article>?) in

        for info in articles! {
            self.authorArray.append(info.author)
            self.descriptionArray.append(info.description)
            if info.publishedAt != nil {
                self.publishedAtArray.append(info.publishedAt)
            }
            self.titleArray.append(info.title)
            self.urlArray.append(info.url)
            self.urlToImageArray.append(info.urlToImage)
            print(self.authorArray)
        }
    }

    let program = urlToImageArray[indexPath.row] //index out of range
    let urlToImage = NSURL(string: program)
    cell.pickupImageView.sd_setImage(with: urlToImage as URL!)

    return cell
}

Wrap anything you want to run on the main queue in DispatchQueue.main.async{ ... } .

That said, your current approach likely won't work. This method gets called a lot . While the user is scrolling, this method gets called every time a cell is about to come on the screen (in iOS 10, sometimes a bit before it'll come on the screen). Cells are often recycled, and you're appending data to the titleArray and other arrays every time a cell is requested (they may not be in order; they might have already been fetched; this array isn't going to wind up in the right order).

You need to move all your data about a cell into a model object and out of the view controller. There shouldn't be a titleArray and an urlArray , etc. There should just be an Article , and the Article should take care of fetching itself and updating its properties. And the job of this method is to fetch the correct Article from your cache, or create a new one if needed, and assign it to an ArticleCell . The ArticleCell should watch the Article and update itself any time the Article changes (ie when the fetch completes). Almost no work should happen directly in this method since it gets called so often, and in possibly random orders.

The common way to build this kind of thing is with a simple model object (often a reference type so it can be observed; there are many other approaches that allow a struct, but they're a little more advanced so we'll keep this simple):

class Article {
    var author: String
    var description: String
    var publishedAt: Date
    var title: String
    var url: URL
    var image: UIImage
    func refresh() { 
       // fetch data from server and replace all the placeholder data
    }
}

Then there's some kind of Model that vends these:

class Model {
    func article(at index: Int) -> Article {
        if let article = lookupArticleInCache(at: index) {
            return article
        }

        let article = createAndCachePlaceholderArticle(at: index)
        article.refresh()
    }
}

And then your code looks like:

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

    cell.article = sharedModel.article(at: indexPath.row)
    return cell
}

You can use KVO or Swift Observables or an ArticleDelegate protocol to let the cell observe the Article . When the Article updates, the cell updates itself.

Again, there are many ways to approach this. You could have a "PlaceHolderArticle" that all the cells share and when the real Article comes in, the cell replaces the whole thing (so that Articles are immutable rather than self-updating). You could use the more generic approaches described by Swift Talk . There are lots of ways. But the key is that there is this model that updates itself, independent of any particular UI, and a UI (views, view controllers) that watch the model and display what it holds.

If you want much, much more on this topic, search for "Massive View Controller." That's the common name for the anti-pattern you're currently using. There are lots of ways to fight that problem, so don't assume that any particular article you read on it is "the right way" (people have come up with some very elaborate, and over-elaborate, solutions). But all of them are based on separating the model from the UI.

 APIManager.getAnotherArticle{ (articles: Array<Article>?) in

    for info in articles! {
        self.authorArray.append(info.author)
        self.descriptionArray.append(info.description)
        if info.publishedAt != nil {
            self.publishedAtArray.append(info.publishedAt)
        }
        self.titleArray.append(info.title)
        self.urlArray.append(info.url)
        self.urlToImageArray.append(info.urlToImage)
        print(self.authorArray)
    }
}

you have to make separate function for this calculation and try to avoid the any calculate functionality in " cellForRowAt "

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM