Is it safe to write data to an NSPasteboard object from a background thread? I can't seem to find a definitive answer anywhere. I think the assumption is that the data will be written to the pasteboard before the drag begins.
Background:
I have an application that is fetching data from Evernote. When the application first loads, it gets the meta data for each note, but not the note content. The note stubs are then listed in an outline view. When the user starts to drag a note, the notes are passed to the background thread that handles getting the note content from Evernote. Having the main thread block until the data is gotten results in a significant delay and a poor user experience, so I have the [outlineView:writeItems:toPasteboard:] function return YES while the background thread processes the data and invokes the main thread to write the data to the pasteboard object. If the note content gets transferred before the user drops the note somewhere, everything works perfectly. If the user drops the note somewhere before the data has been processed... well, everything blocks forever. Is it safe to just have the background thread write the data to the pasteboard?
You can promise the data to the pasteboard without actually having the data yet.
One way is to declare the type of the data on the pasteboard, passing yourself as the pasteboard's owner, and respond to a pasteboard:provideDataForType: message by providing the data (blocking, if necessary, until the data either arrives or fails to arrive). This means that you'll need to remember which objects were copied (by stashing them in an array, for example) so you can extract/generate the data from them when the promise comes due.
The other way, referenced in Harald Scheirich's answer, is to make your model objects conform to the NSPasteboardWriting protocol, ideally in a category (to separate interface-independent logic from Mac-specific logic). This is much cleaner than the old way, but requires Mac OS X 10.6 and later.
With NSPasteboardWriting, you'll implement promises by having the model objects' writingOptionsForType:pasteboard: method return the NSPasteboardWritingPromised option. Their pasteboardPropertyListForType: method will return the data, or at least try to—as before, this method should block until the data either arrives or fails to arrive.
Oh, and to answer the question in the title (“Is NSPasteboard thread-safe?”): There's no specific answer in the Thread Safety Summary, but there is this general statement:
… mutable objects are generally not thread-safe. To use mutable objects in a threaded application, the application must synchronize appropriately.
I would consider an NSPasteboard to be a mutable object, so no.
In practice, this isn't a problem: You typically only work with NSPasteboard in response to an action message (e.g., copy:), a drag, or a service invocation, and those all only happen on the main thread anyway. For them to happen on a secondary thread, you would have to explicitly send such messages yourself from code running on a secondary thread, in which case you are already doing something very wrong.
Conjecture:
I think your problem has nothing to do with threading but the fact that by returning YES you told the system that the data is ready. have you tried moving your data into a custom class supporting NSPasteboardWriting and NSPasteboardReading? this way the accessor to your data can block until the data is ready.
See the Pasteboard Documentation
Related
I deal with the data and do some UI working according to the data in the method
-(void)connection:didReiveiceData(I use delegate as callback), and I find that UI working is always not finished completely.Maybe when the data is received, the UI threading is still busy, so some data losts.You may suggest me to deal with data in -(void)connectionFinishLoading:,it will cause other problems.
You've correctly suggested you need to process the received data in connectionDidFinishLoading:.
Before that, you need to collect all the receivedData (eg into an NSMutableData instance). Append the received data each time didReceiveData: is called (it may be called multiple times before it finishes).
the reason why some data lost is all about the method –rangeOfData:options:range:
I use it wrong.BTW, I think this method is very weird,the option accept only one of two value, NSDataSearchBackwards and NSDataSearchAnchored.why no "NSDataSearchForewards" or something like that?
I have a NSOperationQueue set to NSOperationQueueDefaultMaxConcurrentOperationCount. It is filled with NSOperation objects (nothing weird so far). I subclassed the NSOperation to do some background tasks.
Download data from the internet.
Parse the data so I can read it.
Create a NSManagedObject:
[NSEntityDescription insertNewObjectForEntityForName:#"Channel" inManagedObjectContext:context];
Save it with the context.
[managedObjectContext save:&error]
I like this all to happen in the background so the UI won't get blocked. I read this article about concurrency with core data, and as far as I understood it. The best way would be to create a new NSManagedObjectContext in every NSOperation, but share the same persistent store coordinator.
That's easily done, however, when it comes to saving the context it says in the documentation it is error prone to do so. So my question is the following:
If I have different operations running in the NSOperationQueue, could those operations interfere with each other while saving the managed object context? Or does it wait to execute the following operation till the saving has been complete?
Can I safely save the context in a NSOperation? Or is it really bad practice?
I hope someone can shine a light on this matter, because I am really stuck at the moment.
What you need to do is the following:
Create a managed object context for each NSOperation. Create this new context on the main method, because this is when it's executing on the right thread.
Assign the context persistent store coordinator.
Create an observer to receive the NSManagedObjectContextDidSaveNotification. This is the only way the main context will know at the time the changes were made on the NSOperation's context. Make sure the merge call is made on the thread/block the merging context lives in. If you are merging with the main thread's context, call the mergeChangesFromContextDidSaveNotification: method on the main thread with the notification from the NSOperation's context.
Also, ask yourself if you really want to have all these operations working concurrently. Per the documentation:
The default maximum number of operations is determined dynamically by the NSOperationQueue object based on current system conditions.
You do not have control over how many NSOperations will be operating at the same time. If this is not what you want, you might be better if you just go with a serial NSOperationQueue (maxConcurrentOperation=1), considering the fact that you are going to be locking the database to do the save, and also because you have networking being done as well.
You can safely save inside the NSOperation's main method, if you take the precautions mentioned above.
Hey guys, I've got a subclass of an NSManagedObject. In awakeFromInsert and awakeFromFetch I'm calling an initialization method which, among other things, starts an NSTimer.
Now i need a place to invalidate the timer. However, dealloc, finalize, didTurnIntoFault, prepareForDeletion and willTurnIntoFault aren't getting called.
According to the documentation, these methods should all get called when the object is cleared from memory. None of them are, however all the data is saved in the persistent store. I'm puzzled as to why or how.
Is there anything i could be doing that could cause these methods to not get called during the objects life cycle?
Core data controls the lifetime of NSManagedObjects. It's not going to flush an object from memory by itself unless you ask it to. Looking at the documentation, there appear to be two ways:
sending refresh:mergeChanges: to the MOC causes the object to turn into a fault.
sending reset to the MOC causes it to reset itself as if it has just been created.
However, any of the above requires explicit action on your part, so you might as well add a method to the object to invalidate its timer and invoke that.
In fact, your problem probably indicates a design issue. An NSTimer is essentially a user interface event. It should probably be controlled by your MVC controller which sends a message to the model object (the NSManagedObject) to do the action.
I have a situation similar to this:
Objective-C: How to use memory managment properly for asynchronous methods
I have an object that asynchronously downloads & parses an xml doc. It then has a delegate method that transfers the data it retrieved to the caller.
My 2 questions are:
When do I release the data retrieving object? The link I posted above gives 2 answers, one says release in the delegate and one says release immediately, which is correct (or which is better if both answers are correct)
My second question is, what is the best way to transfer the retrieved data to the caller? At the moment I have
self.imagesDataSource = [articleImagesParserObject.returnedArray copy];
I used copy because, as far as I understand, that makes the mutable array immutable. Is that correct?
I'm going to pick you up on a couple of things.. It might start the ball rolling :)
You say
It then has a delegate method that
transfers the data it retrieved to the
caller
-- EDIT --
You mean that you send a message to the NSURLConnection's delegate. Yes, it is just semantics, but it is clearer.
You say
The link I posted above gives 2
answers, one says release in the
delegate and one says release
immediately
The post you link to says that if you launch your secondary thread with NSThread +detachNewThreadSelector:toTarget:withObject: the thread will retain your object, so if you have finished with it, you can release it, as is normal practise. You are not doing this.
The second suggested method is to supply a method to callback when your background operation has completed. As you are using NSURLConnection and it already provides you with callback hooks, and in fact you are using them to return your downloaded data, this seems like the way to go.
Copying a mutable array does give you an immutable copy, which you own - so it should be self.imagesDataSource = [[articleImagesParserObject.returnedArray copy] autorelease] unless imagesDataSource doesn't retain - which would be irregular.
I have a data model which is sort of like this simplified drawing:
alt text http://dl.dropbox.com/u/545670/thedatamodel.png
It's a little weird, but the idea is that the app manages multiple accounts/identities a person may have into a single messaging system. Each account is associated with one user on the system, and each message could potentially be seen/sent-to multiple accounts (but they have a globally unique ID hence the messageID property which is used on import to fetch message objects that may have already been downloaded and imported by a prior session).
The app is used from a per-account point of view - what I mean is that you choose which account you want to use, then you see the messages and stuff from that account's point of view in your window. So I have the messages attached to the account so that I can easily get the messages that should be shown using a fetch like this:
fetch.fetchPredicate = [NSPredicate predicateWithFormat:#"%# IN accounts", theAccount];
fetch.sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:#"date" ascending:NO]];
fetch.fetchLimit = 20;
This seems like the right way to set this up in that the messages are shared between accounts and if a message is marked as read by one, I want it seen as being read by the other and so on.
Anyway, after all this setup, the big problem is that memory usage seems to get a little crazy. When I setup a test case where it's importing hundreds of messages into the system, and periodically re-fetching (using the fetch mentioned above) and showing them in a list (only the last 20 are referenced by the list), memory just gets crazy. 60MB.. 70MB... 100MB.. etc.
I tracked it down to the many-to-many relation between Account and Message. Even with garbage collection on, the managed objects are still being referenced strongly by the account's messages relationship property. I know this because I put a log in the finalize of my Message instance and never see it - but if I periodically reset the context or do refreshObject:mergeChanges: on the account object, I see the finalize messages and memory usage stays pretty consistent (although still growing somewhat, but considering I'm importing stuff, that's to be expected). The problem is that I can't really reset the context or the account object all the time because that really messes up observers that are observing other attributes of the account object!
I might just be modeling this wrong or thinking about it wrong, but I keep reading over and over that it's important to think of Core Data as an object graph and not a database. I think I've done that here, but it seems to be causing trouble. What should I do?
Use the Object Graph instrument. It'll tell you all of the ownerships keeping an object alive.
Have you read the section of the docs on this topic?