Swift: Maintenir le cycle avec NSOperation

Dans mon application, j'utilise une class de chargeur d'images pour charger des images à partir du Web pour une vue de collection. La class conserve la trace des opérations de téléchargement et les annule lorsque les cellules des images ne sont plus visibles dans la vue de collection. Cette implémentation est basée sur le tutoriel de raywenderlich pour NSOperation: http://www.raywenderlich.com/76341/use-nsoperationnsoperationqueue-swift .

J'utilise NSOperation pour download une image sur le Web. J'ai remarqué avec Instruments qu'aucune des NSoperations n'est publiée. Cela crée une augmentation de la memory utilisée pour chaque image téléchargée. Dans le bloc d'achèvement, je reference «self». J'ai donc compris que j'ai créé un cycle de retenue.

J'ai lu beaucoup d'exemples sur internet. Je comprends que je peux utiliser des lists de capture avec «soi faible» ou «soi-même». J'ai essayé ceci pour le bloc d'achèvement, mais toujours les opérations ne sont pas libérées.

Mon code pour la class du chargeur d'image est le suivant:

import Foundation import UIKit class ImageLoader { lazy var downloadsInProgress = [NSIndexPath:NSOperation]() lazy var downloadQueue:NSOperationQueue = { var queue = NSOperationQueue() queue.name = "Image Download queue" return queue }() let cache = NSCache() // contains NSData objects for images init() { // Max. cache size is 10% of available physical memory (in MB's) cache.totalCostLimit = 200 * 1024 * 1024 // TODO: change to 10% } /** * Download image based on url for given indexpath. * The download is only started if the indexpath is still present in the downloadsInProgress array */ func startDownloadForUrl(url: Ssortingng, indexPath: NSIndexPath, completion: (imageData: NSData?) -> Void) { // check if download request is already present if downloadsInProgress[indexPath] != nil { return } // check cache if let imageData = self.cache.objectForKey(url) as? NSData { NSOperationQueue.mainQueue().addOperationWithBlock() { //remove indexpath from progress queue self.downloadsInProgress.removeValueForKey(indexPath) completion(imageData: imageData) } return } // prepare the download let downloader = ImageDownloader(url: url) downloader.completionBlock = { [unowned self] in if downloader.cancelled { return } // image is resortingeved from web NSOperationQueue.mainQueue().addOperationWithBlock() { [unowned self] in //remove indexpath from progress queue self.downloadsInProgress.removeValueForKey(indexPath) // add image to cache if downloader.imageData != nil { self.cache.setObject(downloader.imageData!, forKey: url, cost: downloader.imageData!.length) } completion(imageData: downloader.imageData) } } // add downloader to operations in progress and start the operation NSOperationQueue.mainQueue().addOperationWithBlock() { [unowned self] in self.downloadsInProgress[indexPath] = downloader self.downloadQueue.addOperation(downloader) } } /** * Suspends queue for downloading images */ func suspendAllOperations() { downloadQueue.suspended = true } /** * Resumes queue for downloading images */ func resumeAllOperations() { downloadQueue.suspended = false } /** * Cancels downloads for NOT visible indexpaths. The parameter specifies an array of visible indexpaths! */ func cancelDownloads(visibleIndexPaths: [NSIndexPath]) { let allPendingOperations = Set(downloadsInProgress.keys) let visiblePaths = Set(visibleIndexPaths) // cancel all pending operations for indexpaths that are not visible var toBeCancelled = allPendingOperations toBeCancelled.subtractInPlace(visiblePaths) for indexPath in toBeCancelled { if let pendingDownloadOperation = downloadsInProgress[indexPath] { pendingDownloadOperation.cancel() } downloadsInProgress.removeValueForKey(indexPath) } } } class ImageDownloader: NSOperation { var url: Ssortingng var imageData: NSData? init(url: Ssortingng) { self.url = url } override func main() { if self.cancelled { return } if let imageUrl = NSURL(ssortingng: url) { // resortingeve data from web setNetworkActivityIndicatorVisible(true) imageData = NSData(contentsOfURL: imageUrl) setNetworkActivityIndicatorVisible(false) if self.cancelled { imageData = nil return } // scale image if imageData != nil { if let image = UIImage(data: imageData!) { let imageData2 = UIImageJPEGRepresentation(image, 1.0) let compressionRate = Float(imageData!.length) / Float(imageData2!.length) let scaleWidth = 244 / image.size.width let scaleHeight = 244 / image.size.height let imageScale = min(scaleWidth, scaleHeight) let rect = CGRectMake(0.0, 0.0, image.size.width * imageScale, image.size.height * imageScale) UIGraphicsBeginImageContext(rect.size) image.drawInRect(rect) let scaledImage = UIGraphicsGetImageFromCurrentImageContext() let scaledImageData = UIImageJPEGRepresentation(scaledImage, CGFloat(compressionRate)) UIGraphicsEndImageContext() imageData = scaledImageData } } } } private func setNetworkActivityIndicatorVisible(visible: Bool) { NSOperationQueue.mainQueue().addOperationWithBlock() { let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate appDelegate.setNetworkActivityIndicatorVisible(visible) } } } 

Où exactement dois-je créer le cycle de retenue? Et comment puis-je résoudre cela? Quand devrais-je utiliser «non utilisé» et quand devrais-je utiliser «faible»?

J'apprécierais que quelqu'un puisse expliquer la solution, ainsi je peux apprendre de mon erreur.

J'ai trouvé le problème. Le cycle de rétention n'est pas provoqué par le referencement de soi, mais en référençant le NSOperation dans le bloc d'achèvement de NSOperation!

Dans la fonction startDownloadForUrl (…) je déclare le téléchargeur de variable. Ensuite, je déclare un bloc d'achèvement pour cette variable. Dans ce bloc d'achèvement, je reference le téléchargeur de variable. Cela provoque le cycle de conservation.

J'ai résolu cela en utilisant [téléchargeur sans propriétaire] dans le bloc d'achèvement.

Cela a créé un autre problème. Dans le bloc d'achèvement, j'appelle de manière asynchronous le thread principal. Dans cet appel, la variable downloader.imageData a été utilisée. En raison de cet appel asynchronous, le NSOperation peut être déjà terminé et le téléchargeur de variable peut ne plus exister. Pour éviter les plantages, je déclare une nouvelle variable pour l'imageData, de sorte que datatables seront toujours disponibles lorsqu'elles seront utilisées dans le thread principal.

Le bloc d'achèvement ressemble maintenant à:

 downloader.completionBlock = { [unowned downloader] in if downloader.cancelled { return } let imageData = downloader.imageData // retain the imageData. It will be used asynchrounous in the main thread. The downloader operation might already be finished and downloader will no longer exists. // image is resortingeved from web NSOperationQueue.mainQueue().addOperationWithBlock() { //remove indexpath from progress queue self.downloadsInProgress.removeValueForKey(indexPath) // add image to cache if imageData != nil { self.cache.setObject(imageData!, forKey: url, cost: imageData!.length) } completion(imageData: imageData) } }