11

I just read the author of MagicalRecord's blog post on Why contextForCurrentThread Doesn't work in MagicalRecord.

contextForCurrentThread is deprecated and saveWithBlock should be used instead because it creates a safe new NSManagedObjectContext for the relevant thread.

I've been using contextForCurrentThread extensively in my app so far. However, I'm having trouble figuring out how to use saveWithBlock instead as my fetching and saving are not necessarily happening sequentially.

Currently I'm doing things like:

localContext = NSManagedObjectContext.MR_contextForCurrentThread
person = Person.MR_createInContext(localContext)
person.name = "John Smith"

The user may then browse around the app, different controllers, views etc. are displayed. Other objects may be created using a similar method to the code above.

Then at some arbitrary point in the future, when the user decides to save, I run this method:

localContext = NSManagedObjectContext.MR_contextForCurrentThread
localContext.MR_saveToPersistentStoreWithCompletion(
  lambda { |success, error|
    # ...
  }
)

What is the recommended way to create & update objects and then save them without using contextForCurrentThread?

Paul Sturgess
  • 3,254
  • 2
  • 23
  • 22
  • Do you manage your threads manually? Are you sure that `person = Person.MR_createInContext(localContext)` and `localContext.MR_saveToPersistentStoreWithCompletion` are performed on the same thread? – Arek Holko Oct 08 '13 at 17:20
  • Or maybe both parts of your code are performed on the main thread? (this would simplify things a lot) – Arek Holko Oct 08 '13 at 17:29
  • Where I am using different threads, I am calling `NSManagedObjectContext.MR_contextForCurrentThread.MR_saveToPersistentStoreAndWait`. – Paul Sturgess Oct 08 '13 at 17:45
  • So you aren't sure that the context in which you create a `Person` will be the same as the context on which you perform `MR_saveToPersistentStoreWithCompletion` ? – Arek Holko Oct 08 '13 at 18:07

3 Answers3

21

here is a tutorial for new api: http://ablfx.com/blog/article/2 here is the refence: http://cocoadocs.org/docsets/MagicalRecord/2.1/

  1. init

    [MagicalRecord setupCoreDataStackWithStoreNamed:@"MyDatabase.sqlite"];

  2. dealloc

    [MagicalRecord cleanUp];

  3. insert

    Person *alex = [Person MR_createEntity]; alex.name = @"Alex"; alex.age = @23;

  4. select

    /Retrieve all for aNSManagedObject subclass NSArray *people = [Person MR_findAll];

    //Retrieve first record Person *aPerson = [Person MR_findFirst];

    //Retrieve records conditionally & sort NSArray *people = [Person MR_findByAttribute:@"name" withValue:@"alex" andOrderBy:@"age" ascending:YES];

  5. update

    //Updating a retrieved entity is as easy as manipulating it's properties aPerson.age = @56;

  6. delete

    //Remove all records [Person MR_truncateAll];

    //Delete single record, after retrieving it [alex MR_deleteEntity];

  7. save

    //For any entities to actually be saved / updated / deleted on disk call following method [[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];

    //Again check the MagicalRecord repo for more save options

koen
  • 5,383
  • 7
  • 50
  • 89
andrewchan2022
  • 4,953
  • 45
  • 48
8

So, I use objective C as opposed to RubyMotion, but you should be able todo things like this:

MagicalRecord.saveWithBlock(
   lambda { |localContext|
       person = Person.MR_createInContext(localContext)
       #update person stuff here
   }
)

EDIT

If you want to save the context later, you just need to hold on to it:

// Somewhere in your ViewController, etc
NSManagedObjectContext *context = [NSManagedObjectContext MR_confinementContext];


// Somewhere else
Person *p = [Person MR_createInContext:context];

// And in yet another method
[context MR_saveToPersistentStoreAndWait];

The main idea here is that you just need to hold on to the context and perform your operations on it when you're ready. If you want a background save to occur, you an use the following method:

[context MR_saveToPersistentStoreCompletion:^(BOOL success, NSError *error){
   //called on the main thread when save is complete
}];
casademora
  • 67,775
  • 17
  • 69
  • 78
  • Isn't that creating and saving all in one go? Can I not create the Person object and then save at another point in the future? – Paul Sturgess Oct 08 '13 at 15:59
  • 1
    `MR_confinementContext` is not available in version 2.2 of MagicalRecord. Which branch on Github would you recommend? – Paul Sturgess Oct 09 '13 at 09:24
  • I was able to get this working in version 3 of MagicalRecord using `MagicalRecord.saveWithBlock` for background threads. Whilst on the main thread using: `MagicalRecordStack.defaultStack.context`. – Paul Sturgess Oct 10 '13 at 13:19
  • Just a note to say that in the answer the method `MR_saveToPersistentStoreCompletion` should be `MR_saveToPersistentStoreWithCompletion` – Paul Sturgess Jan 15 '14 at 09:41
0

I might be mistaken here, but I think that Magical Record isn't prepared for this use case. Here's how I'd do what you want in pure Core Data:

  1. Create an independent or nested context and keep somewhere (AppDelegate, singleton object, etc.) a reference to it
  2. Do the insertions, reads, updates and deletions with performBlock: and performBlockAndWait:
  3. Save the context with:

    [context performBlock:^{
        ZAssert([context save:&error], @"Save failed: %@\n%@", [error localizedDescription], [error userInfo]);
    }];
    

This seems to be the standard approach to these kind of problems. It's nicely described online, e.g. Common Background Practices - Core Data in the Background.

Arek Holko
  • 8,966
  • 4
  • 28
  • 46
  • MagicalRecord most certainly can handle this case, with ease. See http://stackoverflow.com/a/19251873/5619. You should also stay away from data singletons as that will be easy to implement now, but cause you crazy problems later. – casademora Oct 08 '13 at 19:24
  • Oh, I wasn't aware of some of the recent changes in MagicalRecord. In your answer you wrote `The main idea here is that you just need to hold on to the context and perform`, so where would you keep this context assuming that user visits a couple of different View Controllers? Would you pass it between VCs? – Arek Holko Oct 08 '13 at 19:49
  • You can create a new one for each new context. MagicalRecord can help you merge changes between the contexts without bogging down your app delegate (which leads to more pain) – casademora Oct 08 '13 at 20:46
  • I was asking in the context (pun not intended :)) of the original question. Let's assume it looks as follows: a new context is created, some changes are made in it; then a user moves between different view controllers; at some point in time the user wants to save changes in the context (he may be in the different VC than the one in which the context was created). This kind of a setup allows us to not save changes in the current context to the persistent store, e.g. when user cancels at some point. – Arek Holko Oct 08 '13 at 21:00