1

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 
} 

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.

Répondre

0

Étant donné que vous lancez le téléchargement d'image dans la méthode didSet du modèle, il est nécessaire que le programme imageProvider commence le téléchargement. Vous définissez le modèle avant de définir imageProvider.

Essayez par le code de mise à jour comme ci-dessous,

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.imageProvider = imageProvider 
myCell.model = result 
return myCell 

}

+0

Désolé, mais que fais-tu différent là-bas? – AppsDev

+0

Mis à jour ma réponse. –

+0

Désolé, mais le système continue d'afficher la vue d'alerte avec le texte "annulé". – AppsDev