Приложение cra sh: NSInvalidArgumentException - Операция уже помещена в очередь в очереди - PullRequest
1 голос
/ 11 февраля 2020

Я использую Реализация Роба 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)
        }

Я не уверен, хорош ли этот подход или нет. Что я делаю не так?

...