I’ve been working on an application which uses Kotlin Multiplatform as the shared business logic which handles image processing. for iOS, I send in multiple UIImage’s to a kotlin defined function, and then return the image once the acceptance criteria has been met. But right now when I send the UIImage’s into the Kotlin defined function, the memory on iOS will rapidly climb until the app crashes. Even when I remove all processing and just pass the UIImage into the kotlin function, there are identifiable memory leaks and the app will crash eventually due to memory overload.
In swift, I have a camera view class that creates a videostream for the user. I then extract each frame and convert it to a UIImage:
// CameraView.swift
// capture delegate from video output stream
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
DispatchQueue.main.async { [weak self] in
guard let uiImage = self?.imageFromSampleBuffer(sampleBuffer: sampleBuffer) else { return }
connection.videoOrientation = .portrait
// delegate to owning viewModel
self?.delegate.onFrameCaptured(image: uiImage)
}
}
// conversion function to UIImage
func imageFromSampleBuffer(sampleBuffer : CMSampleBuffer) -> UIImage? {
guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return nil }
let ciImage = CIImage(cvPixelBuffer: imageBuffer)
guard let cgImage = context.createCGImage(ciImage, from: ciImage.extent) else { return nil }
return UIImage(cgImage: cgImage, scale: 1.0, orientation: .up)
}
Inside the owning viewModel, I simply pass this UIImage from the delegate responder function down to the Kotlin defined function.
// ImageCaptureViewModel.swift
lazy var documentDelegate = DocumentFrontDelegateImpl(identify:self)
func onFrameCaptured(image: UIImage) {
if (captureState != .autoFailed) {
// Kotlin-defined function
documentDelegate.process(image: image)
}
}
Inside my kotlin defined function, I have a print debug statement showing the correct function is being called.
// DocumentFrontDelegateIml.kt
open class DocumentFrontDelegateImpl(private val identify: IDocumentDelegateImpl) {
fun process(image: UIImage) {
println("image received in KMP")
}
}
I am not doing any processing with the images, but still the application crashes within 15 seconds. I am sending ~30 frames per second into this kotlin function, and the memory usage climbs rapidly before crashing the app. I’ve also tested it out with only sending 1 image down to Kotlin, and that image stays in memory forever.
Using instruments I was able to see the memory leak, and notice that Core Graphics has 2 retain counts for each image, but only 1 release. I.E. each image stays in memory for the duration of the application.
Has anyone experienced anything similar to this? I found that a CGImage will have the same problem with memory. The references are never fully garbage collected and stay in memory forever.
Here is build.gradle.kt file for how I configure the iOS framework that gets built
kotlin {
val framework = XCFramework()
ios {}
iosSimulatorArm64 {}
}
I am currently using the experimental memory model.
# Multiplatform
kotlin.native.binary.memoryModel=experimental
Any insight would be appreciated!! This memory leak is a huge blocker for me right now and I can’t find any way around it.