1

I'm iterating over a large collection of photos, since they're too large to fit all of them in memory I have to update them by: 1) loading them from file individually 2) performing the update 3) saving to file. Here's a simplified example (doesn't work in Simulator because of the CIImage part for some reason):

import SwiftUI

struct Photo {
  let body: Data?
  let size = CGSize(width: 1000, height: 1000)
  let bytesPerPixel: Int = 4

  func load() -> Photo {
    let data = Data(count: Int(size.width) * Int(size.height) * bytesPerPixel)
    return Photo(body: data)
  }
}

struct ContentView: View {
  let photos: [Photo] = Array(repeating: Photo(body: nil), count: 100)

  var body: some View {
    VStack {
      Button(action: { self.overloadMemory() }) { Text("Overload Memory") }
    }
  }

  private func overloadMemory() {
    for photo in photos {
      guard let data = photo.load().body else { continue }
      let bytesPerRow = Int(photo.size.width) * photo.bytesPerPixel
      let ciImage = CIImage(bitmapData: data,
                          bytesPerRow: bytesPerRow,
                          size: photo.size,
                          format: .BGRA8,
                          colorSpace: CGColorSpace(name: CGColorSpace.genericRGBLinear)!)
      let uiImage = UIImage(ciImage: ciImage)
      print("Saving: \(uiImage.size)")
      saveFile(image: uiImage) // Leaving this uncommented causes a memory crash. Leaving it commented shows no memory spike in the "Memory Report" panel.
    }
  }

  private func saveFile(image: UIImage) {
    let id = UUID()
    let fileName = "\(id).png"
    let data = image.pngData()!

    print("Saving: \(id)")

    let appSupportPath = try! FileManager.default.url(for: .applicationSupportDirectory,
                                                      in: .userDomainMask,
                                                      appropriateFor: nil,
                                                      create: true)
    let savePath = appSupportPath.appendingPathComponent(fileName)
    if FileManager.default.createFile(atPath: savePath.path, contents: data, attributes: .none) {
      return
    }

    print("Failed saving preview")
  }
}

If I don't call saveFile I don't see a spike in memory usage at all. If I do call saveFile I get an "out of memory" error. From my understanding of how Swift/ARC allocation works, shouldn't each photo get deallocated at the end of each iteration of the loop? When I call saveFile, why does the memory usage buildup until the loop finishes?

choxi
  • 455
  • 4
  • 18
  • 1
    That may be a case for an `autoreleasepool`, see for example https://stackoverflow.com/a/25880106/1187415. – Martin R Jul 07 '20 at 14:08

0 Answers0