Using NSPersistentDocument to create 'Documents'
Asked Answered
M

2

7

I would like to create an app that uses

  • Swift
  • CoreData
  • 'Documents' which work in the standard macOS fashion [custom extension, a single 'file'/filewrapper containing all data relating to that document]

This does not appear possible. The documentation states very clearly that

NSPersistentDocument does not support some document behaviors: File wrappers. [..]

which makes me think that the usual ways of dealing with images in CoreData - binary data with 'allow external storage' and save them to a different location, store the URL in the database - cannot be used with NSPersistentDocument. I want my users to be able to do the usual Finder operations on my 'file' (duplicate, move to external storage, restore from external backup) and need all my data to be in one single package.

The SQL version of the file store results in the usual three-fold stack when saving - .sqlite, .sqlite-shm, .sqlite-wal - which is useless as a 'document'.

Is there a solution I have overlooked? (examples are very sparse; the Big Nerd Ranch sample does not solve this, either; neither Marcus Zarra nor Objc.io touch on NSPersistentDocument).

Mcgrody answered 27/7, 2017 at 16:13 Comment(0)
B
4

The only option that will work with NSPersistentDocument the way you want it is to store the images directly in the database. You need a Binary Data attribute on your entity, but you cannot turn on the Allows External Storage option.

If you turn on this option, Core Data will decide - depending on the size - whether to store the image directly in the database or in a hidden folder inside the folder where your document is located:

Hidden Folder

(I made the folder visible entering cmd-shift-. in the Finder). The sample document is named Test 1.doof and it contains three images:

Document Window

You can see that the hidden folder .Test 1_SUPPORT/EXTERNAL DATA contains two files, which are the two bigger images (1.3 MB and 494 KB). The third one with only 50 KB is stored inside Test 1.doof. If you move Test 1.doof into another folder, the hidden folder is left behind. Opening the file in that other folder leads to two missing images.

Storing the images inside the database is not that bad if you put the binary data into a separate entity with a one-to-one relation to the rest of the data, like so:

Data Model

That way the image does not interfere with any search or sort operation. NSPersistentDocument gives you a lot of cool functionality for free, so you should use it anyway if possible.

Two additional remarks:

  • If you turn on Allows External Storage for an attribute, you do not have to care about URLs or where to store the images, Core Data does that for you (but not in a useful way for document-based apps).
  • These shm or wal files are temporary files that appear "sometimes", for databases without external storage as well. If they stick, you can safely remove them when you app is closed.
Blaise answered 9/9, 2017 at 12:24 Comment(0)
B
1

If you want to put more then just a database in your document, then you should implement NSDocument instead of NSPersistentDocument. In that case you don't get built-in support for CoreData, but you can use your document as a container for multiple file types.

See also Is NSDocument and CoreData a possible combination, or is NSPersistentDocument the only way?

Bresnahan answered 11/8, 2017 at 17:50 Comment(6)
How do I create individual CoreData stores (sql) inside a filewrapper? The linked example proposes a single CoreData store for all information, with individual documents using a different type of storage (plist etc) - whereas I really am looking for a 'document' that uses CoreData.Mcgrody
You can also instantiate a Core Data stack within the scope of a NSDocument, and save your SQLite, XML or binary files within the document.Bresnahan
What if the document containing the SQLite file is moved to a different dir while open, will that break Core Data? Do we need to detect the document is moved, teardown the Core Data stack and recreate it?Purgatory
@Purgatory In most cases macOS keeps automatically track of files that are moved while being opened in an app.Bresnahan
I gave it a try and when launching app then renaming a folder containing the SQLite files when attempting to save the context it exceptions with CoreData: error: Error opening store: Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory" so my guess is we would need to detect the document being moved and recreate the core data stack at its new location making this thread safe is probably a nightmare.Purgatory
I figured out that NSPersistentDocument detects file moves by overriding setFileURL and calling setURL:forPersistentStore: so context saves keep working.Purgatory

© 2022 - 2024 — McMap. All rights reserved.