【问题标题】:Image in collectionView cell causing Terminated due to memory issue由于内存问题,collectionView 单元格中的图像导致终止
【发布时间】:2023-03-05 01:00:01
【问题描述】:

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

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

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

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

3- 我使用Instruments > Leaks 来查找内存泄漏,并且出现了一些 Foundation 泄漏,但是当我关闭 vc2 时,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])
    }
}

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
}

后细胞

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
}

【问题讨论】:

  • “这些图像最初是用 iPhone 全屏相机拍摄的” 就是这样。您不可能一次加载 20 张相机图像。您说您的单元格大小为 40x40,因此您的图像大小需要为 40x40(或更小)。
  • @matt 在 SDWebImage 回调中,我使用 stackoverflow.com/a/31314494/4833705 调整了图像的大小。它没有工作
  • 您认为一遍又一遍地添加微调器会导致任何这种情况吗?我不明白一遍又一遍地添加和删除视图的意义。可能是图像,但一遍又一遍地添加视图让我想知道。为什么不离开他们?
  • @agibson007 我完全注释掉了微调器,看看会发生什么,同样的问题。这是100%因为图像。由于细胞重复使用,我从不离开它们,并且错误的图像永远不会出现在错误的细胞上。我构建了大约 7 个应用程序或其他东西,这与我在所有应用程序中使用的程序相同,我以前从未遇到过问题。 ARKit 和节点在内存消耗方面有很大的不同。它肯定与图像有关,因为当我使用通用图像时,它可以正常工作。
  • “它不起作用”是什么意思?如果您因为使用 Instruments 很容易知道的图像而导致内存不足,那么它不会调整它们的大小。我对 SDWebImage 一无所知,但我知道从磁盘加载一个大图像文件的小版本,没有缓存,也没有内存中的大图像。

标签: ios swift memory-management uiimageview uicollectionviewcell


【解决方案1】:

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

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

【讨论】:

    猜你喜欢
    • 2021-02-05
    • 1970-01-01
    • 1970-01-01
    • 2017-11-16
    • 2018-08-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-17
    相关资源
    最近更新 更多