Why does AFNetworking hangs on first request? - ios

I am completely baffled on this. Each time I test my app in the simulator or on a real device, it hangs for 30,40,60 seconds on this bit of code, but all following request to this API call will load in milliseconds.
I thought it was related to DNS resolving for the first time, so I switched to an IP address for testing and that did not resolve the issue.
If it's the first request after the app starts, it will just hang for a large amount of time, once it has loaded, you can open the view for the same data set or another and it load the list very fast.
Any recommendations?
-(void)getVendorImages {
//Alloc the image list
self.imageList = [[NSMutableArray alloc] init];
// Prepare the request
NSString* vendorImagesApi = [NSString stringWithFormat:#"%#%#",#"http://example.com/api/v1/vendor/images/",self.imageData.vendorId];
NSLog(#"Getting list of images %#",vendorImagesApi);
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:vendorImagesApi
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
// NSLog(#"JSON: %#", responseObject);
//Get images
for (id imageData in responseObject)
{
// prepare image url
NSString* imageUrl = [NSString stringWithFormat:#"%#%#%#",#"http://example.com/images/",imageData[#"id"],#"-650x650.jpg"];
NSLog(#"Putting this in in a list: %#", imageUrl);
[self.imageList addObject:imageUrl];
}
[self.tableView reloadData];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
EDIT:
Here is the thread stack

So after a bunch of digging, I removed all the files for AFNetworking, then installed it all again, including the UIKit+AFNetworking folder, after that I removed all the frameworks and added back UIKit and SystemConfig. Lastly one of my views that loaded at the start of the app had it's own NSURLConnectionDelegate. I removed all that and had it use AFNetworking, and that did the trick. Apparently the first run that was stalling the connection for AFNetworking was because it was likely fighting over who could use the service.

Related

How to stop call which is sent in operation queue in Objective-C

I am working on iOS App, and I am using AFNetworking for interacting with server API.
My issue is I want to send call and don't want to restrict user until response get from server, so issue is crash. When user move back to that particular screen lets say I have listing screen where I am getting data which is taking 6-7 seconds and meanwhile user move back to previous screen and when data come from API and call back that delete to listing screen but user move backed to that screen then App crashes
Here below is code for fetching data call.
+ (void) getRequestForDocumentListing:(NSDictionary *)headerParams urlQuery: (NSString*)action parameters:(NSDictionary*)params
onComplete:(void (^)(id json, id code))successBlock
onError:(void (^)(id error, id code))errorBlock
{
NSString *authorizationValue = [self setAuthorizationValue:action];
NSString *selectedLanguage = [ApplicationBaseViewController getDataFromDefaults:#"GLOBALLOCALE"];
NSString *language = selectedLanguage;
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
//set headers values
[manager.requestSerializer setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[manager.requestSerializer setValue:language forHTTPHeaderField:#"Accept-Language"];
[manager.requestSerializer setValue:authorizationValue forHTTPHeaderField:#"authorization"];
[manager.requestSerializer setValue:#"x-folder" forHTTPHeaderField:#"inbox"];
[manager GET:action parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"document listing success");
NSInteger statusCode = [operation.response statusCode];
NSNumber *statusObject = [NSNumber numberWithInteger:statusCode];
successBlock(responseObject, statusObject);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSInteger statusCode = [operation.response statusCode];
NSNumber *statusObject = [NSNumber numberWithInteger:statusCode];
id responseObject = operation.responseData;
id json = nil;
id errorMessage = nil;
if (responseObject) {
json = [NSJSONSerialization JSONObjectWithData:responseObject options:kNilOptions error:&error];
errorMessage = [(NSDictionary*)json objectForKey:#"Message"];
}else{
json = [error.userInfo objectForKey:NSLocalizedDescriptionKey];
errorMessage = json;
}
errorBlock(errorMessage, statusObject);
}];
}
What I need is to stop call in ViewdidDisappear View delegate
- (AFHTTPRequestOperation *)GET:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:#"GET" URLString:URLString parameters:parameters success:success failure:failure];
[self.operationQueue addOperation:operation];
return operation;
}
How to solve this particular issue?
I got your point, I think the problem is not about the AFNetWorking or download, it is about how you organize your view controllers.
In short, you need to make sure the synchronization of the data and view.
What cause your crash is when users do some operation(eg. delete, move...), the data is not the same with what view shows.
Let's play back an example:
An array with 12 objects and show it with a table view.
User call a web request to change the array. As we know, it needs time.
User leave and come back again. In this view, table view shows with the old array.
At this point, web request comes back. The array is modified to 10 object.But at this time, the call back dose not cause the table view to load the new data.
When user do some operation, just like delete the 11st object in the table view. Actually, there is no 11st object in array.
So crash comes.
How to deal with it is to keep the synchronization of the data and view.
First get a reference to the Operation object by
AFHTTPRequestOperation *operation = [manager GET:action parameters:nil success:^...blah blah blah...];
Then you can set the completion block to nil when you move away from this screen.
[operation setCompletionBlock:nil];
Please note that even though you move away from the screen, the request may actually execute successfully. However, your app will not crash now.
Thanks RuchiraRandana and childrenOurFuture for your answer, I got help from your answers and finally I come to solution where I am not going to cancel operation and set nil delegate, because my others operation are also in working which is trigger on other screen.
I create a just BOOL and set YES default value in singleton class and also set to no in - (void)dealloc on that particular class and in API class where I am triggering that delegate I added that check.
if ([SHAppSingleton sharedInstance].isDocListControllerPop == YES) {
[delegate documentListResponse:documentList andStatusCode:code];
}
I know this might not be perfect solution but this resolved my issue.
Thanks

How to Modify an Instance in Another Class with AFNetworking - Creating API Client

Having this issue, because I'm trying to develop my code. Before, I was using AFNetworking methods in the classes, but I got 4 of them. Instead of that repeatin sequence, I wanted to have APIClient, which has the methods. I implemented some methods but my issue is about just two of them.
So, in APIClient.m I have the followings:
+(void)GetCurrencyInformationFrom:(NSString *)URLString to:(NSArray *) array inThe:(UITableView *) tableView{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; // User informations.
NSString *accessToken = [defaults objectForKey:#"accessToken"];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
[manager.requestSerializer setValue:accessToken forHTTPHeaderField:#"Token"];
NSLog(#"access token: %#", accessToken);
NSLog(#"id: %#", [defaults objectForKey:#"ID"]);
[manager GET:URLString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
[self update:array withDictionary:responseObject inThe:tableView];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error 2: %#", error);
}];
}
+(void)update:(NSArray *)array withDictionary:(NSDictionary *)dictionary inThe:(UITableView *) tableView{
NSLog(#"Data Count: %lu", [dictionary[#"Data"] count]);
array = [[NSMutableArray alloc] initWithArray:dictionary[#"Data"]];
NSLog(#"Array Count: %lu", [array count]);
[tableView reloadData];
}
Those methods are called in Table View classes. For example, one of my classes I called within the viewload those:
NSString *URL = #"http://api-dvzalt.azurewebsites.net/api/Currency/Doviz";
[APIClient GetCurrencyInformationFrom:URL to:currencyArray inThe: tableView];
For debugging, I am printing the Data count and Array count (Both you can find in update:withDictionary:inThe: method) and number of rows (in the table class). It's normal to number of rows to be zero at the beginning since it is asychronous, however, after I reload my tableView, i.e. after everything is done (see [tableView reloatData] in update:withDictionary:inThe method) number of rows remains zero, where Data and Array's count are 20. And of course, with zero rows, nothing showed up on the table. So, basically my problem is the currencyArray I'm giving to method doesn't change after it comes back to the tableView again even it is changing in the APIClient class.
I feel like it is a simple mistake, but I can't see where it is. Glad if you can help me to find it.
Thanks!
Did you make sure that your UITableView has the correct data source set?
What do your -numberOfSectionsInTableView: and -tableView:numberOfRowsInSection: methods look like?
I don't really like answering my question. However in this case, I bet there are too many newbie people around and searching for the same problem I have. I couldn't find any solution for 4 days. Have been searching on net and asking here, but nowhere I found the solution I needed. Maybe it will be not the case for you, but certainly it will at least take 1 day long, if it is the first time. So, I will give a try to make beginners like me understand deeply.
At first, creating and having a class like APIClient is generally a good idea. It is just a class that you can easily use when you are going to take data from internet. However, things are getting complicated for beginners since we are mostly got used to synchronous execution.
If you are up trying modify your any instance in any class, what you have to do is, simply, not to give that instance to APIClient (or the class that has blocks) like me, instead trying to take any needed information from the APIClient. Like, if we can achieve the information coming from the APIClient, it is easy to that instance in the instance's own class. E.g. giving currencyArray to APIClient, and trying to update the array in APIClient is hard. On the other hand, it is easy to just taking the responceObject from APIClient, which will be exactly the JSON data coming from the URL you have.
Long story short, in that manner, I changed my code in the APIClient.m into this:
+(void)GetCurrencyInformationFrom:(NSString *)URLString to:(NSArray *) array inThe:(UITableView *) tableView{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; // User informations.
NSString *accessToken = [defaults objectForKey:#"accessToken"];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
[manager.requestSerializer setValue:accessToken forHTTPHeaderField:#"Token"];
NSLog(#"access token: %#", accessToken);
NSLog(#"id: %#", [defaults objectForKey:#"ID"]);
[manager GET:URLString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
if(success) success(operation, responseObject)
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if(failure) failure(operation, error)
}];
}
Note that I have no longer update:withDictionary:inThe: method, since the plan is taking the information to the instance's class, and update it there. not here.
For updating purpose, let's call in the viewLoad of the instance's class. So, the method will look like this:
[APIClient GetCurrencyInformationFrom:URL
success:^(AFHTTPRequestOperation *operation, id responseObject) {
[self updateCurrencyArrayWithDictionary:responseObject];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error 2: %#", error);
}];
Again for updating purpose, I also added update method here instead of APIClient, which is not really necessary actually; instead we would have block type instance.
-(void)updateCurrencyArrayWithDictionary:(NSDictionary *)dictionary{
currencyArray= [[NSMutableArray alloc] initWithArray:dictionary[#"Data"]];
[tableView reloadData];
}
Please note that the line dictionary[#"Data"]]; would probably change according to your data.
So, that's it. This is just a simple example of how to create API client for the networking purpose of our application.
Hope, I can help someone in the future with this post.

iOS Keep Order of Async Queries

- (void)loadItems {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager.requestSerializer setValue:#"text/html" forHTTPHeaderField:#"Content-Type"];
[manager GET:#"someurl"
parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
[self reloadData];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
- (void)textFieldDidChange {
[_filteredArray removeAllObjects];
[self loadItems];
}
I am trying to implement instant search by making an API call every time a character changes. Since, the first few calls have less letters, they return more results, making the first few async calls finish slower than than the last few, meaning that if I type in hello quickly, I will end up getting the search results for h instead of the whole word since the last call to finish is the one for h. I need to keep the order of these calls, and make sure that the last query is not overwritten. I understand that I must use a queue structure. However doing something like this in textFieldDidChange doesn't seem to work:
dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
[self loadItems];
});
dispatch_group_notify(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
[self reloadData];
});
I think I need to use some sort of combination of dispatch_group_enter(group); and dispatch_group_leave(group);. However I still can't get the calls to stop overwriting the last call. I'm not sure if there is also a way to just cancel out all the other started calls with the last one, or if I have to wait for all of them to finish in order. Any help would be appreciated.
This was my solution. I just ended up using a counter that I pass into my loadItems function. While that counter updates, the async call still has its own value in it, so I just compare the two, and make sure to only reloadData if the async call's counter is equal to the latest one.
- (void)loadItems:(int)queryInt {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager.requestSerializer setValue:#"text/html" forHTTPHeaderField:#"Content-Type"];
[manager GET:#"someurl"
parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (searchQueryCounter - 1 == queryInt) {
[self reloadDatawithAnimation];
} else {
return;
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
- (void)textFieldDidChange {
[_filteredArray removeAllObjects];
[self loadItems:searchQueryCounter];
searchQueryCounter = searchQueryCounter + 1;
}
You might better address this by canceling the prior requests, not only preventing prior requests reporting results, but also ensuring that system resources are not consumed by requests that are no longer needed:
#interface ViewController () <UITextFieldDelegate>
#property (nonatomic, strong) AFHTTPRequestOperationManager *manager;
#property (nonatomic, weak) NSOperation *previousOperation;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// no need to instantiate new request operation manager each time;
// do it at some logical point of initialization (e.g. in `viewDidLoad`
// for view controllers, etc.).
self.manager = [AFHTTPRequestOperationManager manager];
[self.manager.requestSerializer setValue:#"text/html" forHTTPHeaderField:#"Content-Type"];
}
- (void)loadItems {
[self.previousOperation cancel];
typeof(self) __weak weakSelf = self; // probably should use weakSelf pattern, too
NSOperation *operation = [self.manager GET:#"someurl" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
[weakSelf reloadData];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if ([error.domain isEqualToString:NSURLErrorDomain] && [error code] != NSURLErrorCancelled) {
NSLog(#"Error: %#", error);
}
}];
self.previousOperation = operation;
}
- (void)textFieldDidChange {
[_filteredArray removeAllObjects];
[self loadItems];
}
#end
I actually worked on a very similar problem last week and came up with an approach you might find useful.
I submit each request using performSelector:withObject:afterDelay with a slight delay (I've been experimenting with values ranging from 0.5 to 1.0 seconds. Somewhere from .66 to .75 seems like a good compromise value.)
With each new request, I cancel the previous pending performSelector call. That way nothing gets sent until the user stops typing for a short period of time. It's not perfect, but it reduces the amount of useless queries for word fragments. The code looks something like this:
static NSString *methodWord = nil;
[[self class] cancelPreviousPerformRequestsWithTarget: self
selector: #selector(handleWordEntered:)
object: methodWord];
methodWord = word;
[self performSelector: #selector(handleWordEntered:)
withObject: methodWord
afterDelay: .667];
The method handleWordEntered: actually sends the request to the server.
If the user types a letter, then another letter in less than 2/3 second, the previous pending request is cancelled and a new request is set to fire 2/3 of a second later. As long as the user keeps typing letters every 2/3 second, nothing is sent. As soon as the user pauses more than 2/3 second, a request is sent. Once the performSelector:withObject:afterDelay fires it can't be cancelled any more, so that request goes to the network and the reply is parsed.

why AFNetworking get unsupported url when putting '|' pipe sign in the url

I've been using AFNetworking for a while and ran into a bizarre issue today.
I'm do a GET request using AFNetworking with Google Places API
https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=21.283974,-157.836222&radius=1600&types=food|bar|cafe&key=myownapikeyhere
the places nearby search api uses type restriction to constrain the result you can get from the api. see document
the way you do it is to have parameter types=type1|type2|type3|etc, the type1, type2, type3 are the types of places you want fetch with the url.
I managed to get results when I paste the url into browser and request it. but whenever I use it with AFNetworking, the '|' sign seems to break it. It throws an unsupported url error.
Is there a reason with this issue? Any suggestion would be helpful.
Thanks!
Are you building this URL yourself? If you used the parameters of a AFNetworking GET request, I believe it would percent escape it properly. But if you build the URL yourself, you're not letting AFNetworking do the necessary percent escaping.
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSDictionary *params = #{#"location" : #"21.283974,-157.836222",
#"radius" : #1600,
#"types" : #"food|bar|cafe",
#"key" : #"myownapikeyhere"};
[manager GET:#"https://maps.googleapis.com/maps/api/place/nearbysearch/json" parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"responseObject = %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error = %#", error);
}];
It is too simple. Just replace the | with %7C
NSString *string = #"https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=21.283974,-157.836222&radius=1600&types=food|bar|cafe&key=myownapikeyhere";
NSString *newString = [string stringByReplacingOccurrencesOfString:#"|" withString:#"%7C"];

How to find wifi speed in iOS programmatically

I have to develop an application which will be useful for finding the wifi speed. Based that wifi speed I have to change the position of wifi router. This app main purpose is to decide wifi router position in a room. But how to find speed of the wifi signal? How to find the speed of wifi internet connection?
One way you can do this is by pinging a remote server repeatedly and see if the response time changes. Frankly I think the differences will likely be very minute but either way this is how you can can do it using AFNetworking.
startTime = CACurrentMediaTime(); //This will start the clock.
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"http://example.com/resources.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
double elapsedTime = (CACurrentMediaTime() - startTime); //in case of success you calculate the elapsed time
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
I am adding this great tutorial on how to use AFNetworking.

Resources