You have two choices for how to implement this using managed object methods: use the managed object life cycle events, or use validation.
However, doing so has some tradeoffs and risks There are other approaches that may work better for you (see recommendations).
Managed Object Life Cycle
Managed objects are observed by the NSManagedObjectContext
that owns them. This is the "managed" in managed object. Most of what a NSManagedObject
instance does is actually performed by the NSManagedObjectContext
. The managed object is informed of changes through the life cycle event methods: awakeFromFetch
, awakeFromInsert
, awakeFromSnapshotEvents:
, didSave
, didTurnIntoFault
, prepareForDeletion
, etc. When implementing these methods you must be careful to not change the managed object in a way that would mark it as "dirty" in the context or would otherwise alter the current transaction. For example, attempting to "resurrect" a deleted object in didSave
, or changing relationships in awakeFromFetch
, or accessing a property in didTurnIntoFault
(which would fire a fault, which would be bad).
During a typical deletion of a saved object, the life cycle events are called in the following order:
If a save is subsequently done in the object's parent contexts, additional life cycle events may occur on the instances owned by those contexts. This can be very important to keep in mind when dealing with shared resources outside of Core Data.
When an object has not been saved and is deleted from a context, the life cycle events occur in this order:
Using managed object life cycle methods may not be a good solution in this instance. As you can see, prepareForDeletion
is called in the scenarios you are interested in - but it happens before the delete is validated in the case of a save operation.
Validation
Validation is an important Core Data capability. When objects are saved Core Data applies validation rules set in the model as well as any custom validation implemented in NSManagedObject classes. In the case of a delete, Core Data applies the delete rules defined in the model as part of the save operation. prepareForDeletion
is invoked before validation occurs - so if you were trashing data as part of prepareForDeletion
, you may be removing data for an object that will not actually be deleted as part of a save. This may cause you some problems.
You can implement your deletion as part of validateForDelete:
or as a custom validation method that checks the state of the object (isDeleted
, isInserted
, etc.). The super implementation of validateForDelete:
will execute the delete rules, be sure to call it appropriately. Validation will be called automatically as part of a save operation, but you can call it manually at any point (and this is recommended). To perform validation manually, call the appropriate method from your application, in this case validateForDelete:
. Check the BOOL result, and if it returns NO, handle the error appropriately.
Recommendations
It may be best to implement writing the image data to the local filesystem as part of validation or saving. When Core Data performs a save, it's essentially commiting all of the changes in the context as a transaction. When dealing with external resources, it can make a lot of sense to make commiting changes to those external resources part of the same process. For example, in your validateImageURL:error:
method you should at the least validate that the given URL is local filesystem URL, and that you can write to it. In willSave
/didSave
you may write to the URL specified by imageURL
if the object has been inserted or updated, and delete the data at imageURL
if it has been deleted. In the case of an object that has not yet been saved but is being deleted from the context, the data would not have yet been commited to the local filesytem. It would only exist in memory, like everything else associated with the object.
Note that no matter how you implement your reading, writing, and deleting of external data, you should do so using the NSFileCoordinator APIs to coordinate access to the files and directories.
There are still issues with this approach. An NSManagedObjectContext
(and it's objects) is just a collection of references to data in a persistent store. If you are saving external data from an NSManagedObject
, you can run into problems when you have multiple contexts, nested contexts (which you SHOULD be using!), etc. The NSPersistentStore is what manages the persistence of an NSManagedObject
's data, and ideally your interaction with the filesytem would happen at that level - which would address some of the issues that I've mentioned and more. The best way to do so would be to use Core Data's external storage capabilities to manage this data, as that is already built into (some) persistent stores.
You could also attempt to subclass NSPersistentStoreCoordinator
and override the method executeRequest:withContext:error:
to implement your own external storage.