Post

Replies

Boosts

Views

Activity

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 NSViewResults (25,000 buttons created and removed; only 10 alive at a time): modefootprint (MB) at 5k / 15k / 25kNSKeyValueDependencyNSMallocBlockbutton (NSButton in NSStackView)26 / 43 / 60 (steady growth)292,621251,145view (plain NSView)flat at 13172n/aheap 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_rootAllocWithZoneThe 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: Is this a known issue? Is there a Feedback number? Is there a supported way to avoid or release these dependency objects, other than not recreating NSControls? Does it reproduce on macOS 26.x or 27 betas? Workaround for now: reuse buttons and only rebuild when state actually changes.
Topic: UI Frameworks SubTopic: AppKit Tags:
1
0
33
6h
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 NSViewResults (25,000 buttons created and removed; only 10 alive at a time): modefootprint (MB) at 5k / 15k / 25kNSKeyValueDependencyNSMallocBlockbutton (NSButton in NSStackView)26 / 43 / 60 (steady growth)292,621251,145view (plain NSView)flat at 13172n/aheap 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_rootAllocWithZoneThe 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: Is this a known issue? Is there a Feedback number? Is there a supported way to avoid or release these dependency objects, other than not recreating NSControls? Does it reproduce on macOS 26.x or 27 betas? Workaround for now: reuse buttons and only rebuild when state actually changes.
Topic: UI Frameworks SubTopic: AppKit Tags:
Replies
1
Boosts
0
Views
33
Activity
6h