I have a UIDocument based app that uses NSFileWrappers to store data. The 'master' file wrapper contains many additional directory file wrappers, each of which represents a different page of the document.
Whenever I make a change to the document while the UIDocument is saving (in writeContents:andAttributes:safelyToURL:forSaveOperation:error:), the app crashes. Here is the stack trace:
It seems clear that I am modifying the same instance of file wrapper that the UIDocument is enumerating over in the background. Indeed, I checked that when returning a snapshot of the data model in contentsForType:error:, the returned sub file wrappers point to the same objects as the ones currently residing (and being edited) in the data model, and not copies.
- (id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError
{
if (!_fileWrapper) {
[self setupEmptyDocument];
}
return [[NSFileWrapper alloc] initDirectoryWithFileWrappers:[_fileWrapper fileWrappers]];
}
This is the sanctioned approach to implementing this method (according to WWDC 2012 Session 218 - Using iCloud with UIDocument).
So I suppose the question is: How can this approach be thread safe?
Is the situation somehow different when the master file wrapper's fileWrappers are themselves directory file wrappers? If the sanctioned approach is wrong, how should it be done?
If you are calling any of the writeContents:... methods, you shouldn't be. You should be calling saveToURL:forSaveOperation:completionHandler: instead. The writeContents:... methods are meant for advanced subclassing.
UIDocument uses two threads - the main thread and the "UIDocument File Access" thread (which , if you subclass more of UIDocument, you can do things in via performAsynchronousFileAccessUsingBlock:).
Thread safety with UIDocument is like anything in Objective C - only let the thread owning an object modify it. If the object you want to change is being read, queue it to be changed after the write is complete. Perhaps change a different object owned by your UIDocument subclass and pull them into a new NSFileWrapper in contentsForType:error:. Pass a copy of the fileWrappers NSDictionary.
NSFileWrapper actually loads the entire document into memory. The NSFileWrapper is actually created in the "UIDocument File Access" thread in the readFromURL:error: method, which is then passed to the loadFromContents:ofType:error: method. If you have a large document this can take a while.
When saving you typically want to let UIDocument decide when to do this, and let it know something has changed via the updateChangeCount: method (param is UIDocumentChangeDone). When you want to save something right now you want to use the saveToURL:forSaveOperation:completionHandler: method.
One other thing to note is UIDocument implements the NSFilePresenter protocol, which defines methods for NSFileCoordinator to use. The UIDocument only coordinates writing on the root document, not the subfiles. You might think that coordinating subfiles inside the document might help, but the crash you're getting is related to mutating a dictionary while it's being iterated, so that wont help. You only need to worry about writing your own NSFilePresenter if you (1) wanted to get notifications of file changes, or (2) another object or app was reading/writing to the same file. What UIDocument already does will work fine. You do want to, however, use NSFileCoordinator when moving/deleting whole documents.
I know this is an ancient thread, but I ran into this problem recently, and to help future travelers: If you have subdirectories in your main file wrapper, you need to copy those NSFileWrappers as well (in addition to copying the root NSFileWrapper as above).
Otherwise, a crash can occur when the UIDocument background thread enumerates over them while saving and while simultaneous modifications occur on the main thread. It's not clear, but this might be the problem the OP ran into.
Another tip is you need to also copy over the subdirectory's NSFileWrapper's filename, fileAttributes (and possibly preferredFilename) so that incremental saving works.
HTH.
Related
I have a Realm Object (let's call it File) with a String property called url.
I have created an Alamofire DownloadRequest.DownloadFileDestination block which contains a reference to the url so I can download the file. However, because this block gets executed on a background thread, Realm throws up an exception. I don't need to actually access the entire Realm, or even the entire File object - only the property url - so there is no reason to try to open the File object from the Realm in the background thread.
What is the proper way to copy this string (or properties of other types) out of the Realm object to pass to a different thread?
My current solution seems very inelegant - though it does work.
let url = "\(object.url)"
This question is not related to a specific block of code - it is more a conceptual question.
This is normal, if you read the Realm's docs for threading, it said you cannot have multiple threads sharing the same instances of Realm objects, that mean you fetch the File object on the main thread, you cannot use it on other thread, in your case is Alamofire background thread
What you did is correct, assigning the value of url to another variable, else, just call fetchFile().url where fetchFiles() return the file object from Realm (refetch the file object - but this will have worse performance, only useful in other case where the realm object changed a lot)
Simply assigning let url = object.url will copy the value and is thread safe.
I have a custom subclass of PFObject which keeps track of a large video file on the device. I want to make sure that if i delete the PFObject the videoFile is also deleted.
For now if have override all variants of the delete method, but that seem wrong. Is there a central way to add a behavior when an object is deleted?
There's a hook that catches every delete on the back-end, (beforeDelete in cloud code), but from the question, it sounds like that's the wrong place to catch, because the file in need of deletion is local.
Parse recently open-sourced the SDK. Perusing the code. It looks like the delete variants ultimately all call deleteInBackground. So one idea -- a little too clever, IMO -- would be to override only that one. But I think it would be unwise to depend on this undocumented fact.
If you control the caller side, one idea is to just make a policy to never call delete directly, and provide an "otuswebDelete" method to do the object and file delete.
If you don't control the caller (or don't trust yourself to remember your own policy), I think you're better off, under your current design, to just override the few variants:
– delete
– delete:
– deleteInBackground
– deleteInBackgroundWithBlock:
– deleteEventually
They can all just call super to delete, then call a method in the subclass to delete the local file. Not so bad, IMO.
Finally, for reasons too numerous to detail here, I'm in the habit of "wrapping" my PFObjects (an NSObject subclass that has a PFObject property) rather than subclassing them.
The burden of this approach is a little tedium to create the accessors for the properties, but in return I get more control of (a) the use of SDK methods (as in your issue), (b) serialization, (c) fetching an managing related objects, (d) more...
I have a method that runs in a background thread using a copy of NSManagedObjectContext which is specially generated when the background thread starts as per Apples recommendations.
In this method it makes a call to shared instance of a class, this shared instance is used for managing property values.
The shared instance that managed properties uses a NSManagedObjectContext on the main thread, now even though the background thread method should not use the NSManagedObjectContext on the main thread, it shouldn't really matter if the shared property manager class does or does not use the such a context as it only returns scalar values back to the background thread (at least that's my understanding).
So, why does the shared property class hang when retrieving values via the main threads context when called from the background thread? It doesn't need to pass an NSManagedObject or even update one so I cannot see what difference it would make.
I can appreciate that my approach is probably wrong but I want to understand at a base level why this is. At the moment I cannot understand this whole system enough to be able to think beyond Apples recommended methods of implementation and that's just a black magic approach which I don't like.
Any help is greatly appreciated.
Does using:
[theContext performBlock:^{
// do stuff on the context's queue, launch asynchronously
}];
-- or --
[theContext performBlockAndWait:^{
// do stuff on the context's queue, run synchronously
}];
-- just work for you? If so, you're done.
If not, take a long, hard look at how your contexts are setup, being passed around, and used. If they all share a root context, you should be able to "move" data between them easily, so long as you lookup any objectIDs always on your current context.
Contexts are bound to threads/queues, basically, so always use a given context as a a reference for where to do work. performBlock: is one way to do this.
I have an iOS app that can download files from a website. I have created a NSURLSession in a class Downloads to manage them. The Downloads class has a NSMutableArray that keeps track of all current and past downloads using my DownloadItem objects. I am not happy with this setup.
Currently, I have to have the Downloads class as the delegate for all downloads. I see no way to assign the delegate of each NSURLSessionDownloadTask to a DownloadItem object. So, I have to keep it assigned to my Downloads object and then have it figure out which way DownloadItem to forward the message on to.
Currently I do this by making an NSMutableDictionary called tasksDictionary in the Downloads and use the taskIdentifier as a key.
return [self.tasksDictionary objectForKey:[NSNumber numberWithInteger:task.taskIdentifier]];
This seems to work, but it doesn't seem the most efficient method. I'm also concerned that I saw the first taskIdentifier created was 0 which will make it difficult to discern the difference between a completed task and the first task.
Is there a better way to keep track of these things? Is there a way to assign a new delegate for a task?
Perhaps have these ivars:
NSMutableArray *completedDownloads;
NSMutableDictionary *activeDownloadTasks; // #(taskID) => DownloadItem
When you finish, pop the DownloadItem from activeDownloadTasks and add it to completedDownloads.
Otherwise what you are doing sounds peachy.
While I'm at it, this syntax may be helpful (using your example):
return self.tasksDictionary[#(task.taskIdentifier)];
I am creating a NSManagedObject (subclass) with certain attributes. At the same time, I am executing some code/a block that does some network operation given the attributes of my NSManagedObject. Now, some times that network operation might fail or take too long, so I want to add the ability to cancel the execution of that code/block.
I was thinking of making the code/block an NSThread, and then I have the ability to call [theThread cancel]. However, how do I associate the NSThread with my NSManagedObject, given that I cannot add properties to NSManagedObject Categories? Is it OK to just add the property to the definition of the NSManagedObject itself? Seems legal, but subsequent changes to the Core Data model would overwrite my code, I guess.
But maybe there is an entirely different and better way to accomplish what I am trying to do? Any ideas?
First, new code really should prefer GCD or NSOperationQueue over NSThread. If you find yourself using NSThread it's time to slow down and revisit your design and implementation requirements.
Second, using NSManagedObject across threads is really, really bad. If you do anything but exceedingly trivial things, it can get very difficult to do right as well.
Finally, no matter how you do your threaded network access, you should prefer to grab the data from the managed object, and pass that instead of the managed object itself
If you must access the managed object, make sure your managed object context is of either NSMainQueueConcurrencyType or NSPrivateQueueConcurrencyType and access the managed object like by invoking performBlock or performBlockAndWait using the managedObjectContext property of the managed object.
EDIT
Ok, let me check this with you. What I a currently doing is spawning a
backgroundContext, create a new NSManagedObject using performBlock,
then save that background Context, switch to the parent context (using
performblock), obtain the newly created object in that context using
existingObjectWithId:. Then, I create a NSOperation subclass, tie the
NSManagedObject (from the parent context) to that NSOperation subclass
(it's a property on the subclass) and put that operation in a
NSOperationQueue. Within that NSOperation, the NSManagedObject gets
changed. It seems to work fine, does that look ok? – user1013725
Um... maybe??? I didn't follow that. Could you please post the code? That would be much more precise and more easy to understand.
#JodyHagins So I am not using performBlock, but maybe that's ok
because the managedObjectContext is the main context? – user1013725
No.
If the main context is created with either alloc] init] or alloc] initWithConcurrencyType:NSConfinementConcurrencyType then you must use it only when you know you are running on the main thread.
If it is created with alloc] initWithConcurrencyType:NSMainThreadConcurrencyType then you must use it only when you know you are running on the main thread or within one of the performBlock methods.