I have a problem when users use my app and they lost connection or use airplane mode.
My app server side doesn't set any cache policy and for the time being I can't change it. I migrated from AFNetworking 1.x to 2.0 and now I'm using AFHTTPRequestOperationManager when making requests. The problem is that because I have no cache policy on the server side, every request is made to the server (which for now is fine) but if the user is not able to connect to my server, it doesn't load the cached request.
So, I'm trying the following, using AFHTTPRequestOperation directly like this:
NSURL *URL = [NSURL URLWithString:filePath];
NSMutableURLRequest *request = [[NSURLRequest requestWithURL:URL] mutableCopy];
if (![[AFNetworkReachabilityManager sharedManager] isReachable]) {
[request setCachePolicy:NSURLRequestReturnCacheDataElseLoad];
}
AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
[[NSOperationQueue mainQueue] addOperation:op];
This way, if AFNetworkReachabilityManager tells me there's no connectivity, I configured the cache policy for the request and it is loaded from the cache correctly.
The question is, is this the right approach to this situation?
You just need to subclass the AFHTTPSessionManager and check if the client is offline. Then you can change the cache policy or force the app to use cached data.
Related
I am starting learning about web service, where I use one url api to get data and to display in my table view. But I saw some tutorials - in that they use NSURLConnection or Rest API or AFNetworking.
I am really confused about all type. Which one should I use in that above type. For web service which type should I use. And also I saw some doubts in SO that use synchronous or asynchronous. Thus this any another type to get data from URL?
Actually for all web service, which should I use to get data and display?
-(void)JsonDataParsing
{
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
[manager POST:url parameters:params
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSDictionary *jsonDict = (NSDictionary *) responseObject;
//!!! here is answer (parsed from mapped JSON: {"result":"STRING"}) ->
NSString *res = [NSString stringWithFormat:#"%#", [jsonDict objectForKey:#"result"]];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//....
}
];
}
Firstly, AFNetworking and NSURLConnection are used on Mobile Side.
Rest API is not from mobile side. Rest API you implemented on server side which handle CRUD operations like GET, POST, PUT and DELETE.
Third party libraries are there to ease our work. And AFNetworking is very popular and trustworthy library.
AFNetworking makes asynchronous network requests. To read more about it, visit Introduction to AFNetworking.
AFNetworking does everything NSURLConnection can. Using it now will save you a lot of time writing boilerplate code!
NSURLConnection and NSURLSession is apple API use to manage network operation like download and upload, AFNetworking is the framework that use those 2 API and added multithreading/error handling/network reachability....to make your life easier, RESTful is the architecture for client-server connecting, u can implement it in your serverside to return things back to your clientside in easy to use model (JSON).
synchronous mean u wait for it to complete to do anything else, asynchronous means u just start it but don't need to wait for it, like u do a request to server and user still can interact with your UI at the same time, so its advised that use asynchronous task to request to server then only update the UI in synchronous
hope my explain is easy to understand and correct :)
NSURLConnection
This lets you to load the content of URL by providing the URL request object. By using NSURLConnection you can load URL requests both asynchronously using a callback block and synchronously. See this example
NSURL *URL = [NSURL URLWithString:#"http://example.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// ...
}];
For more you can go to apple docs
AFNetworking
This is third party library built on the top of Foundation URL Loading.
This is very easy to install through pods and handy to use. See below example like how I am using the same in my app
-(AFHTTPRequestOperationManager *)manager
{
if (!_manager)
{
_manager = [AFHTTPRequestOperationManager manager];
_manager.requestSerializer = [AFHTTPRequestSerializer serializer];
_manager.responseSerializer = [AFHTTPResponseSerializer serializer];
}
return _manager;
}
Above we are initializing the instance of AFHTTPRequestOperationManager *manager
[self.manager POST:#"http://example.com" parameters:parameters
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSError *error;
NSMutableDictionary *responseDict = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableContainers error:&error];
// return response dictionary in success block
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
// return error in failure block
}]
Above method will load data asynchronously and remaining is self explanatory. But if you want to block the user interface like a synchronous request than use [operation waitUntilFinished] which is an anti-pattern. Here operation is a instance of AFJSONRequestOperation.
I want to know is there way to download images with my code one by one ? Now async
NSString *urlString = link;
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *requestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest];
requestOperation.responseSerializer = [AFImageResponseSerializer serializer];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
completion(operation, responseObject, nil);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[RequestAPI sharedInstance].downloadsCount -= 1;
DDLogError(#"FAIL download image: %#",error);
}];
[requestOperation start];
Try this solution
Create a NSObject class for downloading the images. Lets call this DownloadImages. The class has an array of images to be downloaded arrayOfImagesURL.
Create a delegate for the class DownloadImages to make a call back when you finish downloading an image. View how to this in detail here.
Implement AFNetworking as you like, you can also accomplish this by using the iOS 7 API NSURLSession. So in the completion block of your download task, you need to do 2 things:
Continue downloading the next URL in arrayOfImagesURL.
Do a call back with the delegate to the parent controller for processing.
Tell me if there's something need to be more clarify.
I want to upload video using PUT request and this is my experience.
I was trying many possibilities of doing PUT request using AFNetworking 2.0, but everything fails.
But I resolved this and this "Question" is information for everybody else who is asking solution.
This works for me:
NSDictionary *headersDict = #{#"Content-Length": [NSString stringWithFormat:#"%#", mySize], #"Content-Type": #"video/mp4", #"Accept": #"application/json", #"Authorization": [NSString stringWithFormat:#"Basic %#", hash]};
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:urlToPut];
[request setHTTPMethod:#"PUT"];
[request setHTTPBody:videoData];
[request setAllHTTPHeaderFields:headersDict];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// if everything run great, we have to invalidate timer to notify
[uploadTimer invalidate];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error: %#", error);
}];
// start timer where app will be checking upload progres on the server
uploadTimer = [NSTimer scheduledTimerWithTimeInterval:.1
target:self
selector:#selector(checkUploadProgressForRecord:)
userInfo:record
repeats:YES];
[operation start];
Anybody has another solution?
You approach has the following issues:
The PUT method is not the suggested method for uploading a file. PUT can be used, but this requires that the server MUST create the resource at the specified URI. In most cases however, the server will create a new URI (which is unknown to the client) when saving the uploaded file locally on the server. In this case, a POST method must be used.
You are using a NSData object which represents the content of the file and assign it the request with setHTTPBody. This is problematic regarding system memory when the file is large and the NSData object uses a heap allocated buffer to hold the file's content. You should either use a NSData object which uses a memory mapped file or a NSStream to represent the file content.
You are too vague about the request headers:
`[request setAllHTTPHeaderFields:headersDict];`
Request header are important. You should set the Content-Type and possible the Content-Length header.
For "Form-based File Upload", see also RFC 1867.
I am developing an iOS app that uses the Evernote API. Everything has been working fine, but then I started getting "code 403" to my requests.
Authentication to the service goes well: I am able log on and download all info I need (Notebooks, Notes, Note content, etc...). But when I try to get the thumbnails, I get 403.
My code for the request:
NSString *thumbnailPath = [NSString stringWithFormat:#"%#thm/note/%#?75", [[EvernoteSession sharedSession] webApiUrlPrefix], note.guid];
NSLog(#"THUMBNAILPATH %#", thumbnailPath);
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:[[EvernoteSession sharedSession] webApiUrlPrefix]]];
[httpClient clearAuthorizationHeader];
[httpClient setAuthorizationHeaderWithToken:[[EvernoteSession sharedSession] authenticationToken]];
[httpClient postPath:thumbnailPath parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
note.thumbnail = responseObject;
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"REQUEST: %#", thumbnailPath);
NSLog(#"Error: %#", error.localizedDescription);
}];
If I copy what the "REQUEST:" log result, it is a well-formatted link that gives me the thumbnail in my browser. But the second log gives me: "Error: Expected status code in (200-299), got 403".
I am out of ideas. Can anyone help?
You are not passing in the auth token correctly. This is how you would request a thumbnail for a note on iOS :
- (void)getThumbnailWithNoteGuid:(NSString*)noteGUID {
EvernoteSession* session = [EvernoteSession sharedSession];
NSString* fullTumbnailURL = [NSString stringWithFormat:#"%#thm/note/%#",[[EvernoteSession sharedSession]webApiUrlPrefix],noteGUID];
NSURL *url = [NSURL URLWithString:fullTumbnailURL];
NSMutableURLRequest* urlReq = [NSMutableURLRequest requestWithURL:url];
[urlReq setHTTPMethod:#"POST"];
[urlReq setHTTPBody:[[NSString stringWithFormat:#"auth=%#",[session.authenticationToken URLEncodedString]] dataUsingEncoding:NSUTF8StringEncoding]];
NSLog(#"full URL %#",fullTumbnailURL);
[NSURLConnection sendAsynchronousRequest:urlReq queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *urlREsp, NSData *respData, NSError *error) {
if(error == nil) {
NSLog(#"Thumbail data : %#",respData);
};
}];
}
403 - means forbidden. I haven't got expirience working with evernote but in based on other SDK's you're doing something wrong with request either you should go to evernote developers page. log in to your's account and look for some triggers. may be there some triggers which you should turn on to use this features
You should:
Make sure that you're hitting the fully constructed URL you intend to
Examine the entire returned body to see if there is additional error info provided
Examine full URL
Set a breakpoint in -[AFHTTPClient postPath:
parameters:
success:
failure:]. In the debugger, type po request to see the full URL that AFNetworking is hitting. Make sure that's what you want.
Examine entire body
In your failure block, you're only looking at the error object, created by AFNetworking to summarize the issue. However, the Evernote API could be providing additional info in the response body, which you can look at with NSLog(#"Response Body: %#", [operation responseString]).
Summary
Ultimately, your AFNetworking code is fine - this looks like an Evernote API issue. You're making the request wrong, your token is expired, or there's a bug on their end.
Side notes
It's inefficient to create a new AFHTTPClient instance for every request. You should probably use the singleton pattern to have one that sticks around for the lifetime of your app.
You should do some error checking before note.thumbnail = responseObject;. responseObject could be anything; make sure it's what you expect before you call your setter.
So all I'm trying to do is serve up a .json file somewhere (I've tried both my own personal server, and also on AppFog), then performing a GET request using AFJSONRequestOperation on the iOS platform. My code to do this request is as follows:
AFHTTPClient *aclient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:kBaseURL]];
NSURLRequest *request = [aclient requestWithMethod:#"GET"
path:#"voucher.json"
parameters:nil];
AFJSONRequestOperation *op = [[AFJSONRequestOperation alloc] initWithRequest:request];
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"%#", (NSString *)responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
[op start];
The problem here is that say the .json file initially contained an array of 2 strings. The above code would fetch these objects just fine. Then if I edited the .json file with another 3 strings, rerunning the code would continue to fetch only the 2 old strings.
Only 10-15 minutes after I made the change to the .json file will a refresh give me the updated data. At first I thought this was a caching issue, but setting [client setCachingPolicy:] didn't make any difference.
This issue is present whether I host my .json file on my static server, or running in a Node express server on AppFog. It just keeps returning an old version of the resource requested.
I'm really puzzled about this: so any help would be greatly appreciated!
Make sure the url request is not cacheing the data. I had to implement the following so I would get new json data each time a fetch was made.
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:5];
This is likely a caching issue - you could change the cache policy on the request or append something onto the query string, like the current time.
To do this you could use something like:
[aclient requestWithMethod:#"GET"
path:[NSString stringWithFormat:#"voucher.json?%#", [NSDate date]]
parameters:nil];