0

When creating an AttributedString for use in AppKit I get a warning that NSFont isn't Sendable:

    var container = AttributeContainer()
    container.appKit.foregroundColor = .red
    container.appKit.font = .systemFont(ofSize: mySize)
//  ^ Conformance of 'NSFont' to 'Sendable' is unavailable

The warning is correct that NSFont isn't Sendable, so is there a way to accomplish this without turning off concurrency warnings? AppKit is well behind SwiftUI and UIKit when it comes to being audited for Sendable conformance, but there's no much I can do about it. Marking the import of Foundation as @preconcurrency has no effect. A quick test project shows that the font is set properly and can be used in, say, an NSTextView. I just don't want to have to stare at those warnings until Apple gets around to AppKit refinement (historically, could be quite a while).

EDIT: I'm using Xcode 15b5. Xcode 14.3.1 doesn't show the warning.

smr
  • 890
  • 7
  • 25

3 Answers3

0

There doesn't seem to be a great way to deal with this. I filed a feedback (FB12885931) on the issue but...you know.

There is reason to be fairly confident in marking NSFont as Sendable as it's documented in the Cocoa Text Architecture Guide as follows: "Font objects are immutable, so it is safe to use them from multiple threads in your app." Hopefully Apple will audit AppKit for concurrency issues more thoroughly sometime soon.

smr
  • 890
  • 7
  • 25
0

The following got rid of the warning for me in Xcode 15.0 beta 6:

    var container = AttributeContainer()
    container.appKit.foregroundColor = .red
    // Produces a value of type `AttributeScopes.SwiftUIAttributes.FontAttribute.Value`
    container.appKit.font = .init(.systemFont(ofSize: mySize))
mkeiser
  • 965
  • 9
  • 17
-2

Edited for misunderstanding:

Wrapping NSFont in another struct like MainThreadFont wouldn't eliminate the compiler warning since NSFont isn't declared Sendable. I made an oversight.

Unfortunately, as far as I know there isn't a perfect solution for this problem. For now, if you're certain about your NSFont usage being safe across different threads, you might need to suppress the warning using @unchecked Sendable attribute, but please use this sparingly and only when you're absolutely certain about thread safety, as this bypasses Swift's concurrency safety checks.

Here's an example:

struct UncheckedSendableFont: Sendable {
    @MainActor
    private let font: NSFont

    init(_ font: NSFont) {
        self.font = font
    }

    @MainActor
    var value: NSFont {
        return font
    }
}

Then use it like so:

var container = AttributeContainer()
container.appKit.foregroundColor = .red
container.appKit.font = UncheckedSendableFont(.systemFont(ofSize: mySize)).value

This will prevent the concurrency warning from appearing.

Please note that this effectively tells Swift to ignore the Sendable conformance issue for NSFont. It should only be used if you're completely confident that your usage of NSFont will not cause any concurrency issues. As a best practice, always ensure that NSFont instances are only accessed on the main thread, and be very careful about passing them between threads.

KFDoom
  • 772
  • 1
  • 6
  • 19
  • 1
    Doesn't that produce the same compiler warning? – Martin R Aug 07 '23 at 17:32
  • ..it does... My bad. if the poster is certain about their NSFont usage being safe across different threads, they might need to suppress the warning using @unchecked Sendable attribute, but please use this sparingly and only when you're absolutely certain about thread safety, as this bypasses Swift's concurrency safety checks. – KFDoom Aug 07 '23 at 17:36
  • Thanks for the feedback. Seems that holding my nose and using @unchecked Sendable is the only way forward. – smr Aug 07 '23 at 19:17