I have started use Parse (which I downloaded using Cocoapods) in a practice iOS application and I having a little bit of trouble understanding certain concepts.
I have written this code so far:
- (IBAction)saveUserButtonClicked:(id)sender {
PFObject *loginCredentials = [PFObject objectWithClassName:#"LoginCredentials"];
loginCredentials[#"name"] = self.usernameTextField.text;
loginCredentials[#"password"] = self.passwordTextField.text;
[loginCredentials saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if(error.code == kPFErrorConnectionFailed){
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Error" message:#"Please check you connection" delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alertView show];
}else if(succeeded && !error){
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Save" message:#"Your object saved" delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alertView show];
}else if(error){
NSLog(#"%#", error);
}
}];
}
My main question I have is what is the purpose of using saveInBackGroundWithBlock. Could I do the same logic by doing:
[loginCredentials saveInBackground];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Save" message:#"Your object saved" delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alertView show];
Is the block only helpful if we want to have access to the succeeded and error variables?
in saveInBackgroundWithBlock the save operation is executed in a background thread not in the main thread(thread used by UI ), once it ends executing it calls back the block to execute it. Not using the main thread for this save-operation makes the user-interface responsive while executing the save in another thread.
you can make the save operation in the main thread by using save method and then show alert based on success, but you will completely block the user interface and its not a best practice unless its necessarily to continue on the app.
bool succeeded = [loginCredentials save];
Synchronous calls will execute code on the main thread (UI thread), which will prevent the UI from responding to user events. This is generally considered bad practice because it gives the user the impression that your app has frozen when in fact one thread can't do two things at once. Therefore, you typically use asynchronous calls (blocks) to execute complex code on background threads, freeing the UI to continue performing some operation so the user isn't confused or just left hanging waiting on the operation to finish.
Looking at the PFObject spec, the – saveInBackground and – saveInBackgroundWithBlock: methods are the asynchronous options. In this case, the callback block is just reporting whether or not the operation was successful, and includes an NSError object for reporting in case it failed.
That all being said, I think you can save the object using your code, but you will not get the opportunity to respond to any errors that arise in the process. Meaning, you are making an assumption that your object saved successfully when you are creating and presenting the alert view.
I'm basing this opinion on the saveInBackground method implementation, which is just returning a BFTask object:
- (BFTask *)saveInBackground {
return [self.taskQueue enqueue:^BFTask *(BFTask *toAwait) {
return [self saveAsync:toAwait];
}];
}
Here's the object documentation on the two methods from Parse:
– saveInBackground
Saves the PFObject asynchronously.
(BFTask PF_GENERIC ( NSNumber *) *)saveInBackground
Return Value
The task that encapsulates the work being done.
Declared In PFObject.h
– saveInBackgroundWithBlock:
Saves the PFObject asynchronously and executes the given callback block.
(void)saveInBackgroundWithBlock:(PF_NULLABLE PFBooleanResultBlock)block
Parameters
block
The block to execute.
It should have the following argument signature: ^(BOOL succeeded, NSError *error).
Declared In PFObject.h
If you aren't interested in handling the errors, and your objects are sufficiently simple to save, have you considered just using one of the synchronous save methods, like - (BOOL)save or - (BOOL)save:(NSError **)error? You could immediately respond to the BOOL value that is returned, in similar fashion to the Parse example you posted above (inside the completion block).
Related
After I call a certain Google's Youtube library, my application suddenly becomes not responsive at all after one of its callback.
Not responsive means all UI components cannot be clicked.
Is there such thing in iOS that can disable entire screen to be not responsive at all?
The code:
self.uploadFileTicket = [service executeQuery:query
completionHandler:^(GTLServiceTicket *ticket,
GTLYouTubeVideo *uploadedVideo,
NSError *error) {
// Callback
_uploadFileTicket = nil;
if (error == nil) {
[_delegate videoUploadedSuccessfully:YES error:nil];
} else {
[_delegate videoUploadedSuccessfully:NO error:error.description];
}
}];
Inside my ViewController:
- (void)videoUploadedSuccessfully:(BOOL)success error:(NSString *)errorMessage{
dispatch_async(dispatch_get_main_queue(), ^{
if(success){
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Youtube"
message:#"Video uploaded successfully"
delegate:nil
cancelButtonTitle:nil
otherButtonTitles:#"OK", nil];
[alert show];
}
else{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Youtube"
message:errorMessage
delegate:nil
cancelButtonTitle:nil
otherButtonTitles:#"OK", nil];
[alert show];
}
});
}
Update
I have tried using Instrument and got following:
Does this mean my Working Thread are blocking Main Thread?
Finally I found the ROOT cause of this issue. There is somewhere in the code before uploading the video to Youtube:
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
I cannot pin-point the issue. But, here are some of my suggestions.
The method call [service executeQuery:query completionHandler: has a completion handler. Therefore, it's mostly an async task. Therefore the task of the service should be done in a background thread and should not block the UI.
In case you're not sure whether the call is in the main thread, use the following LOC to clarify.
[NSThread isMainThread] will return true only if the executing thread is the main thread/ UI-thread.
Can you also put a NSLog at videoUploadedSuccessfully and check whether the delegate method gets called multiple times?
You do not need the block
dispatch_async(dispatch_get_main_queue(), ^{}
The delegate method should get executed on the main thread itself as long as you're calling the service-query method on the main thread.
Finally check whether you're calling the [service executeQuery:query method from the main thread?
I got the following method in a singleton/shared instance and would like update the user with the progress of fetching emails.
- (void)getAllImapEmailsForMailbox:(NSString *)mailbox completionBlock:(void (^)(BOOL success, NSString *errorString, NSArray *emails))block
First I'm unlocking the API with a key, then I'm connecting to their IMAP server, then I'm logging in with their details, then I select a particular mailbox, then I loop through whole mailbox to download the messages.
So I would like to know how would I get these updates from a singleton method to my view controller displaying these messages in a UIAlertView for example.
eg. Connecting.. Logging in.. Selecting mailbox.. Downloading mail 1 of 69..
I'm currently only doing 1 message saying Downloading Emails, but it takes too long and don't want the user to think the app is hanging and not doing anything. This is what I'm doing:
UIAlertView *loadingView = [[UIAlertView alloc] initWithTitle:#"Downloading Emails..." message:nil delegate:nil cancelButtonTitle:nil otherButtonTitles:nil];
[loadingView show];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self getAllEmailsForMailbox:#"Inbox"];
dispatch_async(dispatch_get_main_queue(), ^{
[loadingView dismissWithClickedButtonIndex:-1 animated:YES];
});
});
Thanks!
Add another block param called something like statusBlock. Give the block a string param that will contain a status message. How you get the status to send this block depends on the details of how you do the listed steps, but at an outline level...
- (void)getAllImapEmailsForMailbox:(NSString *)mailbox
statusBlock:(void (^)(NSString *)statusBlock
completionBlock:(void (^)(BOOL success, NSString *errorString, NSArray *emails))block {
statusBlock(#"connecting");
// do connecting stuff
NSInteger numberOfMessagesToFetch = // find this out however you do now
statusBlock([NSString stringWithFormat:#"fetching %d messages", numberOfMessagesToFetch]);
// fetch mail, and so on
On the caller side:
[mailSingleton getAllImapEmailsForMailbox:#"mailbox"
statusBlock:^(NSString *message) { // update UI with message }
completionBlock: ... { // update UI now that you're complete }];
Also , looking at your edit, is it possible to hide the asynch stuff in this method? Would be a lot friendlier for the caller, who could just pass the blocks and assume the asynch.
I am getting a crash on a iOS 7 app with the following error:
-[NSError release]: message sent to deallocated instance 0x3c443fe0
The error is initiated when I add a call to the following method:
-(void)loadMessages:(NSString*)customerUID {
NSString *formatUID = [NSString stringWithFormat:#"%s%#%s", "'", customerUID, "'"];
formatUID = [formatUID stringByReplacingOccurrencesOfString:#"'" withString:#"%27"];
NSString *servicePath = [NSString stringWithFormat:#"/api/messagerecipient?messageid=null&customeruid=%#", formatUID];
[[RKObjectManager sharedManager] getObjectsAtPath:servicePath parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *messagesResult)
{
NSArray *messageResults = messagesResult.array;
if (messageResults != nil || [messageResults count] != 0)
{
//Add some code here
}
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"An Error Has Occurred" message:[error localizedDescription] delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alertView show];
}];
}
I added multiple breakpoints into the code at various points, and it's not returning any error details. Also, nothing in the console log indicates what the problem is (I have added full RestKit logging), just the above NSError release message.
I have also run a Zombie scan in Instruments. It shows the following.
I'm confused because this shows that the zombie is being created by a GSEventRunModal call. When I go to Extended Detail and select the call, it shows the following:
Any pointers would be gratefully appreciated, thanks.
Update: Instrument Extended Details stack trace
I've seen this a lot as well and the root of the problem appears to be in Core Data. I use the MagicalRecord database library (so does RestKit) and we thought the error was there. You can see a discussion here. After all of our investigation it seemed like MagicalRecord was right and Core Data was at fault.
This had actually been filed as a bug that Apple claimed to have fixed, but we are still seeing it. The only way I've been able to work around this is by preventing every instance where I might not be able to save data so no error is reported. You can see some of those tips in the discussion thread linked to above.
Could it be that you are trying to display an AlertView from inside a block? Interaction with the UI has to be on the main thread?
How do display a UIAlertView from a block on iOS?
Can you try to replace:
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"An Error Has Occurred" message:[error localizedDescription] delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alertView show];
With:
NSString * message = [error localizedDescription];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"An Error Has Occurred" message:message delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alertView show];
Although I guess that the init method is getting a string anyway.
i think your problem is not with the method it self.
the error message says that you are sending a release call to an object of the type NSERROR.
please check the instance of the class which contains the method you are calling and make sure it's not deallocated.
or add the calling method to the question in order for us to check it.
In my case threading the database to a separate context helped. I used the following constructor on the class that was receiving the message:
-(id)init {
self = [super init];
if (self) {
self.managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
self.managedObjectContext.parentContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
self.managedObjectContext.retainsRegisteredObjects = YES;
}
return self;
}
I've implemented the following exception handler using:
NSSetUncaughtExceptionHandler(&ExceptionHandler)
The handler then makes a call to handleException using the following:
[[AppContext alloc] init] performSelectorOnMainThread:#selector(handleException:) withObject:exception waitUntilDone:YES];
Which has been implemented as such:
NSString *message = [NSString stringWithFormat:MSG_UNHANDLED_EXCEPTION_FORMAT, exception.reason, [exception callStackSymbols]];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:TITLE_UNHANDLED_EXCEPTION message:message delegate:self cancelButtonTitle:nil otherButtonTitles:nil];
[alert show];
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
while (!_quit) {
for (NSString *mode in (__bridge NSArray *)allModes) {
CFRunLoopRunInMode((__bridge CFStringRef)mode, 0.001, false);
}
}
CFRelease(allModes);
This works well except for the following scenario. I still get the UIAlertView to show but the user interaction is no longer available (user can't scroll message or tap OK).
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(){
// do long work on another thread...
dispatch_async(dispatch_get_main_queue(), ^(){
// updating the UI so get back on main thread...
// exception occurs in this block (example)...
#throw [NSException exceptionWithName:#"DispatchAsyncToMainThreadException" reason:#"Example" userInfo:nil];
});
Note: I am not actually throwing an exception in my production code. This is purely to show where the unhandled exception is being generated.
What am I missing that should be accounted for when the exception is being raised from within a block that has been dispatched back to the main queue.
The documentation for NSSetUncaughtExceptionHandler states that you should be doing last-minute error logging before the application terminates. You should not expect any UI to work at this point and should only be logging somewhere to the file system the details of the error. When the user opens the application again you can let them know something went wrong and possible offer recovery/reporting options.
Discussion Sets the top-level error-handling function where you can perform last-minute logging before the program terminates.
I am getting a EXEC_BAD_ACCESS Error when I attempt to run this code, and the user has not allowed access to the calendar. Does requestAccessToEntityType run on a separate thread, if thats the case how do I access the main thread to display the UIAlertView?
EKEventStore *store = [[EKEventStore alloc] init];
if ([store respondsToSelector:#selector(requestAccessToEntityType:completion:)])
{
[store requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error)
{
if ( granted )
{
[self readEvents];
}
else
{
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"Denied Access To Calendar"
message:#"Access was denied to the calendar, please go into settings and allow this app access to the calendar!"
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil,
nil];
[alert show];
}
}];
}
According to the docs for requestAccessToEntityType
When the user taps to grant or deny access, the completion handler
will be called on an arbitrary queue.
So, yes, it could be on a different thread than the UI one. You can only put up alerts from the main GUI thread.
Look into performSelectorOnMainThread. More information here: Perform UI Changes on main thread using dispatch_async or performSelectorOnMainThread?
Reason why your app is crashing because you are trying to deal with your GUI elements i.e UIAlertView in background thread, you need to run it on the main thread or try to use dispatch queues
Using Dispatch Queues
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
//show your UIAlertView here... or any GUI stuff
});
OR you can show the GUI elements on the main thread like this
[alertView performSelectorOnMainThread:#selector(show) withObject:nil waitUntilDone:YES];
You can have more detail about using GUI elements on Threads on this link