使用后台URLSession在UITableViewCell中下载图像非常慢并且出现奇怪的错误

时间:2017-04-30 22:35:01

标签: ios image uitableview nsurlsession nsurlsessiondownloadtask

我需要正确执行此操作,我认为这是常见的情况:

我有一个UITableViewController,其表格的单元格应该下载图像并显示它。单元格数量取决于用户输入,我首先获取我需要下载的图像的URL,每个单元格一个。在这个UITableViewController我有这个方法:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    guard results.count > 0 else {
        return UITableViewCell()
    }

    let myCell = tableView.dequeueReusableCell(withIdentifier: CustomCell.cellIdentifier, for: indexPath) as! CustomCell
    let result = results[indexPath.row]
    myCell.model = result
    myCell.imageProvider = imageProvider
    return myCell
}

CustomCell是这样的:

class CustomCell: UITableViewCell {

// Several IBOutlets

static let cellIdentifier = "myCell"

var imageProvider: ImageProvider?

var model: MyModel? {
    willSet {
        activityIndicator.startAnimating()
        configureImage(showImage: false, showActivity: true)
    }
    didSet {
        guard let modelUrlStr = model?.imageUrlStr, let imageUrl = URL(string: modelUrlStr) else {
            activityIndicator.stopAnimating()
            configureImage(showImage: false, showActivity: false)
            return
        }

        imageProvider?.getImage(imageUrl: imageUrl, completion: {[weak self] (image, error) in
            DispatchQueue.main.async {
                guard error == nil else {
                    self?.activityIndicator.stopAnimating()
                    self?.configureImage(showImage: false,  showActivity: false)
                    return
                }

                self?.imageView.image = image
                self?.activityIndicator.stopAnimating()
                self?.configureImage(showCoverImage: true, showActivity: false)
            }
        })
    }
}

override func awakeFromNib() {
    super.awakeFromNib()
    configureImage(showCoverImage: false, showActivity: false)
}

override func prepareForReuse() {
    super.prepareForReuse()
    model = nil
}

private func configureImage(showImage: Bool, showActivity: Bool) {
    // Update image view
} 
}

ImageProvider就是这样:

class ImageProvider {
var imageTask: URLSessionDownloadTask?

func getImage(imageUrl: URL, completion: @escaping DownloadResult) {
    imageTask?.cancel()

    imageTask = NetworkManager.sharedInstance.getImageInBackground(imageUrl: imageUrl, completion: { (image, error) -> Void in
        if let error = error {
            completion(nil, error)
        } else if let image = image {
            completion(image, nil)
        } else {
            completion(nil, nil)
        }
    })
}
}

我需要做的是:

  • 用户要求键入文本的搜索,然后我执行给定该文本参数的搜索并获得一些结果(即,表中显示的单元格数量,以及图像中的URL细胞,每个细胞一个)
  • 用户可以随时更改文本以进行搜索或删除,这意味着我应该在该文本中的每次更改后刷新表格(如果正在进行中,请取消之前的搜索 - >新搜索结果 - &gt ;新图片网址 - >新图片下载)。
  • 如果应用程序进入后台状态,我需要继续下载图片,以便在应用程序再次进入前台时显示它们。

然后,最后,这是NetworkManager

class NetworkManager: NSObject {

static let sharedInstance = NetworkManager()
fileprivate var defaultSession: URLSession
fileprivate var backgroundSession: URLSession?
fileprivate var completionHandlers = [URL : ImageResult]()

override init() { 
    let configuration = URLSessionConfiguration.default
    defaultSession = URLSession(configuration: configuration)
    super.init()

    let backgroundConfiguration = URLSessionConfiguration.background(withIdentifier: "com.example.mysearch")
    backgroundSession = URLSession(configuration: backgroundConfiguration, delegate: self, delegateQueue: nil)
}

func getImageInBackground(imageUrl url: URL, completion: ImageResult?) -> URLSessionDownloadTask? {
    guard let backgroundSession = self.backgroundSession else {
        return nil
    }

    completionHandlers[url] = completion

    let request = URLRequest(url: url)
    let task = backgroundSession.downloadTask(with: request)
    task.resume()

    return task
}
}

extension NetworkManager: URLSessionDownloadDelegate {

func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
    if let error = error, let url = task.originalRequest?.url, let completion = completionHandlers[url] {
        completionHandlers[url] = nil
        completion(nil, error)
    }
}

func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
    if let data = try? Data(contentsOf: location), let image = UIImage(data: data), let request = downloadTask.originalRequest, let response = downloadTask.response {
        let cachedResponse = CachedURLResponse(response: response, data: data)
       self.defaultSession.configuration.urlCache?.storeCachedResponse(cachedResponse, for: request)
        if let url = downloadTask.originalRequest?.url, let completion = completionHandlers[url] {
            completionHandlers[url] = nil
            completion(image, nil)
        }
    } else {
        if let url = downloadTask.originalRequest?.url, let completion = completionHandlers[url] {
            completionHandlers[url] = nil
            completion(nil, error)
        }
    }
}

func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
    if let appDelegate = UIApplication.shared.delegate as? AppDelegate, let completionHandler = appDelegate.backgroundSessionCompletionHandler {
        appDelegate.backgroundSessionCompletionHandler = nil
        completionHandler()
    }
}
}

当我运行应用程序时,我发现首先没有下载大部分图像,并且调用urlSession(session:task:didCompleteWithError:)时出现如下错误:

  

错误域= NSURLErrorDomain代码= -999“已取消”UserInfo = {NSErrorFailingURLStringKey = http://xxxxx.jpg,NSLocalizedDescription =已取消,NSErrorFailingURLKey = http://http://xxxxx.jpg}

此外,系统还会显示带有“已取消”文本的警报视图。另一方面,我不明白为什么,当我向上和向下滚动表格时,大部分图像都会被下载并显示出来。

我正在关注Ray Wenderlich网站课程中提供的类似示例,但我不知道为什么我的应用程序效果不佳。

我做错了什么?我需要你的帮助才能搞清楚。

编辑:我在自定义单元格中始终拥有ImageProvider的方式更新了代码。这是CustomCell类:

private let imageProvider = ImageProvider()

但我仍然得到相同的错误和“cabcelled”警报视图。

1 个答案:

答案 0 :(得分:0)

由于您是在模型的didSet方法中启动图像下载,因此实习生需要imageProvider开始下载。您在设置imageProvider之前设置模型。

尝试更新以下代码,

Select ad_id, count(ad_id) as exposures from ad_history group by ad_id order by ad_id asc;

}