繁体   English   中英

collectionView 单元格中的图像因内存问题而导致终止

[英]Image in collectionView cell causing Terminated due to memory issue

我查看了这个问题的几个答案,但没有一个帮助。

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

1- 在单元格内,我使用SDWebImageSDWebImage中显示图像。 一旦我注释掉SDWebImage一切正常,滚动就会流畅,不再崩溃,但当然我看不到图像。 我切换到URLSession.shared.dataTask并且同样的内存问题再次发生。

2- 图像最初是用 iPhone 全屏相机拍摄的,并用jpegData(compressionQuality: 0.3).保存jpegData(compressionQuality: 0.3). 单元格大小为 40x40。 SDWebImage完成块内,我尝试调整图像大小,但内存崩溃仍然存在。

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

4- 正如我在第二点中所述,问题绝对是 imageView/image,因为一旦我将其从进程中删除,一切正常。 如果我不显示任何图像,一切正常(将出现 40 个内部没有图像的红色图像视图)。

我能做些什么来解决这个问题?

分页以拉取更多图像

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

单元格:

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
}

后单元格

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 来说太大了,我需要调整图像的大小。 出于某种原因,我问题中的调整大小链接不起作用,所以我用它来调整图像大小 我还使用了 URLSession 和这个答案

我做了10步,都注释掉了

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

后单元格:

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() { ... }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM