2017-07-18 1 views
1

Ich versuche, Bilder aus meiner Firebase-Datenbank herunterzuladen und sie in collectionviewcells zu laden. Die Bilder werden heruntergeladen, aber ich habe Probleme, sie alle asynchron herunterzuladen und zu laden.Bilder von der URL asynchron herunterladen und zwischenspeichern

Momentan, wenn ich meinen Code letzten Bild heruntergeladen lade. Wenn ich jedoch meine Datenbank aktualisiere, wird die Sammlungsansicht aktualisiert, und das neue Profil des letzten Benutzerprofils wird ebenfalls geladen, aber der Rest fehlt.

Ich würde lieber nicht eine 3rd-Party-Bibliothek verwenden, so dass alle Ressourcen oder Vorschläge sehr geschätzt werden.

Hier ist der Code, der das Herunterladen Griff:

func loadImageUsingCacheWithUrlString(_ urlString: String) { 

    self.image = nil 

//  checks cache 
    if let cachedImage = imageCache.object(forKey: urlString as NSString) as? UIImage { 
     self.image = cachedImage 
     return 
    } 

    //download 
    let url = URL(string: urlString) 
    URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in 

     //error handling 
     if let error = error { 
      print(error) 
      return 
     } 

     DispatchQueue.main.async(execute: { 

      if let downloadedImage = UIImage(data: data!) { 
       imageCache.setObject(downloadedImage, forKey: urlString as NSString) 

       self.image = downloadedImage 
      } 

     }) 

    }).resume() 
} 

ich die Lösung glaube, irgendwo liegt die Kollektion in Neuladen ich wo genau weiß einfach nicht, es zu tun.

Irgendwelche Vorschläge?

EDIT: Hier wird die Funktion aufgerufen; mein cellForItem at indexpath

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 

    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: userResultCellId, for: indexPath) as! FriendCell 

    let user = users[indexPath.row] 

    cell.nameLabel.text = user.name 

    if let profileImageUrl = user.profileImageUrl { 

      cell.profileImage.loadImageUsingCacheWithUrlString(profileImageUrl) 
    } 

    return cell 
} 

Die einzige andere Sache, die ich vielleicht glauben, dass die Bilder Laden beeinflussen könnte, ist diese Funktion, die ich verwenden, um die Benutzerdaten herunterzuladen, die in viewDidLoad genannt wird, aber alle anderen Daten-Downloads korrekt.

func fetchUser(){ 
    Database.database().reference().child("users").observe(.childAdded, with: {(snapshot) in 

     if let dictionary = snapshot.value as? [String: AnyObject] { 
      let user = User() 
      user.setValuesForKeys(dictionary) 

      self.users.append(user) 
      print(self.users.count) 

      DispatchQueue.main.async(execute: { 
      self.collectionView?.reloadData() 
       }) 
     } 


    }, withCancel: nil) 

} 

Aktuelles Verhalten:

Wie für das aktuelle Verhalten der letzte Zelle ist die einzige Zelle, die das heruntergeladene Profil-Bild zeigt; Wenn es 5 Zellen gibt, ist die 5. die einzige, die ein Profilbild anzeigt. Auch wenn ich die Datenbank aktualisiere, dh einen neuen Benutzer darin registriere, wird die Sammlung aktualisiert und zeigt den neu registrierten Benutzer korrekt mit ihrem Profilbild zusätzlich zu der alten letzten Zelle an, die das Bild korrekt heruntergeladen hat. Der Rest bleibt jedoch ohne Profilbilder.

+0

Können Sie Ihren CollectionViewController anzeigen? – javimuu

+0

@javimuu Ich habe eingefügt, wo die Funktion aufgerufen wird. – Stefan

+0

Verwenden Sie sdwebimage Bibliothek, um das Bild einfach zu laden. –

Antwort

6

Ich weiß, dass Sie Ihr Problem gefunden haben, und es war unabhängig von dem obigen Code, aber ich habe noch eine Beobachtung. Insbesondere werden Ihre asynchronen Anforderungen fortgesetzt, selbst wenn die Zelle (und damit die Bildansicht) anschließend für einen anderen Indexpfad wiederverwendet wurde. Daraus ergeben sich zwei Probleme:

  1. Wenn Sie schnell auf die 100. Zeile scrollen, Sie gehen für die Bilder für die ersten 99 Zeilen abgerufen werden warten zu müssen, bevor Sie die Bilder für die sichtbaren Zellen sehen . Dies kann zu sehr langen Verzögerungen führen, bevor Bilder gestartet werden.

  2. Wenn diese Zelle für die 100. Zeile mehrmals wiederverwendet wurde (z. B. für Zeile 0, für Zeile 9, für Zeile 18 usw.), werden Sie Möglicherweise wird das Bild von einem Bild zum nächsten flackern, bis Sie zum Bildabruf für die 100. Zeile gelangen.

Jetzt könnten Sie nicht sofort bemerken, entweder diese Probleme sind, weil sie nur sich selbst manifestieren, wenn die Bildrecherche hat eine harte Zeit mit dem Benutzer Scrolling halten (die Kombination von langsamen Netzwerk und schnelles Scrollen). Nebenbei sollten Sie Ihre App immer mit dem Netzwerk-Link-Conditioner testen, der schlechte Verbindungen simuliert, wodurch es einfacher wird, diese Fehler zu manifestieren.

Wie auch immer, die Lösung besteht darin, (a) den aktuellen URLSessionTask zu verfolgen, der mit der letzten Anfrage verbunden ist; und (b) der aktuelle URL wird angefordert. Sie können dann (a) beim Starten einer neuen Anfrage sicherstellen, dass Sie jede vorherige Anfrage stornieren; und (b) Stellen Sie beim Aktualisieren der Bildansicht sicher, dass die mit dem Bild verknüpfte URL der aktuellen URL entspricht.

Der Trick ist jedoch, wenn Sie eine Erweiterung schreiben, können Sie nicht einfach neue gespeicherte Eigenschaften hinzufügen. Sie müssen also die zugehörige Objekt-API verwenden, damit Sie diese beiden neuen gespeicherten Werte mit dem Objekt UIImageView verknüpfen können. Ich persönlich umgebe diese assoziierte Wert-API mit einer berechneten Eigenschaft, so dass der Code zum Abrufen der Bilder nicht mit solchen Dingen begraben wird. Wie auch immer, dass die Renditen:

extension UIImageView { 

    private static var taskKey = 0 
    private static var urlKey = 0 

    private var currentTask: URLSessionTask? { 
     get { return objc_getAssociatedObject(self, &UIImageView.taskKey) as? URLSessionTask } 
     set { objc_setAssociatedObject(self, &UIImageView.taskKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } 
    } 

    private var currentURL: URL? { 
     get { return objc_getAssociatedObject(self, &UIImageView.urlKey) as? URL } 
     set { objc_setAssociatedObject(self, &UIImageView.urlKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } 
    } 

    func loadImageAsync(with urlString: String?) { 
     // cancel prior task, if any 

     weak var oldTask = currentTask 
     currentTask = nil 
     oldTask?.cancel() 

     // reset imageview's image 

     self.image = nil 

     // allow supplying of `nil` to remove old image and then return immediately 

     guard let urlString = urlString else { return } 

     // check cache 

     if let cachedImage = ImageCache.shared.image(forKey: urlString) { 
      self.image = cachedImage 
      return 
     } 

     // download 

     let url = URL(string: urlString)! 
     currentURL = url 
     let task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in 
      self?.currentTask = nil 

      //error handling 

      if let error = error { 
       // don't bother reporting cancelation errors 

       if (error as NSError).domain == NSURLErrorDomain && (error as NSError).code == NSURLErrorCancelled { 
        return 
       } 

       print(error) 
       return 
      } 

      guard let data = data, let downloadedImage = UIImage(data: data) else { 
       print("unable to extract image") 
       return 
      } 

      ImageCache.shared.save(image: downloadedImage, forKey: urlString) 

      if url == self?.currentURL { 
       DispatchQueue.main.async { 
        self?.image = downloadedImage 
       } 
      } 
     } 

     // save and start new task 

     currentTask = task 
     task.resume() 
    } 

} 

Beachten Sie auch, dass Sie einige imageCache Variable wurden Referenzierung (a global?). Ich würde ein Bild Cache Singletons vorschlagen, die, zusätzlich zu den grundlegenden Caching-Mechanismus bietet, auch Speicher Warnungen beobachtet und spült sich in Speicherdrucksituationen:

class ImageCache { 
    private let cache = NSCache<NSString, UIImage>() 
    private var observer: NSObjectProtocol! 

    static let shared = ImageCache() 

    private init() { 
     // make sure to purge cache on memory pressure 

     observer = NotificationCenter.default.addObserver(forName: .UIApplicationDidReceiveMemoryWarning, object: nil, queue: nil) { [weak self] notification in 
      self?.cache.removeAllObjects() 
     } 
    } 

    deinit { 
     NotificationCenter.default.removeObserver(observer) 
    } 

    func image(forKey key: String) -> UIImage? { 
     return cache.object(forKey: key as NSString) 
    } 

    func save(image: UIImage, forKey key: String) { 
     cache.setObject(image, forKey: key as NSString) 
    } 
} 

Wie Sie sehen können, das asynchrone Abrufen und Caching Wir beginnen, ein wenig komplizierter zu werden, und deshalb empfehlen wir generell, etablierte asynchrone Bildwiedergewinnungsmechanismen wie AlamofireImage oder Kingfisher oder SDWebImage in Betracht zu ziehen. Diese Leute haben viel Zeit damit verbracht, die oben genannten Probleme anzugehen und andere, und sind einigermaßen robust. Aber wenn Sie "selbst rollen", würde ich etwas wie das oben genannte auf ein Minimum vorschlagen.

Verwandte Themen