Call a block body of the method dataTaskWithRequest consistently - objective-c

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];
}

Related

NSURLSession dataTaskWithRequest returns nil data

I am new to Mac application development and our existing Mac application contains the following line of code
[NSURLConnection sendSynchronousRequest:request returningResonse:response error:Error
A warning message getting displayed as
sendSynchronousRequest is deprecated in macOS 10.11
and suggesting to use [NSURLSession dataTaskWithRequest:completionHandler:]
I have implemented the following code changes to use NSURLSession as suggested but it is returning the value of data as "nil". Can you please suggest what needs to be done in order to get the required data in response?
__block NSError *WSerror;
__block NSURLResponse *WSresponse;
__block NSData *myData;
NSURLSession *session =[NSURLSession sharedSession];
[[session completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
myData = data;
WSresponse =response;
WSerror = error;
}] resume];
NSString *theXml = [[NSString alloc] initWithData:myData encoding:NSUTF8StringEncoding];
return theXml;
The completion handler is asynchronous. You have to do your work inside the completion handler.
The __block declarations are pointless.
NSURLSession *session =[NSURLSession sharedSession];
[[session completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(#"%#", error);
} else {
NSString *theXml = [[NSString alloc] initWithData: data encoding:NSUTF8StringEncoding];
// do something with `theXml`
}
}] resume];

NSURLSession synchronous calls

I am not sure seen a clean answer to this. I know how to use blocks, and completion handlers, however for this I cannot use them and need to call the method synchronously :
- (void)codePathWithURL:(NSURLRequest *)request {
// Create session
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.URLCache = nil;
config.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:nil];
// Using semaphores
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSURLSessionDataTask *sessionTask = [urlSession dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
responseData = data;
httpResponse = (NSHTTPURLResponse *)response;
outError = error;
dispatch_semaphore_signal(semaphore);
}];
[sessionTask resume];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// do something with data
}
The problem I see here it is locks the main thread. How do I make this so that the main thread is not blocked?
Just a quick and dirty attempt to help you get going ... maybe use a condition?
// Call this on the background NOT on the main thread
- ( ... ) some message
{
// Added stuff
__block BOOL done = NO; // ... use now to set data as well
__block NSString * data; // ... or whatever is needed
NSCondition * condition = NSCondition.new;
// Your original stuff
// Create session
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.URLCache = nil;
config.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:nil];
NSURLSessionDataTask *sessionTask = [urlSession dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
responseData = data;
httpResponse = (NSHTTPURLResponse *)response;
outError = error;
// Added stuff
condition.lock;
done = YES;
// Set data
data = ... something ...
condition.broadcast;
condition.unlock;
}];
[sessionTask resume];
// New stuff
condition.lock;
while ( ! done )
{
condition.wait;
}
condition.unlock;
// something like ...
return data;
...

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.

Variable value null after if statement

Here i get data from a url.
I declare my NSString in the header file of my view controller
#property NSString *contentsOfUrl;
then i get the contents of a url and assign it to this string
-(void)press
{
NSURLSession *session1 = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
[[session1 dataTaskWithURL:[NSURL URLWithString:#"http://api.lmiforall.org.uk/api/v1/soc/code/2222"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (((NSHTTPURLResponse*) response).statusCode == 200) {
if (data) {
contentsOfUrl = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
}
}] resume];
NSLog(#"%#", contentsOfUrl);
}
For some reason my variable is null. However when i put the NSLog in the if statement it has the correct data.
This is because contentsOfUrl is assigned inside the completion handler that is called asynchronous.
So your block:
^(NSData *data, NSURLResponse *response, NSError *error) {
if (((NSHTTPURLResponse*) response).statusCode == 200) {
if (data) {
contentsOfUrl = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
}
}
is getting called after the NSLog line.
If you want to do something with this data do it right from inside the block

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.