Implementing [NSCell copyWithZone:] in Swift to Avoid Crashes in AppKit

Apple, Cocoa, Coding

Quick Summary

If you add a Swift property to an NSTextFieldCell subclass and you may suddenly start getting random crashes! Implement an override of [NSCell copyWithZone:] that retains any properties to fix this:

// Swift 3.0
class TableViewTextFieldCell: NSTextFieldCell {
private var previousTextColor: NSColor?
override func copy(with zone: NSZone? = nil) -> Any {
let result: TableViewTextFieldCell = super .copy(with: zone) as! TableViewTextFieldCell
if let previousTextColor = result.previousTextColor {
// Add the needed retain now
let _ = Unmanaged<NSColor>.passRetained(previousTextColor)
}
return result
}
}

Gory Details

I hit this in a little app that I’m working on, and I was a bit stumped as to what was happening. Using Zombies in Instruments revealed an overrelease of a color in a simple NSTextFieldCell subclass that added an NSColor property.

// Custom text colors don't automagically invert.
class TableViewTextFieldCell: NSTextFieldCell {
private var previousTextColor: NSColor?
override var backgroundStyle: NSView.BackgroundStyle {
get {
return super.backgroundStyle
}
set(newBackgroundStyle) {
// If we are going to light because we are selected, save off the old color so we can restore it
if self.backgroundStyle == .light && newBackgroundStyle == .dark {
previousTextColor = self.textColor
self.textColor = NSColor.white // or a named color?
} else if self.backgroundStyle == .dark && newBackgroundStyle == .light {
if previousTextColor != nil {
self.textColor = previousTextColor
previousTextColor = nil
}
}
super.backgroundStyle = newBackgroundStyle
}
}
}

I’m using a View-Based NSTableView, and I know that a cell-based NSTableView would have some issues unless you implement [NSCell copyWithZone:], but that shouldn’t be needed for my particular case. 

The Zombies seemed to reveal that the deallocated cell was doing something bad. I did a little bit of digging by the logging of addresses, and discovered my NSColor property was duplicated in a few locations. That was strange, as the instances should have been unique. I found out that it was being copied by AppKit! Here’s the backtrace I hit upon:

copy point

It looks like using a baseline constraint with Autolayout will cause it to copy the cell to determine the baseline.  So, if you are using AutoLayout, be aware of implicit copies that might happen behind your back!

The trouble with [NSCell copyWithZone:] is that it uses NSCopyObject, which blindly assigns ivars from one instance to another and doesn’t do any proper memory management. I didn’t think this would still be an issue in Swift, but apparently it is! See my solution at the top where I simply retain the value during the copy.

I did some searching and numerous people are hitting this problem:

Developer Forums: Swift NSCopying on the Developer

StackOverflow: Copying NSTextFieldCell subclass in Swift causes crash

Swift.org: If NSCopyObject is called on a Swift class with stored properties, it can cause a crash



Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments

Subscribe to new posts:

You'll get an email whenever a I publish a new post to my blog and nothing more. -- Corbin

As an Amazon Associate I earn from qualifying purchases.

(c) 2008-2024 Corbin Dunn

Privacy Policy

Subscribe to RSS feeds for entries.

72 queries. 0.820 seconds.

Log in