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)。

相关问题