NSManagedObjectContext objectWithID lifecycle method (derived property) - multithreading

I have an NSManagedObject subclass with a transient property which is basically a reformatting of one of the persistent to-many relationships. I do this by observing the relationship with KVO and registering the observer in -awakeFromFetch, -awakeFromInsert, etc.
This all works fine, however if I pass the object between threads using the object's objectID and -objectWithID: technique there is no life-cycle method into which I can hook generation of the transient property. None of the life-cycle methods are triggered, in fact, since accessing the object directly using the id isn't considered a fetch, it seems.
There are ways around this, but it would be nice to use a life-cycle based technique. Am I perhaps missing something? Is there another standard method I could be using?
Thanks
Edit: Demonstration project
https://mega.co.nz/#!UsNBTZ7S!UU1qaFuc4W6Z2EYey-9AiMyfM8203Zfrm1lfpG5QITU

When you have a NSManagedObject instance on one thread with a context and then retrieve it from a different thread and different context the -awakeFromFetch or -awakeFromInsert fires.
Are you using the contexts properly so that you are retrieving a new instance?
Have you looked at the pointers in the debugger to make sure you are talking to a new instance of the NSManagedObject?
In my experience those lifecycle methods fire per context.

Ok, so to answer my own question, the issue is caused because objectWithID: always returns an object, even though the object isn't registered in the receiver managed object context. Seems in some circumstances objectRegisteredForID: is more informative. In any case, the conclusion is that the life-cycle methods do fire, but to take care with objectWithID: since it can result in an inconsistent object.

I encountered this same issue, and solved it by creating an extension on NSManagedObjectContext that goes through the normal fetch pathway, therefore triggering all of the expected lifecycle methods:
extension NSManagedObjectContext {
func fetchObject<T: NSManagedObject>(with objectID: NSManagedObjectID) -> T? {
let request = T.fetchRequest()
request.predicate = NSPredicate(format: "SELF = %#", objectID)
guard let result = try? fetch(request) else {
return nil
}
return result.first as? T
}
}

Related

Portable Class Library and ObservableCollection, updating UI Thread

I'm not very experienced with this topic so forgive me if this isn't very clear.
I've created a Portable Class Library that has an ObservableCollection of Sections, and each secion has an ObservableCollection of Items.
Both of these collections are bound to the UI of separate Win8 and WP8 apps.
I'm trying to figure out the correct way to populate these collections correctly so that the UI gets updated from the PCL class.
If the class was inside the win8 project I know I could do something like Dispatcher.BeginInvoke, but this doesn't translate to the PCL, nor would I be able to reuse that in the WP8 project.
In this thread (Portable class library equivalent of Dispatcher.Invoke or Dispatcher.RunAsync) I discovered the SynchroniationContext class.
I passed in a reference to the main app's SynchroniationContext, and when I populate the sections I can do so because it's only the one object being updated:
if (SynchronizationContext.Current == _synchronizationContext)
{
// Execute the CollectionChanged event on the current thread
UpdateSections(sections);
}
else
{
// Post the CollectionChanged event on the creator thread
_synchronizationContext.Post(UpdateSections, sections);
}
However, when I try to do the same thing with articles, I have to have a reference to both the section AND the article, but the Post method only allows me to pass in a single object.
I attempted to use a lambda expression:
if (SynchronizationContext.Current == _synchronizationContext)
{
// Execute the CollectionChanged event on the current thread
section.Items.Add(item);
}
else
{
// Post the CollectionChanged event on the creator thread
_synchronizationContext.Post((e) =>
{
section.Items.Add(item);
}, null);
}
but I'm guessing this is not correct as I'm getting an error about being "marshalled for a different thread".
So where am I going wrong here? how can I update both collections correctly from the PCL so that both apps can also update their UI?
many thanks!
Hard to say without seeing the rest of the code but I doubt is has anything to do with Portable Class Libraries. It would be good to see the details about the exception (type, message and stack trace).
The way you call Post() with more than argument looks correct. What happens if you remove the if check and simply always go through SynchronizationContext.Post()?
BTW: I don't explicitly pass in the SynchronizationContext. I assume that the ViewModel is created on the UI Thread. This allows me to capture it like this:
public class MyViewModel
{
private SynchronizationContext _context = SynchronizationContext.Current;
}
I would recommend that at least in your ViewModels, all publicly observable state changes (ie property change notifications and modifications to ObservableCollections) happen on the UI thread. I’d recommend doing the same thing with your model state changes, but it might make sense to let them make changes on different threads and marshal those changes to the UI thread in your ViewModels.
To do this, of course, you need to be able to switch to the UI thread in portable code. If SynchronizationContext isn’t working for you, then just create your own abstraction for the dispatcher (ie IRunOnUIThread).
The reason you were getting the "marshalled on a different thread" error is that you weren't passing the item to add to the list as the "state" object on the Post(action, state) method.
Your code should look like this:
if (SynchronizationContext.Current == _synchronizationContext)
{
// Execute the CollectionChanged event on the current thread
section.Items.Add(item);
}
else
{
// Post the CollectionChanged event on the creator thread
_synchronizationContext.Post((e) =>
{
var item = (YourItemnType) e;
section.Items.Add(item);
}, item);
}
If you make that change, your code will work fine from a PCL.

Why can't MonoTouch GC kill managed objects with refcount > 1?

I think I'm getting close to understanding how Mono GC and ObjC ref counting live together.
The way it works is that when a native object has a reference count of 1, we do not prevent the managed instance from getting garbage collected. As soon as the reference count increases above 1, we prevent the managed instance from getting garbage collected.
This is because a managed object may contain user state. For managed objects which are mirroring a corresponding native object (such as the managed UIView instance) MonoTouch knows that the instance can not contain any state, so as soon as no managed code has a reference to the managed instance, the GC can collect it. If a managed instance is required at a later stage, we just create a new one.
So if I create a CustomButton that inherits UIButton, add it as subview to my View, let the managed reference slip out of scope and then run GC, this managed CustomButton still won't be eligible for collection.
Why can't it be collected? Of course it may have managed state like properties, but if there is no link to it from managed objects, who cares about this state? It may as well just disappear, why can't it?
I'm thinking of one possible reason: subscribing to CustomButton events won't keep it alive for the GC so when the object gets collected, events stop firing. This would perhaps result in unexpected behavior.
Is this correct? Are there other reasons for keeping the managed object alive even if no one links it?
Why can't it be collected? Of course it may have managed state like properties, but if there is no link to it from managed objects, who cares about this state? It may as well just disappear, why can't it?
Native code might have references to the object, which may cause the object to resurface to managed code again later.
I believe a code sample would illustrate what would happen:
class MyView : UIView {
public string ImportantSecret;
}
class AppDelegate : UIApplicationDelegate {
UIViewController vc;
public override bool FinishedLaunching (UIApplication app,
NSDictionary options)
{
var myView = new MyView ();
myView.ImportantSecret = "MonoTouchRocks";
vc = new UIViewController ();
vc.View = new UIView ();
vc.View.AddSubView (myView);
// When this method returns the only place where myView is referenced
// is from inside the *native* Subviews collection.
BeginInvokeOnMainThread (() =>
{
Console.WriteLine (((MyView) vc.Subviews [0]).ImportantSecret);
// If the MyView instance was garbage collected and recreated
// automatically at this point, ImportantSecret would be null.
});
}
}
Important: this code is just to illustrate the reason why the GC can't collect managed objects which may have state. This particular sample would actually not forget the important secret, since the Subviews array is automatically cached in managed code - but this is not generally true.

Core Data NSManagedObject Insert and Save Methods

Is there an easier way to do something like the following in Core Data:
Entry *entry = [[Entry alloc] init];
entry.name = #"An Entry";
[entry save];
I realize you don't have to allocate an NSManagedObject, have to insert directly into the context like the following:
Entry *entry = [NSEntityDescription insertNewObjectForEntityForName:#"Entry"
inManagedObjectContext:[self managedObjectContext]];
But this is a lot of code. Also I would like to save by just messaging rather than have to save the entire context:
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil)
{
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
Could I put these in an NSManagedObject abstract class and have my managed objects extend that abstract class? Basically, I'm just trying to encapsulate more in my models and write less code in my controllers. Any help appreciated.
You can define your own subclass of NSManagedObject, and set all of your entities to use it.
The subclass can have whatever initialiser/save patterns you define, so long as it calls the proper parent class's initialiser.
You could have a +entity method, which might link to a statically defined context (this will restrict you to a single managed object context of course, but that's not always bad as long as you can also call the more primitive initialisers when you need a second context).
You might even have a +entityWithName: method.
As for saving the context, once again you can always define a subclass and add a simple -save method which saves the context and throws an exception if the save fails. You may choose to do this with a category extending the NSManagedObject class, instead of a subclass.
Note it is impossible to save just the change you made to that one entry object. You can only save all changes to an entire managed object context. If you need to save a single record, then you need to create a temporary managed object context, make a single change in it, then save the temporary context, and then sync the temporary context change over to all other managed object context's that currently exist in the app (there is an API to do make this complicated process relatively easy).
I don't like the code you posted to save the context, for several reasons:
Don't define a managedObjectContext variable that just points to self.managedObjectContext. In almost all situations that's an extra line of code for no benefit. At best you're making your code hard to read, at worst you might be introducing bugs.
Why are you checking if it is nil? Usually you should design your code so that it cannot ever be nil. Do the nil check in the constructor of your object, and if it's nil that is a critical failure. Your whole app is completely useless for the user, and you should make it clear to the user that they can't use the app by doing something drastic, such as a crash (an error alert first would be nice, but I wouldn't bother. I'd just throw an exception).
Why are you doing a check for hasChanges? I cannot think of many situations where you would need to do this check. Perhaps your app is allowing a user to make many changes, and then saving them several minutes later? This is bad. The context should be changed milliseconds after a group of changes are made, or else you're risking data loss. Your app could crash, the phone could run out of battery, or the user might receive a phone call and your app is consuming enough RAM that the OS will terminate it instantly in order to present the "incoming call" screen. You shouldn't need to check for hasChanges because you always perform a save operation immediately after making some changes.
As I kind of mentioned before, if the save fails you should present an error to the user then throw an exception. Avoid using NSLog() in deployment code, it's really only useful for development and beta builds.
Check out
NSManagedObject+ActiveRecord.h
Inside Restkit : http://restkit.org/
It is based on :
https://github.com/magicalpanda/MagicalRecord
I am using in RestKit app, but you can adopt it quite easily.
Good luck

Does an NSManagedObject retain its NSManagedObjectContext?

NSManagedObject provides access to its NSManagedObjectContext, but does it retain it?
According to "Passing Around a NSManagedObjectContext on iOS" by Marcus Zarra, "The NSManagedObject retains a reference to its NSManagedObjectContext internally and we can access it."
How does Zarra know this and is he correct?
I'm asking because I want to know if the NSManagedObjectContext will be dealloc'ed in the tearDown method below. (I'm using CocoaPlant.)
#import <SenTestingKit/SenTestingKit.h>
#import <CocoaPlant/CocoaPlant.h>
#import "AccountUser.h"
#interface AccountUserTests : SenTestCase {
AccountUser *accountUser;
}
#end
#implementation AccountUserTests
- (void)setUp {
accountUser = [AccountUser insertIntoManagedObjectContext:
[NSManagedObjectContext contextWithStoreType:NSInMemoryStoreType error:NULL]];
}
- (void)tearDown {
[accountUser delete];
}
- (void)testFetchWithLinkedAccountUserID {
// Tests go here...
}
#end
NSManagedObject DOES NOT hold strong reference to its NSManagedObjectContext. I've checked that on a test project.
Therefore, you should keep strong reference to NSManagedObjectContext as long as you use its objects.
Wow, over two years old and no accepted answer here :)
When I wrote that post I did indeed mean it keeps a reference to its associated NSManagedObjectContext. If a NSManagedObject retained the NSManagedObjectContext then it would most likely run into problems.
In either case, whether the MOC is retained by the MO is irrelevant to your application design. If you need the MOC to stay around then you need to retain it (now referred to as a strong reference) or it will go away. What the frameworks do internally is not our problem. We just need to make sure we balance our retains and releases.
Matt,
I think Marcus may have miswrote that a NSManagedObject retains its context. Every NSManagedObject maintains a link to the context. Unless individual objects have an internal retain cycle or are retained outside of their context, then, in my experience, they are all released when the context is released. If they retained the context, then this would almost certainly not be the case.
The above said, you can easily write code to test Marcus' claim. Override -dealloc and log when it is called.
IMO, it is a best practice to retain your context until you are done with it. Depending on an undocumented behavior is probably not wise.
Andrew

"Core Data could not fulfill a fault.." error

I am developing an application in cocoa. I am facing a critical problem.
I am deleting entries of an object named "Directory" in Core Data using the following code:
NSEnumerator *tempDirectories = [[folderArrayController arrangedObjects] objectEnumerator];
id tempDirectory;
while (tempDirectory = [tempDirectories nextObject]){
[managedObjectContext deleteObject:tempDirectory];
}
But sometimes an exception like "Core Data could not fulfill a fault.." occurs while trying to save after deletion. I am using the code [managedObjectContext save];
I am new in Core Data... Looking forward to a solution.
This is an old question, I have struggled resolving this for some time now. So, thought it would be best to document it.
As Weichsel above mentioned, the Apple documentation rightly points out reason for this exception. But it is a hectic job to identify the module due to which the NSManagedObject subclass' object is being retained (if 1st cited reason in the documentation is the root cause of the problem).
So, I started out by identifying the parts of my code which was retaining the NSManagedObject, instead I retained the NSManagedObjectID and create the managed object out of it whenever needed. The discussion in similar lines can be found in Restkit documentation:
https://github.com/RestKit/RestKit/commit/170060549f44ee5a822ac3e93668dad3b396dc39
https://github.com/RestKit/RestKit/issues/611#issuecomment-4858605
Updated my setter and getter so that the interface with rest of the modules remain same while internally we now depend upon NSManagedObjectID and avoid retaining of NSManageObject:
-(CSTaskAbstract*)task
{
CSTaskAbstract *theTask = nil;
if (self.taskObjectID)
{
NSManagedObjectContext *moc = [(CSAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
// https://github.com/RestKit/RestKit/commit/170060549f44ee5a822ac3e93668dad3b396dc39 &
// https://github.com/RestKit/RestKit/issues/611#issuecomment-4858605
NSError *theError = nil;
NSManagedObject *theObject = [moc existingObjectWithID:self.taskObjectID
error:&theError];
if ([theObject isKindOfClass:[CSTaskAbstract class]])
{
theTask = (CSTaskAbstract*)theObject;
}
}
return theTask;
}
-(void)setTask:(CSTaskAbstract *)inTask
{
if (inTask!=self.task)
{
// Consequences of retaining a MO when it is detached from its MOC
[self setTaskObjectID:[inTask objectID]];
}
}
The above is the first half of the problem solved. We need to find out dependency in suspicious parts of our app and eliminate.
There was some other problem too, instruments -> allocations is a good source to find out which modules are actually retaining the managed objects, the exception object would have details about which managed object is creating the problem, filter results for that object as shown below:
We were performing KVO on a managed object. KVO retains the observed managed object and hence the exception is thrown and it's back trace would not be from within our project. These are very hard to debug but guess work and tracking the object's allocation and retain-release cycle will surely help. I removed the KVO observation part and it all started working.

Resources