Truncate all with MagicalRecord not saving after app closes - ios

I'm using Magical Record, and what I want to do is when the user enters a multi item selector table view, he can check and uncheck many items, which, when done is pressed, are saved.
When I'm using the app there are no issue, but when I'm leaving and going back, all items that were checked before and the new ones are checked (let's say I checked 5 items at launch, then I go back in the multi item selector, uncheck them and check 5 others, the 10 will be checked when I'm going back in multi item selector after leaving the app).
Here is my code when pressing the "Done" button of my selector:
-(void)selector:(KNMultiItemSelector *)selector didFinishSelectionWithItems:(NSArray *)selectedItems
{
[self dismissViewControllerAnimated:YES completion:^{
currentFriends = selectedItems;
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
[FriendsSelected MR_truncateAll];
for (KNSelectorItem * user in selectedItems) {
FriendsSelected *friend = [FriendsSelected MR_createEntityInContext:localContext];
friend.friendID = user.selectValue;
friend.friendName = user.displayValue;
NSLog(#"Friend %# Saved", friend.friendName);
}
}];
}];
}
I tried saving when app terminate, I tried using completion and background tasks, nothing seams to work. Maybe I'm missing something ?
PS: I tried this other answer but it's not working, and MR_save is deprecated in the version I use.

Use MR_truncateAllInContext: to make sure your data is deleted using the context that is used for the actual save operation you're initiating with saveWithBlock:

Related

Avoid multiple web service call in ios

I am inserting data to server database from my app.
on submit button I am calling inserdata web services.
My data inserted in database. but I have one problem,
before I get first result back when I tap on submit button again then same record inserted multiple times in database.
Please help how can I avoid this. (also by mistake I have tapped multiple times on submit button then same record inserted multiple time).
when web service triggers first time.... set button.selected = yes and in function check if button isSelected then not perform action. Use this bool value to distinguish between both conditions.
When user pressed submit button, show progress hud or an activity Indicator View (loading overlay). This will let user know that some processing is going on and make them not trigger any other action.
Also set synchronous property to that object from which you are calling this WebService. (When you execute something synchronously, you wait for it to finish before moving on to another task. When you execute something asynchronously, you can move on to another task before it finishes.)
And when you got response, clear all data from your form or your app. Because If data is cleared, then user can't insert same record into Database.
So, No need to disable that submit button.
Try this.
- (IBAction)doneCollectionSaveAction:(id)sender {
if([activity isAnimating]==YES){
[ALToastView toastInView:[UIApplication sharedApplication].keyWindow withText:#"Please wait..."];
}else{
[self addToCollection];
}
}
or
- (IBAction)doneCollectionSaveAction:(UIButton *)sender {
if (sender.selected==YES) {
//do nothing
[ALToastView toastInView:[UIApplication sharedApplication].keyWindow withText:#"Please wait..."];
}else{
//send data to server
self.buttonDoneOutlet.selected=YES;
[self addToCollection];
}
}
//and self.buttonDoneOutlet.selected=NO; //failure network call

Why is Firebase returning the same value twice when querying? [duplicate]

I am working on an iOS app and I have built my project on top of Firebase's login-demo app. I can authenticate with Facebook, and communicate with Firebase just fine. When I press the logout button, this is the code that is run:
- (void)logoutButtonPressed
{
// logout of Firebase and set the current user to nil
[self.simpleLogin logout];
[self.ref unauth]; //Added this
[self updateUIAndSetCurrentUser:nil];
[self.items removeAllObjects];
[self.tableView reloadData];
}
And it appears to do the trick. Everything resets and my tableView is cleared. When I log back in, I get the data associated with my FB credentials and it populates and everything is great. I have a textField and a button and when I push the button the textField's text gets saved to firebase and updates locally.
The problem comes when I try and make a new entry to my simple list of strings after I have logged out once already. When I log back in, and try and save an entry, it gets saved to firebase once (which is correct), but my callback gets called twice!
[ref observeEventType:FEventTypeChildAdded withBlock:^(FDataSnapshot *snapshot) {
// Add the chat message to the array.
if (![snapshot.value isEqual:[NSNull null]]) {
[self.items addObject:snapshot.value[#"text"]];
}
// Reload the table view so the new message will show up.
[self.tableView reloadData];
} withCancelBlock:^(NSError *error) {
NSLog(#"%#", error.description);
}];
The same object snapshot shows up in this block twice, which means the same objects gets added twice to the array and my tableView. It gets even more strange if I logout and back in again. The third time, three copies show up. The fourth time, four items, etc. Here is the code for when I push the add button:
- (IBAction)submitButtonPressed {
if ([self.currentUser.provider isEqualToString:#"facebook"]) {
Firebase *postRef = [[[self.ref childByAppendingPath:#"users"] childByAppendingPath:self.currentUser.uid] childByAppendingPath:#"posts"];
NSString *statusText = [NSString stringWithFormat:#"Logged in as %# (Facebook)",
self.currentUser.providerData[#"displayName"]];
[[postRef childByAutoId] setValue:#{#"name" : statusText, #"text": self.textField.text}];
}
}
It seems like I may not be completely logging out of Firebase or FB, but I don't know what else to try.
What would cause the FEventTypeChildAdded callback to get called multiple times for the same new object?
I've never used the Firebase iOS SDK, but it most likely works in a similar way to the other SDKs.
If that is the case, the listener block that you registered, stays registered when the user logs out. Then when the user logs in again, you are registering a second event listener. So from that moment on, your code block will execute twice for every added child.
You should unregister/cancel the event listeners when the user logs out (https://www.firebase.com/docs/ios/api/#firebase_removeAllObservers) or simply not re-register them again if you've already registered them before.
See the Firebase guide for iOS, specifically the section on detaching blocks: https://www.firebase.com/docs/ios/guide/retrieving-data.html#section-detaching

UISwitch latency between taps caused by Firebase

I have a view with a bunch of UISwitch.
My problem is that when I tap on a switch I need to wait about 10 seconds before being able to tap any other switch of the view.
Here is my code :
-(void) didTapSwitch:(UISwitch *)sender
{
NSLog(#"BEGIN didTapSwitch, %#",sender);
DADudesManager *dudesManager = [DADudesManager getInstance];
DADude *updatedDude = [dudesManager.dudesList objectAtIndex:[[self.spendingDudesTableView indexPathForCell:sender.superview.superview] row]];
DAAccountManager *accountManager = [DAAccountManager getInstance];
[accountManager.accountsOperationQueue addOperationWithBlock:^{
NSLog(#"BACKGROUND OPERATION BEGINS switchDudeBeneficiates, %#",sender);
DASpendingsManager *spendingsManager = [DASpendingsManager getInstance];
[[spendingsManager.spendingObserver childByAppendingPath:self.spending.spendingID] updateChildValues:#{updatedDude.dudeName: [sender isOn] ? #"1" : #"0"}];
NSLog(#"BACKGROUND OPERATION ENDS switchDudeBeneficiates, %#",sender);
}];
NSLog(#"END switchDudeBeneficiates, %#",sender);
}
My spendingObserver is a Firebase object initiated before.
When the code above is executed, the NSLogs show almost instantaneously in the console, the data is updated online at the same time, but the switches don't react to any tap for another 9 to 11 secs.
Of course commenting the line [[spendingsManager.spendingObserver childByAppendingPath:self.spending.spendingID] updateChildValues:#{weakDude.dudeName: [weakSwitch isOn] ? #"1" : #"0"}]; removes the latency, so the problem must come from Firebase, but I have no clue what's going on.
I am probably missing something obvious as I'm pretty new to IOS development !
I can think couple of reasons.
You are sending the PayLoad in the main thread, which is causing the User INterface events to be suspended.
The code you ran, might be linked to other functions in the library you are using, that might be causing the lag.
TRY - >
try putting your code in an NSOperation and execute that. Or use GCD to do work on different thread just not the UI thread which is the main thead.
Step back and simplify. Make your switch code simply log the change in value. NSLog includes a timestamp, so you can tell when the switch events occur.
If do-nothing code responds quickly, as I suspect it will, then add log statements at the beginning and end of your switch action method. That way you can see if there is a delay between the beginning and end of the processing.
You could also run the app in instruments (time profiler) and see where your app is spending time.

Initializing new UIDocument with some data

I've been reading everything I can here about UIDocument and UIManagedDocument since there is more help on UIManagedDocument and it's a subclass of UIDocument, but I've not been able to figure what I need to do about this.
When the user hits the add button for my app to create a new document I need to create the document and have a couple of items prepopulated into it. My code for doing this works fine as long as I am single-stepping through the debugger, but at full speed the couple of items don't make it into the document.
I store a reference to the document in my AppDelegate and have a macro defined for simplified reading in the code:
#define SelectedDocument [(AppDelegate *)[[UIApplication sharedApplication] delegate] selectedDocument]
So in handling the add request, my code does this:
GnKDocument *tmp = [[GnKDocument alloc] initWithFileURL:fileURL];
[(AppDelegate *)[[UIApplication sharedApplication] delegate] setSelectedDocument:tmp];
[SelectedDocument saveToURL:fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
if (success) {
[SelectedDocument openWithCompletionHandler:^(BOOL success) {
if (success) {
[SelectedDocument setDateCreated:[NSDate date]];
NSString *c1UUID = [SelectedDocument appendChapterWithName:#"Chapter 1"
withColor:kChapterColorYellow];
NSString *p1c1UUID = [SelectedDocument appendPageWithParent:c1UUID
withName:#"Page 1"
withColor:kPageColorRed];
NSLog(#"Just added Page 1 as %# to chapter %#", p1c1UUID, c1UUID);
[SelectedDocument closeWithCompletionHandler:^(BOOL success) {
}];
}
}
}];
The two append calls into my UIDocument subclass do their work and then call
[self updateChangeCount:UIDocumentChangeDone];
And as a testing step I overrode that method just to log out that changes were being made:
- (void)updateChangeCount:(UIDocumentChangeKind)change
{
[super updateChangeCount:change];
NSLog(#"GnKDocument recording a change");
}
Am I doing things in the right order? Should I be dispatching calls off to various queues?
Init the instance
Save it for creating
Open it (to add my initial items to it)
Make my additions
Close it (according to the docs, closeWithCompletionHandler: asynchronously saves any changes).
Again, if I set breakpoints at each call (saveToURL:, openWithCompletionHandler:, and closeWithCompletionHandler) and "step over" those calls, then run to get into the completion handlers, the document ends up on disk as I intended. If I disable my breakpoints and run again, the document is created on disk and the changes are logged, but the closed file does not contain my two initial elements.
For the longest time I thought the difference was the speed with which the code was executing was either creating or avoiding a race condition. But in investigating that possibility I added lots of NSLog statements everywhere something critical was happening... and the problem went away. So it clearly wasn't a timing issue. Looking back over the contents of the NSLog statements I realized that some of the values were being lazy-loaded, and the act of referencing them in the NSLog statements was causing them to be loaded. Similarly, while single-stepping through the code, I suspect that "Print description of..." commands were having the same effect (maybe).
So in my case, the UIDocument subclass builds a file wrapper that contains two files, one for metadata about the document and another that is the actual document data. There was a test to verify minimal correctness in the document, and this test used values from the metadata file which were meant to be lazy loaded but had never been accessed, so the test determined the file was not valid and set up the initial values all over again, wiping out the two items I was prepopulating the file with.

iOS - MagicalRecord / AFNetworking / NSFetchedResultsController - background resyncing process causes perpetual hang

So the objective I'm trying to achieve
is a syncing process that is supposed to be completed in the background using AFNetworking and Magical Record, but causes a perpetual hang when a view controller hooked up to a NSFetchedResultsController is currently open or has been opened (but popped).
The app syncs the first time the user opens the phone and then uses the data always in the Core Data persistance store via the Magical Record framework. Then when the user wants to make sure that there data is the most recent version, they go into settings and click "Re-Sync", which causes the following code to execute:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
[[CoreDataOperator sharedOperator] commenceSync:self];
});
This starts the syncing process using the singleton CoreDataOperator (subclass of NSObject -- maybe it should be NSOperation?), which fires off the following code:
[[ApiClient sharedClient] getDataRequest];
which then then fires off this bad boy in the singleton AFHTTPClient subclass:
[[ApiClient sharedClient] postPath:url parameters:dict
success:^(AFHTTPRequestOperation *operation, id responseObject) {
[request.sender performSelector:request.succeeded withObject:response];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[request.sender performSelector:request.failed withObject:response];
}
];
which effectively says: AFHTTPClient post this infomation and when it succeeds, pass back the information to the provided selector (I understand that this is generic, but the request isn't the issue)
NOW, AFNetworking is coded such that all completion selectors (in this case specifically, success and failure) are called on the main thread; so in order to prevent blocking the main thread, the code that processes the response and prepares it for saving is sent back into the background thread:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
id responseData;
[self clearAndSaveGetData:responseData];
});
This then calls the function that causes the saving using the Magical Record framework (still in background thread):
NSManagedObjectContext *localContext = [NSManagedObjectContext contextForCurrentThread];
[DATAENTITY truncateAllInContext:localContext];
<!--- PROCESS DATA INTO DATAENTITY -->
[localContext saveNestedContexts];
I chose saveNestedContexts because since I am working in the background, I wanted it pushed all the way up to the defaults contexts, which I'm assuming is a parent context? (but this so far hasn't been an issue).
Now, this data can become thousands and thousands of rows, so I'm using a NSFetchedResultsController to access this data safely and efficiently, and they are used in a different view controller than the settings or main page.
Here are three cases:
The ViewController containing the FRC has not been accessed (not visible and hasn't been visible before) - the background sync works flawlessly, minus a little lag due to the saving up the stack.
The ViewController containing the FRC has been accessed and is currently visible - background sync process HANGS due to what appears to be the FRC receiving context updates.
The ViewController containing the FRC has been previously access, but it isn't currently visible (the VC was popped using the following code: [self.navigationController popViewControllerAnimated:YES];) AND the FRC is set to nil in ViewDidUnload - background sync process HANGS due to what appears to be the FRC receiving context updates (just like in case 2).
[PS: after hanging for a few minutes, I kill the debugger and it gives my a SIGKILL on the following code, which is why I'm assuing the FRC is receiving context updates that causes it to hang:
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchDisplayController.searchResultsTableView;
[tableView endUpdates]; <---- SIGKILL
}
Other important information of note:
I am using 2 separate FRCs, one for normal ALL data and one for searching
I am using a cache for both the FRC and the separate search FRC, which is purged at the appropriate times (before this context update stuff)
The FRCs are fetching in the mainthread (which does cause a slight hang when there is THOUSANDS of rows of data) and I have been looking into fetching in the background, however that is currently not implemented.
The QUESTION(s):
Why is this hanging occuring, where the VC is visible or has been popped?
How can I make it such that the FRC doesn't listen for the save, but uses what it has until the save is completed and then it refreshes the data (unless this is what already occuring and causing the hanging)?
Would implementing a background fetch (because the lag when opening the VC with the FRC to access the thousands of rows of data creates a noticable lag, even with a cache and lessened predicates/section headers - of between 1 and 4 seconds) be viable? Too complex? How can it be done?
Thank you, hope I was detailed enough in my question.
I know this question is old but since this is a common gotcha with MagicalRecord maybe the following will help somebody.
The problem is caused by the fetchRequest of the FRC and the save operation deadlocking each other. The following solved it for me:
Update to the latest MagicalRecord release (2.1 at the time of writing).
Then do all your background saves using the following:
MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
// create new objects, update existing objects. make sure you're only
// accessing objects inside localContext
[myObjectFromOutsideTheBlock MR_inContext:localContext]; //is your friend
}
completion:^(BOOL success, NSError *error) {
// this gets called in the main queue. safe to update UI.
}];

Resources