本地数据库加载图片,但是内存无限增长,内存溢出 - SwiftUI

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

在我的 SwiftUI 程序中,当图像从本地数据库加载(现在模拟,原始由 SQLite3 使用)并向用户显示时,我观察到内存使用量无限增加。随着滚动过程中加载更多图像,内存不会被回收,导致内存消耗持续增长。此行为表明内存泄漏,导致潜在的内存溢出。

主视图:

import SwiftUI
import UIKit

struct ScrollShowView: View {
  // Binding to a parent's state, allowing this view to access and modify the bookName
  @Binding var bookName: String
  
  // State variables to hold the current state of the view
  @State var ids: [Int] = [Int]() // Holds the IDs of the pages
  @State var showImages: [RPViewContent] = [] // Holds the currently loaded images
  @State var showInfo: [RPInfo] = [] // Holds additional information related to the images
  @State private var isLoading = false // Tracks whether content is currently loading
  
  // Constants to define when to trigger loading and how much content to load
  private let triggerLoadNumber = 5 // How many items from the edge should trigger loading
  private let batchSize = 10 // Number of items to load per batch
  private let maxContentCount = 20 // Maximum number of content items to hold in memory
  
  // The load threshold, presumably used to determine when to start loading new content
  private let loadThreshold = 100.0
  
  var body: some View {
    if !showImages.isEmpty {
      ScrollView {
        LazyVStack(spacing: 0) {
          ForEach(ids, id: \.self) { id in
            Group {
              // Attempt to find the view content by ID
              if let viewContent = showImages.first(where: { $0.id == id }) {
                Image(uiImage: viewContent.image!)
                  .resizable()
                  .aspectRatio(contentMode: .fit)
                  .onAppear {
                    // Get the index of the current image
                    let index: Int = showImages.firstIndex(of: viewContent)!
                    // Check if we need to load more content based on scroll position
                    if index < triggerLoadNumber - 1 {
                      // User has scrolled near the top, load previous content
                      loadMoreViewContent(direction: .previous)
                    } else if index > showImages.count - triggerLoadNumber {
                      // User has scrolled near the bottom, load next content
                      loadMoreViewContent(direction: .next)
                    }
                  }
              } else {
                // Display a loading placeholder if image is not available
                Image("Loading")
                  .resizable()
                  .aspectRatio(contentMode: .fit)
                  .onAppear {
                    // Calculate the range of IDs to load
                    let halfRange = maxContentCount / 2
                    let startIdIndex = max(0, id - halfRange)
                    let endIdIndex = min(ids.count, id + halfRange)
                    let loadImagesIds: [Int] = Array(ids[startIdIndex..<endIdIndex])
                    // Load images for the calculated range of IDs
                    BooksDatabase().getOriginal(at: loadImagesIds, from: bookName) { images in
                      showImages = images
                    }
                  }
              }
            }
          }
        }
      }
    } else {
      // If no images are present, display a black background and trigger the initial content load
      Color.black.onAppear { loadInitialContent() }
    }
  }
  
  // Load the initial set of images
  private func loadInitialContent() {
    isLoading = true
    // Fetch all page IDs from the book
    ids = BooksDatabase().getAllIds(from: bookName)
    // Load the initial batch of content based on maxContentCount
    let initialIndexes = Array(ids.prefix(maxContentCount))
    loadViewContent(for: initialIndexes, direction: .next) {
      isLoading = false
    }
  }
  
  // Enum to define the direction of content loading
  private enum LoadDirection {
    case previous, next
  }
  
  // Load more content in the given direction
  private func loadMoreViewContent(direction: LoadDirection) {
    guard !isLoading else { return }
    isLoading = true
    
    // Determine the ID of the relevant content based on the direction of loading
    let relevantId: Int?
    switch direction {
    case .previous:
      relevantId = showImages.first?.id
    case .next:
      relevantId = showImages.last?.id
    }
    
    // Ensure the ID is valid and determine the new range of IDs to load
    guard let id = relevantId, let index = ids.firstIndex(of: id) else {
      isLoading = false
      return
    }
    
    let newIndexes: [Int]
    switch direction {
    case .previous:
      // Load the previous batch of content
      let start = max(0, index - batchSize)
      newIndexes = Array(ids[start..<index])
    case .next:
      // Load the next batch of content
      let end = min(ids.count, index + batchSize)
      newIndexes = Array(ids[index..<end])
    }
    
    // Load the content for the new range of IDs
    loadViewContent(for: newIndexes, direction: direction) {
      // After loading new content, adjust the currently held content based on the maxContentCount
      if direction == .previous {
        // Remove excess items from the end if necessary
        let limitedImages = Array(showImages.prefix(maxContentCount))
        showImages = limitedImages
      } else {
        // Remove excess items from the beginning if necessary
        let limitedImages = Array(showImages.suffix(maxContentCount))
        showImages = limitedImages
      }
      isLoading = false
    }
  }
  
  // Function to load view content for the specified IDs and handle the completion
  private func loadViewContent(for ids: [Int], direction: LoadDirection, completion: @escaping () -> Void) {
    // Fetch the original images from the database for the specified IDs
    BooksDatabase().getOriginal(at: ids, from: bookName) { newImages in
      // Insert or append new images to the showImages array based on the direction
      if direction == .previous {
        // Loading content to be displayed above the current content
        showImages.insert(contentsOf: newImages, at: 0)
      } else {
        // Loading content to be displayed below the current content
        showImages.append(contentsOf: newImages)
      }
      // Call the completion handler
      completion()
    }
  }
}

模拟数据库:

class BooksDatabase {
  func getOriginal(at pages: [Int], from bookName: String, completion: @escaping ([RPViewContent]) -> Void) {

    DispatchQueue.main.async {
      let showImages = pages.map { i in
        return RPViewContent(id: i, imageType: .clear, image: UIImage(named: String(i))!)
      }
      
      completion(showImages)
    }
  }
  
  func getAllIds(from bookName: String) -> [Int] {
    return Array(0...300)
  }
}

定义:

struct Quadrilateral: Codable {
  var topLeft: CGPoint
  var topRight: CGPoint
  var bottomRight: CGPoint
  var bottomLeft: CGPoint
}

enum POSType: String, Codable {
  case noun
  case verb
  case adjective
  case adverb
  case pronoun
  case preposition
  case conjunction
  case interjection
  case determiner
  case other
  
  var stringValue: String {
    switch self {
    case .noun: return "noun"
    case .verb: return "verb"
    case .adjective: return "adjective"
    case .adverb: return "adverb"
    case .pronoun: return "pronoun"
    case .preposition: return "preposition"
    case .conjunction: return "conjunction"
    case .interjection: return "interjection"
    case .determiner: return "determiner"
    case .other: return "other"
    }
  }
}

struct Word: Codable {
  var texts: String
  var pos: POSType
}

enum ShowingImageType {
  case clear
  case mark
}

//RP = ReadingPage
struct RPInfo: Codable {
  var texts: [Word]?
  var positions: [[Quadrilateral]]?
  var unknowWordsIndex: [Int]?
  var learningWordsIndex: [Int]?
  var definitions: [String:[POSType:[String]]]?
}

struct RPViewContent: Equatable {
  let id: Int
  var imageType: ShowingImageType
  var image: UIImage?
}
  1. autoreleasepool {} //对内存不起作用
  2. LazyVStack -> VStack //在显示视图方面不起作用,也不知道内存
  3. 尝试使用https://fatbobman.com/en/posts/memory-usage-optimization/的方式,但没有成功
swift database caching swiftui memory-management
1个回答
0
投票

“Assets.xcassets”会自动缓存

class BooksDatabase {
  func getOriginal(at pages: [Int], completion: @escaping ([RPViewContent]) -> Void) {
    let showImages = pages.compactMap { i -> RPViewContent? in
      guard let path = Bundle.main.path(forResource: String(i), ofType: "jpg") else { return nil }
      let image = UIImage(contentsOfFile: path)
      return RPViewContent(id: i, imageType: .clear, image: image)
    }
    
    completion(showImages)
  }
...
}
© www.soinside.com 2019 - 2024. All rights reserved.