3

某些上下文:我有一個UICollectionView,它將顯示大約一千個微小圖像,但其中只有大約100個可同時顯示。iOS:保持用戶界面在圖像加載到單獨線程時響應

我需要在單獨的線程中從磁盤加載此圖像,以便UI不會被阻止,並且用戶可以在圖像仍然出現時與應用程序進行交互。

要做到這一點,我在斯威夫特實施rob mayoff's answerProper way to deal with cell reuse with background threads?如下,其中PotoCellUICollectionViewCell一個子類:

var myQueue = dispatch_queue_create("com.dignityValley.Autoescuela3.photoQueue", DISPATCH_QUEUE_SERIAL) 
var indexPathsNeedingImages = NSMutableSet() 

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { 
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! PhotoCell 
    cell.imageView.image = nil 
    indexPathsNeedingImages.addObject(indexPath) 
    dispatch_async(myQueue) { self.bg_loadOneImage() } 
    return cell 
} 

func bg_loadOneImage() { 
    var indexPath: NSIndexPath? 
    dispatch_sync(dispatch_get_main_queue()) { 
     indexPath = self.indexPathsNeedingImages.anyObject() as? NSIndexPath 
     if let indexPath = indexPath { 
      self.indexPathsNeedingImages.removeObject(indexPath) 
     } 
    } 
    if let indexPath = indexPath { 
     bg_loadImageForRowAtIndexPath(indexPath) 
    } 
} 

func bg_loadImageForRowAtIndexPath(indexPath: NSIndexPath) { 
    if let cell = self.cellForItemAtIndexPath(indexPath) as? PhotoCell { 
     if let image = self.photoForIndexPath(indexPath) { 
      dispatch_async(dispatch_get_main_queue()) { 
       cell.imageView.image = image 
       self.indexPathsNeedingImages.removeObject(indexPath) 
      } 
     } 
    } 
} 

func collectionView(collectionView: UICollectionView, didEndDisplayingCell cell: UICollectionViewCell, forItemAtIndexPath indexPath: NSIndexPath) { 
    indexPathsNeedingImages.removeObject(indexPath) 
} 

但是,我沒有得到滿意的結果:當我滾動時,當圖像在後臺加載時,UI會凍結幾分之一秒。滾動不夠流暢。此外,在加載前100張圖像時,我無法滾動顯示最後一張圖像。在執行多線程之後,UI仍然被阻塞。這previosly我使用的是自定義的串行隊列

var myQueue = dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0) 

注:

出人意料的是,我可以通過修改我的隊列,以達到理想的平整度。

在這個改變後,用戶界面完全響應,但現在我有一個非常嚴重的問題:我的應用程序偶爾崩潰,我認爲它與幾個線程可能正在訪問和修改indexPathsNeedingImages時間。

試圖使用鎖定/同步使我的圖像有時結束到錯誤的單元格。所以,我想實現平滑性,它給我一個全局背景隊列,但使用cutom串行隊列。我無法弄清楚當我使用自定義串行隊列時爲什麼我的UI會凍結,以及爲什麼當我使用全局隊列時不會。


一些想法:也許設置cell.imageView.image = image周邊的100個細胞發生即使image已經被分配一定的時間。問題可能在這裏,因爲評論這條線使滾動方式更順暢。但我不明白的是,爲什麼滾動是平滑的,當我離開這條線時,我使用了一個全局的後臺優先級隊列(直到應用程序崩潰時拋出一條消息告訴... was mutated while being enumerated)。

有關如何解決此問題的任何想法?

回答

0

在這個函數中它應該是dispatch_ASYNC到主隊列。您應該切換隊列的唯一原因是從網絡獲取數據或執行需要時間並阻止用戶界面的任務。當回到主隊列時,應該完成與UI相關的事情。

func bg_loadOneImage() { 
var indexPath: NSIndexPath? 
dispatch_sync(dispatch_get_main_queue()) { 
    indexPath = self.indexPathsNeedingImages.anyObject() as? NSIndexPath 
    if let indexPath = indexPath { 
     self.indexPathsNeedingImages.removeObject(indexPath) 
    } 
} 
if let indexPath = indexPath { 
    bg_loadImageForRowAtIndexPath(indexPath) 
} 
} 

而且 如果讓indexPath = indexPath是混淆

0

相當長的一段時間後,我已經成功地使其發揮作用。雖然現在我正在測試iPad Air 1,而在它是iPad 2之前,不知道這是否會對「響應」產生一些影響。

無論如何,這是代碼。在您的UIViewController

class TestViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { 

    var collectionView = UICollectionView(frame: CGRectZero, collectionViewLayout: UICollectionViewFlowLayout()) 
    var myQueue = dispatch_queue_create("com.DignityValley.Autoescuela-3.photoQueue", DISPATCH_QUEUE_SERIAL) 
    var indexPathsNeedingImages = NSMutableSet() 

    ... 

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { 
     let cell = collectionView.dequeueReusableCellWithReuseIdentifier(ReuseIdentifiers.testCell, forIndexPath: indexPath) as! TestCell 
     ... 
     cell.imageIdentifiers = record.imageIdentifiers 
     indexPathsNeedingImages.addObject(indexPath) 
     dispatch_async(myQueue) { self.bgLoadOneCell() } 
     return cell 
    } 

    func bgLoadOneCell() { 
     var indexPath: NSIndexPath? 
     dispatch_sync(dispatch_get_main_queue()) { 
      indexPath = self.indexPathsNeedingImages.anyObject() as? NSIndexPath 
      if let indexPath = indexPath { 
       self.indexPathsNeedingImages.removeObject(indexPath) 
      } 
     } 
     if let indexPath = indexPath { 
      loadImagesForCellAtIndexPath(indexPath) 
     } 
    } 

    func loadImagesForCellAtIndexPath(indexPath: NSIndexPath) { 
     if let cell = collectionView.cellForItemAtIndexPath(indexPath) as? TestCell { 
      cell.displayImages() 
     } 
    } 

    ... 
} 

而在你UICollectionViewCell子類:

class TestCell: UICollectionViewCell { 

    ... 

    var photosView = UIView() 
    var imageIdentifiers: [String?] = [] { 

    func displayImages() { 
     for i in 0..<imageIdentifiers.count { 
      var image: UIImage? 
      if let identifier = imageIdentifiers[i] { 
       image = UIImage(named: "test\(identifier).jpg") 
      } 
      dispatch_sync(dispatch_get_main_queue()) { 
       self.updateImageAtIndex(i, withImage: image) 
      } 
     } 
    } 

    func updateImageAtIndex(index: Int, withImage image: UIImage?) { 
     for imageView in photosView.subviews { 
      if let imageView = imageView as? UIImageView { 
       if imageView.tag == index { 
        imageView.image = image 
        break 
       } 
      } 
     } 
    } 

    ... 
} 

我覺得這和我收到的唯一區別是調用dispatch_sync而非dispatch_async當實際更新的圖像(即imageView.image = image)。

相關問題