Firebase withCompletionBlock not called if there is no connection - ios

I am using the following:
Firebase *fb =[[Firebase alloc] initWithUrl:url];
[fb setValue:d withCompletionBlock:^(NSError *error, Firebase *ref) {
if (error) {
// bad news
} else {
}
}];
This seems to work great IF you have a connection, if not it seems the callback is never called. If that is the case do I then need to wrap this whole thing in a connectedRef? Seems like alot of extra work when I would guess the completion block would just fail with an error status of not online.
Anyone else having this issue?

The idea behind Firebase is it synchronizes data for you. It's more than just a simple request / response system. So if you do a setValue while offline, Firebase will hold onto that data until you are online, and then it'll do the setValue at that time (and then the completion block will be called).
So the behavior you're seeing is expected. If you only want to do the setValue if you're online, then yes, you'll need to use a .info/connected observer. But you could still run into issues if for instance you go offline at the moment you try to do the setValue or something along those lines. In general it's better to just do the setValue and let Firebase take care of it for you.

Related

WatchKit extension crash: "Program ended with exit code: 0"

For people wanting to reply quickly without reading the post: I am not hitting any memory limits. Read the whole post for details.
My WatchKit extension cannot properly function without the user first being "onboarded" through the phone app. Onboarding is where the user must accept the permissions that we require, so it's very crucial.
On my WatchKit extension, I wanted to display a simple warning for users who had not finished onboarding within our phone app yet.
As such, I thought I'd get the status of onboarding from the phone in two ways:
When the user opens the app/the app is activated (I use the willActivate method to detect this)
When the app finishes onboarding it sends a message to the watch of its completion (if the extension is reachable, of course)
Both of these combined would ensure that the status of onboarding is always kept in sync with the watch.
I wrote the first possibility in, utilizing reply handlers to exchange the information. It worked just fine, without any troubles. The warning telling the user to complete disappears, the extension does not crash, and all is well.
I then wrote in the second possibility, of the extension being reachable when the user finishes onboarding (with the phone then directly sending the companion the new status of onboarding). My extension crashes when it receives this message, and I am stuck with this odd error.
Program ended with exit code: 0
My extension does not even get a chance to handle the new onboarding status, the extension just quits and the above error is given to me.
I am not hitting any sort of memory limit. I have read the technical Q&A which describes what a memory usage limit error looks like, and I don't receive any sort of output like that whatsoever. As well, before the extension should receive the message, this is what my memory consumption looks like.
I have monitored the memory consumption of the extension right after finishing onboarding, and I see not a single spike indicating that I've gone over any kind of threshold.
I have tried going line by line over the code which manages the onboarding error, and I cannot find a single reason that it would crash with this error. Especially since the reply handler method of fetching the onboarding status works so reliably.
Here is the code of how I'm sending the message to the watch.
- (void)sendOnboardingStatusToWatch {
if(self.connected){
[self.session sendMessage:#{
LMAppleWatchCommunicationKey: LMAppleWatchCommunicationKeyOnboardingComplete,
LMAppleWatchCommunicationKeyOnboardingComplete: #(LMMusicPlayer.onboardingComplete)
}
replyHandler:nil
errorHandler:^(NSError * _Nonnull error) {
NSLog(#"Error sending onboarding status: %#", error);
}];
}
}
(All LMAppleWatchCommunicationKeys are simply #define'd keys with exactly their key as the string value. ie. #define LMAppleWatchCommunicationKey #"LMAppleWatchCommunicationKey")
Even though it's never called by the extension, here is the exact receiving code of the extension which handles the incoming data, if it helps.
- (void)session:(WCSession *)session didReceiveMessage:(NSDictionary<NSString *, id> *)message {
NSString *key = [message objectForKey:LMAppleWatchCommunicationKey];
if([key isEqualToString:LMAppleWatchCommunicationKeyOnboardingComplete]){
BOOL newOnboardingStatus = [message objectForKey:LMAppleWatchCommunicationKeyOnboardingComplete];
[[NSUserDefaults standardUserDefaults] setBool:newOnboardingStatus
forKey:LMAppleWatchCommunicationKeyOnboardingComplete];
dispatch_async(dispatch_get_main_queue(), ^{
for(id<LMWCompanionBridgeDelegate> delegate in self.delegates){
if([delegate respondsToSelector:#selector(onboardingCompleteStatusChanged:)]){
[delegate onboardingCompleteStatusChanged:newOnboardingStatus];
}
}
});
}
}
Before including this onboarding-related code, my WatchKit extension was tested by over 100 people, without any troubles. I am using the exact same custom error dialogue that I was using before, just with a different string. I cannot for the life of me figure out what is causing this crash, and the ambiguity of it has given me very little to work with.
Any help would be greatly appreciated. Thank you very much for taking your time to read my post.
Edit: I just tried creating a symbolic breakpoint for exit(), which is never hit. If I call exit() myself, it calls the breakpoint, so I know the breakpoint itself is working.

iOS Parse.com SDK: Handling Errors

SITUATION I am trying to figure out the best practices for error handling with the parse.com iOS SDK. I have read the parse docs and they do a great job of documenting how to check for connectivity to parse and if objects can be found, but my question would be what do I do then?
EXAMPLE
[object saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if ([error code] == kPFErrorConnectionFailed) {
//COULD NOT REACH PARSE
//SO WHAT NOW?
}
else {
//EVERYTHINGS COOL
}
}];
SO WHAT NOW? Am I supposed to have this on an NSTimer and fire this off again in 5 minutes to see if we can reach parse then?
If saving objects is as important as it seems to be for your case, then this could be a solution, instead of using an NSTimer:
In the SO WHAT NOW? block, just call the method that saves this object recursively. If you ever get an error other than ConnectionFailed you could handle that appropriately, but if you're just worried about saving this even if the first attempt fails, this could be a way.

Stopping an NSOperationQueue

I have an NSOperationQueue that handles importing data from a web server on a loop. It accomplishes this with the following design.
NSURLConnect is wrapped in an NSOperation and added to the Queue
On successful completion of the download (using a block), the data from the request is wrapped in another NSOperation that adds the relevant data to Core Data. This operation is added to the queue.
On successful completion (using another block), (and after a specified delay) I call the method that started it all and return to step 1. Thus, i make another server call x seconds later.
This works great. I'm able to get data from the server and handle everything on the background. And because these are just NSOperations I'm able to put everything in the background, and perform multiple requests at a time. This works really well.
The ONLY problem that I currently have is that I'm unable to successfully cancel the operations once they are going.
I've tried something like the following :
- (void)flushQueue
{
self.isFlushingQueue = YES;
[self.operationQueue cancelAllOperations];
[self.operationQueue waitUntilAllOperationsAreFinished];
self.isFlushingQueue = NO;
NSLog(#"successfully flushed Queue");
}
where self.isFlushingQueue is a BOOL that I use to check before adding any new operations to the queue. This seems like it should work, but in fact it does not. Any ideas on stopping my Frankenstein creation?
Edit (Solved problem, but from a different perspective)
I'm still baffled about why exactly I was unable to cancel these operations (i'd be happy to keep trying possible solutions), but I had a moment of insight on how to solve this problem in a slightly different way. Instead of dealing at all with canceling operations, and waiting til queue is finished, I decided to just have a data structure (NSMutableDictionary) that had a list of all active connections. Something like this :
self.activeConnections = [NSMutableDictionary dictionaryWithDictionary:#{
#"UpdateContacts": #YES,
#"UpdateGroups" : #YES}];
And then before I add any operation to the queue, I simply ask if that particular call is On or Off. I've tested this, and I successfully have finite control over each individual server request that I want to be looping. To turn everything off I can just set all connections to #NO.
There are a couple downsides to this solution (Have to manually manage an additional data structure, and every operation has to start again to see if it's on or off before it terminates).
Edit -- In pursuit of a more accurate solution
I stripped out all code that isn't relevant (notice there is no error handling). I posted two methods. The first is an example of how the request NSOperation is created, and the second is the convenience method for generating the completion block.
Note the completion block generator is called by dozens of different requests similar to the first method.
- (void)updateContactsWithOptions:(NSDictionary*)options
{
//Hard coded for ease of understanding
NSString *contactsURL = #"api/url";
NSDictionary *params = #{#"sortBy" : #"LastName"};
NSMutableURLRequest *request = [self createRequestUsingURLString:contactsURL andParameters:params];
ConnectionCompleteBlock processBlock = [self blockForImportingDataToEntity:#"Contact"
usingSelector:#selector(updateContactsWithOptions:)
withOptions:options andParsingSelector:#selector(requestUsesRowsFromData:)];
BBYConnectionOperation *op = [[BBYConnectionOperation alloc] initWithURLRequest:request
andDelegate:self
andCompletionBlock:processBlock];
//This used to check using self.isFlushingQueue
if ([[self.activeConnections objectForKey:#"UpdateContacts"] isEqualToNumber:#YES]){
[self.operationQueue addOperation:op];
}
}
- (ConnectionCompleteBlock) blockForImportingDataToEntity:(NSString*)entityName usingSelector:(SEL)loopSelector withOptions:(NSDictionary*)options andParsingSelector:(SEL)parseSelector
{
return ^(BOOL success, NSData *connectionData, NSError *error){
//Pull out variables from options
BOOL doesLoop = [[options valueForKey:#"doesLoop"] boolValue];
NSTimeInterval timeInterval = [[options valueForKey:#"interval"] integerValue];
//Data processed before importing to core data
NSData *dataToImport = [self performSelector:parseSelector withObject:connectionData];
BBYImportToCoreDataOperation *importOperation = [[BBYImportToCoreDataOperation alloc] initWithData:dataToImport
andContext:self.managedObjectContext
andNameOfEntityToImport:entityName];
[importOperation setCompletionBlock:^ (BOOL success, NSError *error){
if(success){
NSLog(#"Import %#s was successful",entityName);
if(doesLoop == YES){
dispatch_async(dispatch_get_main_queue(), ^{
[self performSelector:loopSelector withObject:options afterDelay:timeInterval];
});
}
}
}];
[self.operationQueue addOperation:importOperation];
};
}
Cancellation of an NSOperation is just a request, a flag that is set in NSOperation. It's up to your NSOperation subclass to actually action that request and cancel it's work. You then need to ensure you have set the correct flags for isExecuting and isFinished etc. You will also need to do this in a KVO compliant manner. Only once these flags are set is the operation finished.
There is an example in the documentation Concurrency Programming Guide -> Configuring Operations for Concurrent Execution. Although I understand that this example may not correctly account for all multi-threaded edge cases. Another more complex example is provided in the sample code LinkedImageFetcher : QRunLoopOperation
If you think you are responding to the cancellation request correctly then you really need to post your NSOperation subclass code to examine the problem any further.
Instead of using your own flag for when it is ok to add more operations, you could try the
- (void)setSuspended:(BOOL)suspend
method on NSOperationQueue? And before adding a new operation, check if the queue is suspended with isSuspended?

I can't make a new instance of NSManagedObject on background thread with non-main MOC

I have researched a ton of posts regarding Core Data on background threads, and I feel like I understand (on paper) what needs to be going on. I guess we'll see. I am working on migrating an existing OS X app to Core Data, and am having issues making new instances of my NSManagedObject on an async thread.
Here is a sample of the code I am running right after I have moved onto a background thread:
NSLog(#"JSON 1");
NSManagedObjectContext * context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:[[NSApp delegate] persistentStoreCoordinator]];
asset = (MTAssetInfo*)[NSEntityDescription insertNewObjectForEntityForName:#"Info" inManagedObjectContext:context];
NSLog(#"JSON 2");
The result is that the first log message (#"JSON 1") gets called 31 times, and the second one (#"JSON 2") is never called. The object isn't being made and returned correctly.
The model for this Info entity is quite complex with a few transformable attributes that may or may not be setup correctly. The weird thing is that similar code run on the main thread and the main MOC works great. No issues.
EDIT - Some more context
The async call originates from here:
for (NSNumber *sectionID in sectionsToShow) {
dispatch_group_async(group, queue, ^{
MTAssetInfo *asset = [self assetWithRefID:[sectionID unsignedIntegerValue]];
if (asset != nil) {
[sectionsLock lock];
[sectionsTemp addObject:asset];
[sectionsLock unlock];
}
});
}
The assetWithRefID method never returns with an object because of the other code snippet. It never successfully pulls an NSManagedObject out of the context on the background thread.
You are going to have to provide more information to get real help, but I bet your problem is an error happening in the NSManagedDocument background thread.
I'd register a NSNotificationCenter for ALL messages (name:nil object:nil) and just print them out. I bet you see a status change or error message in there that is failing.
You might want to try a #try/#catch block around it just to see if exceptions are being thrown.
Maybe it will give you more to go on.
One other suggestion... Swizzling isn't necessarily the right tool for production stuff, but it's almost unbeatable for debugging. I have method-swizled several entire classes, so that it sends a detailed NSNotification before/after each invocation.
It has saved me tons of time, and helped me track down some wicked bugs. Now, when something is going on in CoreData, I take out my set of classes, link them in, and see all the detail I want.
I know that does not exactly answer you question, but hopefully it will put you on the track so you can provide some more information and get it all fixed.
If that's too much for you, create a subclass and instantiate that, with a similar method for calling super. You can get a real idea of the entire flow pretty easily.

How can I detect that iCloud has finished downloading the document, as well as run the completion handler?

When loading a document from iCloud, one must make a call to
openWithCompletionHandler:^(BOOL success)completionHandler
This function will start a background thread that download the file from iCloud, and upon finishing the load, runs the completion handler.
While that's happening, any code after this call continues running.
How can I stop the program from continuing to run until after the download is complete?
Immediately after making a call to load from the cloud, my code tries to use the document - but of course, since it isn't finished downloading, the code crashes.
Details follow:
I'm currently working on a save-file library to handle saving/loading of files, encryption, compression, and iCloud support. Users make a call to my library function LoadFileAtPath and there is a parameter specifying if they want me to check iCloud for this particular file.
When the function starts and I am meant to check on iCloud, the first thing I do is make a call to my iCloudload function, like so:
bool cloudLoad = [iCloudStorageManager readFileFromiCloud:filePath name:fileName];
within this function I make the call to openWithCompletionHandler, like so:
if ([fileManager fileExistsAtPath:[docURL path]])
{
[doc openWithCompletionHandler:^(BOOL success){
if (!success)
{
// Handle the error.
NSLog(#"Failed to retrieve document:%# from iCloud URL:%#.", fileName, [docURL absoluteString]);
}
else
{
NSLog(#"Replacing document in sandbox with iCloud version");
BOOL* dir = nil;
if (![fileManager fileExistsAtPath:filePath isDirectory:dir])
{
NSError *fileError = nil;
if(![fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:&fileError])
{
NSLog(#"Error creating file directory: %#\n%#\n%#\n%#", [fileError localizedDescription], [fileError localizedFailureReason], [fileError localizedRecoveryOptions], [fileError localizedRecoverySuggestion]);
}
}
// save new data if success
[iCloudStorage replaceDocumentInSandboxWith:doc newData:doc.loadedContents];
}
}];
[doc release];
}
After that, my code continues by trying to pull up the data, and do various things with it such as decrypt, decompress, and hand the file data to the caller.
My current (hacky) solution was to have, after the initial call to readFileFromiCloud, an loop that checks a bool, like so:
while (!cloudDidFinishLoad)
{
sleep(10);
}
This bool is initialized to false, and within the function seen above "replaceDocumentInSandboxWith", the bool is set to true.
However, I have found that this causes the program to hang, it never finishes loading the iCloud file, and the completion handler never gets called.
I'm at a loss as to what I should do. I need the program to stop running code until the download is finished, but I don't know how to do this.
Not sure if you found an answer to this or not, but what I would do is create a protocol for your library and then set up a delegate that you can make a call to when the completion handler finishes.
Then in your main controller can be set up as the delegate and once the file is ready you can receive the call from the library that the file is ready to use. So the pseudo code would be something like this...
Create iCloudStorageManager
Set self as delegate of iCloudStorageManager
Call the file open on the iCloudStorageManager
Do whatever you need to do here, set up activity indicator or lock out the interface or neither and just assume it worked unless the delegate tells you it didn't. Basically here your block will end and when the delegate function is called your main controller will pick back up control of the interface.
In the delegate function you will receive the status from the iCloudStorageManager of whether it was successful or not. Once you get that call from the iCloudStorageManager you can free up the interface, post an alert that things went well or that things went south and offer a reply...whatever you want to do.
But that is the way I would handle it. (Yes this is an old question, but I felt like I had some insight :))
Freezing the User Interface (via that sleep call on the main thread) while waiting for a file to finish downloading seems really harsh to me, and I'd expect your customers aren't going to like that.
Why not throw up an activity spinner, like:
And then take it away when your iCloud completion handler is called? While the activity view is on screen, you can disable the rest of the UI interaction via UIView's userInteractionEnabled property (being set to FALSE).

Resources