que je dois exécuter correctement, je pense commun, scénario:Télécharger des images en UITableViewCell avec un URLSession de fond est très lent et devient étrange erreur
Je les cellules d'un UITableViewController
dont la table doivent télécharger une image et montrer. Le nombre de cellules dépend d'une entrée de l'utilisateur, et je reçois d'abord les URL des images que j'ai besoin de télécharger, une par cellule. Dans ce UITableViewController
j'ai cette méthode:
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
}
Où CustomCell
est comme ceci:
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
}
}
Et ImageProvider
est ceci:
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)
}
})
}
}
Ce que je dois faire est:
- Les États-Unis er demande une recherche en tapant un texte, puis j'effectue la recherche en fonction de ce paramètre et j'obtiens un certain nombre de résultats (c'est-à-dire le nombre de cellules à afficher dans le tableau, ainsi que les URL des cellules, par cellule)
- l'utilisateur peut modifier le texte à rechercher ou supprimer à tout moment, ce qui signifie que je rafraîchir la table après chaque changement de ce texte (annuler la recherche précédente si elle était en cours -> nouvelle recherche de résultats -> nouvelles images URL -> téléchargement de nouvelles images).
- J'ai besoin des images continuer à télécharger si l'application passe à l'état d'arrière-plan, pour pouvoir les montrer quand l'application va à nouveau au premier plan.
Puis, enfin, c'est 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()
}
}
}
Quand je lance l'application, je trouve que d'une part la plupart des images ne sont pas téléchargées et urlSession(session:task:didCompleteWithError:)
est appelée avec une erreur comme ceci:
erreur domaine = NSURLErrorDomain code = -999 "annulé" UserInfo = {NSErrorFailingURLStringKey = http://xxxxx.jpg, NSLocalizedDescription = annulé, NSErrorFailingURLKey = http://http://xxxxx.jpg}
et parfois en plus une vue d'alerte avec le texte « annulé » est de montrer par le système. D'un autre côté, je ne comprends pas pourquoi, quand je fais défiler la table de haut en bas, la plupart des images sont téléchargées et affichées.
Je suivais un exemple similaire fourni dans un cours sur le site Web Ray Wenderlich, mais je ne sais pas pourquoi mon application ne fonctionne pas bien.
Qu'est-ce que je fais mal? J'ai besoin de votre aide pour le comprendre.
EDIT: J'ai mis à jour le code d'une manière que j'ai toujours un ImageProvider
dans la cellule personnalisée. Ceci est en CustomCell
classe:
private let imageProvider = ImageProvider()
Mais je reçois toujours la même erreur et « cabcelled » vue d'alerte.
Désolé, mais que fais-tu différent là-bas? – AppsDev
Mis à jour ma réponse. –
Désolé, mais le système continue d'afficher la vue d'alerte avec le texte "annulé". – AppsDev