Realm and Reactive Cocoa - mvvm

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.

Related

Is it thread safe to get context of NSManagedObject instance?

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.

UI no response due to __psynch_mutexwait

My app sometimes becomes dead , and the call stack is like below,
Update:
I've spent two days but could not handle it yet.
What I have are contexts for each thread, and one retained context for a special serial queue.
What I am wondered about is that how the deadlock comes. Why all the threads are in waiting states? Just one possibility will be much appreciated.
Thanks a lot.
That happens some times when doing work in a notification handler that may result in other notifications being sent, easiest way to avoid it is to dispatch_async the work that needs to be done.
dispatch_async(dispatch_get_current_queue(), ^{
// The work that needs to be done...
});
This usually happens when one tries to access Core Data objects on a background thread using the main-threads context OR using the same managed object context on different threads (background or main) at the same time. For more details check out Core Data concurrency rules.
So to avoid both cases, the main rule is, each thread must have its own managed object context and initialize that context exactly where it's going to be used.
For example:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
//
// Prepare your background core data context
//
if (self.privateContext == nil)
{
self.privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[self.privateContext setParentContext: - main managed object context - ];
[self.privateContext setUndoManager:nil]; // this context should not manage undo actions.
}
//
// Do any Core Data requests using this thread-save context
//
.
.
.
});

Core Data merge behaviour

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.

ARC delegate issue

I need to download some images from server. So I created a seperate class to handle NSURLConnection delegates.
At the end of didFinishDownloadingData, I called a delegate method like [(id)delegate performSelectorselector(finished:) withObject:receivedData]
I have a view controller called ListImages.
I created the above connection class from ListImages class and assigned connection.delegate = self. After image loaded from server the method -(void)didFinishDownloadingData:(NSData *)data; was called successfully, and I could display that image.
My problem starts now. To handle some common tasks, I created a new class called SharedMethods which is a subclass of NSObject. I allocated connection class as
Connection *conn = [[Connection alloc]init];
conn.delegate = self;
[conn startDownload]; //called a method which starts nsurlconnection.
I am using ARC so not released that object. My applicaion got exception in method, (In Connection class)
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[(id)delegate performSelectorselector(finished:) withObject:receivedData]; //Got an exception in this line
}
The exception was [SharedMethods retain] message send to deallocated object. I dont think I have released anything because I am using ARC.
There was also a problem while callingUIAlerView delegates inside a Class which is a subclass of NSobject. It is not called any how. My doubt is, is there any problem with using a NSObject sublass? Is there anything to consider when using NSObject sublass ?
Thanks in advance.
Using ARC doesn't mean than objects never receive the release method, or that they never get deallocated. It just means that you don't have to make explicit calls to retain and release, and that happens automatically.
The problem here is that your objects are getting deallocated because no one is owning them. Your specific problem is that SharedMethods is being deallocated because it's not getting retained, but I can't show you how exactly that's happening because you didn't post the relevant code.
I can, however, show you that you're not managing your Connection properly, and hopefully that can help you figure out what you're doing wrong with SharedMethods.
So you create Connection with alloc init, which with retain-release code would give it a retain count of 1, but since you're not using ARC anymore that's not really relevant. Unless some other object asserts ownership of the Connection, ARC will automatically insert a call to release to bring the retain count back to 0 (it's kind of like if ARC automatically inserted an autorelease).
Since you don't assign Connection to a strong or retain property, or put it in a collection, no other object is asserting ownership to it. So once execution reaches the end of the scope where the variable conn is defined, it will get released and deallocated.
So in ARC, much like in manual retain-and-release code, you still need to make sure objects are owned by some other object in order for them to stick around. The only difference is that you don't need to manually call retain and release, you just have to think about the object ownership graph—which object is owned by which other object—and make sure that any object you want to stick around is owned by some other object.
So to reiterate, you need make sure that SharedMethods is owned by some other object.

iPhone Core Data - cannot fulfill a fault error

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.