iosswiftmultithreadinguikitphotosframework

PHImageManager's requestImage locks UI when scrolling through thousands of images


I have a collection view where I display all user's photos. Basic stuff, a Data Source who fetches PHAssets and use requestImage on a PHCachingImageManager to load thumbnails.

But recently I got a bug report where the UI freezes when you have more then 5000 images and quickly scrolls through it. Upon investigating, I was able to reproduce the issue, and it seems that the main thread is locked (_lock_wait) right after calling requestImage, while dozens of other threads created by that call (the thumbnails for the other cells) are also locked waiting for who knows what.

I tried several things and nothing works:

  1. Implemented UICollectionViewDataSourcePrefetching, and used PHCachingImageManager to startCachingImages on the prefetchItemsAt event. Funny thing, it made things even worse, as it tries to load more images at once. And the cancelPrefetchingForItemsAt, who was supposed to be called when the cell is out of screen, is never called.

  2. Tried to call cancelImageRequest on slower requests, also no success here.

Now I don't know what else to do, I could run requestImage on a background dispatch_queue so it won't lock my main thread, but it feels weird since the method will spawn another thread by itself

My code is something like that:

let requestOptions = PHImageRequestOptions()
requestOptions.resizeMode = .fast
requestOptions.deliveryMode = .opportunistic
requestOptions.isSynchronous = false
requestOptions.isNetworkAccessAllowed = true
return requestOptions

myManager.requestImage(for: asset, targetSize: CGSize(width: 375, height: 375), contentMode: .aspectFill, options: options) { /* use image */ }

ps: I'm not sure, but it seems this only happen on iOS 11, right now I only was able to reproduce on an iPhone X


Solution

  • It turns out this was the issue: GCD dispatch concurrent queue freeze with 'Dispatch Thread Soft Limit Reached: 64' in crash log

    requestImage keep creating new threads until it reaches the dispatch thread limit and UI freezes on this _lock_wait

    Solution was to set the requestOptions.isSynchronous = true, create an OperationQueue with limited concurrency and add the image fetching as jobs on that queue.

    //somewhere like viewDidLoad
    operationQueue.maxConcurrentOperationCount = 54
    
    //when setting up the cells
    operationQueue.addOperation { [weak self] in
          self?.cachingImageManager.requestImage(/* params...*/) { (image) in
                OperationQueue.main.addOperation {
                   //use image
                }
          }
    }