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:
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.
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
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
}
}
}