AFNetworking 3.x: How to know whether response is from cache or not? - afnetworking

The question AFNetworking : How to know if response is using cache or not ? 304 or 200 had been answered well for AFNetworking 2.x. But how do you do the same thing in 3.x?
It's very useful to know whether resources were returned from cache or from the network while debugging.

You can follow the same approach with AFNetworking3.0.
BOOL __block responseFromCache = YES; // yes by default
[self setDataTaskWillCacheResponseBlock:^NSCachedURLResponse * _Nonnull(NSURLSession * _Nonnull session, NSURLSessionDataTask * _Nonnull dataTask, NSCachedURLResponse * _Nonnull proposedResponse) {
responseFromCache = NO;
NSLog(#"Sending back to cache response");
NSCachedURLResponse * responseCached;
NSHTTPURLResponse * httpResponse = (NSHTTPURLResponse *) [proposedResponse response];
if (dataTask.originalRequest.cachePolicy == NSURLRequestUseProtocolCachePolicy) {
NSDictionary *headers = httpResponse.allHeaderFields;
NSString * cacheControl = [headers valueForKey:#"Cache-Control"];
NSString * expires = [headers valueForKey:#"Expires"];
if (cacheControl == nil && expires == nil) {
NSLog(#"Server does not provide info for cache policy");
responseCached = [[NSCachedURLResponse alloc] initWithResponse:dataTask.response
data:proposedResponse.data
userInfo:#{ #"response" : dataTask.response, #"proposed" : proposedResponse.data }
storagePolicy:NSURLCacheStorageAllowed];
}
}
return responseCached;
}];
[self wb_GET:url parameters:nil headerFields:additionalHeadersDict success:^(NSURLSessionDataTask *task, id responseObject) {
if (responseFromCache) {
// response was returned from cache
NSLog(#"RESPONSE FROM CACHE: %#", responseObject);
}
handler(responseObject, nil);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
handler(nil, error);
}];
Besides that you can also implement below delegate method to your AFHTTPSessionManager subclass.
- (void)baseSuccessWithResponseObject:(id)responseObject sessionTask: (NSURLSessionDataTask *) task validationHandler:(void(^)(id responseObject, NSError *error))handler{}

Related

Implement custom cache for AFNetworking 3.x

Hi I'm writing a IOS api to fetch data from server using AFNetworking 3.x My Server does not support caching. This this server header
Connection →Keep-Alive
Content-Length →4603
Content-Type →text/html; charset=UTF-8
Date →Wed, 10 Aug 2016 04:03:56 GMT
Keep-Alive →timeout=5, max=100
Server →Apache/2.4.18 (Unix) OpenSSL/1.0.1e-fips PHP/5.4.45
X-Powered-By →PHP/5.4.45
I want to build custom API to enable cache (for example request and response should be cache for 10 second so that if user make same request, cache data is used. If cache expired app will then re download again)
My AFNetworking Singleton
#pragma mark - Shared singleton instance
+ (ApiClient *)sharedInstance {
static ApiClient *sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
sharedInstance = [[self alloc] initWithBaseURL:[NSURL URLWithString:SINGPOST_BASE_URL] sessionConfiguration:sessionConfiguration];
return sharedInstance;
}
- (id)initWithBaseURL:(NSURL *)url
{
if ((self = [super initWithBaseURL:url])) {
self.requestSerializer = [AFHTTPRequestSerializer serializer];
}
return self;
}
My JSON request
- (void)sendJSONRequest:(NSMutableURLRequest *)request
success:(void (^)(NSURLResponse *response, id responseObject))success
failure:(void (^)(NSError *error))failure {
self.responseSerializer.acceptableContentTypes = [AFHTTPResponseSerializer serializer].acceptableContentTypes;
self.requestSerializer.timeoutInterval = 5;
self.requestSerializer.cachePolicy = NSURLRequestReturnCacheDataElseLoad;
[self setDataTaskWillCacheResponseBlock:^NSCachedURLResponse * _Nonnull(NSURLSession * _Nonnull session, NSURLSessionDataTask * _Nonnull dataTask, NSCachedURLResponse * _Nonnull proposedResponse) {
return [[NSCachedURLResponse alloc] initWithResponse:proposedResponse.response
data:proposedResponse.data
userInfo:proposedResponse.userInfo
storagePolicy:NSURLCacheStorageAllowed];
}];
NSURLSessionDataTask *dataTask = [ApiClient.sharedInstance dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
NSLog(#"Error URL: %#",request.URL.absoluteString);
NSLog(#"Error: %#", error);
failure(error);
} else {
NSDictionary *jsonDict = (NSDictionary *) responseObject;
success(response,jsonDict);
NSLog(#"Success URL: %#",request.URL.absoluteString);
NSLog(#"Success %#",jsonDict);
}
}];
[dataTask resume];
}
How to modify the bellow code to enable caching request and response (I want response to be keep like 10 sec) (Or 1 day if no internet connection) Any help is much appreciate. Thanks!
[self setDataTaskWillCacheResponseBlock:^NSCachedURLResponse * _Nonnull(NSURLSession * _Nonnull session, NSURLSessionDataTask * _Nonnull dataTask, NSCachedURLResponse * _Nonnull proposedResponse) {
return [[NSCachedURLResponse alloc] initWithResponse:proposedResponse.response
data:proposedResponse.data
userInfo:proposedResponse.userInfo
storagePolicy:NSURLCacheStorageAllowed];
}];
Instead of
return [[NSCachedURLResponse alloc] initWithResponse:proposedResponse.response
do
NSMutableDictionary *dictionary = [proposedResponse.response.allHeaderFields mutableCopy];
dictionary[#"Cache-Control" = ...
NSHTTPURLResponse *modifiedResponse =
[[NSHTTPURLResponse alloc initWithURL:proposedResponse.response.URL
statusCode:proposedResponse.response.statusCode
HTTPVersion:#"HTTP/1.1"
headerFields:modifiedHeaders];
[modifiedResponse
return [[NSCachedURLResponse alloc] initWithResponse:modifiedResponse
Note that it is not possible to retrieve the HTTP version from the original request. I'm pretty sure I filed a bug about that. I'm also pretty sure that it doesn't have any effect on anything, and I don't think it even gets stored in the underlying object, so it probably doesn't matter.

YouTube api v3 Invalid Credentials access token

I am getting 401 Invalid Credentials error trying to use the Youtube API in the OAuth 2.0.
I used google sign in sdk and get access_token with params:
GIDSignIn *sharedSignIn = [GIDSignIn sharedInstance];
sharedSignIn.shouldFetchBasicProfile = NO;
sharedSignIn.scopes = [NSArray arrayWithObjects:
#"https://www.googleapis.com/auth/youtube.force-ssl",
#"https://www.googleapis.com/auth/youtube",
#"https://www.googleapis.com/auth/youtube.readonly",
// #"https://www.googleapis.com/auth/youtube.upload",
nil];
[sharedSignIn signIn];
Than I used AFNetworking library for GET request
- (void) getInformationWithParams: (NSDictionary *) params
method: (NSString *) method
onSuccess: (void(^)(NSDictionary *responseObject)) success
onFailure: (void (^) (NSError *error)) failure {
[self.requestOperationManager GET:method
parameters:params
progress:nil
success:^(NSURLSessionDataTask * _Nonnull task, NSDictionary *responseObject) {
NSLog(#"%#", responseObject);
if (success) {
success(responseObject);
}
}
failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(#"getInformationWithParams Error: %#", error);
if (failure) {
failure(error);
}
}];
}
previously I did baseURL init
NSURL *baseURL = [NSURL URLWithString:#"https://www.googleapis.com/youtube/v3"];
self.requestOperationManager = [[AFHTTPSessionManager alloc] initWithBaseURL:baseURL];
and in the end I get request through
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
#"snippet", #"part",
#"true", #"home",
myApiKey, #"key",
myAccessToken, #"access_token",
nil];
[[SMServerManager sharedManager] getInformationWithParams:params
method:#"activities"
onSuccess:^(NSDictionary *responseObject) {
}
onFailure:^(NSError *error) {
}];
I don't understand what I'm doing wrong.
PS: Requests work if they don't need to use acces_token.
It was a very stupid mistake. I'm confused in several google accounts and make wrong files for project.

Make a progress bar for get the response with AFNetworking 3.0

I want to make progress bar for api calling and end with success and i am using the AFNetworking 3.0 version.
I do the following code for measure the progress.
NSURLSessionDataTask *obj = [manager POST:UrlForGetAllCalEntry parameters:jsonDict progress:^(NSProgress * _Nonnull uploadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
if ([[responseObject valueForKey:#"code"] integerValue] == 200)
{
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[TRAVALARMMANAGER setMessage:error.localizedDescription withView:[APPDELEGATE window] textColor:txtMsgColor bgColor:bgMsgColor];
NSLog(#"Error: %#", error);
}];
[manager setDataTaskDidReceiveDataBlock:^(NSURLSession * _Nonnull session, NSURLSessionDataTask * _Nonnull dataTask, NSData * _Nonnull data) {
if (dataTask.countOfBytesExpectedToReceive == NSURLSessionTransferSizeUnknown)
return;
if (dataTask != obj)
return;
NSUInteger code = [(NSHTTPURLResponse *)dataTask.response statusCode];
if (!(code> 199 && code < 400))
return;
long long bytesReceived = [dataTask countOfBytesReceived];
long long bytesTotal = [dataTask countOfBytesExpectedToReceive];
NSLog(#"... %lld/%lld",
bytesReceived,
bytesTotal);
}];
But method return from
if (dataTask.countOfBytesExpectedToReceive == NSURLSessionTransferSizeUnknown)
return;
This statement always return true. I don't understand why? . I also print the header and it has "contact length" option.
From the Apple Docs
Discussion
This value is determined based on the Content-Length header received from the server. If that header is absent, the value is NSURLSessionTransferSizeUnknown.
Have you tried to avoid that if statement?
When I download files I don't use that check, I only calculate the progress with the division you did.
hmm, i use this method to monitor the download progress and it works fine for me.
- (void)setDownloadTaskDidWriteDataBlock:(void (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite))block {
self.downloadTaskDidWriteData = block;
}
↓↓↓↓↓↓ For Example ↓↓↓↓↓↓
Start Download:
NSURL *downloadURL = [NSURL URLWithString:#"example.com/file.mp4"];
NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL];
self.downloadTask = [self.manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
// this progress param is "downloadTask operation" progress, it's not the data receiving progress
return nil;
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
}];
[self.downloadTask resume];
Calculate Download Progress:
__weak typeof(self) vc = self;
[self.manager setDownloadTaskDidWriteDataBlock:^(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) {
dispatch_async(dispatch_get_main_queue(), ^{
// here is what you want
vc.progressView.progress = (CGFloat)totalBytesWritten / totalBytesExpectedToWrite;
});
}];
Result:
TestProject Image
hope it helps.
I hope it help you, you can have a lot of indicator types
https://github.com/jdg/MBProgressHUD

iOS Run two asynchronous method if failed

I have a scenario where I need to quiet refresh auth token (relogin) again if it expired when I accessing other API but I'm having a hard time thinking how to code this without creating redundant codes for every APIs even though the flow is similar.
When user has expired auth token > call paid API A (return 401 unauthorised) > relogin again > call paid API A (run successfully)
I'm having difficult in wrapping my mind to call paid API A the second time with less code and not falling into infinite loop trap. Is there any method useful for this case like NSNotification center?
Note: I need to use API in this format from AFNetworkinglogin
- (NSURLSessionDataTask *)getApiA:(CallbackBlock)block{
CallbackBlock _block = [block copy];
NSString *urlString = [[NSURL URLWithString:GET_API_A_URL relativeToURL:[NSURL URLWithString:HOME_URL]] absoluteString];
return [self GET:urlString parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSDictionary *response = (NSDictionary *)responseObject;
BLOCK_SAFE_RUN(block, response, nil, task);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
if([self unauthorizedAccess:task]){ //401
***//call Login once again > run getApiA again***
}else if ([self forbiddenAccess:task]){ //403
}
BLOCK_SAFE_RUN(block, nil, error, task);
}];
}
If i get it right you could split it into 2 methods. And pass a bool for trying again. e.g.:
- (NSURLSessionDataTask *)getApiA:(id)block {
NSString *urlString = [[NSURL URLWithString:GET_API_A_URL relativeToURL:[NSURL URLWithString:HOME_URL]] absoluteString];
return [self doApiACallWithURL:urlString firstTry:YES completion:block];
}
- (NSURLSessionDataTask *)doApiACallWithURL:(NSString *)url firstTry:(BOOL)first completion:(CallbackBlock)completion {
__weak typeof(self) wself = self;
return [self GET:urlString parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSDictionary *response = (NSDictionary *)responseObject;
BLOCK_SAFE_RUN(block, response, nil, task);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
if ([wself unauthorizedAccess:task]) { //401
if (first) {
[wself doApiACallWithURL:url firstTry:NO completion:completion];
}
} else if ([wself forbiddenAccess:task]) { //403
}
BLOCK_SAFE_RUN(block, nil, error, task);
}];
}
and use a weak self for blocks is in most cases a good idea.

AFNetworking 2.0 cancel specific task

I am trying out afnetworking 2.0 and just trying to figure out how to cancel specific tasks.
The old way would be to use something like
[self cancelAllHTTPOperationsWithMethod:#"POST" path:#"user/receipts"]
but I dont see anything like this in 2.0
I created a sub class of AFHTTPSessionManager which gives me access to the array of pending tasks and I can cancel them directly but I dont know how to identify 1 task from another so I can cancel only specific tasks.
Task does have an taskidentifier but this doesnt appear to be what I need.
NSString *path = [NSString stringWithFormat:#"user/receipts"];
[self.requestSerializer setAuthorizationHeaderFieldWithUsername:[prefs valueForKey:#"uuid"] password:self.store.authToken];
[self GET:path parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
completionBlock(responseObject);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
errorBlock(error);
}];
now if i wanted to cancel this request only how would I approach this?
You can store the task in a variable so you can access it later:
NSURLSessionDataTask* task = [self GET:path parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
completionBlock(responseObject);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
errorBlock(error);
}];
Then simply cancel it with [task cancel].
Another way would be to save the task ID of the task and later ask the URL session for its tasks and identify the task you wish to cancel:
// save task ID
_savedTaskID = task.taskIdentifier;
// cancel specific task
for (NSURLSessionDataTask* task in [self dataTasks]) {
if (task.taskIdentifier == _savedTaskID) {
[task cancel];
}
}
No need to save it, here is my implementation, use your subclass of AFURLSessionManager for cancelling specific request:
- (void)cancelAllHTTPOperationsWithPath:(NSString *)path
{
AFURLSessionManager * yourSessionManager = [self getSessionManager];
[[yourSessionManager session] getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
[self cancelTasksInArray:dataTasks withPath:path];
[self cancelTasksInArray:uploadTasks withPath:path];
[self cancelTasksInArray:downloadTasks withPath:path];
}];
}
- (void)cancelTasksInArray:(NSArray *)tasksArray withPath:(NSString *)path
{
for (NSURLSessionTask *task in tasksArray) {
NSRange range = [[[[task currentRequest]URL] absoluteString] rangeOfString:path];
if (range.location != NSNotFound) {
[task cancel];
}
}
}
you can do the following
NSArray *operations = [[[MyClient sharedClient] operationQueue] operations];
if(operations && operations.count > 0){
for (NSOperation *operation in operations) {
if([operation isKindOfClass:[AFHTTPRequestOperation class]]){
AFHTTPRequestOperation *httpOperation = (AFHTTPRequestOperation *)operation;
NSLog(#"%#", [[httpOperation request] URL]);
//--- if this is your request then cancel it --> [httpOperation cancel];
}
}
}
Where MyClient is a child of AFHTTPClient and the function sharedClient is a static function which returns a singleton instance of MyClient

Resources