NSManagedObject: create on separate thread - objective-c

I understand that Core Data is not thread safe and that NSManagedObjectContext and NSManagedObjects associated with a context cannot be passed from thread to thread.
Question:
However, if I have a NSManagedObjectContext on my main thread, can I create a NSManagedObject object on a background thread (WITHOUT attaching it to any context -- that is, simply call alloc/init on NSManagedObject), then pass that NSManagedObject back to the main thread and add it to the context from there? I've reviewed the docs on Core Data concurrency, but can't find anything that says this usage pattern is okay.
Context:
I have a background thread that performs a complex task and then publishes a result. The result is an NSManagedObject subclass that contains a few attributes: time, a file path, and a success or error message (as a string). I want to create the result object on the background thread, then toss it back to the main thread and add it to the Core Data context where it will be displayed in a tableView.
If I can't create the managedObject on the background thread, then I'll need to create a dictionary, pass the dictionary to the main thread, read the keys, create the managedObject from those values, etc. Just seems cleaner to create the managedObject on the background thread if possible.

The better approach is to have a context per thread. This way each thread will have its own scratch pad to play around with. Then when you background thread finishes, tell your main thread to update its view or ui table view or how every you are presenting your data.
You'll need to notify your main thread when changes occur. The big problem is, the contexts between different threads and main thread are not aware of each other. There is a way in core data to keep the contexts in sync. When you want to save, the context on the background thread should broadcast an NSManagedObjectContextDidSaveNotification notification.
So for example, in your main method in your NSOperation, you could do this:
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:context];
the mergeChanges would be a private method in your NSOperation instance.
example of merge changes
- (void)mergeChanges:(NSNotification *)notification
{
ApplicationController *appController = [[NSApplication sharedApplication] delegate];
NSManagedObjectContext *mainContext = [appController managedObjectContext];
// Merge changes into the main context on the main thread
[mainContext performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:)
withObject:notification
waitUntilDone:YES];
}

Related

how to receive NSWorkspace and accessibility notifications on another thread

I am trying to do window management, but I need the code running on a separate thread.
The first thing I need to do is subscribe to app notifications like this:
NSNotificationCenter *nc = [[NSWorkspace sharedWorkspace] notificationCenter];
NSString *not = NSWorkspaceDidLaunchApplicationNotification;
[nc addObserver:self selector:#selector(appLaunched:) name:not object:nil];
But if I simply call addObserver on another thread, will the notifications be delivered there instead?
Apple has this reference, but it seems overcomplicated:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Notifications/Articles/Threading.html
If the answer to the first question is no, then why couldn't I just forward the message like this?
NSThread *other;
- (void)appLaunched:(NSNotification*)not {
if([NSThread currentThread] != otherThread)
[self performSelector:#selector(appLaunched:) onThread:other withObject:not waitUntilDone:NO];
else
// do respond to notification
}
The second thing I need to do is add an AXObserver to a runloop on the other thread.
If I call CFRunLoopGetCurrent() from another thread, will a run loop automatically be created like calling [NSRunLoop currentRunLoop] or do a I have to create one?
Observers which are registered using -addObserver:selector:name:object: receive the notification on the thread where it's posted, not where they registered. There's also -addObserverForName:object:queue:usingBlock:, which causes the notification to be received on the specified queue, but that doesn't let you make it arrive on a specified background thread. (Only the main queue is tied to a thread.)
You can shunt a notification to another thread in the manner you suggest. However, the original receiving thread has to be idling to receive the notification in the first place. Or, rather, it has to be idling in order to allow NSWorkspace to detect the condition which causes it to post the notification.
All threads create a runloop for themselves as soon as it's requested. It's basically impossible to observe a thread not having a runloop, so you might as well just act as though the runloop is created when the thread is created.
All of that said, your original goal – "I am trying to do window management, but I need the code running on a separate thread" – is problematic. Many GUI manipulations are not legal from background threads. Also, why do you "need" to do it from a background thread? And if your main thread is not free, you're not going to receive the workspace notifications in the first place.

NSNotificationCenter removeObserver: in dealloc and thread-safety

I'm using ARC and I'm calling [[NSNotificationCenter defaultCenter] removeObserver:someObserver]; in observer's dealloc.
From NSNotificationCenter Class Reference
Be sure to invoke this method (or removeObserver:name:object:) before
notificationObserver or any object specified in
addObserver:selector:name:object: is deallocated.
NSNotificationCenter does not retain the observer.
Q1: Is NSNotificationCenter thread-safe?
In case, the observer is being deallocated(and removing observer from the notification center) and another thread post a notification at the same time.
I encounter random crash and I suspect this is the case.
Q2: Is this situation possible?
Q3: Does it lead to EXC_BAD_ACCESS?
Q4: Then, is it safe to call [[NSNotificationCenter defaultCenter] removeObserver:someObserver]; in observer's dealloc?
Q5: If it is not safe, where should I call removeObserver:?
I just stumbled into this problem myself: I had one notification just in the process of being sent (which always happens in the main thread) while the object was in the process of being deallocated from a background thread. I fixed it by simply performing removeObserver in the main thread and waiting:
- (void)removeNotificationCenterObserver
{
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self];
}
- (void)dealloc
{
[self performSelectorOnMainThread:#selector(removeNotificationCenterObserver) withObject:self waitUntilDone:YES];
}
This waits until the current run loop cycle ends and executes this message at the beginning of the next run loop cycle. This ensures that any functions that are still running will finish.
Yes, NSNotificationCenter doesn't retain observer, but it still has a pointer to it in it's dispatch table.
Q1: Quoting Apple docs
Regular notification centers deliver notifications on the thread in which the notification was posted. Distributed notification centers deliver notifications on the main thread. At times, you may require notifications to be delivered on a particular thread that is determined by you instead of the notification center. For example, if an object running in a background thread is listening for notifications from the user interface, such as a window closing, you would like to receive the notifications in the background thread instead of the main thread. In these cases, you must capture the notifications as they are delivered on the default thread and redirect them to the appropriate thread.
Q2,3: Yes.
Q4,5: AFAIK it's safe unless you stumble into circular reference.
I usually add/remove in -viewWillAppear:/-viewWillDisappear: for UIViewControllers and -init/dealloc for other classes.
I've wondered the same thing, and I can't find it documented. Here's what I think is going on.
removeObserver: is not thread safe in the way that you want it to be.
Think about the following situation. The last reference to the observer is released while executing code on thread A. Thread A will call the observer's dealloc method. At the same time, the observed object executes a [NSNotificcationCenter postNotificationName:object:] from thread B. This leads to an unavoidable race condition. That is, a notification will be in flight while your object is within its dealloc method.
- (void)init {
...
[[NSNotificcationCenter defaultCenter] addObserver:self
selector:#selector(callback:)
name:#"whatever"
object:nil];
...
}
- (void)dealloc {
// If the observed object posts the notification on thread B while
// thread A is here, there's a race! At best, thread B will be
// in callback: while thread A is here in dealloc. That's probably
// not what you expect. The worst case is that thread B won't make
// make it to callback: until after thread A completes the dealloc
// and the memory has been freed. Likely crash!
[[NSNotificationCenter defaultCenter] removeObserver:self];
// If the observed object does the post when thread A is here,
// you'll be fine since the observation has been removed.
}
This isn't a problem for main thread objects that are only observing other main thread objects since, by definition, you can't get into the thread A and B scenario I described.
For multi-threaded cases, the only way to guarantee you'll avoid the problem is to ensure that the observation stops before the observer's refcount hits 0. If someone else is responsible for the observer's lifetime (i.e. you have any sort of term or close method), it's easy. If not, I don't know of a solution.

Notify all loaded ViewControllers of a certain event

I have a class that synchronizes data in the background every once in a while. The user can be anywhere in the app's navigation tree and no matter where the user is I need to be able to update the view controllers with whatever new data I just synchronized.
I put the object in charge of the background thread sync as a property of the SharedAppDelegate.
In a way I need to implement something like the Observer pattern and every time I instantiate a view controller set it to listen to some event on the background sync object so that after every sync I can execute a method in the view controllers that are listening.
I'm not sure what the correct way to do this in Objective-C is or if there is even a better or recommended way.
Use a NSNotification with NSNotificationCenter, which fits precisely your purpose :
in your AppDelegate, when the sync ends, call
[[NSNotificationCenter defaultCenter] postNotificationName:#"SyncEnded" object:mySyncObject]
in every view controller you display, call
_myObserver = [[NSNotificationCenter defaultCenter] addObserverForName:#"SyncEnded" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note){ ...your UI refresh code... }
also do not forget to remove the observer when it's not needed anymore (view controller deallocated / unloaded / not visible, up to you ), or the NSNotificationCenter will end up crashing :
[[NSNotificationCenter defaultCenter] removeObserver:_myObserver];
A few notes :
This example uses the block-based API to perform the UI refresh work on the main operation queue (implying on the main thread), because you must not perform UIKit operations on any other thread than the main thread. This is likely that your background sync will send its notification on an other thread, so switching to the main thread is necessary. If you want to use the selector-based API, be sure to send the notification on the main thread.
You can register as many observers on the notification as you want, so this matches perfectly your pattern (NSNotifications are usually the best way to notify different app components of an app-wide event like sync end).
The object parameter passed when posting the notification allows you to access the sync object in the observer block using note.object if you need it.

CoreData: update in background and reading in main thread causes dead lock

I'm displaying a table with some data to the user. As soon as the view is presented, I'm making a web call to see if there's updated data (asynchronously). When the service call returns, I'd like to update my core data and the view.
Unfortunately I'm often getting dead locks because the view reads the same data as the service call writes. How can I solve this?
When I pause the simulator as soon as it's frozen, the waiting threads are waiting at the following places:
Background (updating) thread: (psynch_cvwait)
[mainContext performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:)
withObject:notification
waitUntilDone:YES];
Main thread: (psynch_mutexwait)
performing a filteredArrayUsingPredicate
Thanks a lot!
-mergeChangesFromContextDidSaveNotification: will block the main thread. That call will lock both NSManagedObjectContext instances while it updates the main context with the changes being passed in.
This is generally considered unavoidable in pre-iOS 5 applications. You can minimize it by making more frequent, smaller, saves but it is still going to happen.
The only other option for pre-iOS 5 applications is to tell the main context to -reset: but that will require re-fetching everything -- another delay.
It looks like the main thread is trying to grab some low level lock that the background thread already has (or vice versa). Are you using #synchronized somewhere to provide mutex?
Anyway, is there any reason why your background thread needs to wait for -mergeChangesFromContextDidSaveNotification: to complete? If not, pass NO as the last parameter.
I had the same problem (lock on psynch_cvwait) when I was merging context changes (both ways) between the main and a background context (both using NSConfinementConcurrencyType). The problem was caused by subscribing to NSManagedObjectContextDidSaveNotification on a different queue from which it was sent:
[[NSNotificationCenter defaultCenter]
addObserverForName:NSManagedObjectContextDidSaveNotification
object:mainContext
queue:bgQueue
usingBlock:^(NSNotification * _Nonnull note) {
// bgContext runs on bgQueue
[bgContext mergeChangesFromContextDidSaveNotification:note];
}]
As a result the block was never called, and both main and background queues hung on psynch_cvwait()
I fixed it by not blocking mainContext's queue:
[[NSNotificationCenter defaultCenter]
addObserverForName:NSManagedObjectContextDidSaveNotification
object:mainContext
queue:nil
usingBlock:^(NSNotification * _Nonnull note) {
[bgQueue addOperationWithBlock:^{
[bgContext mergeChangesFromContextDidSaveNotification:note];
}];
}]
However, it doesn't seem to be a problem if I block the background queue when merging changes into the main context.

Help with multithreaded Core Data app design

Above is a simplification of what my model looks like. My app has a NSWindowController object controlling two NSViewController objects for the user and account entities. When a user logs in to the app, they can modify user or account information by bringing up the relevant view controller. In the background I have the application periodically populating the user's logs in the application delegate on a separate thread.
I am using a separate NSManagedObjectContext for the background thread and the application delegate's NSManagedObjectContext for data entry in the view controllers. I would like to know a few things:
1) is this good practice? Should I create a NSManagedObjectContext for each view controller and then merge the contexts whenever the user is done making changes?
2) Because the log entity is created in the background thread, it has it's own NSManagedObjectContext. However, each log includes information from the user and account entities, which are created in the application delegate's NSManagedObjectContext. This is how I am fetching a user:
- (NSManagedObjectID*) fetchUser:(NSString*) userID {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"user":inManagedObjectContext:self.managedObjectContext];
/** snip **/
}
This method is called by the background thread as follows:
NSManagedObjectID* userObjectID = [self fetchUser:userID];
NSManagedObject* userObject = [self.logsManagedObjectContext objectWithID:userObjectID];
Is what I'm doing in fetchUser thread-safe? Do I need to lock the main managed object context while fetching a user in case one of the views is modifying the same user? From this article I understand (perhaps incorrectly) that I may have to do so. So far I haven't run into any problems but I don't want to leave a potential edge case.
3) When one of the view controllers makes changes to the application delegate's NSManagedObjectContext it posts a notification that is handled as follows:
- (void)contextDidSave:(NSNotification *)notification {
SEL selector = #selector(mergeChangesFromContextDidSaveNotification:);
[self.logManagedObectContext performSelector:selector onThread:backgroundThread withObject:notification waitUntilDone:NO];
}
Is this how I should handle the merge or should I be merging the application delegate's NSManagedObjectContext instead? I found that doing that (on the main thread) locked up the UI.
Any help will be appreciated.
NSManagedObjectContext objects are not thread-safe. This means that if you wish to access Core Data from multiple threads, you will need one for each thread (and created on the thread too). Each of these can use the same NSPersistentStoreCoordinator, which will serialise access to the persistent store.
This occurs because each NSManagedObjectContext knows how to properly lock the NSPersistentStoreCoordinator when it is in use, avoiding collisions. By following these rules, you should remain thread-safe.
As you're already doing, NSManagedObjectID objects should be used to pass Core Data objects from one MOC to another (and by extension from one thread to another). However you are calling fetchUser: which uses the MOC from your main thread, on a background one. This isn't correct. That fetchUser: method call must be called from the main thread. Of course, there's nothing to stop you from retrieving the user in the background thread using the background MOC.
In summary, always make calls to an NSManagedObjectContext from the thread it was created in.
The trick here is to make sure that both MOCs know about the other's saves, so you must register to receive the notifications from each context. You should then be performing the mergeChangesFromContextDidSaveNotification: from the appropriate thread for the MOC. At the moment, your background context is being notified about changes from the main thread's context, but not vice versa.
Oh, and there's no need to have a separate context for each NSViewController. As UI elements, their interactions with the context will occur on the same (main) thread, so sharing is fine.