How do I make AFHTTP request wait for operation - afnetworking

At this moment I have a method that calls for the download of data from the web using AFHTTPRequestOperation like so:
- (void)downloadDataForRegisteredObjects:(BOOL)useUpdatedAtDate {
NSLog(#"downloadDataForRegisteredObjects");
NSMutableArray *operations = [NSMutableArray array];
for (NSString *className in self.registeredClassesToSync) {
NSDate *mostRecentUpdatedDate = nil;
if (useUpdatedAtDate) {
mostRecentUpdatedDate = [self mostRecentUpdatedAtDateForEntityWithName:className];
}
NSMutableURLRequest *request = [[SDAFParseAPIClient sharedClient] GETRequestForAllRecordsOfClass:className updatedAfterDate:mostRecentUpdatedDate];
AFHTTPRequestOperation *operation = [[SDAFParseAPIClient sharedClient] HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) {
if ([responseObject isKindOfClass:[NSDictionary class]]) {
// Write JSON files to disk
[self writeJSONResponse:responseObject toDiskForClassWithName:className];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Request for class %# failed with error: %#", className, error);
[[NSNotificationCenter defaultCenter]
postNotificationName:kSDSyncEngineSyncINCompleteNotificationName
object:nil];
}];
[operations addObject:operation];
}
[[SDAFParseAPIClient sharedClient] enqueueBatchOfHTTPRequestOperations:operations progressBlock:^(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations) {
} completionBlock:^(NSArray *operations) {
// Process JSON into CD
if (useUpdatedAtDate) {
[self processJSONDataRecordsIntoCoreData];
}
}];
}
From what I understand, we create an NSURLMutableRequest, pass it to an AFHTTPRequestOperation with a success & failure block.
The success block says, if and when successful, test if dictionary and if so, write it to disk. The failure block says, log the error and post a notification.
The method gets called twice in my app, in series, one after the other. The first time it returns an empty responseObject but the second time it returns a full responseObject.
Why should that be the case?

Related

JSON Request using AFNetworking 2 giving me a NSURLErrorDomain error

I am making a simple GET request using AFNetworking 2, but I am getting a NSURLErrorDomain error.
I created a manager class which subclasses AFHTTPRequestOperationManager and creates a singleton instance so that I can use a shared manager.
+ (id)manager {
static dispatch_once_t pred = 0;
__strong static id _sharedObject = nil;
dispatch_once(&pred, ^{
_sharedObject = [[self alloc] init];
});
return _sharedObject;
}
- (id)init {
NSURL *baseURL = [ZSSAuthentication baseURL];
self = [super initWithBaseURL:baseURL];
if (self) {
[self setRequestSerializer:[AFJSONRequestSerializer serializer]];
[self setResponseSerializer:[AFJSONResponseSerializer serializer]];
[self.requestSerializer setAuthorizationHeaderFieldWithUsername:[ZSSAuthentication username] password:[ZSSAuthentication password]];
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
}
return self;
}
- (void)getData:(NSString *)pubID parameters:(NSDictionary *)parameters completion:(void (^)(NSDictionary *results))completion failure:(void (^)(NSError *error))failure {
NSString *url = [NSString stringWithFormat:#"data/all/%#", pubID];
[self GET:url parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
// Check to see if there are errors
ZSSError *error = [self errorForAPICall:responseObject status:[operation.response statusCode]];
if (error) {
[self logMessage:error.localizedDescription];
failure(error);
return;
}
NSDictionary *data = [responseObject objectForKey:#"data"];
completion(data);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
failure(error);
}];
}
Then, in my viewController's viewDidLoad method I make a call to that method:
[[ZSSManager manager] getData:self.pubID parameters:nil completion:^(NSDictionary *results) {
self.items = results;
[self dataWillReload];
NSLog(#"%#", results);
[self.tableView reloadData];
} failure:^(NSError *error) {
NSLog(#"Error: %# %li", error, (long)error.code);
}];
Then I get this error:
Error Domain=NSURLErrorDomain Code=-999 "The operation couldn’t be completed. (NSURLErrorDomain error -999.)" UserInfo=0x7ff952306610 {NSErrorFailingURLKey=http://test.mysite.com/v1/data/all/5}
The strange thing is, on a previous viewController, I make a different call to the manager, and it completes and returns data correctly. But, when I make this second call, I get the error. AND, if I move that getData call out of the viewDidLoad method, and invoke it with a button press, it DOES WORK. What the heck?
What could be causing this?

AFNetworking completion block wait until done

I'm using AFNetworking for POST requests. I need to wait until the completion block is done to return data, and I've run into problems.
I had a solution that was working until I switched to AFNetworking:
int i = 0;
while (!done)
{
[NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
i++;
NSLog(#"While Loop:%i", i);
if (i == 149) //request timed out
done = YES;
}
Now, the solution works sporadically. Sometimes it completes with NSLog(#"While Loop:%i", i); only logging 1 or 2, but sometimes it logs until 149 and times out. It seems like the NSRunLoop sometimes runs in a different thread, but sometimes runs on the same thread blocking my request.
Here's the code that I currently have:
- (id)postRequestWithBaseURLString:(NSString *)baseURLString function:(NSString *)function parameters:(NSDictionary *)parameters
{
if (baseURLString)
function = [baseURLString stringByAppendingString:function];
__block BOOL done = NO;
__block id returnObject = nil;
[self.manager POST:function parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(#"Success: %#", responseObject);
done = YES;
returnObject = responseObject;
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"Error: %#", error);
done = YES;
}];
[manager.operationQueue waitUntilAllOperationsAreFinished];
int i = 0;
while (!done)
{
[NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
i++;
NSLog(#"While Loop:%i", i);
if (i == 149) //request timed out
done = YES;
}
return returnObject;
}
I've tried doing a dispatch_group without success. Help?
EDIT
Ok, I've got more info. My solution works when I first call the API (for example, when I first call a ViewController), but not afterwards. Perhaps, after the view is loaded, the while loop is called on the same thread as the API call, therefore blocking it?
This also seems likely, since the NSLog(#"Success: %#", responseObject); is called almost exactly after timeout happens.
Found an elegant solution online. I created my own completion block. Here's what I did:
+ (void)jsonRequestWithBaseURL:(NSString *)baseURL function:(NSString *)function parameters:(NSDictionary *)parameters completion:(void (^)(NSDictionary *json, BOOL success))completion
{
if (baseURL)
function = [baseURL stringByAppendingString:function];
NSLog(#"%# function:%#, parameters:%#", self.class, function, parameters);
[AFHTTPRequestOperationManager.manager POST:function parameters:parameters success:^(AFHTTPRequestOperation *operation, id jsonObject)
{
//NSLog(#"Success: %#", jsonObject);
NSDictionary *jsonDictionary = (NSDictionary *)jsonObject;
if (completion)
completion(jsonDictionary, YES);
} failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"%# AFError: %#", self.class, [error localizedDescription]);
completion(nil, NO);
}];
}
Then, when I need to call the method, I do this:
NSDictionary *parameters = #{#"email":#"me#example.com", #"password":#"mypassword"};
[ZAPRootViewController jsonRequestWithBaseURL:#"http://www.myserver.com/" function:#"login.php" parameters:parameters completion:^(NSDictionary *json, BOOL success)
{
if (success)
{
//do something
}
}];

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

Restkit RKObjectManager requests timeout when downloading a lot of files

First off: I'm quite new to RestKit so perhaps this is an easy question to solve.
I am trying to download a lot of files. Currently I use the getObjectsAtPath:parameters:success:failure: method of RKObjectManager to fetch and map my objects towards restkit.
However, It seems as it starts some downloads prematurely and then timesout when they are in the queue.
The code I am using:
- (void)removeResponseAndRequestDescriptors
{
RKObjectManager *objectManager = [RKObjectManager sharedManager];
[objectManager.requestDescriptors enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[objectManager removeRequestDescriptor:obj];
}];
[objectManager.responseDescriptors enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[objectManager removeResponseDescriptor:obj];
}];
}
.
- (void)downloadAudioFileForAudio:(IBAudio *)audio
inBook:(IBBook *)book
downloadStatus:(void (^)(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))downloadStatus
success:(void (^)(void))success
failure:(void (^)(NSError *error))failure
{
RKObjectManager *objectManager = [RKObjectManager sharedManager];
NSString *sessionID = (book.parent ? book.parent.user.session.sessionID : book.user.session.sessionID);
[objectManager.HTTPClient setDefaultHeader:#"Accept" value:#"application/octet-stream"];
[objectManager.HTTPClient setDefaultHeader:#"Session-Id" value:sessionID];
[objectManager.HTTPClient getPath:[IBProperties downloadAudioEndPointWithIsbn:book.isbn andAnchor:audio.anchor] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSString *folderPath = [self folderPathForBook:book];
NSString *audioPath = [folderPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.mp3", audio.anchor]];
NSData *audioData = [NSData dataWithData:responseObject];
NSError *fileSystemSaveError;
[self saveFile:audioData toFilePath:audioPath error:&fileSystemSaveError];
if (fileSystemSaveError) {
failure(fileSystemSaveError);
return;
}
// Saving the context asap in case the app dies before it can autosave.
NSError *coreDataSaveerror;
[[[[RKObjectManager sharedManager] managedObjectStore] mainQueueManagedObjectContext] save:&coreDataSaveerror];
if (coreDataSaveerror) {
failure(coreDataSaveerror);
return;
}
[audio setFilePath:audioPath];
success();
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
failure(error);
}];
[objectManager.HTTPClient.operationQueue.operations.lastObject setDownloadProgressBlock:downloadStatus];
}
.
- (void)downloadAudioFiles
{
for (IBAudio *audio in self.book.bookData.audios) {
self.numberOfDownloads++;
[self.downloadPercentagesFiles addObject:[[IBDownloadStatusOfAFile alloc] init]];
NSInteger arrayIndex = [self.downloadPercentagesFiles count] - 1;
[[IBDataBackendFetcher sharedBackendFetcher] downloadAudioFileForAudio:audio inBook:self.book downloadStatus:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
IBDownloadStatusOfAFile *statusOfFile = [self.downloadPercentagesFiles objectAtIndex:arrayIndex];
statusOfFile.bytesRead = bytesRead;
statusOfFile.totalBytesRead = totalBytesRead;
statusOfFile.totalBytesExpectedToRead = totalBytesExpectedToRead;
[self updateDownloadProgress];
} success:^{
[self downloadDidComplete];
} failure:^(NSError *error) {
[self.dataProviderDelegate downloadDidFailWithError:error forBookDownloader:self];
}];
}
}
It seems as it starts all downloads at once, but the actuall download is not started. So the last downloads gets a timeout.
Is there a better way for do this to solve this problem?
All the downloads will run simultaneously, because you're making all the getPath: calls right in a row (they are asynchronous calls). Since each download takes a while to finish, this causes the timeout on the later calls.
If you want each download to occur only after the previous one completes, I would make a method called getNextAudioFile: and an iterator class property. Then, in both the success and failure blocks of getPath:, increment your iterator and call getNextAudioFile:.
Example code:
- (void)downloadAudioFiles
{
// No for loop
self.iterator = 0;
// your call to DownloadAudioFileForAudio: ... for the first audio goes here
}
- (void)downloadAudioFileForAudio:(IBAudio *)audio
inBook:(IBBook *)book
downloadStatus:(void (^)(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))downloadStatus
success:(void (^)(void))success
failure:(void (^)(NSError *error))failure
{
// your code ...
[objectManager.HTTPClient getPath:[IBProperties downloadAudioEndPointWithIsbn:book.isbn andAnchor:audio.anchor] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject){
// your code...
// increment and get the next file
self.iterator++;
[self getNextAudioFile];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
failure(error);
// increment and get the next file
self.iterator++;
[self getNextAudioFile];
}];
}
- (void)getNextAudioFile
{
if(self.iterator < [self.book.bookData.audios count]){
// make your downloadAudioFileForAudio: call for the next audio
}
}
That's the idea at least! Hope it helped.
Set the maximum number of concurrent operation on the queue to some reasonable value like 5.
[objectManager.HTTPClient.operationQueue setMaxConcurrentOperationCount:5];
(before you start any requests)

AFNetworking retry timeout requests using enqueueBatchOfHTTPRequestOperations

There are few similar questions to mine with answers,but could not find a way to use that solutions on my code. I would be glad, if anyone can help me on code. How to retry timeout request?
while block for pagination purpose:
while(mult < (int)totalCount) {
AFHTTPRequestOperation *opr = [self getRequestForAllRecordsOfClass:className updatedAfterDate:mostRecentUpdatedDate withinPage:page+1];
[pagedOperations addObject:opr];
mult = mult + PAGINATION_SIZE;
page = page + 1;
}
[[SDAFParseAPIClient sharedClient] enqueueBatchOfHTTPRequestOperations:operations progressBlock:^(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations) {
NSLog(#"totalNumberOfOperations: %u numberOfCompletedOperations: %u",totalNumberOfOperations,numberOfCompletedOperations);
} completionBlock:^(NSArray *operations) {
...
}];
and building request operation
-(AFHTTPRequestOperation *)getRequestForAllRecordsOfClass:(NSString *)className updatedAfterDate:(NSDate *)mostRecentDate withinPage:(int)page {
NSMutableURLRequest *request = [ZurmoHelper GETRequestForAllRecordsOfClass:className updatedAfterDate:mostRecentDate inPage:page];
AFHTTPRequestOperation *operation = [[SDAFParseAPIClient sharedClient] HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"RESPONSE pagination!!! %#:", className);
NSError *error = nil; //error in parsing json
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:responseObject options:kNilOptions error:&error];
if (!error) {
NSLog(#"json dict paged in : %d",page );
id dataObject = [jsonDict objectForKey:#"data"];
NSArray *itemsObject = [dataObject objectForKey:#"items"];
[self addItems:itemsObject className:className];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Request for class %# failed with error: %#", className, error);
if (error.code == -1001) {
NSLog(#"for class %# page: %d,",className,page);
//need to retry this operation???
}
}];
return operation;
}

Resources