NSButton added/removed in a window leaks ~12 NSKeyValueDependency + block objects per button (macOS 26.5.1)

Adding and removing NSButton (any NSControl) in a window leaks AppKit-internal objects. A plain NSView does not.

Environment: macOS 26.5.1 (25F80), Apple silicon (arm64), Swift, swiftc -O, no third-party code.

Repro (single file):

import AppKit
func mb()->Int{ var i=task_vm_info_data_t(); var c=mach_msg_type_number_t(MemoryLayout<task_vm_info_data_t>.size/4); withUnsafeMutablePointer(to:&i){ $0.withMemoryRebound(to:integer_t.self,capacity:1){ _=task_info(mach_task_self_,task_flavor_t(TASK_VM_INFO),$0,&c)}}; return Int(i.phys_footprint/1048576)}
let app = NSApplication.shared; app.setActivationPolicy(.accessory)
let mode=CommandLine.arguments[1]
let win=NSWindow(contentRect:NSRect(x:100,y:100,width:400,height:100),styleMask:[.titled],backing:.buffered,defer:false)
let stack=NSStackView(); win.contentView=stack; win.orderFrontRegardless()
var n=0
print(mode,"start",mb()); fflush(stdout)
Timer.scheduledTimer(withTimeInterval:0.002,repeats:true){ t in
  autoreleasepool {
    for v in stack.arrangedSubviews { stack.removeArrangedSubview(v); v.removeFromSuperview() }
    for _ in 0..<10 {
      let b: NSView = mode=="view" ? NSView(frame:.zero) : NSButton(title:"", target:nil, action:nil)
      if mode=="plainadd" { win.contentView!.addSubview(b); b.removeFromSuperview() } else { stack.addArrangedSubview(b) }
    }
  }
  n+=1
  if n%500==0 { print(mode,"iter",n*10,mb()); fflush(stdout) }
  if n>=2500 { t.invalidate() }
}
app.run()
swiftc -O repro.swift -o tw
./tw button &   # then: heap $(pgrep -x tw) -s | grep -E "NSKeyValueDependency |__NSMallocBlock__|NSButton "
./tw view   &   # control case: plain NSView

Results (25,000 buttons created and removed; only 10 alive at a time):

NSMallocBlockmodefootprint (MB) at 5k / 15k / 25kNSKeyValueDependency
button (NSButton in NSStackView)26 / 43 / 60 (steady growth)292,621251,145
view (plain NSView)flat at 13172n/a

heap shows only 10 live NSButton instances, so the buttons themselves are freed. leaks reports no leaks, so AppKit still holds references to the objects. That is about 12 objects (~3 KB) per button.

Allocation stack (MallocStackLogging=1 + malloc_history -callTree NSKeyValueDependency, taken on my real app):

-[NSStackView insertArrangedSubview:atIndex:]
 -[NSView addSubview:]
  -[NSView _setSuperview:]
   -[NSView(NSViewRoot) _updateViewRoot]
    -[NSControl _viewRootDidChange]
     -[NSControl _setupGestureRecognizers]
      __cons_NSDP_NSDPTypeBool_block_invoke.273
       NSDP_getComputedPropertyValue
        -[NSKeyValueDependencyInfo addDependencyInContext:onValueForKey:]
         _objc_rootAllocWithZone

The allocation happens when an NSControl is added to a view hierarchy that has a window (_viewRootDidChange → _setupGestureRecognizers). The KVO dependency objects are never released after the control is removed and deallocated.

Real-world impact: an app rebuilt about 40 buttons every 5 s. That leaked about 40 objects per second (~16 MB/hour). After 19 days it reached 7.2 GB footprint, mostly small malloc blocks, with ~1.4M NSKeyValueDependency objects and ~1.4M blocks.

Questions:

  1. Is this a known issue? Is there a Feedback number?
  2. Is there a supported way to avoid or release these dependency objects, other than not recreating NSControls?
  3. Does it reproduce on macOS 26.x or 27 betas?

Workaround for now: reuse buttons and only rebuild when state actually changes.

Thanks for the post.

To just answer first your questions. The feedback number is created after you file a bug and I haven’t seen that issue and I do recommend you to update your Xcode and macOS.

Another suggestions is to reuse NSButton instances and update their properties (.title, .target, .action, .isEnabled, .isHidden) rather than removing and recreating them. Another suggestion will be to create a focused sample instead of posting the code as is hard to use without the project and configuration.

That'll help us better understand what's going on.
If you're not familiar with preparing a test project, take a look at
Creating a test project.

Albert  WWDR