自定义NSTableHeaderCell添加Delegate属性致App崩溃问题咨询
问题现象
在NSTableHeaderCell子类中添加自定义Delegate属性并赋值后,App会崩溃到App Delegate的@Main处。使用Address Sanitizer检测到错误:
NSTableView Custom Header Cell(17503,0x11071e600) malloc: nano zone abandoned due to inability to preallocate reserved vm space.
需求目标
实现一个包含两列的NSTableView,要求在第1列表头中添加复选框控件,触发操作时通过自定义Delegate回调响应(非表头自身的Delegate)。
测试过程
已剔除无关代码,将问题范围缩小到仅包含Delegate协议、NSTableHeaderCell子类和ViewController核心代码:
Delegate协议与表头子类代码
protocol CheckboxDelegate: AnyObject { func checkboxClicked() -> Void } class CheckboxTableViewHeader : NSTableHeaderCell { var myDelegate: CheckboxDelegate? }
ViewController核心代码
class ViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource, CheckboxDelegate { ... override func viewDidLoad() { super.viewDidLoad() let firstCol = self.myTableView.tableColumns[0] self.myTableView.removeTableColumn(firstCol) let col0 = NSTableColumn(identifier: col0Id) col0.title = "col 0" let col1 = NSTableColumn(identifier: col1Id) col1.title = "col 1" self.myTableView.addTableColumn(col0) self.myTableView.addTableColumn(col1) let header = CheckboxTableViewHeader(textCell: "Col 1") header.myDelegate = self // <-- 此行赋值后,viewDidLoad执行完毕会触发崩溃 self.myTableView.tableColumns[1].headerCell = header self.myTableView.dataSource = self self.myTableView.delegate = self } //MARK: - 自定义表头Delegate实现 func checkboxClicked() { print(#function) }
- 移除
header.myDelegate = self一行后,App运行正常 - 若在子类中添加普通属性(如
var test: Int)并赋值,也可正常运行
环境信息
- macOS: 12.6.3
- XCode: 14.2
解决方案
问题根源
NSTableHeaderCell是NSCell的子类,而NSCell采用Copy语义而非Retain语义。当你将自定义表头cell赋值给tableColumn.headerCell时,系统会自动复制该cell实例,但默认的copy方法不会处理你自定义的myDelegate属性,导致复制后的cell实例出现引用异常,进而触发内存错误。
解决方法
方法一:重写Copy方法处理自定义属性
修改CheckboxTableViewHeader子类,显式处理Delegate属性的复制逻辑,同时避免循环引用:
protocol CheckboxDelegate: AnyObject { func checkboxClicked() -> Void } class CheckboxTableViewHeader : NSTableHeaderCell { weak var myDelegate: CheckboxDelegate? // 显式标注weak,防止循环引用 override func copy(with zone: NSZone? = nil) -> Any { let copiedCell = super.copy(with: zone) as! CheckboxTableViewHeader copiedCell.myDelegate = self.myDelegate return copiedCell } }
重写copy(with:)方法后,系统复制cell时会同步传递Delegate引用,确保tableColumn使用的复制后cell能正确关联回调对象。
方法二:用通知替代Delegate(避免Copy逻辑)
如果不想处理NSCell的Copy机制,可以通过通知中心传递事件,无需在cell中持有Delegate引用:
- 定义通知名称:
extension Notification.Name { static let headerCheckboxClicked = Notification.Name("HeaderCheckboxClicked") }
- 修改表头子类,点击时发送通知:
class CheckboxTableViewHeader : NSTableHeaderCell { // 移除myDelegate属性 override func mouseUp(with event: NSEvent) { super.mouseUp(with: event) // 此处替换为复选框点击的触发逻辑 NotificationCenter.default.post(name: .headerCheckboxClicked, object: nil) } }
- 在ViewController中监听通知:
class ViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource { ... override func viewDidLoad() { super.viewDidLoad() // ... 其他初始化代码 ... NotificationCenter.default.addObserver(self, selector: #selector(handleCheckboxClick), name: .headerCheckboxClicked, object: nil) } @objc func handleCheckboxClick() { print(#function) } deinit { NotificationCenter.default.removeObserver(self) } }
内容的提问来源于stack exchange,提问作者Jay

