[英]SwiftUI Memory leak Issue in ForEach
我在屏幕上有一个垂直列表来显示图像类别,每个类别/列表都包含水平显示的图像列表。 (附图供参考)
现在,当我水平或垂直滚动时,应用程序由于内存泄漏而崩溃。 我猜很多人在ForEach
循环中都面临这个问题。
我也尝试使用List
而不是ForEach
和ScrollView
进行垂直/水平滚动,但不幸的是面临同样的问题。
下面的代码是创建垂直列表的主视图:
@ObservedObject var mainCatData = DataFetcher.sharedInstance
var body: some View {
NavigationView {
VStack {
ScrollView(showsIndicators: false) {
LazyVStack(spacing: 20) {
ForEach(0..<self.mainCatData.arrCatData.count, id: \.self) { index in
self.horizontalImgListView(index: index)
}
}
}
}.padding(.top, 5)
.navigationBarTitle("Navigation Title", displayMode: .inline)
}
}
我使用下面的代码在每个类别中创建水平列表,我使用LazyHStack
、 ForEach
循环和ScrollView
@ViewBuilder
func horizontalImgListView(index : Int) -> some View {
let dataContent = self.mainCatData.arrCatData[index]
VStack {
HStack {
Spacer().frame(width : 20)
Text("Category \(index + 1)").systemFontWithStyle(style: .headline, design: .rounded, weight: .bold)
Spacer()
}
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack(spacing: 20) {
ForEach(0..<dataContent.catData.count, id: \.self) { count in
VStack(spacing : 0) {
VStack(spacing : 0) {
if let arrImgNames = themeContent.catData[count].previewImgName {
// Use dynamic image name and it occurs app crash & memory issue and it reached above 1.0 gb memory
Image(arrImgNames.first!).resizable().aspectRatio(contentMode: .fit)
// If I will use the same image name then there is no memory issue and it consumes only 75 mb
// Image("Category_Image_1").resizable().aspectRatio(contentMode: .fit)
}
}.frame(width: 150, height: 325).cornerRadius(8.0)
}
}
}
}
}
}
下面是我用来从 json 文件中获取图像并将其显示在列表中的数据模型
class DataFetcher: ObservableObject {
static let sharedInstance = DataFetcher()
@Published var arrCatData = [CategoryModel]()
init() {
do {
if let bundlePath = Bundle.main.url(forResource: FileName.CategoryData, withExtension: "json"),
let jsonData = try? Data(contentsOf: bundlePath) {
let decodedData = try JSONDecoder().decode([CategoryModel].self, from: jsonData)
DispatchQueue.main.async { [weak self] in
self?.arrCatData = decodedData
}
}
} catch {
print("Could not load \(FileName.CategoryData).json data : \(error)")
}
}
}
struct CategoryModel : Codable , Identifiable {
let id: Int
let catName: String
var catData: [CategoryContentDataModel]
}
struct CategoryContentDataModel : Codable {
var catId : Int
var previewImgName : [String]
}
崩溃日志:
malloc: can't allocate region
:*** mach_vm_map(size=311296, flags: 100) failed (error code=3)
(82620,0x106177880) malloc: *** set a breakpoint in malloc_error_break to debug
2021-07-01 18:33:06.934519+0530 [82620:5793991] [framework] CoreUI: vImageDeepmap2Decode() returned 0.
2021-07-01 18:33:06.934781+0530 [82620:5793991] [framework] CoreUI: CUIUncompressDeepmap2ImageData() fails [version 1].
2021-07-01 18:33:06.934814+0530 [82620:5793991] [framework] CoreUI: Unable to decompress 2.0 stream for CSI image block data. 'deepmap2'
(82620,0x106177880) malloc: can't allocate region
:*** mach_vm_map(size=311296, flags: 100) failed (error code=3)
(82620,0x106177880) malloc: *** set a breakpoint in malloc_error_break to debug
注意:所有类别的图像仅从资产加载,如果我将在循环中使用图像的静态名称,则没有内存压力,它只会消耗 75 mb。
我认为存在图像缓存问题。 即使我从资产加载图像,我是否必须管理图像缓存?
谁能帮我解决这个问题? 任何帮助都感激不尽。 谢谢!!
尽量不要在 ForEach 中使用显式 self。 我的 SwiftUI 视图中有一些奇怪的漏洞,切换到隐式 self 似乎可以摆脱它们。
您的主要问题是您使用的是 ScrollView/VStack 与使用列表。 List 就像 UITableView 智能地只维护正在显示的单元格的内容。 ScrollView 不对结构做任何假设,因此保留其中的所有内容。 VStack 是惰性的仅意味着它不会立即分配所有内容。 但是当它滚动到底部(或 HStack 到侧面)时,内存会累积,因为它不会释放不可见的项目
您说您尝试过 List,但该代码是什么样的? 您应该已经替换了 ScrollView 和 LazyVStack。
不幸的是,目前没有水平列表,因此您要么需要滚动自己的列表(可能基于 UICollectionView),要么只需最小化水平行的内存占用。
你的图片尺寸是多少? 图像足够聪明,不需要重新加载重复的内容:单个图像文字有效的原因。 但是,如果您正在加载不同的图像,它们都将保留在内存中。 话虽如此,您应该能够加载许多小型预览图像。 但听起来你的源图像可能没有那么小。
尝试仅使用一列的LazyVGrid
而不是使用Foreach
。
let columns = [GridItem(.flexible(minimum: Device.SCREEN_WIDTH - "Your horizontal padding" , maximum: Device.SCREEN_WIDTH - "Your horizontal padding"))]
ScrollView(.vertical ,showsIndicators: false ){
LazyVGrid(columns: columns,spacing: 25, content: {
ForEach(0..< dataContent.catData.count, id: \.self) { index in
"Your View"
}
}
}
在使用 SwiftUI 框架构建应用程序时,我遇到了同样的问题。 我从服务器(200 毫秒)获取了大约 600 个项目,然后尝试使用 ForEach 在 UI 中显示它。 但它需要3 GB 的 RAM 。 经过研究,我明白这不是 SwiftUI 的问题。 由于循环( for
-loop)而发生内存问题。
我发现了以下内容:
在 ARC Obj-C 之前的手动内存管理时代,必须使用retain () 和release () 来控制 iOS 应用程序的内存流。 由于 iOS 的内存管理基于对象的保留计数工作,因此用户可以使用这些方法来指示对象被引用的次数,以便在该值达到零时可以安全地释放它。
即使循环数百万次,以下代码仍保持在稳定的内存级别。
for _ in 0...9999999 {
let obj = getGiantSwiftClass()
}
但是,如果您的代码处理遗留的 Obj-C 代码,尤其是 iOS 中的旧Foundation类,情况就不同了。 考虑以下加载大量时间的大图像的代码:
func run() {
guard let file = Bundle.main.path(forResource: "bigImage", ofType: "png") else {
return
}
for i in 0..<1000000 {
let url = URL(fileURLWithPath: file)
let imageData = try! Data(contentsOf: url)
}
}
即使我们在 Swift 中,这也会导致 Obj-C 示例中显示的同样荒谬的内存峰值! Data
init 是原始 Obj-C [NSData dataWithContentsOfURL]
的桥梁——不幸的是,它仍然在其中的某个地方调用autorelease 。 就像在 Obj-C 中一样,您可以使用 Swift 版本的@autoreleasepool
来解决这个问题; 没有@的autoreleasepool
:
autoreleasepool {
let url = URL(fileURLWithPath: file)
let imageData = try! Data(contentsOf: url)
}
在您的情况下,请在 ForEach 中使用autoreleasepool
:
ForEach(0..<dataContent.catData.count, id: \.self) { count in
autoreleasepool {
// Code
}
}
参考:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.