I am having an issue parsing two JSON urls at once. YouTube only permits 50 results per request, so I'd like to add a second with a start-index of 51, to continue the request.
NSString *urlAsString = #"https://gdata.youtube.com/feeds/api/playlists/PLgw1uRYia2CRvuF4Y3KLuvFSWY6lmuY8T?v=2&alt=json&max-results=50&orderby=published";
NSString *urlAsString2 = #"https://gdata.youtube.com/feeds/api/playlists/PLgw1uRYia2CTSBBNrTDjdEcswVFjPkCr9?v=2&alt=json&max-results=50&orderby=published";
Combining two of them, I tried this:
NSString *finallink = [NSString stringWithFormat:#"%#,%#", urlAsString, urlAsString2];
Then making the actual request with Afnetworking, I added:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:finallink parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
manager.requestSerializer = [AFHTTPRequestSerializer serializer];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
NSDictionary *feed = [[NSDictionary alloc] initWithDictionary:[responseObject valueForKey:#"feed"]];
videoArray = [NSMutableArray arrayWithArray:[feed valueForKey:#"entry"]];
[self.videoMetaData addObjectsFromArray:[videoArray valueForKeyPath:#"title.$t"]];
operation.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"text/html"];
[self.videolist reloadData];
[self->activityind startAnimating];
NSLog(#"JSON: %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Error Retrieving Videos"
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alertView show];
NSLog(#"Error: %#", error);
}];
This does not work for some reason. I get this error:
Error: Error Domain=NSCocoaErrorDomain Code=3840 "The operation couldn’t be completed. (Cocoa error 3840.)"
What could be wrong?!
As mentioned in the comments, this code fails because you are trying to retrieve the result of two URLs joined to one another. The way it is set up now is equivalent to trying to visit http://google.com,http://google.com in a web browser, which will of course fail.
Instead, the solution is to retrieve the results in batches, one after the other. Here's one way to do it:
Write a method which retrieves the YouTube results at a given offset. If you want to retrieve all links starting at 51, then a good idea would be to have a method which takes an offset and returns the results in a completion block.
Write another method which can use the previous one to retrieve the entire list of results. This will need to send multiple network requests to YouTube, one for each batch of 50 that you need, and collect the results somewhere.
There are a couple other issues I noticed in your code sample. One is that you are setting a new request and response serializer in the success block of your network request – instead, you should set these once somewhere in your app, because reallocating them after each request is inefficient. AFHTTPRequestOperationManager does create default instances of these, so you can get away with not setting them at all.
Another potential issue is that you are displaying an alert view in your failure block. Because AFNetworking performs network requests on a background thread by default (from what I recall), you might run into some weird problems (the usual symptom is that your UI will not show up for a few seconds). Apple requires that UI-related methods are called on the main thread.
Related
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
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.
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.
I want search result like Google search.
Here if I search for 'tokyo' then it gives me four results containing place 'tokyo'.
Same functionality I want to do using Foursquare API. Currently I am able to find near places from current position or given position.
You need to use the https://api.foursquare.com/v2/venues/search endpoint and use the query parameter so that it will return matches based on your keyword. Since you're on iOS you can use UITableView to show the results as search suggestions or other libraries that fit your requirement. Since it will be like an autocomplete/autosuggest search, I suggest that you try to call the endpoint on the change event of the text field with a delay.
Example using AFNetworking Library for iOS:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
[params setObject:FOURSQUARE_CLIENTID forKey:#"client_id"];
[params setObject:FOURSQUARE_CLIENTSECRET forKey:#"client_secret"];
[params setObject:searchTextField.text forKey:#"query"];
[params setObject:LatLong forKey:#"ll"];
[params setObject:FOURSQUARE_VERSION forKey:#"v"];
[manager GET:#"https://api.foursquare.com/v2/venues/search" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
//Process the JSON response here and output each venue name as search suggestion
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
I suggest you read more on the FourSquare documentation and how to process JSON results and show them to a UITableView if you don't know how to yet.
Also notice that you still have to provide your "ll" as parameter since you can only query for nearby places using the endpoint.
For global search and not using "ll" or "nearby" parameter. You can try to use the global intent which according to the documentation:
Finds the most globally relevant venues for the search, independent of
location. Ignores all other parameters other than query and limit.
When logging a user into my application I need to pull a user object down from the server using only the username. This returns the userId (among other things) that I need in order to make other API calls. From that point I'll make a couple other HTTP calls using the userId. How can I make a synchronous call to completely pull down the user object before sending the other calls?
I've setup my object mapping in my app delegate class, which works perfectly, and am using this code to pull the user object down from the server:
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:[#"/api/users/" stringByAppendingString:[_userNameField text]] delegate:self];
This is what I've tried... as suggested here: Making synchronous calls with RestKit
RKObjectLoader* loader = [[RKObjectManager sharedManager] objectLoaderForObject:currentUser method:RKRequestMethodPUT delegate:nil];
RKResponse* response = [loader sendSynchronously];
However this code (1) uses the deprecated method objectLoaderForObject and (2) crashes saying 'Unable to find a routable path for object of type '(null)' for HTTP Method 'POST''.
Putting aside the question of whether this is the ideal design for an iPhone application, I was able to accomplish what I was hoping using blocks.
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:[#"/api/users/" stringByAppendingString:[_userNameField text]] usingBlock:^(RKObjectLoader* loader) {
loader.onDidLoadResponse = ^(RKResponse *response) {
NSLog(#"Response: \n%#", [response bodyAsString]);
};
loader.onDidLoadObjects = ^(NSArray *objects) {
APIUser *apiUser = [objects objectAtIndex:0];
NSLog(#"user_id is %i", apiUser.user_id);
};
loader.onDidFailWithError = ^(NSError *error) {
UIAlertView *badLoginAlert = [[UIAlertView alloc]initWithTitle:NSLocalizedString(#"LOGIN_FAILED", nil)
message:NSLocalizedString(#"BAD PASSWORD OR USERNAME", nil)
delegate:self
cancelButtonTitle:NSLocalizedString(#"OK", nil)
otherButtonTitles:nil];
[badLoginAlert show];
};
}];
Hope this helps someone.