I'm migrating my project to AFNetworking 2.0. When using AFNetworking 1.0, I wrote code to log each request/response in the console. Here's the code:
-(AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)request
success:(void (^)(AFHTTPRequestOperation *, id))success
failure:(void (^)(AFHTTPRequestOperation *, NSError *))failure
{
AFHTTPRequestOperation *operation =
[super HTTPRequestOperationWithRequest:request
success:^(AFHTTPRequestOperation *operation, id responseObject){
[self logOperation:operation];
success(operation, responseObject);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error){
failure(operation, error);
}];
return operation;
}
-(void)logOperation:(AFHTTPRequestOperation *)operation {
NSLog(#"Request URL-> %#\n\nRequest Body-> %#\n\nResponse [%d]\n%#\n%#\n\n\n",
operation.request.URL.absoluteString,
[[NSString alloc] initWithData:operation.request.HTTPBody encoding:NSUTF8StringEncoding],
operation.response.statusCode, operation.response.allHeaderFields, operation.responseString);
}
I'm trying to do the same thing using AFNetworking 2.0, which to my understanding, means using a NSURLSessionDataTask object instead of AFHTTPRequestOperation. Here's my shot at it.
-(NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLResponse *, id, NSError *))completionHandler {
NSURLSessionDataTask *task = [super dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error){
[self logTask:task];
completionHandler(response, responseObject, error);
}];
return task;
}
-(void)logTask:(NSURLSessionDataTask *)task {
NSString *requestString = task.originalRequest.URL.absoluteString;
NSString *responseString = task.response.URL.absoluteString;
NSLog(#"\n\nRequest - %#\n\nResponse - %#\n\n", requestString, responseString);
}
The dataTaskWithRequest:completionHandler method is successfully intercepting each call, so I think that's the right method to override, but when I try to log the task in the completionHandler, task is nil. Thus getting nulls printed in the console. However a proper task object is still returned from that method. What's happening here? How can I properly log the request/response for each call?
you can use the library AFNetworking/AFNetworkActivityLogger
https://github.com/AFNetworking/AFNetworkActivityLogger
from the doc:
AFNetworkActivityLogger is an extension for AFNetworking 2.0 that logs
network requests as they are sent and received.
usage:
[[AFNetworkActivityLogger sharedLogger] startLogging];
output:
GET http://example.com/foo/bar.json
200 http://example.com/foo/bar.json
using devel logging level you should have responseHeaderFields and responseString too
Related
So when using "AFNetworking" in my project, i tried the very basic examples just to make sure it's working but i keep getting the following error:
[NSConcreteMutableData appendData:]: message sent to deallocated instance 0x83aa8030
My code is:
NSDictionary *params = #{#"username": username,
#"password": password,
#"comment_id": comment_id]};
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc]initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
[manager POST:url parameters:params progress:nil success:^(NSURLSessionTask * _Nonnull task, id _Nullable responseObject) {
NSLog(#"JSON: %#", responseObject);
} failure:^(NSURLSessionTask * _Nullable operation, NSError * _Nonnull error) {
NSLog(#"Error: %#", error);
}];
The error occurs in the following function which is part of script: ([AFURLSessionManagerTaskDelegate URLSession:dataTask:didReceiveData:] AFURLSessionManager.m:262)
#pragma mark - NSURLSessionDataDelegate
- (void)URLSession:(__unused NSURLSession *)session
dataTask:(__unused NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data{
self.downloadProgress.totalUnitCount = dataTask.countOfBytesExpectedToReceive;
self.downloadProgress.completedUnitCount = dataTask.countOfBytesReceived;
[self.mutableData appendData:data];}
Try to store an instance of AFHTTPSessionManager in your class as a property. Something like:
#property (nonatomic, strong) AFHTTPSessionManager *manager;
Then you should not see an error about the deallocated object, hopefully!
Else please post more context to the problem, so that I can help you with this.
I have a singleton networking class as well as a singleton object that needs to persist throughout my app. The singleton is initialized based on data retrieved from a web call, so right now my code works, and I have the following in my singleton networking class:
- (void)initializeObjectWithSuccess:(void (^)(BOOL))success
failure:(void (^)(NSError *error))failure {
[self.HTTPClient postPath:[NSString stringWithFormat:#"users/%#/", [CPUser sharedUser].name parameters:[self createParameters] success:^(AFHTTPRequestOperation *operation, id responseObject) {
id json = [NSJSONSerialization JSONObjectWithData:responseObject
options:NSJSONReadingAllowFragments
error:nil];
[[CPList sharedList] setIdentifier:json[#"id"]];
[[CPList sharedList] setImages:json[#"images"]];
if (success) {
success(YES);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
failure(error);
}];
}
I don't know how to initialize all the properties I need on my singleton CPList without setting them within this method, however I know that this is not proper encapsulation because the CPRequestManager Class should know nothing about the CPList Class
If your issue is that you don't want this class to know the name of CPList and the detail that it's a singleton and that it can access it with +[CPList sharedInstance] then you can just pass in an object that conforms to a protocol. This basically moves the knowledge of the singleton somewhere else
- (void)initializeObjectWithList:(id<CPList>)list
success:(void (^)(BOOL))success
failure:(void (^)(NSError *error))failure;
{
[self.HTTPClient postPath:[NSString stringWithFormat:#"users/%#/", [CPUser sharedUser].name parameters:[self createParameters] success:^(AFHTTPRequestOperation *operation, id responseObject) {
id json = [NSJSONSerialization JSONObjectWithData:responseObject
options:NSJSONReadingAllowFragments
error:nil];
[list setIdentifier:json[#"id"]];
[list setImages:json[#"images"]];
if (success) {
success(YES);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
failure(error);
}];
}
Or you could remove all knowledge that there is a "list" and just have this method return the actual data and then the caller can set it on the list
- (void)initializeObjectWithSuccess:(void (^)(NSString *ID, NSArray *images))success
failure:(void (^)(NSError *error))failure;
{
[self.HTTPClient postPath:[NSString stringWithFormat:#"users/%#/", [CPUser sharedUser].name parameters:[self createParameters] success:^(AFHTTPRequestOperation *operation, id responseObject) {
id json = [NSJSONSerialization JSONObjectWithData:responseObject
options:NSJSONReadingAllowFragments
error:nil];
if (success) {
success(json[#"id"], json[#"images"]);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
failure(error);
}];
}
Without any further context it's hard to suggest structural changes but here's two potential refactoring that might get you thinking about what you coudld do
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.
I have void method declared in a file called LHJSonData.h:
-(void)UserLogin:(NSString *)user andPassWordExists:(NSString *)password;
and in my LHJsonData.m file I have this line:
#implementation LHJSonData
which gives me this warning:
/Users/jsuske/Documents/SSiPad(Device Only)ios7/SchedulingiPadApplication/Classes/LHJSonData.m:12:17: Method definition for 'UserLogin:andPassWordExists:' not found
and I have this method in LHJsonData.m
-(void)UserLogin:(NSString *)user andPassWordExists:(NSString *)password completionHandler:(void (^)(NSArray *resultsObject, NSError *error))completionHandler
{
NSURL *url = [NSURL URLWithString:kIP];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]
initWithRequest:request];
[operation setCredential:[NSURLCredential credentialWithUser:[#"domain" stringByAppendingString:user]
password:password persistence:NSURLCredentialPersistenceForSession]];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
[[NSOperationQueue mainQueue] addOperation:operation];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
if (completionHandler) {
completionHandler(responseObject, nil);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (completionHandler) {
completionHandler(nil, error);
}
}];
[operation start];
}
I get no errors or warnings with that code.
When I call this method in another file, lets call it Login.m:
- (void)Login
{
NSString *rawString = [self.idTextField text];
NSCharacterSet *whitespace = [NSCharacterSet whitespaceAndNewlineCharacterSet];
[self.idTextField setText:[rawString stringByTrimmingCharactersInSet:whitespace]];
//BOOL *isAuthenticated = [userName User:self.idTextField.text andPassWordExists:self.passwordTextField.text];
[userName UserLogin:self.idTextField.text andPassWordExists:self.passwordTextField.text:^(id responseObject, NSError *error) {
if (responseObject) {
[self CustomAlert:#"You have login"];
/*[self.idTextField removeFromSuperview];
[self.passwordTextField removeFromSuperview];
[self.loginButton removeFromSuperview];
self.idTextField = nil;
self.passwordTextField = nil;
self.loginButton = nil;
[self CreateMenu];*/
}else{
[self CustomAlert:#"Sorry Login Failed, User and/or Passsword Incorrect"];
}
}];
and I get this error:
ARC Semantic Issue
LHLoginController.m:240:15: No visible #interface for 'LHJSonData' declares the selector 'UserLogin:andPassWordExists::'
I went into Build Settings and add this to Login.m:
-fno-objc-arc
That got rid of the error, but now I get a warning and my app crashes, the warning is:
Semantic Issue
LHLoginController.m:240:15: Instance method '-UserLogin:andPassWordExists::' not found (return type defaults to 'id')
How can I fix this?
Declare your method in your .h file just like you do in your .m file.
-(void)UserLogin:(NSString *)user andPassWordExists:(NSString *)password completionHandler:(void (^)(NSArray *resultsObject, NSError *error))completionHandler;
The .m method and .h declaration do not match.
You should be seeing the warning/error in Xcode on this line:
[userName UserLogin:self.idTextField.text andPassWordExists:self.passwordTextField.text:^(id responseObject, NSError *error) {
You are missing a name for your third parameter, going straight from passwordTextField.text to :. The compiler is reading that as an unnamed parameter and translating it to the selector UserLogin:andPassWordExists::. Notice that it has two colons at the end rather than one. Since you don't ever declare the selector, the error/warning is raised.
The line should look like:
[userName UserLogin:self.idTextField.text andPassWordExists:self.passwordTextField.text completionHandler:^(id responseObject, NSError *error) {
As others mentioned your method signature is different in your header than your implementation. They need to be the same. It is likely you got into this situation because you autocompleted a method that didn't have a completion handler and tried to fix it.
Also, make sure to turn ARC back on for that file so you don't run into memory leaks. As you can see it wasn't really an ARC problem since both settings produced a similar warning/error. The reason ARC refused to compile is that when it encounters an undeclared selector (in this case UserLogin:andPassWordExists::), it doesn't know whether or not the returned value is an object or not and it can't make a memory management decision. Before ARC a developer could look up the undeclared method, see the return type and apply the correct action. ARC's just stricter.
I am trying to retrieve data using from a JSON request using AFJSONRequestOperation.
On success I am able to successfully retrieve the data but unable to complete the request and forward the data further for processing.
Following is my code
-(void) retrieveBrandList:(void (^)(NSArray *brandList))success failure:(void (^)(NSError *error))failure
{
//__block NSArray *brandList =[[NSArray alloc] init];
NSString *BrandListURL= http://127.0.0.1:8888/know/rest/brand
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSLog(#"Brand List URL = %#", BrandListURL);
AFJSONRequestOperation *operation =[AFJSONRequestOperation
JSONRequestOperationWithRequest: request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id responseObject)
{
NSLog(#"%#", responseObject);
brandList = [self successBandList:responseObject]; // parsing the JSON response in separate method (success block code)
if (success)
success(brandList);
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id responseObject)
{
message:[NSString stringWithFormat:#"%#",error];
if (failure)
failure(error);
}];
[operation start];
[operation waitUntilFinished];
}
Following is the data manager to retrieve data.
- (NSArray *)getBrandList
{
#try
{
[brand retrieveBrandList:^(NSArray *brandList)
{
brands = brandList;
}
failure:^(NSError *error) {
}];
NSLog(#"Retriving Brand list completed");
return brands;
}
#catch (NSException * e) {
NSLog(#"Exception: %# , Error while getting the brand list", e);
}
return NULL;
}
How do i complete the operation and use or store the results for further processing in some other method?
id jsonObject = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingAllowFragments error:&error];
if ([jsonObject isKindOfClass:[NSDictionary class]]) {
self.jsonDictionary = jsonObject;
}
and you can check for other option too but this worked for me