NSNotification and Multithreading - cocoa

I'm trying to get the notification NSTaskDidTerminateNotification in my multithreaded app but I can't get it working. It does seem to work when I tested it on a single threaded app. In init I have [[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(taskDidEnd:) name: NSTaskDidTerminateNotification object: myTask]; and I'm quite sure that it gets called because other objects (like myTask) are being initiated there. And the taskDidEnd: method is defined as
- (void)taskDidEnd: (NSNotification *)aNotification
{
NSLog(#"Task succeeded.");
}
And in dealloc the observer gets removed.
This all happens in an object which is initiated inside a separate thread and I would like to receive that notification inside the same object.

Did you run the run loop on that thread? If not, NSTask won't notice that the task ended (or the task won't have ended yet) and won't post the notification.

Related

Design strategy to save in the background with MagicalRecord

Recently I started a new app requiring just one store (no document based app). For some time I was quite happy thinking I could finally get rid of throwing around the NSManagedObjectContext... until I wanted to save in the background :-(
Now I am confused about my own code. For example:
- (void)awakeFromInsert
{
[super awakeFromInsert];
[self resetCard];
self.creationDate = TODAY;
self.dictionary = [Dictionary activeDictionary];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center postNotificationName:NOTE_NEWCARD object:self];
}
[Dictionary activeDictionary] is a NSManagedObject static function returning a pointer to a NSManagedObject created in the main thread. That will cause a cross/context error during the background save. Because my program always read from the same store, I thought I could avoid writing this:
[Dictionary activeDictionaryWithContext:...]
I suppose that with MagicalRecord, as long as I work always with the same backend is is possible to avoid passing the context pointer. Which function should I use to get that context?
[NSManagedObjectContext MR_defaultContext]
[NSManagedObjectContext MR_context]
[NSManagedObjectContext MR_contextForCurrentThread]
In the example the object sends itself within a notification, something almost granted to cause more conflicts.
In the case of the notification should I always send only the objectID?
It seems to me that my objects should issue side effect operations/notifications only if they are running in the main context. However some of those side operations change my object graph creating new instances of other entities.
Can I safely omit the two problematic function calls I have mentioned if I save with [MagicalRecord MR_saveAll] ?
Should I assume that the objects of the new background saving context will be an exact copy of the ones in my main thread without calling those extra functions?
Now I am having problems because I never expected awakeFromInsert to run several times for the same object of the same store. I was thinking about something like this:
- (void)awakeFromInsert
{
[super awakeFromInsert];
if ([self managedObjectContext] == [NSManagedObjectContext MR_defaultContext]) {
[self resetCard];
self.creationDate = TODAY;
self.dictionary = [Dictionary activeDictionary];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center postNotificationName:NOTE_NEWCARD object:self];
}
}
That should make my awakeFromInsert code run only once, but not in the background saving context. I am concerned about losing information if I do so
While you can certainly send your object in a notification that way, I would recommend against that. Remember, even with the new parent-child contexts in CoreData, NSManagedObjects are NOT thread safe. If you create or import objects, you will need to save them prior to using them in another context.
MagicalRecord provides a relatively simple API for background saving:
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext){
MyEntity *newEntity = [MyEntity MR_createInContext:localContext];
//perform other entity operations here
}];
This block does all the work for you, without worrying about setting up the NSManagedObjectContext properly.
Another reason you should not pass NSManagedObjects across a notification is that you do not know what thread the notification will be received on. This can potentially lead to a crash, because, again, NSManagedObjects are NOT thread safe.
Another alternative to the notification approach you present is to add an observer to NSManagedObjectContextDidSaveNotification, and merge your changes on that notification. This will fire only after your objects are saved, and are safe for crossing contexts through either the parent-child relationship, or the persistent store (the old way).

NSOperationQueue and Dispatch Queue as replacement of NSThread performing a repetitive task

I have an application in which I am repetitively calling a method in background. I implemented this by following below steps:
created a background thread,
called the appropriate method on the created thread,
called sleep method on the thread, and
again called the previously invoked method.
Below is the code which I used:
- (void) applicationDidFinishLaunching:(NSNotification *)notification
[NSApplication detachDrawingThread:#selector(refreshUserIdPassword) toTarget:self withObject:nil];
}
-(void)refreshUserIdPassword
{
[self getAllUserIdsPasswordsContinousely];
[NSThread sleepForTimeInterval:180];
[self refreshUserIdPassword];
}
I have read that NSThread is not the best way to perform background task, and there are other classes provided in cocoa, such as - NSOperationQueue and GCD, which should be preferred over NSThread to perform an asynchronous task. So I am trying to implement the above specified functionality using the alternative classes.
Problem is - though I am able to perform an asynchronous task using
these classes, I am unable to perform a repetitive task (as in my
case) using these classes.
Can someone throw some light on this and guide me towards the correct direction?
I think you'll get a stack overflow (no pun intended) using the code you've posted. -refreshUserIdPassword recurses infinitely...
How about using GCD?
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
dispatch_source_t timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
dispatch_source_set_timer(timerSource, dispatch_time(DISPATCH_TIME_NOW, 0), 180*NSEC_PER_SEC, 10*NSEC_PER_SEC);
dispatch_source_set_event_handler(timerSource, ^{
[self getAllUserIdsPasswordsContinuously];
});
dispatch_resume(timerSource);
self.timer = timerSource;
}
You're looking in the wrong place. As you say, NSOperationQueue isn't suited for this type of task. NSTimer is Cocoa's solution to this problem.
As the question also has a grand-central-dispatch tag:
If you need to run something in the background based on a regular interval, you could also use a dispatch_source timer.
Apple provides a very extensive example in the Concurrency Programing Guide.
If you don't need a background thread, you could use NSTimer (as paulbailey mentioned) or even more simple:
NSObject's performSelector:withObject:afterDelay:

When NSDocument Asynchronous Saving is enabled, is there any point in calling -unblockUserInteraction in a simple -dataOfType:error:?

I have a straight-forward, Mac OS X, Cocoa, Document-based application which uses the new 10.7 Autosave, Versions and Asychronous Saving APIs. I am fully using the NSDocument APIs to get all of Apple's Document-based application features for free.
In order to support the new Lion Autosave/Versions/AsyncSaving, I have overridden the following methods in my NSDocument subclass like so:
#implementation MyDocument
...
+ (BOOL)autosavesInPlace { return YES; }
- (BOOL)canAsynchronouslyWriteToURL:(NSURL *)URL ofType:(NSString *)type forSaveOperation:(NSSaveOperationType)op {
return YES;
}
I have also overridden -dataOfType:error: to help implement saving the document's data to disk:
- (NSData *)dataOfType:(NSString *)typeName error:(NSError **)outErr {
NSData *data = nil;
if ([typeName isEqualToString:MY_SUPPORTED_TYPE_NAME]) {
data = makeSnapshotCopyOfMyDocumentData(); // assume return value is autoreleased
} else if (outErr) {
*outErr = [NSError errorWithDomain:NSOSStatusErrorDomain code:unimpErr userInfo:nil];
}
// not sure this is doing much good, since i take no action after this.
[self unblockUserInteraction];
return data;
}
...
#end
See how I'm calling -unblockUserInteraction at the end there?
When supporting the new 10.7 AsyncSaving feature, Apple advises calling -unblockUserInteraction as early as possible (after making a snapshot copy of your document's data) in your -dataOfType:error: implementation. But Apple's example showed them doing much more work after calling -unblockUserInteraction.
However, considering I take no other action after this, I'm wondering if there's any point in calling -unblockUserInteraction there at all.
So my questions:
Considering I take no other action after it, is my call to -unblockUserInteraction doing any good?
Do the Apple Frameworks just call -unblockUserInteraction immediately after -dataOfType:error: returns anyway? Should I just leave it to them?
I just noticed a subtle wording difference between the NSDocument documentation and the comment in NSDocument.h:
Docs:
If saveToURL:ofType:forSaveOperation:completionHandler: is writing on
a non-main thread because
canAsynchronouslyWriteToURL:ofType:forSaveOperation: has returned YES,
but it is still blocking the main thread, this method unblocks the
main thread. Otherwise, it does nothing.
Header:
If -saveToURL:ofType:forSaveOperation:completionHandler: is writing on
a non-main thread because
-canAsynchronouslyWriteToURL:ofType:forSaveOperation: has returned YES, but is still blocking the main thread, unblock the main thread.
Otherwise, do nothing.
I assume the Header is more up to date.
I am working on an application that calls unblockUserInteraction after the last line that has to run on the main thread. (At least that's the way I understood it)
I think our code fits the scenario that Apple had in mind when designing the async saving part of NSDocument:
in fileWrapperOfType: we ...
create a QL preview for our file wrapper (that has to run on the
main thread) ...
unblockUserInteraction ...
... "long" running file saving task (involving compression)

Can I save to a managed object context from a completion block on an NSOperation?

My app uses Core Data and NSOperationQueue. In keeping with Apple's guidelines, I'm creating a separate managed object context for each queue. In my case this is pretty simple: I have one background queue that does all the heavy work, and another on the main thread that just reads the data.
It would seem to make sense for me to do something like this:
On the background queue, create an operation that does a bunch of work on the managed object context.
Add a completion block to that operation that saves the managed object context.
But I read in the NSOperation documentation:
The exact execution context for your completion block is not guaranteed but is typically a secondary thread. Therefore, you should not use this block to do any work that requires a very specific execution context. Instead, you should shunt that work to your application’s main thread or to the specific thread that is capable of doing it.
Of course, it's essential that this save be carried out from the same thread that 'owns' the managed object context. But I'm not always clear on whether 'thread' refers to operation queues or not. (It's sometimes used in more or less specific ways.)
Is my 'completion block' strategy workable?
Do a little trick to solve this issue anywhere in code:
Create moc:
moc = [[NSManagedObjectContext alloc] init];
[moc setUndoManager:nil];
[moc setPersistentStoreCoordinator:coordinator];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(importerDidSave:) name:NSManagedObjectContextDidSaveNotification object:self.moc];
don't forget remove observer :
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:self.moc];
[moc release];
[super dealloc];
}
and, finally, check before u will merge changes, if it main thread:
- (void)importerDidSave:(NSNotification *)saveNotification {
NSLog(#"MERGE in client controller");
if ([NSThread isMainThread]) {
[self.mainMoc mergeChangesFromContextDidSaveNotification:saveNotification];
} else {
[self performSelectorOnMainThread:#selector(importerDidSave:) withObject:saveNotification waitUntilDone:NO];
}
}

Why Won't Asynchronous SOAP Web Services Calls Work

I used WSMakeStubs (in the dev tools) to generate stub code for accessing a SOAP web service. The calls I make to the object just block currently. When I try to use the async calls, nothing happens and I'm sure it has to do with my understanding of run loops. I initialize an object and try to schedule it on a run loop like this:
BeginPartnerSession *call = [[BeginPartnerSession alloc] init];
[call setParameters:kPartnerID in_Password:kPartnerPassword];
[call setCallBack:self selector:#selector(sessionIDRequestDidFinish:)];
[call scheduleOnRunLoop:[NSRunLoop currentRunLoop] mode:NSDefaultRunLoopMode];
[call release];
The stub call for scheduling on the run loop looks like this:
- (void) scheduleOnRunLoop:(NSRunLoop*) runloop mode:(NSString*) mode
{
WSMethodInvocationScheduleWithRunLoop([self getRef], [runloop getCFRunLoop], (CFStringRef) mode);
}
The call to [self getRef] returns an invocation object that has set the callback. The callback is then supposed to call out to my target and selector, but it never hits that break point after calling schedule with run loop. What needs to change in the run loop scheduling to get it to work correctly?
Synchronous calls work fine, so I'm pretty sure it's not a server issue.
I finally broke this out into a separate project where I could isolate the problem. The async call worked just fine there which made the runloop suspect to me. Turns out the issue had to do with the fact that I am running this as a plugin for iPhoto. I simply changed the runloop mode so the call looked like this:
[call scheduleOnRunLoop:[NSRunLoop currentRunLoop] mode:NSRunLoopCommonModes];
Then the callback got called. Voila!

Resources