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.
Related
I use NSPersistentContainer as a dependency in my classes. I find this approach quite useful, but there is a dilemma: I don't know in which thread my methods will be called. I found a very simple solution for this
extension NSPersistentContainer {
func getContext() -> NSManagedObjectContext {
if Thread.isMainThread {
return viewContext
} else {
return newBackgroundContext()
}
}
}
Looks wonderful but I still have a doubt is there any pitfalls? If it properly works, why on earth Core Data confuses us with its contexts?
It's OK as long as you can live with its inherent limitations, i.e.
When you're on the main queue, you always want the viewContext, never any other one.
When you're not on the main queue, you always want to create a new, independent context.
Some drawbacks that come to mind:
If you call a method that has an async completion handler, that handler might be called on a different queue. If you use this method, you might get a different context than when you made the call. Is that OK? It depends what you're doing in the handler.
Changes on one background context are not automatically available in other background contexts, so you run the risk of having multiple contexts with conflicting changes.
The method suggests a potential carelessness about which context you're using. It's important to be aware of which context you're using, because managed objects fetched on one can't be used with another. If your code just says, hey give me some context, but doesn't track the contexts properly, you increase the chance of crashes from accidentally crossing contexts.
If your non-main-queue requirements match the above, you're probably better off using the performBackgroundTask(_:) method on NSPersistentContainer. You're not adding anything to that method here.
[W]hy on earth Core Data confuses us with its contexts?
Managed object contexts are a fundamental part of how Core Data works. Keeping track of them is therefore a fundamental part of having an app that doesn't corrupt its data or crash.
Can I have a single Private Managed Object context that is being accessed by Multiple NSOperation ?
I have 2 two options :
Have a managed object context per NSOperation.
i.e if there are 100 NSoperation 100 context will be created.
Have a single context and multiple NSOperation.
i.e Single Context and 100 NSOperations accessing it.
Which can be a better option.
The correction solution is option 1. Create a queue with a concurrency count of 1 and do all your writing with the queue. This will avoid any write conflict which can lead to losing information. If you need to access information for the main thread you should use a global main thread context (in NSPersistentContainer it is call viewContext).
If this is going to slow then you should investigate the work that you are doing. Generally each operation should be pretty quick, so if you are finding that they are not you might be doing something wrong (a common issue is doing a fetch for each imported object - instead of one large fetch). Another solution is to split up large tasks into several smaller task (importing large amount of data). You can also set different priority - giving higher priority to actions that the user initiated.
You shouldn't be afraid of creating contexts. They are not that expensive.
According to what you said in the comments, that you didn't actually write lots of data in the single operation and just did a fetch for an object, I suggest using single MOC.
Usual reason to have multiple MOC is to read/update/save lots of data independently of Main context and of any other contexts. In such flow you would be able to save that objects concurrently from different contexts.
But it's not your case, If I understood correctly.
For the single fetch there would be enough just one Private context, however I believe there wouldn't be lots overhead in creating many contexts. But why to do extra work?
1.So, you create private MOC
let privateContext = NSManagedObjectContext(concurrencyType:.privateQueueConcurrencyType)
2.Create each operation and pass MOC
let operation = MyOperation(context: privateContext)
3.In the operation perform sync call to private MOC with function.
In such way you should avoid any concurrent problem with single MOC
func performAndWait(_ block: #escaping () -> Swift.Void)
for example
let myObject: Object?
privateContext.performAndWait {
myObject = privateContext.fetch(...)
}
// do what you need with myObject
I noticed that when saving NSManagedObjectContexts, RestKit wraps the save call on each NSManagedObjectContext with a performBlockAndWait.
https://github.com/RestKit/RestKit/blob/development/Code/CoreData/NSManagedObjectContext%2BRKAdditions.m#L64
It was my understanding of managing parent and child NSManagedObjectContexts that only a NSManagedObjectContext with type MainQueueConcurrencyType should be saved this way (and that is usually the child context of another NSManagedObjectContext of type PrivateQueueConcurrencyType which is what is actually associated with persistentStoreCoordinator). I thought that the idea was that saving to the persistent store (ie disk) is a longer operation and doesn't, and shouldn't, be waited for. Where am I going wrong?
Everything you do with a ManagedObjectContext has to be done on that context's dispatch queue. The easiest way to make sure that happens is to do it in a block called by performBlock or performBlockAndWait. If there is code later in the method depending on the result of the block, performBlockAndWait is the way to go. If you have to add these blocks to older Core Data code, as in the case of RestKit, wrapping NSManagedObjectContext calls in a performBlockAndWait is a not-too-painful way to make your Core Data code (more) thread-safe.
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.
NSManagedObjectContext's have had the performBlock: and performBlockAndWait: methods added to help make concurrency easier. I've been using them -- potentially fairly naively -- and I just realized that there's a question I've never really asked.
If I create an NSManagedObject subclass inside one of the performBlock methods, it's 'home' thread is the thread of it's parent context -- which in the case of the NSPrivateQueueConcurrencyType is probably an independent thread I have no other access to.
So do I need to do a performBlock call just to access the data contained inside my managed objects? Or is there a background magic going on to help protect me in the case of using getters? (Or setters, though that seems like a bad idea...)
NSManagedObject is not supposed to be used outside of its managedObjectContexts thread/queue (sometime it works and some times you crash ==> don't do it).
CoreData does not guarantee safe read access to the object.
To access an object owned by a "private queue" context, always use either [context performBlock:...] or [context performBlockAndWait:...], unless you access its objectID or managedObjectContext properties.
You do need to use performBlock: or performBlockAndWait:, but there's one exception. If you're using NSMainQueueConcurrencyType and you are using the managed object on the main queue, you can access it directly, with no block. This can be a great convenience when you need to update your UI from a managed object, or vice versa.