Я использую Metal
и CADisplayLink
, чтобы отфильтровать CIImage
и преобразовать его в MTKView
.
// Starting display link
displayLink = CADisplayLink(target: self, selector: #selector(applyAnimatedFilter))
displayLink.preferredFramesPerSecond = 30
displayLink.add(to: .current, forMode: .default)
@objc func applyAnimatedFilter() {
...
metalView.image = filter.applyFilter(image: ciImage)
}
Согласно монитору памяти в Xcode, использование памяти стабильно на iPhone X и никогда не превышает 100 Мб, на устройствах, таких как iPhone 6 или iPhone 6s, потребление памяти продолжает расти, пока в конечном итоге система не убьет приложение.
Я проверил на утечки памяти, используя Instruments
, но об утечках не сообщалось. Запуск приложения через Allocations также не показывает никаких проблем, и приложение не будет закрыто системой. Мне также интересно, что на новых устройствах использование памяти стабильно, но на старых оно продолжает расти и расти.
Сложность фильтра не имеет значения, поскольку я пробовал даже самые простые фильтры, и проблема не устранена. Вот пример из моего металлического файла:
extern "C" { namespace coreimage {
float4 applyColorFilter(sample_t s, float red, float green, float blue) {
float4 newPixel = s.rgba;
newPixel[0] = newPixel[0] + red;
newPixel[1] = newPixel[1] + green;
newPixel[2] = newPixel[2] + blue;
return newPixel;
}
}
Интересно, что может вызвать проблему на старых устройствах и в каком направлении мне следует искать.
Обновление 1: здесь представлены два 1-минутных графика: один из Xcode
и один из Allocations
, оба используют один и тот же фильтр. Allocations
график стабилен, в то время как Xcode
график постоянно растет:
Обновление 2: Прикрепленный снимок экрана со списком распределений, отсортированным по размеру, приложение работало 16 минут, применяя фильтр без остановок:
Обновление 3: Немного больше информации о том, что происходит в applyAnimatedFilter()
:
Я отфильтровал изображение в metalView
, что MTKView
. Я получаю отфильтрованное изображение от filter.applyFilter(image: ciImage)
, где в Filter
классе происходит следующее:
func applyFilter(image: ciImage) -> CIImage {
...
var colorMix = ColorMix()
return colorMix.use(image: ciImage, time: filterTime)
}
, где filterTime
- это просто переменная типа Double. И, наконец, вот весь класс ColorMix:
import UIKit
class ColorMix: CIFilter {
private let kernel: CIKernel
@objc dynamic var inputImage: CIImage?
@objc dynamic var inputTime: CGFloat = 0
override init() {
let url = Bundle.main.url(forResource: "default", withExtension: "metallib")!
let data = try! Data(contentsOf: url)
kernel = try! CIKernel(functionName: "colorMix", fromMetalLibraryData: data)
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func outputImage() -> CIImage? {
guard let inputImage = inputImage else {return nil}
return kernel.apply(extent: inputImage.extent, roiCallback: {
(index, rect) in
return rect.insetBy(dx: -1, dy: -1)
}, arguments: [inputImage, CIVector(x: inputImage.extent.width, y: inputImage.extent.height), inputTime])
}
func use(image: CIImage, time: Double) -> CIImage {
var resultImage = image
// 1. Apply filter
let filter = ColorMix()
filter.setValue(resultImage, forKey: "inputImage")
filter.setValue(NSNumber(floatLiteral: time), forKey: "inputTime")
resultImage = filter.outputImage()!
return resultImage
}
}