我已经编写一个应用程序几个月了,刚刚开始检查内存泄漏 - 事实证明我有很多内存泄漏 - 25 种独特的类型(紫色感叹号),如果我足够浏览我的应用程序,总共会超过 500 个.
Xcode 中的内存图主要指向 1.) UIDiffableDataSource 的不同元素,2.) “CFString”/“CFString (Storage)”,以及 3.) “Malloc Blocks”。 Leaks Instrument 给了我类似的反馈,说大约 90% 的内存泄漏都是这些“Malloc 块”,并且还说责任框架是 newJSONString 或 newJSONValue。在许多泄漏中,都涉及一个“4 节点循环”,其中包含 Swift Closure Context、My UIDiffableDataSource
这让我相信我在 UICollectionViewDiffableDataSource 的自定义数据源闭包中创建了某种类型的内存泄漏。我尝试过将 DataSource 设为弱变量,并且尝试在每个闭包中使用弱 self 和无主 self - 尤其是创建数据源的闭包,但它没有任何影响。
任何额外的信息或额外的资源都会非常有帮助。到目前为止,我发现 iOS Academy 和 Mark Moeykens YouTube 视频有助于理解基础知识,但在将其应用到我的应用程序中时遇到困难。如果有帮助的话,我可以提供代码块,但是有很多代码可能会导致它,并且不确定要在这里转储什么。
发布此内容后我找到了一些额外的信息。基于 这篇文章,我能够使用 Backtrace 窗格,并且 95% 的内存泄漏都指向我的 createDataSource() 和 applySnapshotUsing(sectionIDs, itemsBySection) 方法 [删除了下面的那些]。
我觉得我已经弄清楚了泄漏的根源,但仍然困惑于如何或是否应该修复泄漏......我尝试过将闭包设置为“弱自我”并将任何可能的变量设置为弱,但无济于事。任何帮助将不胜感激:)
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
}
经过数月的调试,我想我终于发现了问题。由于某种原因,我不完全理解我的集合视图(supplementaryViewProvider)中的节标题导致我的许多对象被捕获在内存中并且没有被释放。
将此行添加到我的 CollectionViewController 的 deinit{} 中完全消除了我的应用程序中的内存泄漏(使用 Memory Graph Debugger & Leaks Instrument 进行了验证):
deinit { dataSource.supplementaryViewProvider = nil }
为了得出这个结论,我一点一点地重建了应用程序的相关部分,并在每次添加新代码时进行测试,以识别泄漏的位置。
如果能进一步澄清为什么会导致泄漏,那就太好了。