由于内存问题,导致CollectionView单元格中的图像终止

问题描述

我为这个问题看了几个答案,但是都没有帮助。

vc1是常规vc,我从firebase抓取20张图像,在vc2中,我使用ARKit在用户选择时显示这些图像。我在vc2中有一个collectionView,可从firebase中分页另外20张图像。问题是当接下来的20张图像正在加载并且我开始滚动时,该应用程序崩溃并显示Message from debugger: Terminated due to memory issue。滚动这些新图像时,我查看了内存图,它最多拍摄了1个演出,所以这是崩溃的原因。 ARKit和我周围漂浮的节点也会导致内存增加,但这并不是造成崩溃的原因,如下所述。

1-在单元格内部,我使用SDWebImage在imageView中显示图像。一旦我注释掉SDWebImage一切正常,滚动就很流畅,并且没有更多的崩溃,但是我当然看不到图像。我切换到URLSession.shared.dataTask,并且再次出现相同的内存问题。

2-最初使用iPhone全屏相机拍摄图像,并使用jpegData(compressionQuality: 0.3).保存,像元大小为40x40。在SDWebImage完成块中,我尝试进行resize the image,但是内存崩溃仍然持续。

3-我使用Instruments> Leaks查找内存泄漏,出现了一些基金会泄漏,但是当我关闭vc2时,Deinit始终运行。在vc2内部,没有任何长时间运行的计时器或循环,我在所有闭包中都使用[weak self]

4-正如我在第二点所述,问题肯定是imageView / image,因为一旦我从流程中将其删除,一切都会正常进行。如果我不显示任何图像,则一切正常(将出现40个红色imageView,其中没有图像)。

如何解决此问题?

通过分页操作可以拉出更多图片

for child in snapshot.children.allObjects as! [DataSnapshot] {

    guard let dict = child.value as? [String:Any] else { continue }
    let post = Post(dict: dict)

    datasource.append(post)

    let lastIndex = datasource.count - 1
    let indexPath = IndexPath(item: lastIndex,section: 0)
    UIView.performWithoutAnimation {
        collectionView.insertItems(at: [indexPath])
    }
}

cellForItem:

func collectionView(_ collectionView: UICollectionView,cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: self.cellId,for: indexPath) as! PostCell

    cell.resetAll()
    cell.post = dataSource[indexPath.item]

    return cell
}

PostCell

private lazy var imageView: UIImageView = {
    let iv = UIImageView()
    iv.translatesAutoresizingMaskIntoConstraints = false
    iv.contentMode = .scaleAspectFill
    iv.layer.cornerRadius = 15
    iv.layer.masksToBounds = true
    iv.backgroundColor = .red
    iv.isHidden = true
    return iv
}()

private lazy var spinner: UIActivityIndicatorView = {
    let actIndi = UIActivityIndicatorView(style: UIActivityIndicatorView.Style.medium) // ...
}()

var post: Post? {
    didSet {

        guard let urlSr = post?.urlStr,let url = URL(string: urlSr) else { return }

        spinner.startAnimating()

        // if I comment this out everything works fine
        imageView.sd_setimage(with: url,placeholderImage: placeHolderImage,options: [],completed: { 
            [weak self] (image,error,cache,url) in

            // in here I tried resizing the image but no diff

            dispatchQueue.main.async { [weak self] in
               self?.showAll()
            }
         })

        setAnchors() 
    }
}

func resetAll() {
     spinner.stopAnimating()
     imageView.image = nil
     imageView.removeFromSuperView()
     imageView.isHidden = true
}

func showAll() {
     spinner.stopAnimating()
     imageView.isHidden = false
}

func setAnchors() {

    contentView.addSubview(imageView)
    imageView.addSubview(cellSpinner)
    // imageView is pinned to all sides
}

解决方法

在评论中@matt是100%正确的,问题是图像对于40x40的collectionView太大,我需要调整图像的大小。由于某种原因,我问题中的调整大小链接不起作用,因此我将其用于resize image。我还使用了URLSession和这个answer

我做了10个步骤,全部注释掉了

// 0. set this anywhere outside any class
let imageCache = NSCache<AnyObject,AnyObject>()

PostCell:

private lazy var imageView: UIImageView = { ... }()
private lazy var spinner: UIActivityIndicatorView = { ... }()

// 1. override prepareForReuse,cancel the task and set it to nil,and set the imageView.image to nil so that the wrong images don't appear while scrolling
override func prepareForReuse() {
    super.prepareForReuse()
    
    task?.cancel()
    task = nil
    imageView.image = nil
}

// 2. add a var to start/stop a urlSession when the cell isn't on screen
private var task: URLSessionDataTask?

var post: Post? {
    didSet {

        guard let urlStr = post?.urlStr else { return }

        spinner.startAnimating()

        // 3. crate the new image from this function
        createImageFrom(urlStr)

        setAnchors() 
    }
}

func createImageFrom(_ urlStr: String) {
    
    if let cachedImage = imageCache.object(forKey: urlStr as AnyObject) as? UIImage {
        
        // 4. if the image is in cache call this function to show the image
        showAllAndSetImageView(with: cachedImage)
        
    } else {
        
        guard let url = URL(string: urlStr) else { return }

        // 5. if the image is not in cache start a urlSession and initialize it with the task variable from step 2
        task = URLSession.shared.dataTask(with: url,completionHandler: {
            [weak self](data,response,error) in
            
            if let error = error { return }
            
            if let response = response as? HTTPURLResponse {
                print("response.statusCode: \(response.statusCode)")
                guard 200 ..< 300 ~= response.statusCode else { return }
            }
            
            guard let data = data,let image = UIImage(data: data) else { return }
            
            // 6. add the image to cache
            imageCache.setObject(image,forKey: photoUrlStr as AnyObject)
            
            DispatchQueue.main.async { [weak self] in

                // 7. call this function to show the image
                self?.showAllAndSetImageView(with: image)
            }
            
        })
        task?.resume()
    }
}

func showAllAndSetImageView(with image: UIImage) {

    // 8. resize the image
    let resizedImage = resizeImageToCenter(image: image,size: 40) // can also use self.frame.height

    imageView.image = resizeImage

    showAll()
}

// 9. func to resize the image
func resizeImageToCenter(image: UIImage,size: CGFloat) -> UIImage {
    let size = CGSize(width: size,height: size)

    // Define rect for thumbnail
    let scale = max(size.width/image.size.width,size.height/image.size.height)
    let width = image.size.width * scale
    let height = image.size.height * scale
    let x = (size.width - width) / CGFloat(2)
    let y = (size.height - height) / CGFloat(2)
    let thumbnailRect = CGRect.init(x: x,y: y,width: width,height: height)

    // Generate thumbnail from image
    UIGraphicsBeginImageContextWithOptions(size,false,0)
    image.draw(in: thumbnailRect)
    let thumbnail = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return thumbnail!
}

func resetAll() { ... }
func showAll() { ... }
func setAnchors() { ... }