iOS completion block not returning control - ios

I have written a lot of completion blocks but not sure why this is happening. The control of a block based function should not go forward if we call the block with the appropriate argument. But in my case it is doing so.
- (void) validateFormWithCompletion: (void(^)(BOOL valid)) completion
{
if (! [NetworkConstant appIsConnected])
{
[[AppThemeManager sharedInstance] showNoInternetMessage];
completion(NO);
}
emailIdTF.text = [emailIdTF.text trimWhiteSpaceAndNextLine];
if (emailIdTF.text.length == 0)
{
[[AppThemeManager sharedInstance] showNotificationWithTitle:#"Incomplete" subtitle:#"Please fill in a valid email id" duration:durationForTSMessage withTypeOfNotification:notificationWarning];
completion(NO);
}
else
{
completion(YES);
}
}
If there is no internet connection, the control should return from the first occurrence of completion(NO);. But instead it moves forward to email length check. Am I doing something wrong here?

If I understand your question, you need to add a return.
if (! [NetworkConstant appIsConnected])
{
[[AppThemeManager sharedInstance] showNoInternetMessage];
completion(NO);
return;
}
The return prevents the rest of the method from being executed if there is no network connection.
It also seems like there is no reason to be using a completion handler. There is no asynchronous processing inside your method.

Most probably the other times you called completion blocks they were placed within other completion blocks, called by asynchronous tasks, which is not the case in the given example. Thus using an completion block does not make sense how I understand your example.
- (BOOL) validateFormWithCompletion:(void(^)(BOOL valid)) completion
{
if (! [NetworkConstant appIsConnected]) {
[[AppThemeManager sharedInstance] showNoInternetMessage];
return NO;
}
emailIdTF.text = [emailIdTF.text trimWhiteSpaceAndNextLine];
if (emailIdTF.text.length == 0) {
[[AppThemeManager sharedInstance] showNotificationWithTitle:#"Incomplete" subtitle:#"Please fill in a valid email id" duration:durationForTSMessage withTypeOfNotification:notificationWarning];
return NO;
} else {
return YES;
}
}

Related

Waiting for network call to finish

What I'm trying to achieve is to make a network request and wait for it to finish, so that I can make a decission what should be apps next step.
Normally I would avoid such solution, but this is a rare case in which codebase has a lot of legacy and we don't have enough time to apply necessary changes in order to make things right.
I'm trying to write a simple input-output method with following definition:
- (nullable id<UserPaymentCard>)validCardForLocationWithId:(ObjectId)locationId;
The thing is that in order to perform some validation inside this method I need to make a network request just to receive neccessary information, so I'd like to wait for this request to finish.
First thing that popped in my mind was using dispatch_semaphore_t, so I ended up with something like this:
- (nullable id<UserPaymentCard>)validCardForLocationWithId:(ObjectId)locationId {
id<LocationsReader> locationsReader = [self locationsReader];
__block LocationStatus *status = nil;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[locationsReader fetchLocationProviderStatusFor:locationId completion:^(LocationStatus * _Nonnull locationStatus) {
status = locationStatus;
dispatch_semaphore_signal(sema);
} failure:nil];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
return [self.paymentCards firstCardForStatus:status];
}
Everything compiles and runs, but my UI freezes and I actually never receive sempahore's signal.
So, I started playing with dispatch_group_t with exactly the same results.
Look like I might have some problems with where code gets executed, but I don't know how to approach this and get the expected results. When I try wrapping everything in dispatch_async I actually stop blocking main queue, but dispatch_async return immediatelly, so I return from this method before the network request finishes.
What am I missing? Can this actually be acheived without some while hacks? Or am I trying to fight windmills?
I was able to achieve what I want with the following solution, but it really feels like a hacky way and not something I'd love to ship in my codebase.
- (nullable id<UserPaymentCard>)validCardForLocationWithId:(ObjectId)locationId {
id<LocationsReader> locationsReader = [self locationsReader];
__block LocationStatus *status = nil;
__block BOOL flag = NO;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[locationsReader fetchLocationProviderStatusFor:locationId completion:^(LocationStatus * _Nonnull locationStatus) {
status = locationStatus;
flag = YES;
} failure:nil];
});
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) && !flag){};
return [self.paymentCards firstCardForStatus:status];
}
I guess fetchLocationProviderStatusFor:completion:failure: calls those callbacks in main queue. That's why you get deadlock. It's impossible. We can't time travel yet.
The deprecated NSURLConnection.sendSynchronousRequest API is useful for those instances when you really can't (or just can't be bothered to) do things properly, like this example:
private func pageExists(at url: URL) -> Bool {
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
request.timeoutInterval = 10
var response: URLResponse?
try! NSURLConnection.sendSynchronousRequest(request,
returning: &response)
let httpResponse = response as! HTTPURLResponse
if httpResponse.statusCode != 200 { return false }
if httpResponse.url != url { return false }
return true
}
Currently, your method causes work to be done on the main thread, which freezes the UI. Your solution works, but it would be best to change the method to include a completion block. Then, you could call the completion block at the end of the async block. Here's the example code for that:
- (void)validCardForLocationWithId:(ObjectId)locationId completion:(nullable id<UserPaymentCard> (^)(void))completion {
id<LocationsReader> locationsReader = [self locationsReader];
__block LocationStatus *status = nil;
[locationsReader fetchLocationProviderStatusFor:locationId completion:^(LocationStatus * _Nonnull locationStatus) {
status = locationStatus;
completion([self.paymentCards firstCardForStatus:status]);
} failure:nil];
}

iOS AFNetwork 3.0: Is there a faster way to send multiple API requests and wait until all of it is finished?

I am currently using the following method to send GET API requests. This method works, but I was wondering if there is a faster way. All I need regarding requirements is to know when all of the Deleted mail has been synced. Any tips or suggestions are appreciated.
- (void)syncDeletedMail:(NSArray *)array atIdx:(NSInteger)idx {
if (idx < array.count) {
NSInteger idNumber = array[idx];
[apiClient deleteMail:idNumber onSuccess:^(id result) {
[self syncDeletedMail:array atIdx:(idx + 1)];
} onFailure:^(NSError *error){
[self syncDeletedMail:array atIdx:(idx + 1)];
}];
} else {
NSLog(#"finished");
}
}
Edit: I don't care what order it is completed (not sure if it matters in terms of speed), as long as all the API requests come back completed.
You can just send deleteMail requests at once and use dispatch_group to know when all the requests are finished. Below is the implementation,
- (void)syncDeletedMail:(NSArray *)array {
dispatch_group_t serviceGroup = dispatch_group_create();
for (NSInteger* idNumber in array)
{
dispatch_group_enter(serviceGroup);
[apiClient deleteMail:idNumber onSuccess:^(id result) {
dispatch_group_leave(serviceGroup);
} onFailure:^(NSError *error){
dispatch_group_leave(serviceGroup);
}];
}
dispatch_group_notify(serviceGroup,dispatch_get_main_queue(),^{
NSLog(#"All email are deleted!");
});
}
Here you can see all the requests are fired at the same time so it will reduce the time from n folds to 1.
Swift Version of #Kamran :
let group = DispatchGroup()
for model in self.cellModels {
group.enter()
HTTPAPI.call() { (result) in
// DO YOUR CHANGE
switch result {
...
}
group.leave()
}
}
group.notify(queue: DispatchQueue.main) {
// UPDATE UI or RELOAD TABLE VIEW etc.
// self.tableView.reloadData()
}
I suppose your request is due to the fact that you might have huge amounts of queued delete requests, not just five or ten of them.
In this case, I'd also try and consider adding a server side API call that allows you to delete more than just one item at a time, maybe up to ten or twenty, so that you could also reduce the overhead of the network traffic you'd be generating (a single GET isn't just sending the id of the item you are deleting but also a bunch of data that will basically sent on and on again for each and every call) by grouping the mails in batches.

How to set a variable within calling a method - eg [... { here }]

I have this piece of code and tries to set the variable NotAllSaved to true but after call to the saveObject returns, the variable is false again.
NotAllSaved=false
[healthStore saveObject:theSample withCompletion:^(BOOL success, NSError *error){
if (!success){
if (error.code==HKErrorAuthorizationDenied) {
NotAllSaved=true;
} else {
...
}
}
}];
if (NotAllSaved) {
// Does never come here
}
How can I set the variable so I can handle the error outside the call to SaveObject? If I try to popup an alert there, the app takes a lot of time before showing the popup.
-
Added:
Thank you Lytic and Saheb Roy, your answers (and some additional Googling) solved my problem, so the solution that works for me is this:
__block bool NotAllSaved=false;
dispatch_group_t theWaitGroup = dispatch_group_create();
dispatch_group_enter(theWaitGroup);
[HDBI.healthStore saveObject:theSample withCompletion:^(BOOL success, NSError *error){
if (!success){
if (error.code==HKErrorAuthorizationDenied){
NotAllSaved=true;
} else {
.. .
}
}
dispatch_group_leave(theWaitGroup);
}];
dispatch_group_wait(theWaitGroup, DISPATCH_TIME_FOREVER);
if (NotAllSaved) {
This is a behavior of using blocks asynchronously.
The code contained in withCompletion:{} is actually not executed until the operation queue runs the block. Which typically will happen AFTER the check if(NotAllSaved)
The error must be handled within the completion block (or could call an error handler)
For Example:
NotAllSaved=false
[healthStore saveObject:theSample withCompletion:^(BOOL success, NSError *error)
{
if (!success)
{
if (error.code==HKErrorAuthorizationDenied)
{
NotAllSaved=true;
}
else
{
...
}
}
if (NotAllSaved)
{
// Will execute now
}
}];
This is because this isnt a method, its a block starting with the caret symbol ^, when you want to access any local variable inside a block, the block makes a copy of the variable or rather think of it like this, inside the block the variable is read only, to make the variable read write use this
__block BOOL NotAllSaved = false;
Now setting this by using this __block (2 underscores) you call the variable by its reference

Unit test case for call back methods ios

I have a following method in my app for which I need to write unit test cases.
Can anyone suggest how can I test whether the success block or error block is called.
- (IBAction)loginButtonTapped:(id)sender
{
void (^SuccessBlock)(id, NSDictionary*) = ^(id response, NSDictionary* headers) {
[self someMethod];
};
void (^ErrorBlock)(id, NSDictionary*, id) = ^(NSError* error, NSDictionary* headers, id response) {
// some code
};
[ServiceClass deleteWebService:#“http://someurl"
data:nil
withSuccessBlock:SuccessBlock
withErrorBlock:ErrorBlock];
}
You have to use expectations, a relatively recently introduced API. They were added to solve exactly the problem you are having right now, verifying callbacks of asynchronous methods are called.
Note that you can also set a timeout that will affect the result of the test (slow network connections for example can fire false positives, unless you are checking for slow connections of course, although there are much better ways to do that).
- (void)testThatCallbackIsCalled {
// Given
XCTestExpectation *expectation = [self expectationWithDescription:#"Expecting Callback"];
// When
void (^SuccessBlock)(id, NSDictionary*) = ^(id response, NSDictionary* headers) {
// Then
[self someMethod];
[expectation fulfill]; // This tells the test that your expectation was fulfilled i.e. the callback was called.
};
void (^ErrorBlock)(id, NSDictionary*, id) = ^(NSError* error, NSDictionary* headers, id response) {
// some code
};
[ServiceClass deleteWebService:#“http://someurl"
data:nil
withSuccessBlock:SuccessBlock
withErrorBlock:ErrorBlock];
};
// Here we set the timeout, play around to find what works best for your case to avoid false positives.
[self waitForExpectationsWithTimeout:2.0 handler:nil];
}

iOS Synchrounous calling asynchronous methods

I am calling checkWeathermethod then I will store into the database by calling storeWeatherIntoDB. After storeWeatherIntoDB completed, it will call the sendWeatherToServer via SQLdelegate. After sendWeatherToServer completed, I will a variable to notify checkWeathermethod.
Question: How checkWeathermethod wait all these process to complete so that it can return the variable?
(int)checkWeathermethod --call--> storeWeatherIntoDB
storeWeatherIntoDB carry out process
storeWeatherIntoDB delegate return --call--> sendWeatherToServer
sendWeatherToServer carry out process
sendWeatherToServer delegate return --notify--> checkWeathermethod to return int
I am thinking of using Notification center to notify checkWeathermethod but I don't know how to make it wait for the processes.
If not I wish to have a timer with 10 sec timeout if nothing return
If result return within 10 sec, checkWeathermethod will return the int.
One possible solution is to use blocks. For instance, if you want to execute code after storeWeatherIntoDB has finished, you can do it this way:
- (void)checkWeathermethod {
[self storeWeatherIntoDB:^(int returnCode) {
dispatch_async(dispatch_get_main_queue(), ^{
// When you get here, the weather has been stored on the server.
// E.g. you can now show success or failure here
if (returnCode == 0) {
// OK
}
else {
// Error
}
});
}];
}
- (void)storeWeatherIntoDB:(void(^)(int returnCode))completionBlock {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// Store weather into db here.
// You can do this even synchronously.
int returnCode = [self storeWeather];
// Call callback
if (completionBlock) {
completionBlock(returnCode);
}
});
}

Resources