iOS - Why is this method so slow? - ios

I have an iOS method that is now deprecated --NSURLConnection sendSynchronousRequest. This method worked and was fast.
I must be doing something wrong with the new method, as it is unacceptably slow.
The new method code I'm showing the whole routine is:
- (void)getData {
NSLog(#"%s", __FUNCTION__);
pathString = #"https://api.wm.com/json/jRoutes/.......";
NSURL *url = [NSURL URLWithString:pathString......];
NSURLSessionDataTask *downloadTask = [[NSURLSession sharedSession]
dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if ([response respondsToSelector:#selector(statusCode)]) {
if ([(NSHTTPURLResponse *) response statusCode] == 404) {
dispatch_async(dispatch_get_main_queue(), ^{
// alert
NSLog(#" NO DATA");
return;
});
}
}
// 4: Handle response here
[self processResponseUsingData:data];
}];
[downloadTask resume];
}
- (void)processResponseUsingData:(NSData*)data {
NSLog(#"%s", __FUNCTION__);
NSError *error = nil;
NSMutableDictionary* json = nil;
if(nil != data)
{
json = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
}
if (error || !json)
{
NSLog(#"Could not parse loaded json with error:%#", error);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
allRoutesArray = [json valueForKey:#"Routes"];
NSLog(#"allRoutesArray count: %lu", (unsigned long)allRoutesArray.count);
[self.tableView reloadData];
});
}
}

Related

Wait for Asynchronous request before execution of other code

I'am using [NSURLSession sharedSession] dataTaskWithRequest to get data from webserver and display it in a label & set button images according to it
But my problem is first label code is executed and they are displayed as empty then async block code is finished.
if(![self connected])
{
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
arrMyres = [[NSMutableArray alloc] initWithArray:[prefs objectForKey:#"Myres"]];
}
else
{
// POSTrequest with URL to get data from server
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:
^(NSData * _Nullable responseData,
NSURLResponse * _Nullable urlResponse,
NSError * _Nullable error) {
if (error) {
//Display error
}
else
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)urlResponse;
if([httpResponse statusCode ]>=200 && [httpResponse statusCode]<300)
{
dispatch_async(dispatch_get_main_queue(), ^{
NSDictionary *dataDictionary = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
NSArray *array=[[dataDictionary objectForKey:#"GetExistingFavorites"] isKindOfClass:[NSNull class]]? nil:[dataDictionary objectForKey:#"GetExistingFavorites"];
arrMyres=[[NSMutableArray alloc]initWithArray:array];
});
}
}
}] resume];
}
//This block of code executes first and displays empty label
if([arrMyres count]>0 )
{
//Set Button Image and display label
}
else
{
// Do something else
}
How to wait for asynchrous request to complete execution and use it results somewhere after it? During research I found about dispatch groups and completion handlers. but couldnt understand how to implement
Any suggestions would be helpful.
Update firstLabel UI code inside of async code using main thread. Refer below code
if(![self connected])
{
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
arrMyres = [[NSMutableArray alloc] initWithArray:[prefs objectForKey:#"Myres"]];
[self updateUI];
}
else
{
// POSTrequest with URL to get data from server
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:
^(NSData * _Nullable responseData,
NSURLResponse * _Nullable urlResponse,
NSError * _Nullable error) {
if (error) {
//Display error
}
else
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)urlResponse;
if([httpResponse statusCode ]>=200 && [httpResponse statusCode]<300)
{
dispatch_async(dispatch_get_main_queue(), ^{
NSDictionary *dataDictionary = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
NSArray *array=[[dataDictionary objectForKey:#"GetExistingFavorites"] isKindOfClass:[NSNull class]]? nil:[dataDictionary objectForKey:#"GetExistingFavorites"];
arrMyres=[[NSMutableArray alloc]initWithArray:array];
//This block of code executes first and displays empty label
if([arrMyres count]>0 )
{
[self updateUI];
}
}
}
}] resume];
}
-(void)updateUI {
dispatch_async(dispatch_get_main_queue(), ^{
//update UI in main thread.
//Add your ui updates
});
}

Asynchronous call using xCode

I have another very beginner's question related to xCode. I am completely new to iOS development so I appreciate you guys to reply me.
I have written the following class to access the Restful API. The code in the method "makePostRequest" works fine if I write it directly in the calling method. But, I want to make it asynchronous and I don't know exactly how can I make this work asynchronous. Can somebody help me please to write this as asynchronos call?
#import <Foundation/Foundation.h>
#import "ServerRequest.h"
#import "NetworkHelper.h"
#implementation ServerRequest
#synthesize authorizationRequest=_authorizationRequest;
#synthesize responseContent=_responseContent;
#synthesize errorContent=_errorContent;
#synthesize url=_url;
#synthesize urlPart=_urlPart;
#synthesize token=_token;
- (void)makePostRequest : (NSString *) params {
NSString *urlString = [NSString stringWithFormat:#"%#%#", [self getUrl], [self getUrlPart]];
NSURL *url = [NSURL URLWithString:urlString];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:[NSOperationQueue mainQueue]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json" forHTTPHeaderField:#"Accept"];
if([self isAuthorizationRequest]) {
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setValue:#"Basic" forHTTPHeaderField:#"Authorization"];
}
else {
NSString *authorizationValue = [NSString stringWithFormat:#"Bearer %#", [self getToken]];
[request setValue:authorizationValue forHTTPHeaderField:#"Authorization"];
}
if(params.length > 0)
[request setHTTPBody:[params dataUsingEncoding:NSUTF8StringEncoding]];
#try {
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(error) {
NSLog(#"Error: %#", error);
}
if([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
if(statusCode == [NetworkHelper HTTP_STATUS_CODE]) {
self.responseContent = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers|NSJSONReadingMutableLeaves
error:nil];
}
else {
self.errorContent = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers|NSJSONReadingMutableLeaves
error:nil];
}
}
}];
[dataTask resume];
}
#catch (NSException *exception) {
NSLog(#"Exception while making request: %#", exception);
} #finally {
NSLog(#"finally block here");
}
}
- (void)setAuthorization : (bool)value {
self.authorizationRequest = &value;
}
- (bool)isAuthorizationRequest {
return self.authorizationRequest;
}
- (NSDictionary *)getResponseContent {
return self.responseContent;
}
- (NSDictionary *)getErrorContent {
return self.errorContent;
}
- (void)setToken:(NSString *)token {
self.token = token;
}
- (NSString *)getToken {
return self.token;
}
- (void)setUrl:(NSString *)value {
//self.url = value;
_url = value;
}
- (NSString *)getUrl {
return self.url;
}
- (void)setUrlPart:(NSString *)value {
self.urlPart = value;
}
- (NSString *)getUrlPart {
if(self.urlPart.length == 0)
return #"";
return self.urlPart;
}
#end
I'm giving you an example how you can make your method serve you data when available. It's block based. So you don't have to consider asynchronous task here.
First define your completion block in your ServerRequest.h:
typedef void(^myCompletion)(NSDictionary*, NSError*);
And change your method's signature to this:
- (void) makePostRequest:(NSString *)params completion: (myCompletion)completionBlock;
Now change your method's implementation to something like this (I'm only posting your #try block, so just change your try block. Others remain same)
#try {
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(error) {
NSLog(#"Error: %#", error);
if (completionBlock) {
completionBlock(nil, error);
}
}
if([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
if(statusCode == [NetworkHelper HTTP_STATUS_CODE]) {
NSError *error;
self.responseContent = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers|NSJSONReadingMutableLeaves
error:&error];
if (completionBlock) {
if (error == nil) {
completionBlock(self.responseContent, nil);
} else {
completionBlock(nil, error);
}
}
} else {
NSError *error;
self.errorContent = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers|NSJSONReadingMutableLeaves
error:&error];
if (completionBlock) {
if (error == nil) {
completionBlock(self.errorContent, nil);
} else {
completionBlock(nil, error);
}
}
}
}
}];
[dataTask resume];
}
Finally, when you call this method from somewhere else, use this as:
[serverRequestObject makePostRequest:#"your string" completion:^(NSDictionary *dictionary, NSError *error) {
// when your data is available after NSURLSessionDataTask's job, you will get your data here
if (error != nil) {
// Handle your error
} else {
// Use your dictionary here
}
}];

Code Review for filling tableview cells with response returned from server

Connection Class
-(NSDictionary *)getResponseFromSearchByRoutewithUrl:(NSString *)url :(HttpCompletionBlock)completionHandler {
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
[urlRequest setHTTPMethod:#"GET"];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSDictionary *responseDictionary;
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
NSURLSessionDataTask *task = [session dataTaskWithRequest: urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
//check if we encountered an error
if(error != nil){
NSLog(#"%#", [error localizedDescription]);
}else{
//get and check the HTTP status code
NSInteger HTTPStatusCode = [(NSHTTPURLResponse *)response statusCode];
if (HTTPStatusCode != 200) {
NSLog(#"HTTP status code = %ld", (long)HTTPStatusCode);
}
[task resume];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
if(data != nil){
NSError *parseError = nil;
NSDictionary *responseDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
NSLog(#"The response is - %#",responseDictionary);
return completionHandler(responseDictionary,nil);
}
else {
return completionHandler(nil,error);
}
}];
}
}];
[task resume];
return responseDictionary;
}
ViewController having Table view cells Class
-(void)viewDidLoad
{
_appDelegate= (AppDelegate *)[[UIApplication sharedApplication] delegate];
[[ConnectionManager sharedConnectionManager]getResponseFromSearchByRoutewithUrl:_urlString :^(NSDictionary *responseDictionary, NSError *error) {
if(error == nil)
{
_appDelegate.flightsArray=[responseDictionary objectForKey:#"scheduledFlights"];
NSLog(#"the flights array is%# ", _appDelegate.flightsArray);
}
else
{
NSLog(#"Error == %# ",error);
}
}];
}
Table View delegate class methods
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return _appDelegate.flightsArray.count;
}
Kindly Review the code and tell why the number of rows is zero...even when the response dictionary is returning dictionary...
try below code
-(void)viewDidLoad
{
_appDelegate= (AppDelegate *)[[UIApplication sharedApplication] delegate];
[[ConnectionManager sharedConnectionManager]getResponseFromSearchByRoutewithUrl:_urlString :^(NSDictionary *responseDictionary, NSError *error) {
if(error == nil)
{
_appDelegate.flightsArray=[responseDictionary objectForKey:#"scheduledFlights"];
NSLog(#"the flights array is%# ", _appDelegate.flightsArray);
[self performSelector:#selector(reloadTableview) withObject:nil afterDelay:0.2];
}
else
{
NSLog(#"Error == %# ",error);
}
}];
}
- (void)reloadTableview{
[self.tableview reloadData];
}

Objective-C Return a String in NSURLSessionDataTask

I am trying to create a NSString function to return a string that has the NSURLSessionDataTask
- (NSString *) retrieveData
{
self.session = [NSURLSession sharedSession];
self.dataTask = [self.session dataTaskWithURL:[NSURL URLWithString:URL] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
if(data)
{
self.json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
NSLog(#"%#", self.json);
for(NSDictionary *stateArray in self.json)
{
NSString *sName = stateArray[#"State"];
if(self.state == sName)
{
NSString *sFlag = stateArray[#"State_Flag_Path"];
return sFlag;
}
}
}
else
{
NSLog(#"Failed to fetch URL: %#", error);
}
}];
[self.dataTask resume];
}
Then, I get an error message for incompatible block pointer types issue.
Can anyone please help?
Thank you in advance.
I found a way...
return the string after
[self.dataTask resume];

Execute task after another

Recently I started developing for iOS and faced problem which is maybe obvious for you but I couldn't figure it out by myself.
What I'm trying to do is to execute task after another one, using multithreading provided by GCD.
This is my code for fetching JSON (put in class with singleton)
CategoriesStore
- (instancetype)initPrivate {
self = [super init];
if (self) {
[self sessionConf];
NSURLSessionDataTask *getCategories =
[self.session dataTaskWithURL:categoriesURL
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
if (error) {
NSLog(#"error - %#",error.localizedDescription);
}
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse *) response;
if (httpResp.statusCode == 200) {
NSError *jsonError;
NSArray *json =
[NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:&jsonError];
if (!jsonError) {
_allCategories = json;
NSLog(#"allcategories - %#",_allCategories);
}
}
}];
[getCategories resume];
}
return self;
}
Then in ViewController I execute
- (void)fetchCategories {
NSLog(#"before");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
CategoriesStore *categories = [CategoriesStore sharedStore];
dispatch_async(dispatch_get_main_queue(), ^(void) {
_allDirectories = categories.allCategories;
[self.tableView reloadData];
NSLog(#"after");
});
});
}
-fetchCategories is executed in viewDidAppear. The result is usually before, after and then JSON. Obviously what I want to get is before, json after.
I also tried to do this with dispatch_group_notify but didn't workd.
How can I get it working? Why it doesn't wait for first task to be finished?
Thank's for any help!
Regards, Adrian.
I would suggest to define a dedicated method in CategoriesStore that fetches data from remote server and takes callback as an argument:
- (void)fetchDataWithCallback:(void(^)(NSArray *allCategories, NSError* error))callback
{
NSURLSessionDataTask *getCategories =
[self.session dataTaskWithURL:categoriesURL
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
if (error) {
NSLog(#"error - %#",error.localizedDescription);
callback(nil, error);
return;
}
NSError *jsonError = nil;
NSArray *json =
[NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:&jsonError];
if (!jsonError) {
_allCategories = json;
NSLog(#"allcategories - %#",_allCategories);
callback(_allCategories, nil);
} else {
callback(nil, jsonError);
}
}];
[getCategories resume];
}
And you can use it in your ViewController:
- (void)fetchCategories {
[[CategoriesStore sharedStore] fetchDataWithCallback:^(NSArray *allCategories, NSError* error) {
if (error) {
// handle error here
} else {
_allDirectories = allCategories;
[self.tableView reloadData];
}
}]
}
In this way you will reload your table view after data loading & parsing.
You have to wait for the reload data so you may do something like this, another option if you don't want to wait for the whole block and just for the fetch is to use a custom NSLock
dispatch_sync(dispatch_get_main_queue(), {
_allDirectories = categories.allCategories;
[self.tableView reloadData];
}
NSLog(#"after");
I used method suggested by #sgl0v, although it wasn't solution I expected.
Another way to do this is by using notification center and listening for event to occur.

Resources