NSURLSessionDataTask completion handler not executing in command line project - objective-c

I have looked on multiple stack overflow posts regarding this issue and attempted to implement those fixes to no avail. Neither of the top two answers from this question worked NSURLSessionDataTask not executing the completion handler block
Here is my very simple code
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
#autoreleasepool {
NSURLSession* session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:#"https://itunes.apple.com/search?term=apple&media=software"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"%#", json);
}];
[dataTask resume];
}
return 0;
}
I'm never getting any console output.
I have tried instantiating the session in different ways, like
[NSURLSession sharedSession]
which didn't work,
as well as trying to execute the code in the completion block in a different thread
dispatch_sync(dispatch_get_main_queue(), ^{
// Completion code goes here
});
which also didn't work.
I've also tried different URL's. I have no idea why it's not working.

A command line interface does not have a runloop by default so asynchronous tasks cannot work.
You have to start and stop the runloop explicitly
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
#autoreleasepool {
CFRunLoopRef runloop = CFRunLoopGetCurrent();
NSURLSession* session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:#"https://itunes.apple.com/search?term=apple&media=software"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"%#", json);
CFRunLoopStop(runloop);
}];
[dataTask resume];
CFRunLoopRun();
}
return 0;
}
You should handle the dataTask error to exit with a value != 0

add this code in main function.
[[NSRunLoop currentRunLoop] run];
this code will keep the runloop still run,so that the request can be executed totally.

Every progress has one thread, that is, main thread in it by default. A program ends up with return 0; in general, that is, your command line program will be exited after the last line return 0; no matter how many threads alive there.
If you want to get the request response, you should let the main thread wait until the background request thread finishes the task. Check the dispatch_semaphore_signal out, it helps you.
How about the generic iOS/macOS UI application? Because they have an infinite loop called event loop inside to receive the user interactions, so the return 0; in the main function won't be called immediately.

Related

NSURLSessionDataTask completionHandler no longer executes when I switch to Release build configuration

I built a simple command line tool that fetches some data using an NSURLSessionDataTask. Now that I'm done coding, I find that the executable hangs when I switch to the Release build configuration in Xcode. I'm using a while loop that waits for the NSURLSessionData completionHandler to complete. The while loop works with Debug, but not with Release. If instead I use [NSThread sleepForTimeInterval:2.0f] to pause the code to wait for the completionHandler to complete, everything works fine in both Release and Debug, however I'd prefer to use the while loop as it is quicker and more logical.
Here is the relevant code:
// Configure Session
NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
// Authentication for the session
NSString *auth = [[[NSString stringWithFormat:#"%#:%#",cpanelUser, WxWaPpIUPA] dataUsingEncoding:NSUTF8StringEncoding] base64EncodedStringWithOptions:0];
config.HTTPAdditionalHeaders = #{#"Authorization":[NSString stringWithFormat:#"Basic %#",auth]};
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
// Run Data Task
__block BOOL taskComplete = NO;
NSURLSessionDataTask *task = [session dataTaskWithURL:urlComponents.URL
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
if(error) {
// *HTTP response failed
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
[result addEntriesFromDictionary:queryReturn(EMAIL_TYPE_ERROR, [NSString stringWithFormat:#"There was a connection issue with the session data task.<br><br>Error: %# HTTP Status Code for the <a href='%#'>URL</a> requested: %li.",error,urlComponents.URL,statusCode])];
} else {
NSError *jsonError;
NSDictionary *jsonObject = (NSDictionary *)[NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingAllowFragments
error: &jsonError];
if(!jsonObject){
// *JSON parsing Error
[result addEntriesFromDictionary:queryReturn(EMAIL_TYPE_ERROR, [NSString stringWithFormat:#"There was an error while parsing the JSON data returned from cPanel.<br><br>Error: %#",jsonError])];
} else {
if([jsonObject[#"cpanelresult"] objectForKey:#"error"] != nil) {
// *cPanel API returned an Error
NSString *cpanelError = [NSString stringWithFormat:#"%#",[jsonObject[#"cpanelresult"] objectForKey:#"error"]];
if(verbose) NSLog(#"cPanel API 2 Error: %#\n", cpanelError);
[result addEntriesFromDictionary:queryReturn(EMAIL_TYPE_ERROR, [NSString stringWithFormat:#"cPanel API 2 returned an error while executing function '%#'.<br><br>Error: %#",funct, cpanelError])];
} else {
[result addEntriesFromDictionary:jsonObject];
}
}
}
taskComplete = YES;
}];
[task resume];
// Wait for task to complete
while(!taskComplete);
//[NSThread sleepForTimeInterval:2.0f];

Call a block body of the method dataTaskWithRequest consistently

for example:
I have a function
-(void) someFunc:(NSString *) searchRequest {
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
[[session dataTaskWithRequest:request
completionHandler:^(NSData *data,NSURLResponse *response, NSError *error)
{
// body block
}
]
resume];
}
and I call someFunc in:
- (IBAction)searchButtonPressed:(id)sender {
NSString * searchRequest = #"blablabla";
[self someFunc:searchRequest];
}
In this case, I realized that the body of the block is executed in a separate thread, which leads to data loss.
I want all actions are performed sequentially. How to do it?
If you want someFunc: to return a value look into using a dispatch_semaphore_t to block the calling thread until the request is finished and assign the return value to a __block qualified variable.
You could also modify someFunc: to take a block as an additional argument that would be invoked when the request completes.
- (void)someFunc:(NSString *)searchRequest completion:(void(^)(id result))completion {
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
[[session dataTaskWithRequest:request
completionHandler:^(NSData *data,NSURLResponse *response, NSError *error)
{
completion(data);
}]
resume];
}

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.

Nested NSURLSessionDataTask on background thread

My Code works just fine. What I need help, or clarification on is Nested NSURLSessionDataTask instances.
I'm making two asynchronously calls, the second call is dependent on the first.
So I make the first NSURLSessionDataTask (firstUrlCall) call which returns an array of objects. For each object in my array I then call the second NSURLSessionDataTask (secondUrlCall) and pass in a dataID.
As I mentioned before, it works. I just see alot of lines repeated and REPEATED CODE IS NOT SEXY!!!
So is there anything I can do to prevent this catastrophe? I need my code to be SEXY!
#property (nonatomic, strong) NSURLSession *Session;
FIRST CALL
-(void) firstUrlCall {
NSString *urlString = #"https://api.FIRSTURLCALL.com";
NSURLSessionDataTask *dataTask = [session
dataTaskWithURL:[NSURL URLWithString:urlString]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (!error) {
NSDictionary *returnData = [NSJSONSerialization JSONObjectWithData:data
options:0
error:nil];
[returnData enumerateKeysAndObjectsUsingBlock:^(id dataID, id obj, BOOL *stop) {
/*
-->here is where I call secondUrlCall<--
*/
[self secondUrlCall:dataID];
}];
}
});
}];
[dataTask resume];
}
SECOND CALL
-(void) secondUrlCall:(NSString *)dataID {
NSString *urlString = [NSString stringWithFormat:#"https://api.SECONDURLCALL.com?dataID=%#",dataID];
NSURLSessionDataTask *dataTask = [session
dataTaskWithURL:[NSURL URLWithString:urlString]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (!error) {
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data
options:0
error:nil];
if ([[json objectForKey:#"type"] isEqualToString:#"sexy"]) {
[tableArray addObject:json];
// Reload table data
[self.tableView reloadData];
}
}
});
}];
[dataTask resume];
}
PS: Sorry if you were offended from my extensive use of the word SEXY :)
Oh my goodness! What if the network is intermittent or goes down half way through?
I would take the results of the first call and put each one into an operation queue, then when processing each operation if it fails you can re-queue it.

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.