Search code examples
swiftmacoscifilterswift4.2ciimage

CIImage memory not released


I am trying to take a sequence of images, blend X images at the time and produce a new image. This is the code I have to accomplish this:

static func blendImages(blendFrames: Int, blendMode: CIImage.BlendMode, imagePaths: [URL], completion: @escaping (_ progress: Double) -> Void) {
    var currentIndex = 0

    while currentIndex + blendFrames < imagePaths.count {            
        let currentSubset = Array(imagePaths[currentIndex..<(currentIndex + blendFrames)])

        var currentSubsetIndex = 0

        var firstImage: CIImage?

        while currentSubset.count > currentSubsetIndex + 1 {                
            if firstImage == nil { firstImage = CIImage(contentsOf: currentSubset[currentSubsetIndex]) } //First image allocated in memory

            if let secondImage = CIImage(contentsOf: currentSubset[currentSubsetIndex + 1]) {//Second image allocated in memory
                firstImage = firstImage?.blend(with: secondImage, blendMode: blendMode)
            } //Would expect secondImage to be freed from memory at this point, but it does not happen

            currentSubsetIndex += 1
        } 

        if let finalImage = firstImage {
            let bitMapRep = NSBitmapImageRep(ciImage: finalImage)
            let data = bitMapRep.representation(using: .jpeg, properties: [:])

            do {
                try data?.write(to: URL(fileURLWithPath: "Final-" + String(format: "%03d", currentIndex) + ".jpg"))
            } catch let error as NSError {
                print("Failed to write to disk: \(error)")
            }

        }

        firstImage = nil //Would expect firstImage to be freed from memory at this point (or at beginning of next cycle in while-loop), but it does not happen

        currentIndex += 1
    }
}

And the code for CIImage's blend function

extension CIImage {

    enum BlendMode: String {
        case Lighten = "CILightenBlendMode"
        case Darken = "CIDarkenBlendMode"
    }

    func blend(with image: CIImage, blendMode: BlendMode) -> CIImage? {
        let filter = CIFilter(name: blendMode.rawValue)!
        filter.setDefaults()

        filter.setValue(self, forKey: "inputImage")
        filter.setValue(image, forKey: "inputBackgroundImage")

        return filter.outputImage
    }
}

As detailed in the comments in the code I would expect the memory allocated in firstImage and secondImage to be freed up at the end of the scope they are created in. Since they are both created within a while-loop I would expect them to be freed up at the end of that loop, however, that appears not to happen. But there is no memory leak here, because when the blendImages function finishes the memory is appropriately freed up (however, this is too late as the code could run through hundreds or thousands of images, making the app consume several TBs of data before it is freed up). If anyone can see the flaw in my code and how to make it more memory-efficient that would be very much appreciated.


Solution

  • You should use the autoreleasepool function to force the release of the objects inside its scope. This function will help you to manage the memory footprint more precisely.

    static func blendImages(blendFrames: Int, blendMode: CIImage.BlendMode, imagePaths: [URL], completion: @escaping (_ progress: Double) -> Void) {
        var currentIndex = 0
    
        while currentIndex + blendFrames < imagePaths.count {
            autorelease {
                // your code
            }
    
            currentIndex += 1
        }
    }
    

    You can check this post to have more informations.