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

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

Related

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

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

Best memory management practice for blocks and completion handlers

I have the following code in my app, and there is a memory leak with the 'dict' object. So I have a few questions about the best practice for this after the code:
// Convert JSON to dict
NSError *error = nil;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
dispatch_async(dispatch_get_main_queue(), ^{
// If error return nil
if (error)
completion(nil, [self handleSerializationError:error]);
else if (![UsefulFunctions objectContainsData:dict[#"data"]])
completion(nil, NO);
// No error then return dict
else
completion(dict, NO);
});
About the code: The completion handler passes back the dict object which is then used to create core data entities (hence the main thread) based upon the calling function. The data being serialised is from an NSURLConnection. So the questions are as follows:
1) Is this the correct practice for passing back data in a completion handler?
2) Which function should take care of the memory management, should it be the calling class?
3) Is it worth wrapping this in an auto-release pool, or is that not how they are supposed to be used (kind of a separate question).
4) Can anyone see any obvious reasons from this function alone why the dict is retained, or is it purely down to the calling class?
Thanks for any help
EDIT (Whole Function), just to confirm I am using ARC and this function is called from the main thread:
- (void)downloadJSONFromURL:(NSURL *)url withCompletionHandler:(void (^)(id object, BOOL retry))completion
{
// Check URL
if (url)
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSString *authToken = [NSString stringWithFormat:#"token %#", [UsefulFunctions returnActiveAPIKey]];
[request setValue:authToken forHTTPHeaderField:#"Authorization"];
[request setHTTPMethod:#"GET"];
// Create an asynch request, don't want to hold up main queue
[NSURLConnection sendAsynchronousRequest:request queue:[self operationQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError)
{
// If data exists
if (data)
{
// Convert JSON to dict
NSError *error = nil;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
dispatch_async(dispatch_get_main_queue(), ^{
// If error return nil
if (error)
completion(nil, [self handleSerializationError:error]);
else if (![UsefulFunctions objectContainsData:dict[#"data"]])
completion(nil, NO);
// No error then return dict
else
completion(dict, NO);
});
}
// If error
else if (connectionError)
{
//NSLog(#"Connection Error: %#, Code: %lu", connectionError.description, (long)connectionError.code);
dispatch_async(dispatch_get_main_queue(), ^{
// Return nil
completion(nil, [self handleConnectionError:connectionError]);
});
}
}];
}
}

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.

Twitter Streaming Endpoint not working with SLRequest object

I am trying to pull data from the Twitter Streaming API using the SLRequest class.
When I use the endpoint and parameters documented in the code below the program "hangs"
and no JSON data is printed. I am using an endpoint based on an example from the twitter dev
website https://dev.twitter.com/docs/streaming-apis/parameters#with I am requesting
tweets at a certain location.
When I use this code to query my timeline using the REST API (the code and request is included but
commented out) the program does not hang and I get a valid response.
Is there something else in the code that I need to implement to access the data using the streaming API? What additional modifications or changes need to be made?
ACAccountStore * accountStore = [[ACAccountStore alloc] init];
ACAccountType * twitterAccountType =
[accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
// Ask the user permission to access his account
[accountStore requestAccessToAccountsWithType:twitterAccountType options:nil completion:^(BOOL granted, NSError *error) {
if (granted == NO) {
NSLog(#"-- error: %#", [error localizedDescription]);
}
if (granted == YES){
/***************** Create request using REST API*********************
***************** This URL is functional and returns valid data *****
NSURL * url = [NSURL URLWithString:#"https://userstream.twitter.com/1.1/user.json"];
SLRequest * request = [SLRequest requestForServiceType:SLServiceTypeTwitter requestMethod:SLRequestMethodGET URL:url parameters:#{#"screen_name": #"your_twitter_id"}];
***************************************************************/
// Create request using Streaming API Endpoint
NSURL * url = [NSURL URLWithString:#"https://stream.twitter.com/1.1/statuses/filter.json"];
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
[params setObject:#"track" forKey:#"twitter&"];
[params setObject:#"locations" forKey:#"-122.75,36.8,-121.75,37.8"];
SLRequest * request = [SLRequest requestForServiceType:SLServiceTypeTwitter requestMethod:SLRequestMethodPOST URL:url parameters:params];
NSArray * twitterAccounts = [accountStore accountsWithAccountType:twitterAccountType];
if ([twitterAccounts count] == 0) {
(NSLog(#"-- no accounts available"));
} else if ([twitterAccounts count] >0){
[request setAccount:[twitterAccounts lastObject]];
NSLog([request.account description]);
NSLog(#"Twitter handler of user is %#", request.account.username);
// Execute the request
[request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
NSError * jsonError = nil;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:&jsonError];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// NSLog(#"-- json Data is %#", json);
NSLog([json description]);
}];
}];
}
}
}];
SLRequest doesn't play well with the streaming API.
Here is how to do with STTwitter:
self.twitter = [STTwitterAPI twitterAPIOSWithAccount:account];
[_twitter verifyCredentialsWithSuccessBlock:^(NSString *username) {
NSLog(#"-- access granted for %#", username);
[_twitter postStatusesFilterUserIDs:nil
keywordsToTrack:#[#"twitter"]
locationBoundingBoxes:#[#"-122.75,36.8,-121.75,37.8"]
delimited:nil
stallWarnings:nil
progressBlock:^(id response) {
NSLog(#"-- %#", response);
} stallWarningBlock:^(NSString *code, NSString *message, NSUInteger percentFull) {
NSLog(#"-- stall warning");
} errorBlock:^(NSError *error) {
NSLog(#"-- %#", [error localizedDescription]);
}];
} errorBlock:^(NSError *error) {
NSLog(#"-- %#", [error localizedDescription]);
}];
Internally, STTwitter builds a NSURLConnection instance with the request from -[SLRequest preparedURLRequest]. You can replicate this trick in your code if you wish.