斯威夫特:保持与NSOperation周期

在我的应用程序中,我使用图像加载器类从Web加载图像的集合视图。 该类会跟踪下载操作,并在收集视图中不再显示图像的单元格时将其取消。 这个实现基于NSOperation的raywenderlich教程: http ://www.raywenderlich.com/76341/use-nsoperation-nsoperationqueue-swift。

我使用NSOperation从网上下载图片。 我注意到仪器没有任何NSoperations被释放。 这会增加下载的每个图像的已用内存。 在完成块我引用“自我”。 所以我发现我创造了一个保留周期。

我在互联网上阅读了很多例子。 我知道我可以使用“弱自我”或“无主”的捕获列表。 我尝试了这个完成块,但仍然没有释放操作。

我的图像加载器类的代码如下所示:

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: String, 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 retrieved 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: String var imageData: NSData? init(url: String) { self.url = url } override func main() { if self.cancelled { return } if let imageUrl = NSURL(string: url) { // retrieve 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) } } } 

我在哪里创build保留周期? 我该如何解决这个问题? 什么时候应该使用“无主”,什么时候应该使用“弱”?

如果有人能解释这个解决scheme,我将不胜感激,所以我可以从我的错误中吸取教训。

我发现了这个问题。 保留周期不是由引用自己引起的,而是通过在NSOperation的完成块中引用NSOperation!

在函数startDownloadForUrl(…)我声明variables下载 。 接下来我为这个variables声明一个完成块。 在这个完成块中,我引用了variables下载器 。 这导致保留周期。

我通过在完成块中使用[unowned downloader]解决了这个问题。

这造成了另一个问题。 在完成块我asynchronous调用主线程。 在这个调用中使用了variablesdownloader.imageData 。 由于这种asynchronous调用,NSOperation可能已经结束,variables下载器可能不再存在。 为了避免崩溃,我为imageData声明了一个新variables,所以在主线程中使用这些数据仍然可用。

完成块现在看起来像:

 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 retrieved 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) } }