I.e. to access the managedObjectContext property of NSManagedObject from other thread? For example:
class StoredObject: NSManagedObject {
#NSManaged public var interestProperty: String
}
------- somewhere on background -------
let context = storedObject.managedObjectContext // is it safe?
context.perform { [storedObject] in
// do something with interestProperty
}
---------------------------------------
NSManagedObjectContext is not thread safe. Even if you grab the instance of such an object, using it on a different thread might lead to undefined behaviour.
This is specified in the Apple documentation (emphasis mine):
Core Data is designed to work in a multithreaded environment. However, not every object under the Core Data framework is thread safe. To use Core Data in a multithreaded environment, ensure that:
Managed object contexts are bound to the thread (queue) that they are associated with upon initialization.
Managed objects retrieved from a context are bound to the same queue that the context is bound to.
So while reading the managedObjectContext property might be thread safe, as that property is readonly, you will not be able to use it without risking race conditions. And you also need to take into consideration the lifetime of the managed object, as unless properly retained, you might end up asking a deallocated managed object for its context.
Related
I am using Realm 2.0.4 and ReactiveCococa 2.5 (Objective-C API)
I am trying to use MVVM.
I am trying to "bind" my VM to an underlying RLMObject. Which I have done with some success. Like this.
RACSignal *invalidationSignal = [[[RACObserve(self, boundedProfile.invalidated)
takeUntil:self.rac_willDeallocSignal]
filter:^BOOL (NSNumber *invalid) {
return [invalid boolValue] == true; // We're only interested in the cases where it was invalidated.
}]
replayLast]; // For multicasting the same value for all subscribers.
#weakify(self);
self.updateSignalDisposable = [[RACObserve(self, boundedProfile.currentProfileTimestamp)
takeUntil:invalidationSignal]
subscribeNext:^(id x) {
#strongify(self)
CYRLMProfile * profile = [CYRLMProfile profileWithID:self.userId];
self.userId = profile.userId;
...
}];
My problem here is that when my VM gets dealloc'ed, I set nil to my realm object, and that also deallocates the realm associated with the bounded object.
Some times this happens when Realm is in the middle of a write transaction. and it crashes because it's canceling a write transaction on a different thread of the thread where it was running.
EDIT:
To expand on the issue:
- I have a ViewModel with a strong reference to a RealmObject
- That RealmObject was retrieved from a Realm from any thread
- When the ViewModel is deallocated:
- the RealmObject is nilled leading it to be deallocated since it was the only strong reference to it
- that in turn leads to that object's RLMRealm being released because it was the remaining strong reference to that realm
- when deallocating the realm checks if it is in a write transaction and if so tries to cancel it, leading to the crash on incorrect thread
I think that if I remove the RAC observation the issue still happens.
I am able to reproduce this forceably, by creating a viewmodel with a realm object in a thread other than main and call beginWriteTranscaction and then nilling that object in the main thread, leading to its deallocation
The code is something like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
RLMRealm *realm = [RLMRealm defaultRealm];
CYRLMProfile *profile = [CYRLMProfile profileWithId:10 inRealm:realm];
__block ViewModel *vm = [ViewModel viewModelWithProfile:profile];
[realm beginWriteTransaction];
dispatch_async(dispatch_get_main_queue(), ^{
vm = nil;
});
});
Realm accessed from incorrect thread means that you tried to access realm instance created on another thread. As I suggested in a comment above, make sure you initialize a new instance of Realm for each thread and get a new instance of your object from that Realm instance.
The only thing you have to be aware of is that you cannot have multiple threads sharing the same instances of Realm objects. If multiple threads need to access the same objects they will each need to get their own instances
See more at https://realm.io/docs/objc/latest/#threading.
Since the shared singleton instance will always be around, can we safely use [unowned self] in all closures within that singleton class?
Sure, it's safe. But that's not a good reason.
Whether you use weak references or strong references should be based on the memory management characteristics in the function you are writing. For example, if a closure is referred to strongly by the object, then the closure should capture a weak reference to the object; and that is safe since nobody else has a reference to the closure, so it only can execute while the main object is alive, etc. If there is no retain cycle, and the closure is given to a separate API so that it is not tied to the lifetime of the main object, then the closure should have a strong reference to the main object. This reasoning applies equally for singletons and non-singletons alike.
Yes the singleton holds a strong reference to itself, and can't be disposed.
Base on that is safe to say that you can safely create weak or unowned references to it.
From Apple documents:
The class lazily creates its sole instance the first time it is
requested and thereafter ensures that no other instance can be
created. A singleton class also prevents callers from copying,
retaining, or releasing the instance.
An easy way to test it is to test from the main class.
Create a new class (let's call "first class"), which initializes the singleton with some values and is disposed after finishing a unique job.
After that in the main class create another class (let's call "second class") which retrieves the singleton instance and reads its values.
Between the first (disposed) class and the second (newly created) class there are no references to the singleton.
Now read the values and if values still there it proves that the singleton has kept alive by its own reference.
I tried to find an answer to this question, but I couldn't figure out a question from neither the doc nor StackOverflow. If there is already a question like this, I just didn't find it, so it will be very welcomed as the solution in case.
My situation is:
I have two core data entities, a User and a Driving Licence.
User <--- 1 to 1 ---> Driving Licence
I'm using Magical Record as an abstraction layer for the core data operations.
My User class (derived from NSManagedObject) exposes 2 methods.
One to access a singleton instance of the User (the only one used throughout the app):
+ (User *)currentUser {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if ([User MR_findFirst] == nil) {
User *user = [User MR_createEntity];
user.drivingLicence = [DrivingLicence MR_createEntity];
[[user managedObjectContext] MR_save];
}
});
return [User MR_findFirst];
}
And a method used to reset the user data (derived from NSManagedObject):
- (void)resetFields
{
self.name = nil;
self.surname = nil;
....
[self.drivingLicence MR_deleteEntity];
self.drivingLicence = [DrivingLicence MR_createEntity];
[self.managedObjectContext MR_save];
}
Sometimes, I would say quite randomly, the drivingLicence field happens to be null.
There may be occasions when the resetFields method is called by a background thread.
Could it be that, for the merge with the other contexts, tha sequence of instructions
[self.drivingLicence MR_deleteEntity];
self.drivingLicence = [DrivingLicence MR_createEntity];
can cause some confusion, bringing the drivingLicence to be just deleted at end?
Or what else could it be the reason for this unexpected null value?
When you use MR_createEntity, you are implicitly using the default context, accessed through [NSManagedObjectContext MR_defaultContext]. It is quite dangerous to do this unless you are ABSOLUTELY POSITIVE you are calling that from the main thread. In your examples, all this should work correctly if everything is called from the main thread AND your self.managedObjectContext instance variable is also pointing to the default context. Otherwise, you will need to be explicit about which contexts you are using. MagicalRecord provides these conventions for you by having an inContext: optional parameter at the end of every method that requires a context to work. Have a look at the MR_createInContext: method and be explicit with your context usage
Just hit this problem. Based on our discoveries, I wanted to add some comments to casademora's answer above:
It is important to remember that core data, as well as any of the MR_save methods, are not thread safe. We delegate our MagicalRecord actions to a queue to get around this issue. Specifically, it's important to remember not to do save actions (such as MR_saveToPersistentStoreAndWait) in multiple threads at the same time.
However the connection between MR_createEntity, MR_defaultContext, and the Main thread is more subtle. As of MagicalRecord version 2.3.x, I do not believe that MR_createEntity, with no arguments, defaults to MR_defaultContext. I believe it defaults to the MR_contextForCurrentThread. MR_contextForCurrentThread returns the default context if it's on the Main thread, but otherwise it does not. This is dangerous if you don't understand the consequences, as you could easily see what the original poster sees above (lost data).
In fact, this note indicates that you should use MR_createEntityInContext:localContext rather than MR_createEntity due to problems with MR_contextForCurrentThread when using GCD.
See the function definition in github for the logic where MR_createEntity uses MR_contextForCurrentThread.
I have three classes, A, B and C. A is the main class.
When the user wants to see the list of all objects that were purchased, Class B is called from A and shows the list of objects in a core data entity.
Inside class B, the user can buy new objects (in-app purchase). When the user wants to buy another object, class C is called.
When class C is called, a new object is created on the core data entity using
anObject = [NSEntityDescription insertNewObjectForEntityForName:#"Objects" inManagedObjectContext:context];
this object is then assigned to a local reference on Class C, using something like
self.object = anObject;
this object variable was declared like this:
.h
MyObjects *object;
#property (nonatomic, retain) MyObjects *object;
and #synthesized on .m
MyObjects is a core data class representing the entity.
In theory, object will retain anything assigned to it, so the line self.object = anObject I typed previously will retain anObject reference on self.object, right?
The problem is that when I try to access self.object in the same class after buying the new object, I receive an error "CoreData could not fulfill a fault for XXX", where XXX is exactly self.object.
At no point in the code there's any object removal from the database. The only operation to the database I could identify was a saving operation done by another class moments before the crash. The save is done by something like
if (![self.managedObjectContext save:&error]) ...
Is there any relation? what may be causing that?
CoreData manages the lifetime of managed objects and you should not retain and release them. If you want to keep a reference to the object so that it can be retrieved later then you have to store the object's id (obtained using -[NSManagedObject objectID]). Then use that to retrieve the object later using -[NSManagedObjectContext objectWithID:].
Make sure you understand about CoreData faulting. Read the documentation.
I had a similar issue a few days ago (using NSFetchedResultsController) where I was placing my fetchedObjects into an array and gathering attributes to populate tables from the array objects. It seems that if the objects in the array are faulted, you cannot unfault it unless you are acting on the direct object. In my case, I solved the issue by taking the lines of code in question and calling [[_fetchedResultsController objectAtIndexPath:indexPath] someAttribute]. I would assume that doing something similar would fix your problem as well. It seems a bit tedious to need to fetch from the managedObjectContext to obtain a faulted value, but this was the only way I could personally get past the issue.
Core Data is responsible for managing the lifetime of managed objects in memory. It's really important to understand Managed Object Contexts - Read the documentation.
Apple also provides an entire troubleshooting section here, and it contains among other things the causes for your error. But it's really only useful if you understand how core data works.
Most likely error is that the object you are saving does not belong to the managed object context.
Say you use the same object on different threads and those different threads use different managed object context, then this will happen.
Is a transient property a "managed property" in terms of Core Data? Does Core Data manage it's memory too? Or must I -release that manually in -dealloc?
(I think no, because I might choose to not create a subclass - but I guess it doesn't make sense when I have a transient property, since I need a subclass to calculate that derived value --- or not??)
Memory management in Core Data is a bit tricky.
If you don't do anything special with a transient property, then in most cases the synthesized accessors will manage the property's object life-cycle for you. (When Xcode generates the source for a managed object class from the data model, it doesn't bother to create a final release for the property's object. It would if it was always necessary.) Unfortunately, you're often doing something special with a transient property so you do need to release it or any other objects created in the process.
However, you don't release in dealloc. The Apple docs strongly recommend that you never modify either the init or the dealloc of NSManagedObject subclass. Instead, to release a transient property, you need to put the release in didTurnIntoFault. The reason for this is that when Core Data converts an object to a fault, it purges all its attributes even though the object is still resident in memory and alive.
Because so much goes on behind the scenes with Core Data it is very important to check that the transient property's object is not nil before you send it a final release.
If your managed object allocates anything special at any time to support your transient property, then you should release that in dealloc. Basically, release anything that you create.