UIDiffableDataSource、CFString、Malloc 块等导致 Swift 内存泄漏

问题描述 投票:0回答:1

我已经编写一个应用程序几个月了,刚刚开始检查内存泄漏 - 事实证明我有很多内存泄漏 - 25 种独特的类型(紫色感叹号),如果我足够浏览我的应用程序,总共会超过 500 个.

Xcode 中的内存图主要指向 1.) UIDiffableDataSource 的不同元素,2.) “CFString”/“CFString (Storage)”,以及 3.) “Malloc Blocks”。 Leaks Instrument 给了我类似的反馈,说大约 90% 的内存泄漏都是这些“Malloc 块”,并且还说责任框架是 newJSONString 或 newJSONValue。在许多泄漏中,都涉及一个“4 节点循环”,其中包含 Swift Closure Context、My UIDiffableDataSource 对象和 __UIDiffableDataSource。 CFString 只有一个对象 - CFString,没有其他任何东西。我将尝试添加显示 2 个示例的图像,但 StackO 限制了我添加它们的能力。

这让我相信我在 UICollectionViewDiffableDataSource 的自定义数据源闭包中创建了某种类型的内存泄漏。我尝试过将 DataSource 设为弱变量,并且尝试在每个闭包中使用弱 self 和无主 self - 尤其是创建数据源的闭包,但它没有任何影响。

  • 当 Memory Graph/Leaks Instrument 指向这些通用“CFString”或“Malloc Block”对象时,这意味着什么?
  • 有谁知道实际上可能导致这些内存泄漏的原因以及我如何解决它们?
  • 内存图和泄漏工具是否曾经错误报告泄漏/产生不必要的噪音,或者这些是否合法?

任何额外的信息或额外的资源都会非常有帮助。到目前为止,我发现 iOS Academy 和 Mark Moeykens YouTube 视频有助于理解基础知识,但在将其应用到我的应用程序中时遇到困难。如果有帮助的话,我可以提供代码块,但是有很多代码可能会导致它,并且不确定要在这里转储什么。

overview of errors

4 node cycle (diffableDataSource)

CFString (Storage)

发布此内容后我找到了一些额外的信息。基于 这篇文章,我能够使用 Backtrace 窗格,并且 95% 的内存泄漏都指向我的 createDataSource() 和 applySnapshotUsing(sectionIDs, itemsBySection) 方法 [删除了下面的那些]。

我觉得我已经弄清楚了泄漏的根源,但仍然困惑于如何或是否应该修复泄漏......我尝试过将闭包设置为“弱自我”并将任何可能的变量设置为弱,但无济于事。任何帮助将不胜感激:)

Backtrace1 Backtrace2

func applySnapshotUsing(sectionIDs: [SectionIdentifierType], itemsBySection: [SectionIdentifierType: [ItemIdentifierType]],animatingDifferences: Bool, sectionsRetainedIfEmpty: Set<SectionIdentifierType> = Set<SectionIdentifierType>()) {
    var snapshot = NSDiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType>()
    for sectionID in sectionIDs {
        guard let sectionItems = itemsBySection[sectionID], sectionItems.count > 0 || sectionsRetainedIfEmpty.contains(sectionID) else { continue }
        snapshot.appendSections([sectionID])
        
        snapshot.appendItems(sectionItems, toSection: sectionID)
    }
    
    self.apply(snapshot, animatingDifferences: animatingDifferences)
}
func createDataSource() -> DataSourceType {
    //use DataSourceType closure provided by UICollectionViewDiffableDataSource class to setup each collectionViewCell
    let dataSource = DataSourceType(collectionView: collectionView) { [weak self] (collectionView, indexPath, item) in
        //loops through each 'Item' in itemsBySection (provided to the DataSource snapshot) and expects a UICollectionView cell to be returned
        
        //unwrap self
        guard let self = self else {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SeeMore", for: indexPath)
            return cell
        }
        
        //figure out what type of ItemIdentifier we're dealing with
        switch item {
        case .placeBox(let place):
            //most common cell -> will display an image and Place name in a square box
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Restaurant", for: indexPath) as! RestaurantBoxCollectionViewCell
            
            //Place object stored in each instance of ViewModel.Item and can be used to configure the cell
            cell.placeID = place.ID
            cell.segment = self.model.segment
            
            //fetch image with cells fetchImage function
            //activity indicator stopped once the image is returned
            cell.imageRequestTask?.cancel()
            cell.imageRequestTask = nil
            cell.restaurantPic.image = nil
            
            cell.fetchImage(imageURL: place.imageURL)
            
            //image task will take time so animate an activity indicator to show activity in progress
            cell.activityIndicator.isHidden = false
            cell.activityIndicator.startAnimating()
            
            cell.restaurantNameLabel.text = place.name
            
            //setup long press gesture recognizer - currently contains 1 context menu item (<3 restaurant)
            let lpgr : UILongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.handleLongPress))
            lpgr.minimumPressDuration = 0.5
            lpgr.delegate = cell as? UIGestureRecognizerDelegate
            lpgr.delaysTouchesBegan = true
            self.collectionView?.addGestureRecognizer(lpgr)

            //return cell for each item
            return cell
        case .header:
            //top cell displaying the city's header image
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Header", for: indexPath) as! CityHeaderCollectionViewCell
            
            self.imageRequestTask = Task {
                if let image = try? await ImageRequest(path: self.city.headerImageURL).send() {
                    cell.cityHeaderImage.image = image
                }
                self.imageRequestTask = nil
            }
            
            return cell
        }
    }
    
    dataSource.supplementaryViewProvider = { (collectionView, kind, indexPath) in
        //Setup Section headders
        let header = collectionView.dequeueReusableSupplementaryView(ofKind: "SectionHeader", withReuseIdentifier: "HeaderView", for: indexPath) as! NamedSectionHeaderView
        
        //get section from IndexPath
        let section = dataSource.snapshot().sectionIdentifiers[indexPath.section]
        //find the section we're currently in & set the section header label text according
        switch section {
        case .genreBox(let genre):
            header.nameLabel.text = genre
        case .neighborhoodBox(let neighborhood):
            header.nameLabel.text = "The best of \(neighborhood)"
        case .genreList(let genre):
            header.nameLabel.text = genre
        case .neighborhoodList(let neighborhood):
            header.nameLabel.text = "The best of \(neighborhood)"
        default:
            print(indexPath)
            header.nameLabel.text = "favorites"
        }
        
        return header
    }
    
    return dataSource
}
ios swift memory-management memory-leaks instruments
1个回答
0
投票

经过数月的调试,我想我终于发现了问题。由于某种原因,我不完全理解我的集合视图(supplementaryViewProvider)中的节标题导致我的许多对象被捕获在内存中并且没有被释放。

将此行添加到我的 CollectionViewController 的 deinit{} 中完全消除了我的应用程序中的内存泄漏(使用 Memory Graph Debugger & Leaks Instrument 进行了验证):

deinit { dataSource.supplementaryViewProvider = nil }

为了得出这个结论,我一点一点地重建了应用程序的相关部分,并在每次添加新代码时进行测试,以识别泄漏的位置。

如果能进一步澄清为什么会导致泄漏,那就太好了。

© www.soinside.com 2019 - 2024. All rights reserved.