I'm using the [MagicalRecord saveWithBlock: completion:] method but I'm not sure how to access the saved object on the completion block. My code is the following
NSLog(#"saving player");
__block PSPlayer *player;
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
// parse json
player = [self parsePlayer:playerInfoJson inContext:localContext];
NSLog(#"player.md5Id %#", player.md5Id);
} completion:^(BOOL success, NSError *error) {
NSLog(#"player.md5Id in success %# error %#", player.md5Id, error);
...
}];
The player.md5Id is correctly set at the end of the save block but is nil in the completion one. Is this a correct usage?
cheers,
Jan
The completion block captures the player reference before it's set so it will be nil when that block gets executed.
If you want to use the new managed object later you should store it in a property and then call a method from the completion block, (possibly switching to the main thread, not sure if MR does that for you) to find that object in the main context.
Alternatively I think you could define the completion block earlier and then pass a copy to the method, then the copy would have access to the updated player reference (I don't do that very often really but IIRC it should work).
Related
There are two Entities- Document and Page. Document has a one-to-many relationship with Page.
I save the managed object context when I add document. At this point, there are no pages in them. While debugging I found that the writer context's save method does get called and is executed without error. I close and reopen the app and I can't find the previously saved Document objects. But, if I add a page in one of the document, then, the Document object appear in the table. I use a tool to view the SQLite file but my observation is not based on what I see in the tool. Even when I debug and see the number of documents present, I get 0 back when there is no page in them.
I am guessing that the Persistent Store Coordinator is doing some kind of optimization to write in batch. Can I force it to write and update the persistent store immediately? Is there a option that I can add while calling addPersistentStoreWithType on the persistent store object?
Note: Just FYI, I use this pattern to organize the Managed Object Context(s)
Fixed the issue. Here is the update
So, I was saving the whole stack all the way up to the writer context. The bug was very silly. I was trying to save the main context on the main thread like this:
- (void)saveMainContext {
[self.mainManagedObjectContext performBlock:^{
// Ensure that the main object context is being saved on the main queue
__block NSError *error = nil;
dispatch_async(dispatch_get_main_queue(), ^{
[self.mainManagedObjectContext save:&error];
});
if(!error)
{
//Write to disk after saving on the main UI context
[self saveWriterContext];
}
}];
}
As you can see, after trying to save the main context, I save the writer context. But, the bug was that I wasn't waiting for the main context to finish saving. After fixing the bug, my code looks like this:
- (void)saveMainContext {
[self.mainManagedObjectContext performBlock:^{
// Ensure that the main object context is being saved on the main queue
dispatch_async(dispatch_get_main_queue(), ^{
NSError *error = nil;
[self.mainManagedObjectContext save:&error];
if(!error)
{
//Write to disk after saving on the main UI context
[self saveWriterContext];
}
});
}];
}
And, this fixed the issue! Very silly mistake on my part.
Are you making sure you are saving your entire stack? If you make a change in a private context you need to save that private context. If you make a change in the main context (from the UI) then you need to save that context. Only after all of your other contexts report NO to -hasChanges should you save the writer context (aka the master context in his design).
I suspect that is your issue.
Response to OP
Hmm. Did not know that. Thanks! So, are you suggesting that I may be well off if I do not check for "error" at all, and just check for the save's return?
What I am saying is that your save should look like this (note I also correct your unnecessary dispatch_async):
- (void)saveMainContext {
[self.mainManagedObjectContext performBlock:^{
// Ensure that the main object context is being saved on the main queue
NSError *error = nil;
if (![[self mainManagedObjectContext] save:&error]) {
NSLog("Failed to save context: %#\n%#", [error localizedDescription], [error userInfo]);
exit(1);
}
[self saveWriterContext];
}];
}
The dispatch_async will be ignored because you are already on the right queue.
The call to -save: returns a bool. If and ONLY if that returns NO do you react to the error.
I'm using MagicalRecord with its saveWithBlock: method:
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
// some work
// ...
// -> ups! I changed my mind, I don't want to save!
}];
If I'd like to cancel the saving operation inside that block, how can I achieve that?
E.g. I have a long running download/sync operation when the user logs in - if the user logs out during this operation I'd like to cancel the saving inside the saveWithBlock:
If you want to perform cancellation then don't wrap your changes into a MagicalRecord saveWithBlock. You could simply use the Context's performBlock API and discard the changes if you are not happy.
[context performBlock:^{
// some work
// ...
if(timeToCancel) {
[context reset];
} else {
[context MR_saveToPersistentStoreWithCompletion:nil];
}
}];
I searched for a many days, but I can not find it. What would be the equivalent of the following MagicalRecord statement in Realm.io?
[MagicalRecord saveUsingCurrentThreadContextWithBlock:^(NSManagedObjectContext *localContext) {
// Save block
} completion:^(BOOL success, NSError *error) {
// Completion block
}];
I need "Save block", and when it finish, execute "Completion block"
Thanks!!
the equivalent in Realm is -[RLMRealm transactionWithBlock:].
Since both your MagicalRecord example and Realm's equivalent run in the current thread, the completion block is a bit redundant because it's equivalent to adding code immediately after the call to this method.
When I call saveWithBlock:completion: it actually saves the data but doesn't call the completion Block. Is there a bug, or am I using this method the wrong way?
I use MagicalRecord 2.2 from CocoaPods. I've tried to downgrade to 2.1 with no luck -- the problem remains.
[MagicalRecord setupCoreDataStackWithInMemoryStore];
[MagicalRecord setDefaultModelNamed:#"Model.momd"];
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
TestUser *test = [TestUser MR_createInContext:localContext];
test.name = #"Lweek";
} completion:^(BOOL success, NSError *error) {
NSLog(#"Yeah");
}];
This outputs nothing. The completion Block is not triggered, although the data are saved properly.
I have a NSOperation that I put in a queue. The NSOperation does some long running photo processing then I save the information/meta data in core data for that photo. In the main method of my custom NSOperation class is where I execute the below block of code
-(void)main
{
//CODE ABOVE HANDLES PHOTO PROCESSING...
//........
//THEN I SAVE ALL DATA BELOW LIKE SO
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
Post *post = [Post createInContext:localContext];
//set about 15 pieces of data, all strings and floats
post.XXXX = XXXXX;
post.DDDD = DDDDD;
etc...
} completion:^(BOOL success, NSError *error) {
NSLog(#"Done saving");
}];
}
My issue is that even with only 3 photos when it saves it really freezes my UI. I would have thought executing this in the NSOperation I would be fine.
I should add that each NSOperation processes one photo, so at times the queue could have 5-10 photos, but I would not think this would make any difference, even with just three like I said its freezing the UI.
Thank you for the help.
UPDATE:------------*--------------
I switched to version 2.2 but that seems to be blocking the UI even more...also now I'm using
-(void)main
{
NSManagedObjectContext *localContext = [NSManagedObjectContext contextForCurrentThread];
//CODE BELOW HANDLES PHOTO PROCESSING...
//........
//THEN I SAVE ALL DATA BELOW LIKE SO
Post *post = [Post createInContext:localContext];
//set about 15 pieces of data, all strings and floats
post.XXXX = XXXXX;
post.DDDD = DDDDD;
[localContext saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
}];
}
This is all done in my NSOperation class, am I doing something wrong?
Don't put the saveWithBlock calls in a background thread. You're effectively creating a background thread from a background thread, which, in this case, is just slowing you down. You should just be able to call saveWithBlock and it should put all your saving code in the background. However, I'm also noticed that you make all your changes in the main UI page of the code, and only call save afterward. This is the wrong usage of this method. You want to do something more like this:
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
Post *post = [Post createInContext:localContext];
//photo processing
//update post from photo processing
} completion:^(BOOL success, NSError *error) {
//This is called when data is in the store, and is called on the main thread
}];
If you do need an NSOperation, I suggest a different pattern:
- (void) main {
NSManagedObjectContext *localContext = [NSManagedObjectContext confinementContext];
// Do your photo stuff here
Post *post = [Post createInContext:localContext];
//more stuff to update post object
[localContext saveToPersistentStoreAndWait];
}
Be careful in how you start the operation.
[operation start]
will start the operation on the current thread, so if you call it from the main thread (which is the UI one) it will block the interface.
You should add the operation to a queue, so that it runs in background without hogging the main thread
[[NSOperationQueue new] addOperation:operation];