Post

Replies

Boosts

Views

Activity

AVAudioConverter buffers are over-retained in a DispatchQueue.concurrentPerform context
I have an audio analysis tool which I’m trying to parallelise, but it uses an escalating amount of heap space when working on a set of audio files. It turns out that when I use the convert(to:error:withInputFrom:) method of AVAudioConverter inside a concurrentPerform block, a large amount of the memory associated with each conversion is retained until the very end of the concurrent performance, rather than being released at the end of each iteration’s execution. As such, the memory usage climbs and climbs while a batch of files is processed, rather than staying effectively flat during parallel operations. I’m assuming that this is some flaw in my code rather than a bug in either framework, and hope someone can help me find it. See below a simplified version of the code, which converts the same file an arbitrary number of times (rather than operating on a large batch of different files as is the case in my real application). Any help appreciated! let outputFormat = AVAudioFormat(commonFormat: .pcmFormatFloat32, sampleRate: 8000, channels: 1, interleaved: false)! DispatchQueue.global(qos: .userInitiated).async { DispatchQueue.concurrentPerform(iterations: 1000) { index in let inputFile = try! AVAudioFile(forReading: URL(filePath: "/path/to/some/song.m4a")) let inputFormat = inputFile.processingFormat let inputFrameCount = AVAudioFrameCount(inputFile.length) let inputBuffer = AVAudioPCMBuffer(pcmFormat: inputFormat, frameCapacity: inputFrameCount)! try! inputFile.read(into: inputBuffer) let sampleRateCoefficient = inputFormat.sampleRate / outputFormat.sampleRate let outputFrameCount = UInt32(Double(inputFrameCount) / sampleRateCoefficient) let outputBuffer = AVAudioPCMBuffer(pcmFormat: outputFormat, frameCapacity: outputFrameCount)! let converter = AVAudioConverter(from: inputBuffer.format, to: outputFormat)! let converterInputBlock: AVAudioConverterInputBlock = { _, outStatus in outStatus.pointee = AVAudioConverterInputStatus.haveData return inputBuffer } // If we just do some arbitrary work here, even big mallocs, memory usage is flat as everything is released at the end of the concurrentPerform closure. // But if we run this conversion, each iteration retains a big chunk of memory until they're all finished. :( converter.convert( to: outputBuffer, error: nil, withInputFrom: converterInputBlock ) } }
2
0
1.1k
Apr ’23
iOS VoiceOver: can adjustable elements also have custom accessibility actions?
I'm building a custom UIAccessibilityElement for a complex control, and would like to expose both the .adjustable UIAccessibilityTrait (to move between discrete members listed in the control) and accessibilityCustomActions (to enable multiple possible interactions). My (limited) experience is that both adjustment and custom action selection employ vertical swipes, so they collide if an element exposes both: vertical swipes call accessibilityIncrement and accessibilityDecrement, which I'm using to move between the members, and the accessibility element is described as having "Actions available" but there seems to be no way to access them. Is there a way, beyond reimplementing my increment and decrement methods to append the custom actions to the discrete element list somehow, to enable a user to interrogate both these extra dimensions on a single accessibility element?
1
0
901
Mar ’23
NSTableView full reload works, partial reload doesn't
I'm using an NSTableView (and DifferenceKit, but that's likely irrelevant). This is all programmatic, no Interface Builder at all. I'd previously implemented only tableView(_:objectValueFor:row) in order to get values into my table. At that point I could apply full and partial reloads and everything worked fine. Now I've added an implementation of tableView(_:viewFor:row:) in order to format some columns differently, and it's affected reloading. A full reloadData() still works, but a call to reloadData(forRowIndexes:columnIndexes) doesn't call either my datasource or delegate methods; the reload seems to simply disappear. I also tried removing the datasource method and running only with tableView(_:viewFor:row:) but no dice. A partial reload still doesn't call the delegate method. Has anyone come across this? Is there a nuance of NSTableView I'm missing? My (truncated) code: init() { ... tableView.dataSource = self tableView.delegate = self myColumnDefinitions.forEach { tableView.addTableColumn($0) } } func tableView( _ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int ) -> Any? { ... return someStringProvider(column, row) } func tableView( _ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int ) -> NSView? { ... let identifier = someDerivedValue(column) if let existingView = tableView.makeView(withIdentifier: identifier, owner: self) as? NSTableCellView { existingView.textField?.stringValue = someStringProvider(column, row) return existingView } let textField = NSTextField() ... textField.stringValue = someStringProvider(column, row) let view = NSTableCellView() view.identifier = identifier view.addSubview(textField) view.textField = textField view.addConstraints([ ... (pin textField to view) ]) textField.bind( .value, to: view, withKeyPath: "objectValue", options: nil ) return view }
1
0
851
Jan ’22