Creating NSAttributedString on background queue: "[NSCell init] must be used from main thread only"

160 Views Asked by At

I have a Cocoa app based on NSDocument that presents text documents to the user. Document contents are read on a background queue, which causes a problem:

I use NSAttributedString with images, i.e. it can contain NSTextAttachment and NSTextAttachmentCell. When I try to initialize an attachment for an image and I have the main thread checker activated in Xcode, I get the following error:

// On background queue:
let attachment = NSTextAttachment()
attachment.attachmentCell = NSTextAttachmentCell(imageCell: image) <-

"[NSCell init] must be used from main thread only"

My first attempt was to wrap that code in DispatchQueue.main.sync {}, but this caused a deadlock with NSDocument once in a while when autosaving took place or when the user saved the document.

Autosaving would block the main queue, my code would run in the background trying to read the document, but this ended up in a deadlock, because I could not call out to the main queue to create the text attachment.

My question:

Is it possible for me to ignore the main thread checker in Xcode and instantiate NSTextAttachmentCell on a background queue anyway?

All I'm doing on the background queue is initializing the attributed string with its attachments. Further modifications are made on the main queue.

Sequence of events

  1. Thread 2 (bg queue): Need to update abc.txt for some reason. Get read/write access to abc.txt via NSFileCoordinator
    • Thread 2 (bg) now in NSFileCoordinator block
  2. Thread 1 (MAIN): User-initiated NSDocument save, NSDocument requests write access to abc.txt via NSFileCoordinator
    • Thread 1 (MAIN) now blocked, waiting for file coordinator lock of Thread 2
  3. Thread 2 (bg queue): Moving along in file coordinator block..., trying to initialize NSAttributedString, oh, it contains an attachment, can't initialize NSTextAttachmentCell on background queue, let me hand this off to the MAIN queue real' quick... ⚡️ DEADLOCK ⚡️
    • Thread 2 (bg) is now waiting for Thread 1 (MAIN), which is waiting in front of its file coordinator access block for Thread 2 (bg) to finish with its file coordinator block.
1

There are 1 best solutions below

4
Lucas Derraugh On

You should not ignore the main thread warnings. If you use main.async rather than sync there should never be an issue with adding the attachment. This reference also helps out defining which classes behave well in different threads. Generally speaking, any AppKit view type class should only be used on the main thread.