Variable value null after if statement - objective-c

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

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

synchronous NSURLSessionDataTask using objective-c

I'm try to do synchronous NSURLSessionDataTask with the below code but unable to proceed.
__block NSData *rData = nil;
__block BOOL taskDone = NO;
__block NSData *rError = nil;
NSURL *url = [NSURL URLWithString:dataURL];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:1 timeoutInterval:30];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration] delegate:nil delegateQueue:nil];
NSURLSessionDataTask *taskData = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
rData = [NSData dataWithData:data];
rError = [error copy];
taskDone = YES;
}];
[taskData resume];
while (taskDone == NO) {
if (_close == YES) {
[taskData cancel];
return nil;
}
usleep(20000);
}
I need to synchronous call so that I can remove the while loop which is not needed.
Below is my code with synchronous call using semaphore
dispatch_semaphore_t sem;
__block NSData *rData = nil;
__block BOOL taskDone = NO;
__block NSData *rError = nil;
NSURL *url = [NSURL URLWithString:dataURL];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:1 timeoutInterval:30];
// creating semaphore
    sem = dispatch_semaphore_create(0);
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration] delegate:nil delegateQueue:nil];
NSURLSessionDataTask *taskData = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
rData = [NSData dataWithData:data];
rError = [error copy];
taskDone = YES;
//call semaphore
        dispatch_semaphore_signal(sem);
}];
[taskData resume];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_release(sema);
// THIS part not sure... how can we accommodate this below code
while (taskDone == NO) {
if (_close == YES) {
[taskData cancel];
return nil;
}
usleep(20000);
}
above code could be correct ?
I understand that what you want to do is wait for the DataTask to be completed before continue with you code, the best way is to put your request in a function with a completionHandler.
First create a function that will return a NSURLSessionDataTask with a completion handler:
-(NSURLSessionDataTask*)startSessionDataTaskWithCompletionHandler:(void (^)(NSData *myData))completionBlock {
//Set your request
NSString *dataURL = #"www.yoururl.com";
NSURL *url = [NSURL URLWithString:dataURL];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:1 timeoutInterval:30];
// I recommend to use sharedSession because is a simple request, so its not needed a specific session configuration.
NSURLSessionDataTask *dataTask = [[NSURLSession sharedSession] dataTaskWithRequest: request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!error) {
if (completionBlock){
completionBlock(data);
return;
//When you call this function, the completionBlock will use this data
}
} else {
//Error handle
return;
}
}];
[dataTask resume];
return dataTask;
}
Then you can call this function from anywhere:
NSURLSessionTask *task = [self startSessionDataTaskWithCompletionHandler:^(NSData *myData) {
// put whatever code you want to perform when the asynchronous data task finish, for example:
rData = [NSData dataWithData:myData];
}];
if (!task) {
// handle failure to create task any way you want
}
You can make NSURLSessionDataTask synchronous with PromiseKit. Install it manually or add the following line to the Podfile if you use CocoaPods (tested with CocoaPods 1.7.3):
pod "PromiseKit", "6.10.0"
Add the following line to the top of the code file:
#import PromiseKit;
Then create a wrapper for your task:
- (AnyPromise*)promiseToLoadData:(NSString*)dataURL {
return [AnyPromise promiseWithResolverBlock:^(PMKResolver _Nonnull resolver) {
NSURL *url = [NSURL URLWithString:dataURL];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:1 timeoutInterval:30];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration] delegate:nil delegateQueue:nil];
NSURLSessionDataTask *taskData = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error != nil) {
resolver([error copy]);
} else {
resolver([NSData dataWithData:data]);
}
}];
[taskData resume];
}];
}
Use wait to resolve the promise synchronously:
id value = [self promiseToLoadData:#"http://your.url"].wait;
if ([value isKindOfClass:[NSData class]]) {
NSLog(#"%#", [[NSString alloc] initWithData:value encoding:NSUTF8StringEncoding]);
}

Asynchronously Api calls and returning data outside the block

I don't understand why I am getting null array outside the block code, even though I am using __block keyword on my array.
I am successfully getting data from a backend api with following code
`-(void)getJsonResponse:(NSString *)urlStr success:(void (^)(NSArray *responseDict))success failure:(void(^)(NSError* error))failure
{
NSURLSession *session = [NSURLSession sharedSession];
NSURL *url = [NSURL URLWithString:urlStr];
// Asynchronously API is hit here
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// NSLog(#"%#",data);
if (error)
failure(error);
else {
NSArray *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
// NSLog(#"%#",json);
success(json);
}
}];
[dataTask resume]; // Executed First
}`
Then in my function for returning the data I am using following
`- (NSArray *)get_data:(NSDictionary *)credentials{
NSString *urlStr =[ NSString stringWithFormat:#"http://test.com %#",credentials];
__block NSArray *jsonArray= [[NSArray alloc]init];
[self getJsonResponse:urlStr success:^(NSArray *responseArray) {
jsonArray = responseArray;
NSLog(#"%#",responseArray);
} failure:^(NSError *error) {
// error handling here ...
}];
NSLog(#"%#",jsonArray);
return jsonArray;
}
`
The issue here is although I am successfully getting data within getJsonResponse block, but when I am trying to return the response data array as function return I am getting null for jsonArray. I thought assigning __block infront of jsonArray should retain the data assign within the block code ?
The second approach is not to use Async way like following
`- (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error
{
NSError __block *err = NULL;
NSData __block *data;
BOOL __block reqProcessed = false;
NSURLResponse __block *resp;
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable _data, NSURLResponse * _Nullable _response, NSError * _Nullable _error) {
resp = _response;
err = _error;
data = _data;
reqProcessed = true;
}] resume];
while (!reqProcessed) {
[NSThread sleepForTimeInterval:0];
}
*response = resp;
*error = err;
return data;
}`
That way its blocking the main thread whilst waiting for data.
I would suggest using the same approach of getJsonResponse for your get_data function:
- (void)get_data:(NSDictionary *)credentials finish:(void(^)(NSArray *data))finish{
NSString *urlStr =[ NSString stringWithFormat:#"http://test.com %#",credentials];
__block NSArray *jsonArray= [[NSArray alloc]init];
[self getJsonResponse:urlStr success:^(NSArray *responseArray) {
jsonArray = responseArray;
if (finish) {
finish(jsonArray);
}
} failure:^(NSError *error) {
// error handling here ...
}];
}

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.

How to get variable which is return from method

I have two methods and I need to use a variable from first as input parameter in the second. How can I do it? My code is :
First method
-(NSString*)getResponseData :(NSString*) apiHttp {
NSString *code = #"&code=";
NSString *finalLink = [[NSString alloc] initWithFormat:#"%#%#",apiHttp,phoneNumber];
NSURLRequest *request = [NSURLRequest requestWithURL:
[NSURL URLWithString:finalLink]];
NSLog(#"%#", finalLink);
__block NSDictionary *json;
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
json = [NSJSONSerialization JSONObjectWithData:data
options:0
error:nil];
NSLog(#"Async JSON: %#", json);
NSError *error;
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
myString = [jsonDict objectForKey:#"result"];
// NSLog(#"%#", myString);
}];
return myString;
}
Second method:
-(void)showCodeView:(NSString*) ifString{
if([ifString isEqualToString:#"200"]){
aPasswordField.hidden = NO;
[aPasswordField setBorderStyle:UITextBorderStyleLine];
aPasswordField.layer.cornerRadius=1.0f;
aPasswordField.layer.masksToBounds=YES;
aPasswordField.layer.borderColor=[[UIColor whiteColor]CGColor];
aPasswordField.layer.borderWidth= 0.8f;
UIColor *color = [UIColor lightTextColor];
aPasswordField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:#"Код" attributes:#{NSForegroundColorAttributeName: color}];
self.aPasswordField.delegate = self;
}
}
And this is how I call them:
[self getResponseData:apiHttp];
[self showCodeView:myString];
So I can't understand why my myString is null after [self getResponseData:apiHttp]; was called even if my method retutns it.
You are calling two methods after another but are missing that the first one is asynchronous.
When you call sendAsynchronousRequest:queue:completionHandler: it will perform the request asynchronously (not waiting) and call the completion block once it has a response. Since the code doesn't wait for this to happen, getResponseData: immediately returns the current value of myString which is nil if it's not set yet.
You can see how this is working by adding a some log statements before and after each method call:
NSLog(#"Before getResponseData:");
[self getResponseData:apiHttp];
NSLog(#"After getResponseData:");
NSLog(#"Before showCodeView:");
[self showCodeView:myString];
NSLog(#"After showCodeView:");
and the same for the asynchronous request
NSLog(#"Before sendAsynchronousRequest:");
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data,
NSError *connectionError) {
NSLog(#"After sendAsynchronousRequest:");
// the rest of the completion block ...
There are many ways to deal with this. One would be to add a block argument for the getResponseData: method that is called from the completion handler of the request.
If you are unused to working with blocks, a simpler but more tightly coupled alternative is to call [self showCodeView:myString]; from inside of the completion handler.
You want to perform showCodeView only when your asynchronous getResponseData finishes, so implement your own rendition of the completion block pattern:
- (void)getResponseData :(NSString*) apiHttp completionHandler:(void (^)(NSDictionary *, NSError *))completion {
NSString *code = #"&code=";
NSString *finalLink = [[NSString alloc] initWithFormat:#"%#%#",apiHttp,phoneNumber];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:finalLink]];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (completion) {
if (connectionError) {
completion(nil, connectionError);
} else {
NSError *parseError;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
completion(json, parseError);
}
}
}];
}
Note, I've eliminated that __block variable and changed the return type to void (since this doesn't return anything ... the value is passed back via the completion block).
You can then do:
[self getResponseData:apiHttp completionHandler:^(NSDictionary *json, NSError *error) {
if (error) {
// handle this however appropriate for your app
} else {
NSString *myString = json[#"result"];
[self showCodeView:myString];
}
}];