AFNetworking 2: How to cancel a AFHTTPRequestOperationManager request? - afnetworking-2

I migrated my networking functionality from AFNetworking to AFNetworking v2 and instead of AFHttpClient I am using AFHTTPRequestOperationManager to support iOS6 as well.
My issue is that while in AFHttpClient there was the functionality to cancel a pending request using the
- (void)cancelAllHTTPOperationsWithMethod:(NSString *)method path:(NSString *)path;
method, in the AFHTTPRequestOperationManager there is no such obvious method.
What I've done up to now is subclassing AFHTTPRequestOperationManager and declaring an iVar
AFHTTPRequestOperation *_currentRequest;
When I make a request the code is something like
- (void)GetSomething:(NSInteger)ID success:(void (^)(MyResponse *))success failure:(void (^)(NSError *))failure
{
_currentRequest = [self GET:#"api/something" parameters:#{#"ID": [NSNumber numberWithInteger:ID]} success:^(AFHTTPRequestOperation *operation, id responseObject) {
MyResponse *response = [MyResponse responseFromDictionary:responseObject];
success(response);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
failure(error);
}];
}
and I have a
- (void)cancelCurrentRequest;
methods which all that does is
- (void)cancelCurrentRequest
{
if(_currentRequest) {
[_currentRequest cancel]; _currentRequest = nil;
}
}
Now, I don't think this is good practice and when the method is called I get (NSURLErrorDomain error -999.) which is why I need some advice on getting this done correctly.
Thank you in advance.

Objective-C
[manager.operationQueue cancelAllOperations];
Swift
manager.operationQueue.cancelAllOperations()

You don't have to subclass AFHTTPRequestOperationManager , because when you send request, AFHTTPRequestOperation returns from the
- (AFHTTPRequestOperation *)GET:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
simply save it somewhere or make static and then perform cancel when the request need to be canceled.
Example:
- (void)sendRequestToDoSomething
{
static AFHTTPRequestOperation *operation;
if(operation) //cancel operation if it is running
[operation cancel];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
//configure manager here....
operation = [manager GET:urlString parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
//do something here with the response
operation = nil;
} failure:^(AFHTTPRequestOperation *op, NSError *error) {
{
//handle error
operation = nil;
}

Related

AFNetworking cancel post request

i know, this question ask before. But not working for me this scenario. i am working on AFNetworking for transfer layer. My code below.
AFHTTPRequestOperation *post;
AFHTTPRequestOperationManager *manager;
AFHTTPRequestOperationManager * manager = [AFHTTPRequestOperationManager manager];
NSMutableDictionary *paramDict = [[NSMutableDictionary alloc] init] ;
delegate = (id<ConnectionUtilDelegate>) delegateObject;
for (Parameter *param in parameterArray)
{
[paramDict setObject:param.value forKey:param.name];
}
NSDictionary *params = paramDict;
manager.responseSerializer = [AFJSONResponseSerializer serializer];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"text/html"];
[manager.operationQueue cancelAllOperations];
post = [manager POST:[NSString stringWithFormat:#"%#",url] parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
[delegate requestCompleted:responseObject];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[delegate requestFailed:error];
NSLog(#"Error: %#", error);
}];
this is my post request. it's work like charm. But sometimes some request could be late. For instance, i send 2 different post request this name P1(Post 1) and P2(Post 2). if these request response return as a R1(Response 1) and than R2( response 2) is ok for me. But if this request response return as a R2(Response 2) and than R1(Response 1) its a cause for my app. So i want to cancel old request when i send new request. and
[post cancel];
Error:
[AFHTTPRequestOperation cancel]: message sent to deallocated instance 0x1723cbf0
not working for me. Do you have any suggestion?
As I see in your code, you create a new AFHTTPRequestOperationManagerinstance for each request and then perform cancelAllOperations over the operationQueue of newly created AFHTTPRequestOperationManager. I suggest you creating a sharedManager of type AFHTTPRequestOperationManager and use this singleton object for all your requests:
+ (instancetype)sharedManager {
static AFHTTPRequestOperationManager *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [AFHTTPRequestOperationManager manager];
});
return instance;
}
Then, you can cancel all requests before performing a new request using:
+ (void)cancelAllRequests {
[[AFHTTPRequestOperationManager sharedManager].operationQueue cancelAllOperations];
}
When you call above method, all requests will fail and failureblock will be called. So you need to check if operation is cancelled in failure block. And here comes the final code:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager sharedManager];
NSMutableDictionary *paramDict = [[NSMutableDictionary alloc] init] ;
delegate = (id<ConnectionUtilDelegate>) delegateObject;
for (Parameter *param in parameterArray)
{
[paramDict setObject:param.value forKey:param.name];
}
NSDictionary *params = paramDict;
manager.responseSerializer = [AFJSONResponseSerializer serializer];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"text/html"];
AFHTTPRequestOperation *post = [manager POST:[NSString stringWithFormat:#"%#",url] parameters:params
success:^(AFHTTPRequestOperation *operation, id responseObject) {
[delegate requestCompleted:responseObject];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (!operation.cancelled) {
[delegate requestFailed:error];
}
}];
[post start];

AFNetworking 2.0 in AFHTTPRequestOperationManager how to pass HTTPBody tag

I am using AFNetworking 2.0, in this I have to pass json object in body part, How to pass HTTP Body in AFHTTPRequestOperationManager.
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSDictionary *parameters = #{#"foo": #"bar"};
NSURL *filePath = [NSURL fileURLWithPath:#"file://path/to/image.png"];
[manager POST:#"http://example.com/resources.json" parameters:parameters constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileURL:filePath name:#"image" error:nil];
} success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Success: %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
Thanks,
Chatting with you offline, it sounds like the main concern is how to send profile image, name, email address, userid, and password in single request. It might look like:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSURL *filePath = ...
[manager POST:#"http://example.com/registration.json" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileURL:filePath name:#"image" error:nil];
[formData appendPartWithFormData:[name dataUsingEncoding:NSUTF8StringEncoding] name:#"name"];
[formData appendPartWithFormData:[email dataUsingEncoding:NSUTF8StringEncoding] name:#"email"];
[formData appendPartWithFormData:[userid dataUsingEncoding:NSUTF8StringEncoding] name:#"userid"];
[formData appendPartWithFormData:[password dataUsingEncoding:NSUTF8StringEncoding] name:#"password"];
} success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Success: %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
No JSON is needed.
If you wanted to use JSON, you can do that, but you wouldn't use the constructingBodyWithBlock method (which creates a multipart/form-data request). Instead, you'd just create a plain old JSON request, e.g.:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
NSDictionary *parameters = #{
#"name" : name,
#"email" : email,
#"userid" : userid,
#"password" : password,
#"image" : base64EncodedImage
};
[manager POST:#"http://example.com/registration.json" parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Success: %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
Clearly, these are very different approaches, so whatever approach you adopt requires that the server code be written to handle the same API.

Mapping a primitive json response with RestKit v0.21

I have an API that requires I post a complex JSON object. The API saves and responds with a primitive ID (or an error object). I am not able to map the primitive ID. Any ideas? The example below, for simplicity's sake, is not doing the POST object mapping, but some of my API calls will require that as well.
I saw suggestions to utilize RestKit to build the request, and pass it to AFNetworking, but this will not parse a possible error return object.
RKObjectMapping* map = [RKObjectMapping mappingForClass:[MyPrimitiveResponse class]];
[map addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:nil toKeyPath:#"success"]];
RKResponseDescriptor *errDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:[MyErrorResponse objectMap] method:RKRequestMethodGET pathPattern:nil keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassServerError)];
RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:map method:RKRequestMethodGET pathPattern:nil keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)];
NSURL *URL = [NSURL URLWithString:apiUrl];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
RKObjectRequestOperation *objectRequestOperation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:#[ errDescriptor, responseDescriptor ]];
[objectRequestOperation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
RKLogInfo(#"Load collection of Articles: %#", mappingResult.array);
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
RKLogError(#"Operation failed with error: %#", error);
}];
[objectRequestOperation start];
I get the following error in the debugger:
2013-10-29 10:23:15.196 TestParser[6988:70b] E app:MyHomeViewController.m:54 Operation failed with error: Error Domain=org.restkit.RestKit.ErrorDomain Code=-1017 "Loaded an unprocessable response (200) with content type 'application/json'" UserInfo=0x8daca20 {NSErrorFailingURLKey=....., NSUnderlyingError=0x8db4cd0 "The operation couldn’t be completed. (Cocoa error 3840.)", NSLocalizedDescription=Loaded an unprocessable response (200) with content type 'application/json'}
UPDATE:
This is my final bits of code to handle this situation. It may prove useful to others...
// Manually Map Request to JSON & send to server
NSDictionary *parameters = [RKObjectParameterization parametersWithObject:payload requestDescriptor:[payload.class requestDescriptor] error:&error];
NSMutableURLRequest* request = [self.apiManager requestWithObject:nil method:RKRequestMethodPOST path:url parameters:parameters];
RKHTTPRequestOperation *requestOperation = [[RKHTTPRequestOperation alloc] initWithRequest:request];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
unichar firstChar = [operation.responseString characterAtIndex:0];
NSData *newData;
if (firstChar != '{' && firstChar != '[') {
// Force into JSON array so it can be parsed normally
newData = [[[#"[" stringByAppendingString:operation.responseString] stringByAppendingString:#"]"] dataUsingEncoding:NSUTF8StringEncoding];
} else {
newData = operation.responseData;
}
// Parse JSON response into native object, whether primitive NSNumber (integer or boolean) response or a full fledged JSON error object.
RKResponseDescriptor *errDescriptor = [MyErrorResponse responseDescriptor];
RKObjectResponseMapperOperation* mapper = [[RKObjectResponseMapperOperation alloc] initWithRequest:request response:operation.response data:newData responseDescriptors:#[errDescriptor, [payload.class responseDescriptor]]];
[mapper setDidFinishMappingBlock:^(RKMappingResult *mappingResult, NSError *error) {
if (mappingResult) { //Success
RKLogInfo(#"Load response: %#", mappingResult.firstObject);
} else {
RKLogError(#"Operation failed with error: %#", error);
}
}];
[mapper start];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
RKLogError(#"Operation failed with error: %#", error);
}];
[requestOperation start];
I saw suggestions to utilize RestKit to build the request, and pass it to AFNetworking
Yes, do that.
but this will not parse a possible error return object
True, but you can use a mapping operation in RestKit to process that. Or, if the error response is relatively simple just use NSJSONSerialization (or similar) to convert the response.
The 'primitive ID' should be mappable with a nil-keypath mapping. But you may still have issues in mapping an error response if it isn't being mapped back into the source object used in the POST request.

AFNetworking paramaters for Google API

I am trying to send a Google Places API request using AFNetworking.
I have the following code:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager setRequestSerializer:[AFJSONRequestSerializer serializer]];
[manager.requestSerializer setAuthorizationHeaderFieldWithUsername:OPIA_USERNAME password:OPIA_PASSWORD];
[manager.requestSerializer setValue:API_HEADER_RESPONSE_FORMAT forHTTPHeaderField:#"Accept"];
[manager setResponseSerializer:[AFJSONResponseSerializer serializer]];
parameters = #{#"key": GOOGLE_API_BROWSER_KEY, #"location": "-27.476383,153.014786", #"radius": [NSString stringWithFormat:#"%i", GOOGLE_PLACES_SEARCH_RADIUS], #"sensor": #"true", #"keyword": type, #"opennow": #"true", #"rankby": #"distance"};
requestURL = [NSString stringWithFormat:#"%#%#", GOOGLE_PLACES_API_URL, API_RESPONSE_FORMAT];
[manager GET:requestURL parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
It generates a URL like this:
https://maps.googleapis.com/maps/api/place/nearbysearch/json?key=XXXX&keyword=coffee&location=-27.476383%2C153.014786&opennow=true&radius=100&rankby=distance&sensor=true.
As you can see, the "," in the location parameter is being encoded by AFNetworking.
I am using AFNetworking 2.0.
I use ut this way like this:
NSString* urlString = [NSString stringWithFormat:GOOGLE_PLACES_API_JSON, latitude,longitude, radius, types];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:urlString
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
//do stuff
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//show error
}];
I had to move away from the "parameters" approach, and instead manipulate the URL string.

AFNetworking + download file OR JSON response + setDownloadProgressBlock

I have a problem to download a file or manage an error in some cases (JSON)
The download works very fine. The problem is the server can send a 404 or 200 (with JSON) in a few cases when the user requests the download.
How to handle the JSON in this case? When we send the request we don't know if we will receive JSON error (with a 200 status oR 404) or the zipped file...
I don't see how responseObject can help me.
Here is my code:
AFHTTPRequestOperation *operation = [[[AFHTTPRequestOperation alloc] initWithRequest:request] autorelease];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:zipPath append:NO];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(#"Successfully downloaded zip file to %#", zipPath);
// is-it impossible to handle a JSON response here ?
// responseObject can help me ? don't think !
// do what I have to do after the download is complete
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
// 404
// is-it impossible to handle a JSON response here ?
if ([error code] == -1011)
{
}
}];
[operation setDownloadProgressBlock:^(NSInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
float progress = ((float)((int)totalBytesWritten) / (float)((int)totalBytesExpectedToWrite));
self.progressView.progress = progress;
}];
[operation start];
Does I need to make a AFJSOnRequestOperation ? But in this case, how to receive the downloaded file that is not JSON ?
Thanks for helping.
EDIT: as I wroted in my comments, I can get and catch the responseData in success block ONLY if I comment the line:
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:zipPath append:NO];
It's logical, I suppose the response goes into outputStream instead of success block. Is there a solution for that ?
The server should signal the MIME type in the header Content-Type.
If it doesn't you need to detect the data type yourself.
This line ensures to get all responses with statuscode 200-499 in the success block:
[AFHTTPRequestOperation addAcceptableStatusCodes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(300, 200)]];
In the success block you need to identify the type of response:
id jsonObject = AFJSONDecode(operation.responseData, NULL);
if (jsonObject) {
// handle JSON
} else {
// handle zip file
}
You can also inspect operation.response.statusCode and operation.response.MIMEType beforehand.
I've tried with AFJSOnRequestOperation, in .m I simply put the outputStream beneath the init:
AFJSONRequestOperation *requestOperation = [[[self alloc] initWithRequest:urlRequest] autorelease];
requestOperation.outputStream = [NSOutputStream outputStreamToFileAtPath:[FullyLoaded filePathForResourceAtURL:urlRequest.URL.absoluteString] append:NO];
And the same happens, responseData is nil unless commenting the outputStream line, so it's not HTTPRequest or JSOnRequest issue. The way I use to get around is to JSON sequence the object from the responseData and write it to file, rather than directly outputstream the request.
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFCompoundResponseSerializer serializer];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"application/octet-stream"];
AFHTTPRequestOperation *operation = [manager GET:url parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (responseObject) {
}
else{
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
[operation start];
// manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"application/octet-stream"]; : can vary based on what you expect