Swift:使用NSOperation保持周期 [英] Swift: Retain cycle with NSOperation

查看:131
本文介绍了Swift:使用NSOperation保持周期的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的应用程序中,我使用图像加载器类从网络加载图像以进行收集视图.当图像的单元格在集合视图中不再可见时,该类会跟踪下载操作并取消下载操作.此实现基于NSOperation的raywenderlich教程: http://www.raywenderlich. com/76341/use-nsoperation-nsoperationqueue-swift .

In my app I use an image loader class to load images from the web for a collection view. The class keeps track of the download operations and cancels them when the cells for the images are no longer visible in the collection view. This implementation is based on the raywenderlich tutorial for NSOperation: http://www.raywenderlich.com/76341/use-nsoperation-nsoperationqueue-swift.

我使用NSOperation从网络上下载图像.我注意到在Instruments公司,没有发布任何NSoperation.这会增加下载的每个图像的已用内存.在完成代码块中,我引用自我".所以我发现我创建了一个保留周期.

I use NSOperation for downloading an image from the web. I noticed with Instruments that none of the NSoperations is released. This creates an increase of the used memory for each image that is downloaded. In the completion block I references 'self'. So I figured out that I created a retain cycle.

我在互联网上阅读了很多例子.我了解我可以将捕获列表与弱我"或无主我"一起使用.我为完成块尝试了此操作,但仍未释放操作.

I read a lot of examples on internet. I understand that I can use capture lists with 'weak self' or 'unowned self'. I tried this for the completion block, but still the operations are not released.

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

My code for the image loader class is as follows:

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

我到底在哪里创建保留周期?我该如何解决呢? 什么时候应该使用无主",什么时候应该使用弱"?

Where exactly do I create the retain cycle? And how do I solve this? When should I use 'unowned' and when should I use 'weak'?

如果有人可以解释解决方案,我将不胜感激,因此我可以从错误中学习.

I would appreciate it if someone can explain the solution, so I can learn from my mistake.

推荐答案

我发现了问题.保留周期不是由引用自身引起的,而是由NSOperation的完成块中的NSOperation引起的!

I found the problem. The retain cycle is not caused by referencing self, but by referencing the NSOperation in the completion block of the NSOperation!

在函数startDownloadForUrl(...)中,我声明了变量 downloader .接下来,我声明此变量的完成块.在此完成块中,我引用变量 downloader .这会导致保留周期.

In the function startDownloadForUrl(...) I declare the variable downloader. Next I declare an completion block for this variable. In this completion block I reference the variable downloader. This causes the retain cycle.

我通过在完成代码块中使用[unknown downloader]解决了这个问题.

I solved this by using [unowned downloader] within the completion block.

这造成了另一个问题.在完成模块中,我异步调用主线程.在此调用中,使用了变量 downloader.imageData .由于此异步调用,NSOperation可能已经结束,并且变量 downloader 可能不再存在.为了避免崩溃,我为imageData声明了一个新变量,因此当在主线程中使用该数据时,这些数据仍然可用.

This created anaother problem. In the completion block I asynchronously call the main thread. In this call the variable downloader.imageData was used. Because of this asynchronous call, the NSOperation may be already ended and the variable downloader may not longer exists. To avoid crashes I declare a new variable for the imageData, so the data will still be available when used in the main thread.

完成块现在看起来像:

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

这篇关于Swift:使用NSOperation保持周期的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆