Swift内存泄漏与UIDiffableDataSource,CFString,Malloc Blocks和其他

piwo6bdm  于 11个月前  发布在  Swift
关注(0)|答案(2)|浏览(92)

我已经写了几个月的应用程序,现在刚刚开始检查内存泄漏-原来我有很多- 25个独特的类型(紫色感叹号)和超过500个,如果我浏览我的应用程序足够。
Xcode中的内存图主要指向1.)UIDiffableDataSource的不同元素,2.)“CFString”/“CFString(Storage)",3.)“Malloc Blocks”。Leaks Instrument也给了我类似的反馈,我说大约90%的内存泄漏是这些“Malloc块”,还说负责帧是newJSONString或newJSONValue。在许多泄漏中,有一个“4节点-cycle”包含Swift Closure Context,My UIDiffableDataSource<Item,Section>对象和__UIDiffableDataSource。CFString只有一个对象- CFString,没有其他。我将尝试添加显示2个示例的图像,但StackO限制了我添加它们的能力。
这让我相信我在为UICollectionViewDiffableDataSource自定义的dataSource闭包中创建了某种类型的内存泄漏。我试着让DataSource成为一个弱var &我试着在每个闭包中使用弱self和unowned self--特别是创建闭包的闭包,但它没有任何影响。

  • 当Memory Graph/Leaks Instrument指向这些通用的“CFString”或“Malloc Block”对象时,这意味着什么?
  • 有没有人知道是什么导致了这些内存泄漏,以及如何解决它们?
  • 内存图和泄漏工具是否会误报泄漏/产生不必要的噪音,或者这些是合法的?

任何额外的信息或额外的资源将是超级有帮助的。到目前为止,我发现iOS学院和Mark Moeykens YouTube视频有助于理解基础知识,但在将其应用于我的应用程序时遇到了麻烦。如果有帮助,我可以提供代码块,但有很多代码可能会导致它,并且不确定在这里倾倒什么。
overview of errors
4 node cycle (diffableDataSource)
CFString (Storage)
在发布这个之后,我能够找到一些额外的信息。基于this post,我能够使用回溯窗格,95%的内存泄漏都指向我的DataSource()和我的applySnapshotUsing(sectionIDs,itemsBySection)方法[下面删除了这些]。
我觉得我已经找到了漏洞的来源,但仍然在如何或是否应该修复漏洞上遇到了困难......我试过将闭包设置为“弱自我”,并将任何可能的变量设置为弱,但都无济于事。任何帮助都将不胜感激:)
Backtrace1Backtrace2

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
}

字符串

oogrdqng

oogrdqng1#

经过几个月的调试,我想我终于找到了问题所在。出于某种原因,我不完全理解我的集合视图(集合视图提供程序)中的节头导致我的许多对象被捕获在内存中,而不是被释放。
将这一行添加到我的CollectionViewController的deinit{}中,完全消除了我的应用程序中的内存泄漏(使用Memory Graph Trigger和Leaks Instrument验证):
第一个月
为了得出这个结论,我一点一点地重建了我的应用程序的相关部分,并在每次添加新代码时进行测试,以确定漏洞在哪里引入。
任何额外的澄清,为什么这导致泄漏将是伟大的太。

roejwanj

roejwanj2#

我知道它很旧,但我认为它可能有用。
当您访问dataSource以获取section enum值时,它可以引用在该函数中创建的dataSource,这是泄漏的。
你可以在这里有两个选择:
使用[weak self]并明确使用self.dataSource

dataSource.supplementaryViewProvider = { [weak self] (collectionView, kind, indexPath) in
        guard let self else { return }

        [...]
        
        // Explicitly using self (as weak reference above) in here
        let section = self.dataSource.snapshot().sectionIdentifiers[indexPath.section]
        
        [...]

        return header
    }

字符串

您可以捕获dataSource [weak dataSource]的弱引用并正常使用它

dataSource.supplementaryViewProvider = { [weak dataSource] (collectionView, kind, indexPath) in
        guard let dataSource else { return }

        [...]
        
        // Using a weak reference of the dataSource
        let section = dataSource.snapshot().sectionIdentifiers[indexPath.section]
        
        [...]

        return header
    }


只是提醒一下,如果你正在使用来自self的任何东西,并且你想非常明确,你应该使用[weak self, weak dataSource]来只获取那些的弱引用。

相关问题