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

piwo6bdm  于 2024-01-05  发布在  Swift
关注(0)|答案(2)|浏览(119)

我已经写了几个月的应用程序,现在刚刚开始检查内存泄漏-原来我有很多- 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

  1. func applySnapshotUsing(sectionIDs: [SectionIdentifierType], itemsBySection: [SectionIdentifierType: [ItemIdentifierType]],animatingDifferences: Bool, sectionsRetainedIfEmpty: Set<SectionIdentifierType> = Set<SectionIdentifierType>()) {
  2. var snapshot = NSDiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType>()
  3. for sectionID in sectionIDs {
  4. guard let sectionItems = itemsBySection[sectionID], sectionItems.count > 0 || sectionsRetainedIfEmpty.contains(sectionID) else { continue }
  5. snapshot.appendSections([sectionID])
  6. snapshot.appendItems(sectionItems, toSection: sectionID)
  7. }
  8. self.apply(snapshot, animatingDifferences: animatingDifferences)
  9. }
  10. func createDataSource() -> DataSourceType {
  11. //use DataSourceType closure provided by UICollectionViewDiffableDataSource class to setup each collectionViewCell
  12. let dataSource = DataSourceType(collectionView: collectionView) { [weak self] (collectionView, indexPath, item) in
  13. //loops through each 'Item' in itemsBySection (provided to the DataSource snapshot) and expects a UICollectionView cell to be returned
  14. //unwrap self
  15. guard let self = self else {
  16. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SeeMore", for: indexPath)
  17. return cell
  18. }
  19. //figure out what type of ItemIdentifier we're dealing with
  20. switch item {
  21. case .placeBox(let place):
  22. //most common cell -> will display an image and Place name in a square box
  23. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Restaurant", for: indexPath) as! RestaurantBoxCollectionViewCell
  24. //Place object stored in each instance of ViewModel.Item and can be used to configure the cell
  25. cell.placeID = place.ID
  26. cell.segment = self.model.segment
  27. //fetch image with cells fetchImage function
  28. //activity indicator stopped once the image is returned
  29. cell.imageRequestTask?.cancel()
  30. cell.imageRequestTask = nil
  31. cell.restaurantPic.image = nil
  32. cell.fetchImage(imageURL: place.imageURL)
  33. //image task will take time so animate an activity indicator to show activity in progress
  34. cell.activityIndicator.isHidden = false
  35. cell.activityIndicator.startAnimating()
  36. cell.restaurantNameLabel.text = place.name
  37. //setup long press gesture recognizer - currently contains 1 context menu item (<3 restaurant)
  38. let lpgr : UILongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.handleLongPress))
  39. lpgr.minimumPressDuration = 0.5
  40. lpgr.delegate = cell as? UIGestureRecognizerDelegate
  41. lpgr.delaysTouchesBegan = true
  42. self.collectionView?.addGestureRecognizer(lpgr)
  43. //return cell for each item
  44. return cell
  45. case .header:
  46. //top cell displaying the city's header image
  47. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Header", for: indexPath) as! CityHeaderCollectionViewCell
  48. self.imageRequestTask = Task {
  49. if let image = try? await ImageRequest(path: self.city.headerImageURL).send() {
  50. cell.cityHeaderImage.image = image
  51. }
  52. self.imageRequestTask = nil
  53. }
  54. return cell
  55. }
  56. }
  57. dataSource.supplementaryViewProvider = { (collectionView, kind, indexPath) in
  58. //Setup Section headders
  59. let header = collectionView.dequeueReusableSupplementaryView(ofKind: "SectionHeader", withReuseIdentifier: "HeaderView", for: indexPath) as! NamedSectionHeaderView
  60. //get section from IndexPath
  61. let section = dataSource.snapshot().sectionIdentifiers[indexPath.section]
  62. //find the section we're currently in & set the section header label text according
  63. switch section {
  64. case .genreBox(let genre):
  65. header.nameLabel.text = genre
  66. case .neighborhoodBox(let neighborhood):
  67. header.nameLabel.text = "The best of \(neighborhood)"
  68. case .genreList(let genre):
  69. header.nameLabel.text = genre
  70. case .neighborhoodList(let neighborhood):
  71. header.nameLabel.text = "The best of \(neighborhood)"
  72. default:
  73. print(indexPath)
  74. header.nameLabel.text = "favorites"
  75. }
  76. return header
  77. }
  78. return dataSource
  79. }

字符串

oogrdqng

oogrdqng1#

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

roejwanj

roejwanj2#

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

  1. dataSource.supplementaryViewProvider = { [weak self] (collectionView, kind, indexPath) in
  2. guard let self else { return }
  3. [...]
  4. // Explicitly using self (as weak reference above) in here
  5. let section = self.dataSource.snapshot().sectionIdentifiers[indexPath.section]
  6. [...]
  7. return header
  8. }

字符串

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

  1. dataSource.supplementaryViewProvider = { [weak dataSource] (collectionView, kind, indexPath) in
  2. guard let dataSource else { return }
  3. [...]
  4. // Using a weak reference of the dataSource
  5. let section = dataSource.snapshot().sectionIdentifiers[indexPath.section]
  6. [...]
  7. return header
  8. }


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

展开查看全部

相关问题