how to update other contexts with changes from alternate threads - cocoa

I have a mainContext that is used on the main thread. When the mainContext is created, I add an observer to the NotificationCenter for NSManagedObjectContextDidSaveNotification notifications.
If a new thread gets created and needs an NSManagedObjectContext, I create the context on the new thread and store some info for it. I save changes to the context on the new thread.
My notification handler gets called and merges changes for all contexts on their threads. I have a merge policy for each context in affect and I am merging changes on the appropriate threads.
I still randomly get "optimistic locking failure". Is there something I am missing?
- (void)contextChanged:(NSNotification *)notif
{
//gets called from the thread(where the context was) that made the changes
//iterate over all contexts and mergeChanges on their thread
NSLog(#"NotifContext %# %p", [notif object], [NSThread currentThread]);
//always check the main
if([notif object] != [self mainContext]){
NSLog(#"merge with main %#", [self mainContext]);
[[self mainContext] performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:) withObject:notif waitUntilDone:NO];
}
//check alternate cotexts and merge changes on their threads
NSDictionary *altContexts = [self.altContexts copy];
for(NSString *threadAddress in altContexts){
NSDictionary *info = [altContexts objectForKey:threadAddress];
NSManagedObjectContext *context = [info objectForKey:#"context"];
if(context != NULL && [notif object] != context){
NSLog(#"merge with %#", context);
NSThread *thread = [info objectForKey:#"thread"];
[context performSelector:#selector(mergeChangesFromContextDidSaveNotification:) onThread:thread withObject:notif waitUntilDone:NO];
}else{
NSLog(#"not with %#", context);
}
}
[altContexts release];
}

waitUntilDone:NO //should have been YES
I overlooked this. I meant to wait until it was done. Otherwise, the save happens (on thread 2), the notification gets dispatched, the contextChanged: handler gets triggered, the other contexts are told to merge changes on their thread (say thread 1), and thread 2 continues before the context on thread 1 actually gets to save.

Related

Inject keyboard event into NSRunningApplication immediately after foregrounding it

I am trying to bring to foreground a NSRunningApplication* instance, and inject a keyboard event.
NSRunningApplication* app = ...;
[app activateWithOptions: 0];
inject_keystrokes();
... fails to inject keyboard events, but:
NSRunningApplication* app = ...;
[app activateWithOptions: 0];
dispatch_time_t _100ms = dispatch_time( DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC) );
dispatch_after(
_100ms,
dispatch_get_main_queue(),
^{ inject_keystrokes(); }
);
... succeeds.
I imagine it takes a certain amount of time for the window to render in the foreground, and maybe this happens on a separate thread, and this explains the injection failure.
However this is a very ugly solution. It relies on an arbitrary time interval.
It would be much cleaner to somehow wait for the window to complete foregrounding.
Is there any way of doing this?
PS inject_keystrokes() uses CGEventPost(kCGHIDEventTap, someCGEvent)
PPS Refs:
- Virtual keypress goes to wrong application
- Send NSEvent to background app
- http://advinprog.blogspot.com/2008/06/so-you-want-to-post-keyboard-event-in.html
Adding an observer for the KVO property isActive on NSRunningApplication works for me.
for (NSRunningApplication* ra in [[NSWorkspace sharedWorkspace] runningApplications])
{
if ([ra.bundleIdentifier isEqualToString:#"com.apple.TextEdit"])
{
[ra addObserver:self forKeyPath:#"isActive" options:0 context:ra];
[ra retain];
[ra activateWithOptions:0];
}
}
// ...
- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context
{
if ([keyPath isEqualToString:#"isActive"])
{
NSRunningApplication* ra = (NSRunningApplication*) context;
[ra removeObserver:self forKeyPath:#"isActive"];
[ra release];
inject_keystrokes();
}
}
Note that I manually retain and then release the NSRunningApplication to keep its reference alive, since I'm not keeping it in a property or ivar. You have to be careful that the reference doesn't get dropped with the observer still attached.

How do you safely lock a variable using GCD?

I have an NSMutableArray I need to add objects to from multiple blocks I have dispatched. Is this an acceptable way to make sure that the array is safely being changed? These are already being dispatched from inside and NSOperation and running in the background. I was loading the data from within that thread serially but it was getting very slow to load a list of locations at once.
NSMutableArray *weatherObjects = [[NSMutableArray alloc] init];
ForecastDownloader *forecastDownloader = [[ForecastDownloader alloc] init];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t serialQueue;
serialQueue = dispatch_queue_create("us.mattshepherd.ForecasterSerialQueue", NULL);
for (NSDictionary *theLocation in self.weatherLocations) {
// Add a task to the group
dispatch_group_async(group, queue, ^{
NSLog(#"dispatching...");
int i = 0;
WeatherObject *weatherObject = [forecastDownloader getForecast:[theLocation objectForKey:#"lat"] lng:[theLocation objectForKey:#"lng"] weatherID:[[theLocation objectForKey:#"id"] intValue]];
}
if(!weatherObject){
//need to implement delegate method to show problem updating weather
NSLog(#"problem updating weather data");
}else{
NSLog(#"got weather for location...");
dispatch_sync(serialQueue, ^{
[weatherObjects addObject:weatherObject];
});
}
});
}
// wait on the group to block the current thread.
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(#"finished getting weather for all locations...");
//we will now do something with the weatherObjects
That's not going to work because you're making a new lock each time, rather than using a single lock for the variable (analogy: imagine a locked door to a room. If everyone gets their own door with a lock, it hardly matters that they lock it, since everyone else will come in their own door).
You can either use a single NSLock for all iterations, or (basically equivalently) a single serial dispatch queue.

NSOperationQueue waitUntillAllOperationsAreFinished blocks operations

I've created my own NSOperation subclass and now I want some of its instances to run in parallel. As it's a concurrent operation I've overwritten
- (void)start {
[self willChangeValueForKey:#"isExecuting"];
isExecuting = YES;
[self didChangeValueForKey:#"isExecuting"];
error=NO;
startedSection=NO;
// start the task
[task launch];
}
- (BOOL)isConcurrent {
return YES;
}
- (BOOL)isExecuting {
return isExecuting;
}
- (BOOL)isFinished {
return isFinished;
}
and the following code is executed when the operation finishes:
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
isExecuting = NO;
isFinished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
When I add [myQueue waitUntillAllOperationsAreFinished] after having added the operations to myQueue none of the operations even starts! When I remove the [myQueue waitUntillAllOperationsAreFinished] I get what I want, except for the fact that this could lead to some errors because of the main thread interfering with these operations ...
How is the concurrency of the operations implemented? I suspect you're dispatching things to the main thread and you're also blocking in the main thread in -waitUntillAllOperationsAreFinished. That will, of course, deadlock.

NSOutlineView not refreshing when objects added to managed object context from NSOperations

Background
Cocoa app using core data Two
processes - daemon and a main UI
Daemon constantly writing to a data store
UI process reads from same data
store
Columns in NSOutlineView in UI bound to
an NSTreeController
NSTreeControllers managedObjectContext is bound to
Application with key path of
delegate.interpretedMOC
NSTreeControllers entity is set to TrainingGroup (NSManagedObject subclass is called JGTrainingGroup)
What I want
When the UI is activated, the outline view should update with the latest data inserted by the daemon.
The Problem
Main Thread Approach
I fetch all the entities I'm interested in, then iterate over them, doing refreshObject:mergeChanges:YES. This works OK - the items get refreshed correctly. However, this is all running on the main thread, so the UI locks up for 10-20 seconds whilst it refreshes. Fine, so let's move these refreshes to NSOperations that run in the background instead.
NSOperation Multithreaded Approach
As soon as I move the refreshObject:mergeChanges: call into an NSOperation, the refresh no longer works. When I add logging messages, it's clear that the new objects are loaded in by the NSOperation subclass and refreshed. It seems that no matter what I do, the NSOutlineView won't refresh.
What I've tried
I've messed around with this for 2 days solid and tried everything I can think of.
Passing objectIDs to the NSOperation to refresh instead of an entity name.
Resetting the interpretedMOC at various points - after the data refresh and before the outline view reload.
I'd subclassed NSOutlineView. I discarded my subclass and set the view back to being an instance of NSOutlineView, just in case there was any funny goings on here.
Added a rearrangeObjects call to the NSTreeController before reloading the NSOutlineView data.
Made sure I had set the staleness interval to 0 on all managed object contexts I was using.
I've got a feeling this problem is somehow related to caching core data objects in memory. But I've totally exhausted all my ideas on how I get this to work.
I'd be eternally grateful to anyone who can shed any light as to why this might not be working.
Code
Main Thread Approach
// In App Delegate
-(void)applicationDidBecomeActive:(NSNotification *)notification {
// Delay to allow time for the daemon to save
[self performSelector:#selector(refreshTrainingEntriesAndGroups) withObject:nil afterDelay:3];
}
-(void)refreshTrainingEntriesAndGroups {
NSSet *allTrainingGroups = [[[NSApp delegate] interpretedMOC] fetchAllObjectsForEntityName:kTrainingGroup];
for(JGTrainingGroup *thisTrainingGroup in allTrainingGroups)
[interpretedMOC refreshObject:thisTrainingGroup mergeChanges:YES];
NSError *saveError = nil;
[interpretedMOC save:&saveError];
[windowController performSelectorOnMainThread:#selector(refreshTrainingView) withObject:nil waitUntilDone:YES];
}
// In window controller class
-(void)refreshTrainingView {
[trainingViewTreeController rearrangeObjects]; // Didn't really expect this to have any effect. And it didn't.
[trainingView reloadData];
}
NSOperation Multithreaded Approach
// In App Delegate (just the changed method)
-(void)refreshTrainingEntriesAndGroups {
JGRefreshEntityOperation *trainingGroupRefresh = [[JGRefreshEntityOperation alloc] initWithEntityName:kTrainingGroup];
NSOperationQueue *refreshQueue = [[NSOperationQueue alloc] init];
[refreshQueue setMaxConcurrentOperationCount:1];
[refreshQueue addOperation:trainingGroupRefresh];
while ([[refreshQueue operations] count] > 0) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.05]];
// At this point if we do a fetch of all training groups, it's got the new objects included. But they don't show up in the outline view.
[windowController performSelectorOnMainThread:#selector(refreshTrainingView) withObject:nil waitUntilDone:YES];
}
// JGRefreshEntityOperation.m
#implementation JGRefreshEntityOperation
#synthesize started;
#synthesize executing;
#synthesize paused;
#synthesize finished;
-(void)main {
[self startOperation];
NSSet *allEntities = [imoc fetchAllObjectsForEntityName:entityName];
for(id thisEntity in allEntities)
[imoc refreshObject:thisEntity mergeChanges:YES];
[self finishOperation];
}
-(void)startOperation {
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isStarted"];
[self setStarted:YES];
[self setExecuting:YES];
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isStarted"];
imoc = [[NSManagedObjectContext alloc] init];
[imoc setStalenessInterval:0];
[imoc setUndoManager:nil];
[imoc setPersistentStoreCoordinator:[[NSApp delegate] interpretedPSC]];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:imoc];
}
-(void)finishOperation {
saveError = nil;
[imoc save:&saveError];
if (saveError) {
NSLog(#"Error saving. %#", saveError);
}
imoc = nil;
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
[self setExecuting:NO];
[self setFinished:YES];
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
-(void)mergeChanges:(NSNotification *)notification {
NSManagedObjectContext *mainContext = [[NSApp delegate] interpretedMOC];
[mainContext performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:)
withObject:notification
waitUntilDone:YES];
}
-(id)initWithEntityName:(NSString *)entityName_ {
[super init];
[self setStarted:false];
[self setExecuting:false];
[self setPaused:false];
[self setFinished:false];
[NSThread setThreadPriority:0.0];
entityName = entityName_;
return self;
}
#end
// JGRefreshEntityOperation.h
#interface JGRefreshEntityOperation : NSOperation {
NSString *entityName;
NSManagedObjectContext *imoc;
NSError *saveError;
BOOL started;
BOOL executing;
BOOL paused;
BOOL finished;
}
#property(readwrite, getter=isStarted) BOOL started;
#property(readwrite, getter=isPaused) BOOL paused;
#property(readwrite, getter=isExecuting) BOOL executing;
#property(readwrite, getter=isFinished) BOOL finished;
-(void)startOperation;
-(void)finishOperation;
-(id)initWithEntityName:(NSString *)entityName_;
-(void)mergeChanges:(NSNotification *)notification;
#end
UPDATE 1
I just found this question. I can't understand how I missed it before I posted mine, but the summary is: Core Data wasn't designed to do what I'm doing. Only one process should be using a data store.
NSManagedObjectContext and NSArrayController reset/refresh problem
However, in a different area of my application I have two processes sharing a data store with one having read only access and this seemed to work fine. Plus none of the answers to my last question on this topic mentioned that this wasn't supported in Core Data.
I'm going to re-architect my app so that only one process writes to the data store at any one time. I'm still skeptical that this will solve my problem though. It looks to me more like an NSOutlineView refreshing problem - the objects are created in the context, it's just the outline view doesn't pick them up.
I ended up re-architecting my app. I'm only importing items from one process or the other at once. And it works perfectly. Hurrah!

NSThread with _NSAutoreleaseNoPool error

I have an method which save files to the internet, it works but just slow. Then I'd like to make the user interface more smooth, so I create an NSThread to handle the slow task.
I am seeing a list of errors like:
_NSAutoreleaseNoPool(): Object 0x18a140 of class NSCFString autoreleased with no pool in place - just leaking
Without NSThread, I call the method like:
[self save:self.savedImg];
And I used the following to use NSThread to call the method:
NSThread* thread1 = [[NSThread alloc] initWithTarget:self
selector:#selector(save:)
object:self.savedImg];
[thread1 start];
Thanks.
Well first of all, you are both creating a new thread for your saving code and then using NSUrlConnection asynchronously. NSUrlConnection in its own implementation would also spin-off another thread and call you back on your newly created thread, which mostly is not something you are trying to do. I assume you are just trying to make sure that your UI does not block while you are saving...
NSUrlConnection also has synchronous version which will block on your thread and it would be better to use that if you want to launch your own thread for doing things. The signature is
+ sendSynchronousRequest:returningResponse:error:
Then when you get the response back, you can call back into your UI thread. Something like below should work:
- (void) beginSaving {
// This is your UI thread. Call this API from your UI.
// Below spins of another thread for the selector "save"
[NSThread detachNewThreadSelector:#selector(save:) toTarget:self withObject:nil];
}
- (void) save {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// ... calculate your post request...
// Initialize your NSUrlResponse and NSError
NSUrlConnection *conn = [NSUrlConnection sendSyncronousRequest:postRequest:&response error:&error];
// Above statement blocks until you get the response, but you are in another thread so you
// are not blocking UI.
// I am assuming you have a delegate with selector saveCommitted to be called back on the
// UI thread.
if ( [delegate_ respondsToSelector:#selector(saveCommitted)] ) {
// Make sure you are calling back your UI on the UI thread as below:
[delegate_ performSelectorOnMainThread:#selector(saveCommitted) withObject:nil waitUntilDone:NO];
}
[pool release];
}
You need to mainly create an autorelease pool for the thread. Try changing your save method to be like this:
- (void) save:(id)arg {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//Existing code
[pool drain];
}
You will not you that the above does not call release on the NSAutoreleasePool. This is a special case. For NSAutoreleasePool drain is equivalent to release when running without GC, and converts to a hint to collector that it might be good point to run a collection.
You may need to create a run loop. I will add to Louis's solution:
BOOL done = NO;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[NSRunLoop currentRunLoop];
// Start the HTTP connection here. When it's completed,
// you could stop the run loop and then the thread will end.
do {
SInt32 result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1, YES);
if ((result == kCFRunLoopRunStopped) || (result == kCFRunLoopRunFinished)) {
done = YES;
}
} while (!done);
[pool release];
Within the thread, you need to create a new autorelease pool before you do anything else, otherwise the network operations will have issues as you saw.
I don't see any reason for you to use threads for this. Simply doing it asynchronously on the run loop should work without blocking the UI.
Trust in the run loop. It's always easier than threading, and is designed to provide the same result (a never-blocked UI).

Resources