最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

core graphics - Swift AppKit Rendering Excessive Memory Consumption - Stack Overflow

programmeradmin2浏览0评论

In SwiftUI / AppKit I build a simple grid view composed of blue squares. Once a square is hovered, the square turns red, re-drawing only the updated square. Here is my code:

struct CoreGraphicsSquaresView: NSViewRepresentable {
  let rows: Int
  let cols: Int
  let squareSize: CGFloat
  let spacing: CGFloat
  
  func makeNSView(context: Context) -> NSView {
    return SquaresNSView(rows: rows, columns: cols, squareSize: squareSize, spacing: spacing)
  }
  
  func updateNSView(_ nsView: NSView, context: Context) {
    nsView.setNeedsDisplay(nsView.bounds) // Trigger a redraw if needed
  }
}

class SquaresNSView: NSView {
  let rows: Int
  let columns: Int
  let squareSize: CGFloat
  let spacing: CGFloat
  
  override var acceptsFirstResponder: Bool { return true }
  
  var cellColors: [IndexPath: NSColor] = [:]
  var lastHovered: IndexPath? = nil
  
  init(rows: Int, columns: Int, squareSize: CGFloat, spacing: CGFloat) {
    self.rows = rows
    self.columns = columns
    self.squareSize = squareSize
    self.spacing = spacing
    super.init(frame: .zero)
    
    let trackingArea = NSTrackingArea(
      rect: self.bounds,
      options: [.mouseMoved, .activeAlways, .inVisibleRect],
      owner: self,
      userInfo: nil
    )
    self.addTrackingArea(trackingArea)
  }
  
  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
  
  
  override func draw(_ dirtyRect: NSRect) {
    guard let context = NSGraphicsContext.current?.cgContext else { return }
    
    let cornerRadius: CGFloat = 2
    let path = NSBezierPath()  // Reusable path object
    
    for row in 0..<rows {
      for col in 0..<columns {
        let x = CGFloat(col) * (squareSize + spacing)
        let y = CGFloat(row) * (squareSize + spacing)
        let squareRect = CGRect(x: x, y: y, width: squareSize, height: squareSize)
        
        if dirtyRect.intersects(squareRect) {
          let color = cellColors[IndexPath(item: row, section: col)] ?? NSColor.blue
          context.setFillColor(color.cgColor)
          
          path.removeAllPoints()
          path.appendRoundedRect(squareRect, xRadius: cornerRadius, yRadius: cornerRadius)
          path.fill()
        }
      }
    }
  }
  
  
  func updateSquare(atRow row: Int, column: Int, to color: NSColor) {
    let indexPath = IndexPath(item: row, section: column)
    
    if cellColors[indexPath] == color {
      return // No need to redraw if the color hasn't changed
    }
    
    cellColors[indexPath] = color  // Update color
    
    let x = CGFloat(column) * (squareSize + spacing)
    let y = CGFloat(row) * (squareSize + spacing)
    let squareRect = CGRect(x: x, y: y, width: squareSize, height: squareSize)
    
    self.setNeedsDisplay(squareRect)
  }
  
  override func mouseMoved(with event: NSEvent) {
    let locationInView = convert(event.locationInWindow, from: nil)
    let y = Int(round(locationInView.y)) // use round() because it has pixel perfect accuracy on y axis
    let x = Int(ceil(locationInView.x)) // use ceil() because it has pixel perfect accuracy on x axis
    
    let isInYRange = (1 <= y % 12) && (y % 12 <= 10) // Accounts for 2px spacing
    let isInXRange = (1 <= x % 12) && (x % 12 <= 10) // Accounts for 2px spacing
    

    if isInXRange && isInYRange {
      let colIdx: Int = (x - 1) / 12
      let rowIdx: Int = (y - 1) / 12
      
      let indexPath = IndexPath(item: rowIdx, section: colIdx)
      
      if lastHovered == indexPath {
        return
      } else {
        lastHovered = indexPath
      }
      
      print("ColIdx: \(colIdx), RowIdx: \(rowIdx)")
      
      updateSquare(atRow: rowIdx, column: colIdx, to: .red)
    }
  }
}

struct CGHeatMap: View {
  var body: some View {
    CoreGraphicsSquaresView(rows: 7, cols: 40, squareSize: 10, spacing: 2)
      .frame(width: 40*12 - 2, height: 7*12 - 2) // Force SwiftUI to respect size
      .background(Color.white)
  }
}

And in my SwiftUI ContentView I create 36 of these CGHeatMap views.

When profiling the application, it starts at around 35MiB of persistent memory allocation. As I hover on squares on different grids (turning them red) the persistent allocation increases (expected since, for example, we add elements to the cellColors dictionary) but I believe it increases too much. For example when turning red less than 200 squares, the allocation settles at around 61MiB. Why is so much memory consumed?

EDIT: When studying the behavior more deeply, I noticed a strange behavior. When coloring the first cell of a new grid the consumption increases by 1/2 MiB, while when coloring successive cells for the same grid increases it by much less (0.01/0.02 MiB).

发布评论

评论列表(0)

  1. 暂无评论