NSURLSessionDataTask wait completion failed - objective-c

I am passing a POST message using NSURLSessionDataTask in Objective-c.
The transfer task is non-blocking. I have to wait for the result so I use dispatch_semaphore_t to wait.
Unfortunately, when the corresponding function is called, the task does not work, why is that? The code below is shown.
NSString *urlString = [NSString stringWithFormat:#"http://localhost/api/test"];
char json_string[20] = "reqtestmsg";
size_t jsonLength = strlen(json_string);
NSData *jsonBodyData = [NSData dataWithBytes:json_string length:jsonLength];
NSMutableURLRequest *request = [NSMutableURLRequest new];
request.HTTPMethod = #"POST";
// for alternative 1:
[request setURL:[NSURL URLWithString:urlString]];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[request setHTTPBody:jsonBodyData];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config
delegate:nil
delegateQueue:[NSOperationQueue mainQueue]];
printf ("curl semaphore\n");
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block bool result = false;
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:^(NSData * _Nullable data,
NSURLResponse * _Nullable response,
NSError * _Nullable error) {
NSHTTPURLResponse *asHTTPResponse = (NSHTTPURLResponse *) response;
NSLog(#"curl The response is: %#", asHTTPResponse);
if (asHTTPResponse.statusCode == 200) {
printf ("curl status 200 ok\n");
result = true;
}
dispatch_semaphore_signal(semaphore);
}];
[task resume];
printf ("curl wait!!!");
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // ==> blocked , task does not work!!!!
printf ("curl wait!!! -1");
return result;

You have specified your delegate queue to be the main queue. But you have blocked the main thread with dispatch_semaphore_wait. This is a classical deadlock, waiting for code to run on a queue which is blocked.
You could specify nil for your session’s delegate queue, and then you wouldn't deadlock. Or use [NSURLSession sharedSession].
I would also encourage you to consider eliminating the semaphores entirely. I understand the appeal of semaphores, but it is almost always the wrong solution. Apple removed synchronous networking API for a reason. The semaphore trick feels like an intuitive work-around, but it is inefficient, leads to substandard UX, and can even cause your app to be terminated by the watchdog process in certain situations.

Related

Downloading data from a number of urls in background thread using NSOperationQueue synchronously

I need to download data from a number of urls synchronously which needs to be done in the background thread so that it won't affect the front end activities.I would like to use NSOpeartionQueue and NSURLSession. Right now I am using the below code.
for (int i=0;i<[tempArray count];i++) {
CheckList * checklist = (CheckList *)[tempArray objectAtIndex:i];
[operationQueue addOperationWithBlock:^{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSString * urlStr = [[BASE_URL stringByAppendingString:DOWNLOAD_CHECKLIST_METADATA_SUBURL] stringByAppendingFormat:#"%#/%d/%#",userName,[checklist.checklistId intValue],checklist.language];
NSURL * url = [NSURL URLWithString:urlStr];
NSLog(#"url is %#",url);
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
NSMutableURLRequest * request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setTimeoutInterval:240.0];
[request setValue:#"Application/JSON" forHTTPHeaderField:#"Content-Type"];
NSURLSessionDataTask * downloadTask =[session dataTaskWithRequest:request completionHandler:^(NSData * data, NSURLResponse * response, NSError * error)
{
NSString * str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"response is %#",str);
}];
[downloadTask resume];
[indexSetBg addIndex:[checklist.checklistId intValue]];
// but have the thread wait until the task is done
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}];
}
But if I want to send the request synchronously, is there any way for that.Means after getting 1st response ,second request needs to be sent. Can anyone of you please help me to accomplish this.
Thank you very much in advance.
you need to add dispatch_semaphore_signal(semaphore);
in the end of your NSURLSessionTask completion handler
for (int i=0;i<[tempArray count];i++) {
CheckList * checklist = (CheckList *)[tempArray objectAtIndex:i];
[operationQueue addOperationWithBlock:^{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSString * urlStr = [[BASE_URL stringByAppendingString:DOWNLOAD_CHECKLIST_METADATA_SUBURL] stringByAppendingFormat:#"%#/%d/%#",userName,[checklist.checklistId intValue],checklist.language];
NSURL * url = [NSURL URLWithString:urlStr];
NSLog(#"url is %#",url);
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
NSMutableURLRequest * request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setTimeoutInterval:240.0];
[request setValue:#"Application/JSON" forHTTPHeaderField:#"Content-Type"];
NSURLSessionDataTask * downloadTask =[session dataTaskWithRequest:request completionHandler:^(NSData * data, NSURLResponse * response, NSError * error)
{
NSString * str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"response is %#",str);
dispatch_semaphore_signal(semaphore);
}];
[downloadTask resume];
[indexSetBg addIndex:[checklist.checklistId intValue]];
// but have the thread wait until the task is done
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}];
}

Waiting or not exiting the code block in getting json

I am using GET method on Json. The GET method is inside a for loop, and the issue is it is not finishing the task or not getting the result. Instead the loop increments. I've placed a breakpoint inside the block where I am setting the result data to a NSDictionary but it never goes there.
Is it possible for the GET method to be directly called. I mean the code will be read line by line. And it will not skip or wait for the json to finish processing?
Here's what I've done:
- (void)downloadJsonFeed
{
for(int i = 1;i < self.numberOfEpisodes;i++)
{
NSString *endPoint = [[[[baseJsonUrl stringByAppendingString:getEpisodes]stringByAppendingString:self.title]stringByAppendingString:#"&episodeNumber="]stringByAppendingString:[NSString stringWithFormat:#"%i",i]];
NSLog(#"End point %#",endPoint);
[JsonDownload getJson:token andEndpointString:endPoint WithHandler:^(__weak id result)
{
NSArray *episodeArray =result;
//will do some task here
}];
}
}
- (void)getJson:(NSString *)authData andEndpointString:(NSString *)urlString WithHandler:(void(^)(__weak id result))handler
{
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: nil delegateQueue: [NSOperationQueue mainQueue]];
NSURL * url = [NSURL URLWithString:urlString];
NSMutableURLRequest * urlRequest = [NSMutableURLRequest requestWithURL:url];
//NSString *auth = [NSString stringWithFormat:#"Bearer {%#}", authData];
[urlRequest setValue:#"application/json" forHTTPHeaderField:#"Content-type"];
[urlRequest setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[urlRequest setValue:authData forHTTPHeaderField:#"Cookie"];
[urlRequest setHTTPMethod:#"GET"];
NSURLSessionDataTask * dataTask =[defaultSession dataTaskWithRequest:urlRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(error == nil)
{
id returnedObject = [NSJSONSerialization JSONObjectWithData: data options: NSJSONReadingMutableLeaves error:nil];
handler(returnedObject);
}
else{
NSLog(#"error %#",error);
}
}];
[dataTask resume];
}
I've placed a break point in this line NSArray *episodeArray =result; and it never goes there. But when I put the break point on [JsonDownload getJson:token andEndpointString:endPoint WithHandler:^(__weak id result) line it is responding
And on the commented line //will do some task here I need to a task there before getting another json again. But I can't cause it never go inside the code block
Fixed the issue by replacing white space characters in my endPoint by %20

Xcode loop array of URLs with NSURLSessionDataTask

I must be making this harder than it is... or implementing the solutions I see online incorrectly.
I have an array of URLs which I would like to loop through and push the results to a dictionary in order or the array. How can I make it wait for the dictionary to be updated before running the next request? Basically I want to make the calls synchronously in a background thread.
Here is where I call the download:
for (NSString *path in paths) {
NSURLSession *session = [NSURLSession sessionWithConfiguration [NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:path]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:10];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error)
{
}
else
{
NSError *parsingError = nil;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingAllowFragments
error:&error];
if (parsingError)
{
}
else
{
[myDictionary addObject:dict];
}
}
}];
[task resume];
}
Unless one request really requires the results of the prior request before being issued (which is not the case here), you should not run them sequentially. It may feel more logical to issue the sequentially, but you pay a huge performance penalty to do so. Issue them concurrently, save the results in some unordered structure (like a dictionary), and then when all done, build your ordered structure.
NSMutableDictionary *results = [NSMutableDictionary dictionaryWithCapacity:[paths count]];
// don't create new session for each request ... initialize this outside of the loop
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; // or since you're not doing anything special here, use `sharedSession`
// since we're going to block a thread in the process of controlling the degree of
// concurrency, let's do this on a background queue; we're still blocking
// a GCD worker thread as these run (which isn't ideal), but we're only using
// one worker thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// only do for requests at a time, so create queue a semaphore with set count
dispatch_semaphore_t semaphore = dispatch_semaphore_create(4); // only do four requests at a time
// let's keep track of when they're all done
dispatch_group_t group = dispatch_group_create();
// now let's loop through issuing the requests
for (NSString *path in paths) {
dispatch_group_enter(group); // tell the group that we're starting another request
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // wait for one of the four slots to open up
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:path]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:10];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
// ...
} else {
NSError *parsingError = nil;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (parsingError) {
} else {
// synchronize updating of dictionary
dispatch_async(dispatch_get_main_queue(), ^{
results[path] = dict;
});
}
}
dispatch_semaphore_signal(semaphore); // when done, flag task as complete so one of the waiting ones can start
dispatch_group_leave(group); // tell the group that we're done
}];
[task resume];
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// trigger whatever you want when they're all done
// and if you want them in order, iterate through the paths and pull out the appropriate result in order
for (NSString *path in paths) {
// do something with `results[path]`
}
});
});
I tried to reduce the amount of extra dependencies here, so I used dispatch groups and semaphores. In the above, I use semaphores to constrain the degree of concurrency and I use dispatch group to identify when it's all done.
Personally, I wouldn't use semaphores and groups, but rather I'd wrap these requests in asynchronous NSOperation subclass, but I was trying to limit the changes I made to your code. But the NSOperation idea is logically the same as the above: Run them concurrently, but constrain the degree of concurrency so you don't end up with them timing out on you, and trigger the retrieval of the results only when all the results are retrieved.

Store NSURLSessionDataTask response to SQLite database?

My code calls HTTP post call to remote server and obtains results in JSON format, I have extracted this JSON, but now I need to store these results to SQLite. Based on my reading NSURLSessionDataTask is background thread, so my confusion is, should I call SQL open and insert inside completionHandler (or) is there any other best practice to handle this type of requirements?
EDIT 1
The point I am struggling more is, is it valid to write SQLite operations inside "completionHandler"? does "completionHandler" will be considered as method on separate thread (which is executing SessionDataTask) or main thread?
EDIT 2
I am open to CoreData related solutions too.
Any help would be appreciated.
NSURL *loginUrl = [NSURL URLWithString:#"myurl"];
NSURLSession *session = [NSURLSession sharedSession];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:loginUrl];
request.HTTPMethod = #"POST";
NSString *ipData = [NSString stringWithFormat:#"uName=%#&pwd=%#",self.userName.text,self.userPwd.text];
request.HTTPBody = [ipData dataUsingEncoding:NSUTF8StringEncoding];
NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *jsonError) {
NSLog(#"Inside post data task......");
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse *)response;
if(httpResp.statusCode == 200)
{
NSLog(#"Response succesfull.........");
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonError];
if(!jsonError)
{
//No Json error
NSString *uName = jsonDict[#"userName"];
NSString *uID = jsonDict[#"uID"];
//HOW CAN I INSERT THESE DETAILS TO SQLITE DB BEFORE CALLING SEGUE TO MOVE TO NEXT SCREEN?
dispatch_async(dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:#"mysegueID" sender:self];
});
}
}else
{
NSLog(#"Response is not succesfulll...");
}
}];
[postDataTask resume];
A lot of people use FMDB as objective-c wrapper around sqlite.
In case of NSURLSession, the block of the completion handler will be executed on the "delegate queue" (see delegateQueue property of NSURLSession).
It is valid to do SQLite in completion handler as long as you follow SQLite threading rules. I recommend FMDB her again because it has helpers for this. See Using FMDatabaseQueue and Thread Safety.
So your example would look like:
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];
NSURL *loginUrl = [NSURL URLWithString:#"myurl"];
NSURLSession *session = [NSURLSession sharedSession];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:loginUrl];
request.HTTPMethod = #"POST";
NSString *ipData = [NSString stringWithFormat:#"uName=%#&pwd=%#",self.userName.text,self.userPwd.text];
request.HTTPBody = [ipData dataUsingEncoding:NSUTF8StringEncoding];
NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *jsonError) {
NSLog(#"Inside post data task......");
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse *)response;
if(httpResp.statusCode == 200)
{
NSLog(#"Response succesfull.........");
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&jsonError];
if(!jsonError)
{
//No Json error
NSString *uName = jsonDict[#"userName"];
NSString *uID = jsonDict[#"uID"];
//HOW CAN I INSERT THESE DETAILS TO SQLITE DB BEFORE CALLING SEGUE TO MOVE TO NEXT SCREEN?
[queue inDatabase:^(FMDatabase *db) {
NSDictionary *argsDict = #{ #"uName" : uName, #"uID" : uID};
[db executeUpdate:#"INSERT INTO myTable (name) VALUES (:name)" withParameterDictionary:argsDict];
}];
dispatch_async(dispatch_get_main_queue(), ^{
[self performSegueWithIdentifier:#"mysegueID" sender:self];
});
}
}
else
{
NSLog(#"Response is not succesfulll...");
}
}];
[postDataTask resume];
A SQLite DB can be accessed from any thread in an app. The only restriction is that SQLite does not happily tolerate simultaneous access from multiple threads (and "simultaneous" here applies to the duration of a "transaction", not simply the duration of a call to SQLite methods).
So you must somehow assure that there is never simultaneous access. A simple way to do this is to always use the same thread (eg, the main thread) for access. Or you can implement "soft" protocols such that you know that two actions are not simultaneously trying to use the DB because they are separated in time. Or you can make use of Objective-C lock or other synchronization mechanisms in the software/OS.

NSURLSessionDataTask not executing the completion handler block

I am trying to pull some data from my local node server. The server is getting the get request and logging it, but for some reason my iOS app will not execute any of the code that I have in the completion handler. Here is the code:
- (IBAction) buttonPressed{
NSURL *url = [NSURL URLWithString:#"http://127.0.0.1:3000/"];
NSURLSessionDataTask *dataTask =
[self.session dataTaskWithURL:url
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error){
nameLabel.text = #"yay!";
/*
if (!error){
nameLabel.text = #"noerr";
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse *)response;
if (httpResp.statusCode == 200){
NSError *jsonErr;
NSDictionary *usersJSON =
[NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingAllowFragments
error:&jsonErr];
if (!jsonErr){
// nameLabel.text = usersJSON[#"username"];
nameLabel.text = #"nojerr";
}
else{
nameLabel.text = #"jsonErr";
}
}
}
else{
nameLabel.text = #"Err";
}
*/
}];
[dataTask resume];
}
When the program is run, the nameLabel is not changed to "yay". However if I try to change the nameLabel before the NSURLSessionDataTask line, it changes.
NSURLSessionDataTask runs in a background thread. To update anything in the user interface such as labels, buttons, table views, etc, you must do so on the main thread. If you want to update the label text from the completionHandler block then you need to update the label in the main thread like so:
dispatch_sync(dispatch_get_main_queue(), ^{
nameLabel.text = #"yay!";
});
try this magic:
static NSURLSession* sharedSessionMainQueue = nil;
if(!sharedSessionMainQueue){
sharedSessionMainQueue = [NSURLSession sessionWithConfiguration:nil delegate:nil delegateQueue:[NSOperationQueue mainQueue]];
}
NSURLSessionDataTask *dataTask =
[sharedSessionMainQueue dataTaskWithURL:url completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error){
//now will be on main thread
}];
[dataTask resume];
This gives you the original behavior of NSURLConnection with the completing handler on the main thread so you are safe to update the UI. However, say you would like to parse the download or do some heavy processing, in that case you might benefit from the completion handler on the operation queue's background thread and then using dispatch_sync to the main thread as a final step.