Я использую Реализация Роба AsynchronousOperation
Ниже приводится мой подкласс AsynchronousOperation
class AsynOperation : Operation {
// Same implementation as mentioned in Rob's answer
}
Я пытаюсь сделать эскиз для изображений / видео , Я поместил функции создания миниатюр в NSOperation
class AssetDocFetchOperation : AsynOperation, AssetDocGrabberProtocol {
var cacheKey: URL?
var url : String
var image : UIImage?
init(url : String, cacheKey : URL?) {
self.url = url
self.cacheKey = cacheKey
}
}
Генератор изображений
class AssetImageGrabber : AssetDocFetchOperation{
let client = NetworkClient()
override init(url : String,cacheKey : URL?) {
super.init(url: url,cacheKey : cacheKey)
// name = cacheKey?.absoluteString
}
override func main() {
guard let docURL = URL(string: self.url) else {
self.finish()
return
}
if let cKey = cacheKey ,let imageData = MemoryCache.shareInstance.object(forKey: cKey.absoluteString) {
self.image = UIImage(data: imageData)
self.finish()
return
}
client.shouldCache = false
let service = AssetDocFetchService(url:docURL )
client.request(customHostService: service) {[weak self] (result) in
guard let this = self else {return}
switch result {
case .success(let data) :
if let imageData = data, let i = UIImage(data: imageData){
if let cKey = this.cacheKey {
MemoryCache.shareInstance.set(object: imageData , forKey: cKey.absoluteString, cost: 1)
}
this.image = i
this.finish()
}
case .failure(let e):
print(e)
this.image = nil
this.finish()
}
}
}
override func cancel() {
super.cancel()
client.cancel()
}
}
Создатель миниатюр видео
class AssetVideoThumbnailMaker : AssetDocFetchOperation {
private var imageGenerator : AVAssetImageGenerator?
override init(url : String,cacheKey : URL?) {
super.init(url: url,cacheKey : cacheKey)
//name = cacheKey?.absoluteString
}
override func main() {
guard let docURL = URL(string: self.url) else {
self.finish()
return
}
if let cKey = cacheKey ,let imageData = MemoryCache.shareInstance.object(forKey: cKey.absoluteString) {
self.image = UIImage(data: imageData)
self.finish()
return
}
generateThumbnail(url: docURL) {[weak self] (image) in
self?.image = image
self?.finish()
}
}
override func cancel() {
super.cancel()
imageGenerator?.cancelAllCGImageGeneration()
}
private func generateThumbnail(url : URL ,completion: @escaping (UIImage?) -> Void) {
let asset = AVAsset(url: url)
imageGenerator = AVAssetImageGenerator(asset: asset)
let time = CMTime(seconds: 1, preferredTimescale: 60)
let times = [NSValue(time: time)]
imageGenerator?.generateCGImagesAsynchronously(forTimes: times, completionHandler: { [weak self] _, image, _, _, _ in
if let image = image {
let uiImage = UIImage(cgImage: image)
if let cKey = self?.cacheKey , let data = uiImage.pngData() {
MemoryCache.shareInstance.set(object: data , forKey: cKey.absoluteString, cost: 1)
}
completion(uiImage)
} else {
completion(nil)
}
})
}
}
Я создам NSOperation
из cellForItemAt
метода
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let file : AssetFile = // set the file here
....
let grabOperation = taskMaker(fromDocFile : file)
cell.assetFile = grabOperation
if grabOperation.state.value == .initial {
start(operation: grabOperation)
}
return cell
}
func taskMaker(fromDocFile file : AssetFile)->AssetDocFetchOperation{
switch file.fileType {
case .image:
return AssetImageGrabber(url : file.path ?? "",cacheKey: file.cacheKeyURL)
case .video :
return AssetVideoThumbnailMaker(url : file.path ?? "",cacheKey: file.cacheKeyURL)
}
}
Я добавлю их в очередь операций следующим образом
lazy var docsOperations : OperationQueue = {
let queue = OperationQueue()
queue.name = "assetVideoOperations"
queue.maxConcurrentOperationCount = 3
return queue
}()
lazy var docsOperationInProgress : [AssetGrabOperation] = []
func start(operation : AssetGrabOperation){
if !docsOperationInProgress.contains(operation) && !operation.task.isFinished && !operation.task.isExecuting {
docsOperationInProgress.append(operation)
docsOperations.addOperation(operation.task)
}
}
Для сбой / истекло время ожидания, я повторяю метод
func reloadDocument(atIndex: IndexPath?) {
if let index = atIndex {
let tile : AssetFile = // get file here
let grabOperation = // get current opration
remove(operation: grabOperation)
reloadFile(atIndexPath: index) // i'll recreate the operation with taskMaker(fromDocFile : file)
documentList.reloadItems(at: [index])
}
}
Метод операции удаления
func remove(operation :AssetGrabOperation ) {
operation.task.cancel()
docsOperationInProgress = docsOperationInProgress.filter({ return $0 == operation ? false : true })
}
Проблема заключается в том, если я прокручиваю назад и вперед в моем UICollectionView
приложение вылетает, выдавая следующую ошибку. (Иногда, когда я звоню также reloadDocument
)
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSOperationQueue addOperation:]: operation is already enqueued on a queue'
Я также нашел решение / обходной путь, проверив имена операции
let alreadyOn = docsOperations.operations.filter({
return $0.name == operation.task.name
})
// i'll assign the cacheURL to name while the init of opration
if alreadyOn.count == 0 {
docsOperationInProgress.append(operation)
docsOperations.addOperation(operation.task)
}
Я не уверен, хорош ли этот подход или нет. Что я делаю не так?