I have a large NSArray I am wanting to split into chunks and send to my web server, upon completion of each chunk I then need to update the fields in my SQLite DB that relate to each item in each array chunk.
This is the code I am currently running, where I try to use a call back to receive success or failure then update my local SQLite DB where appropriate.
- (void)postlowData:(NSArray *)lowMArray Callback:(void (^)(NSError *error, BOOL success))callback;
{
// Currently this method is sending the whole lowMArray
// What I want to do is Split lowMArray into a chunkArray (where chunk is 20 of the leading items from lowMArray)
// I would then send chunkArray with the following code, when I receive a response I then want to update local SQLite DB with result and recall this method to start on the next 20 chunks.
// Create Json data from lowMArray
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:lowMArray
options:NSJSONWritingPrettyPrinted
error:nil];
// Construct post request
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#/lows", _silServerBaseUrl]]];
request = [self applyAuth:request];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json; charset=UTF-8" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:jsonData];
// Send post request
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
// NSLog(#"Response Failed!");
callback(error, NO);
} else {
// NSLog(#"Response Success!");
callback(error, YES);
// On success add itmes from lowChunkArray so that you can adjust sent_Flag later
}
}];
[dataTask resume]; // runs task
}
The issue I am running into is that when I run this code if I am splitting the array into chunks sending the chunk adjusting the main array for the next chunk I don't get a confirmed callback till the very end of all the requests, at which point I have lost track of what success or failure?
Maybe I am going about this the wrong way?
Update
I am now trying to do this using AFHTTPRequestOperation which seems to be working as a batch upload however the
setHTTPBody:jsonData
Never seems to make it to the server.
I used this Batch of Operations example to help me construct this method however as I said above the JSON data never makes it to the server.
- (void)postlowData:(NSArray *)lowMArray;
{
NSLog(#"Syncing Local");
NSArray *chunklow = [[NSArray alloc] init];
NSMutableArray *mutableOperations = [NSMutableArray array];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"%#/lows", _silServerBaseUrl]];
//Test: creating 10 things to send
for (int i = 0; i < 10; i++) {
if ([lowMArray count] > 0) {
if ([lowMArray count] >= 20) {
low = [lowMArray subarrayWithRange:NSMakeRange(0, 20)];
} else if ([lowMArray count] < 20) {
low = [lowMArray subarrayWithRange:NSMakeRange(0, [lowMArray count])];
}
}
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:low
options:NSJSONWritingPrettyPrinted
error:nil];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request = [self applyAuth:request];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json; charset=UTF-8" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:jsonData];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[mutableOperations addObject:operation];
}
NSArray *operations = [AFURLConnectionOperation batchOfRequestOperations:mutableOperations progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
NSLog(#"%lu of %lu complete", numberOfFinishedOperations, totalNumberOfOperations);
} completionBlock:^(NSArray *operations) {
NSLog(#"All operations in batch complete");
NSLog(#"Syncing complete");
}];
[[NSOperationQueue mainQueue] addOperations:operations waitUntilFinished:NO];
}
For your problem of splitting insertion of a large array into chunks to be inserted to a DB via network operations, an NSOperationQueue can be created that will allow you to add a separate operation for each chunk of data to be inserted.
The queue can be set to run in a serial manner so that each operation will need to be complete before the next one is started.
Using a queue makes the multiple operations more manageable than having the flow be controlled by callbacks.
In summary, you create a queue and set its maximum concurrent operation count to 1. Then create an NSOperation subclass that performs the necessary steps to insert data into the database. Each chunk of data will correspond to a separate operation that will be added to the queue. Each operation will be performed in series until all are complete.
Here is an outline for the solution:
// Create a new queue to hold network operations.
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
// Split the large array into chunks of 20 items each.
NSInteger chunkSize = 20;
NSInteger i = 0;
NSInteger total = [lowMArray count];
while (i < total) {
NSInteger j = i;
NSMutableArray *chunk = [NSMutableArray alloc] init];
while (j < i + chunkSize - 1 && j < total) {
[chunk addObject:lowMArray[j]];
j++;
}
MyOperation *myOperation = [[MyOperation alloc] initWithArray:chunk];
self.operationQueue.addOperation(myOperation)
i += chunkSize;
}
MyOperation.h:
#interface MyOperation : NSOperation
- (instancetype)initWithArray:(NSArray *)chunk;
#property NSArray *chunk;
#end
MyOperation.m:
#implementation MyOperation
- (instancetype)initWithArray:(NSArray *)chunk
{
if (self = [super init]) {
self.chunk = chunk;
}
return self;
}
- (void)main
{
// Create Json data from lowMArray
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:self.chunk
options:NSJSONWritingPrettyPrinted
error:nil];
// Construct post request
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#/lows", _silServerBaseUrl]]];
request = [self applyAuth:request];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json; charset=UTF-8" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:jsonData];
// Send post request
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
// NSLog(#"Response Failed!");
} else {
// NSLog(#"Response Success!");
}
}];
[dataTask resume]; // runs task
}
#end
AFNetworking has support for its own NSOperation subclass in AFHTTPRequestOperation. An example can be found here. Also, the AFNetworking GitHub repository has an example for batch operations.
Based on your revised question, setting the completion block of each AFHTTPRequestOperation to handle the response and error can help to debug the problem.
Here is how it is done:
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation * _Nonnull operation, id _Nonnull responseObject) {
NSString* decodedResponse = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
NSLog(#"response %#", decodedResponse);
} failure:^(AFHTTPRequestOperation * _Nonnull operation, NSError * _Nonnull error) {
NSLog(#"error %#", error);
}];
It would be inserted after AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];.
Related
I have an app which downloads a set of photos from a server. I am using an Asynchronous request because I don't want the UI to be blocked. However, I am finding that the request is very slow and takes ages to load.
I know you can set the queue type to [NSOperationQueue mainQueue] but that just puts the Asynchronous request back on the main thread which defeats the whole point of making the request Asynchronously in the first place.
Is there anyway to speed up the request or to tell iOS: "Run this request in the background, but do it ASAP, don't leave it till the end of the queue"???
Here is my code:
// Set up the photo request.
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:PHOTO_URL, pass_venue_ID, PHOTO_CLIENT_ID, PHOTO_CLIENT_SECRET]];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// Begin the asynchromous image loading.
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (error == nil) {
// Convert the response data to JSON.
NSError *my_error = nil;
NSDictionary *feed = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&my_error];
// Check to see if any images exist
// for this particular place.
int images_check = [[NSString stringWithFormat:#"%#", [[[feed objectForKey:#"response"] valueForKey:#"photos"] valueForKey:#"count"]] intValue];
if (images_check > 0) {
// Download all the image link properties.
images_prefix = [[[[feed objectForKey:#"response"] valueForKey:#"photos"] valueForKey:#"items"] valueForKey:#"prefix"];
images_suffix = [[[[feed objectForKey:#"response"] valueForKey:#"photos"] valueForKey:#"items"] valueForKey:#"suffix"];
images_width = [[[[feed objectForKey:#"response"] valueForKey:#"photos"] valueForKey:#"items"] valueForKey:#"width"];
images_height = [[[[feed objectForKey:#"response"] valueForKey:#"photos"] valueForKey:#"items"] valueForKey:#"height"];
// Set the image number label.
number_label.text = [NSString stringWithFormat:#"1/%lu", (unsigned long)[images_prefix count]];
// Download up to 5 images.
images_downloaded = [[NSMutableArray alloc] init];
// Set the download limit.
loop_max = 0;
if ([images_prefix count] > 5) {
loop_max = 5;
}
else {
loop_max = [images_prefix count];
}
for (NSUInteger loop = 0; loop < loop_max; loop++) {
// Create the image URL.
NSString *image_URL = [NSString stringWithFormat:#"%#%#x%#%#", images_prefix[loop], images_width[loop], images_height[loop], images_suffix[loop]];
// Download the image file.
NSData *image_data = [NSData dataWithContentsOfURL:[NSURL URLWithString:image_URL]];
// Store the image data in the array.
[images_downloaded addObject:image_data];
}
// Load the first image.
[self load_image:image_num];
}
else if (images_check <= 0) {
// error...
}
}
else {
// error
}
}];
Thanks for your time, Dan.
i think your problem isnt the request running slow, its that you are updating UI elements not on the main thread, surround any UI updates (like setting the text on labels) with
dispatch_sync(dispatch_get_main_queue(), ^{
<#code#>
});
As Fonix said its not iOS that responding slow but dataWithContentsOfURL doesn't work in background thread. Apple's recommendation is that you should use NSURLConnection asynchronously with delegates
- didReceiveResponse
- didReceiveData
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:theURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:_mAuthenticationTimeoutInterval];
In these methods you can make use of chunks of data as well.
If you actually want these multiple downloads to be faster you should use parallel downloading using NSOperationQueue. You can refer enter link description here
I think a good solution could be using AFNetworking when combined with NSOperation, check this code I wrote to do more than one operation asynchronously
NSMutableArray *operations = [[NSMutableArray alloc] init];
for(NSObject *obj in caches) {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:url];
//...set up your mutable request options here
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
operation.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"application/json"];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSInteger statusCode = operation.response.statusCode;
if(statusCode==200) {
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"API Call error:%#", error.localizedDescription);
}];
[[requestManager operationQueue] addOperation:operation];
[operations addObject:operation];
if([operations count] >= MAX_API_CALL) break;
}
[AFHTTPRequestOperation batchOfRequestOperations:operations progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
} completionBlock:^(NSArray *operations) {
NSError *error;
for (AFHTTPRequestOperation *op in operations) {
if (op.isCancelled){
}
if (op.responseObject){
// process your responce here
}
if (op.error){
error = op.error;
}
}
}];
I have a NSURLConnection (two of them), and they're running in the wrong order.
Here's my method:
- (void)loginToMistarWithPin:(NSString *)pin password:(NSString *)password {
NSURL *url = [NSURL URLWithString:#"https://mistar.oakland.k12.mi.us/novi/StudentPortal/Home/Login"];
//Create and send request
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setHTTPMethod:#"POST"];
NSString *postString = [NSString stringWithFormat:#"Pin=%#&Password=%#",
[self percentEscapeString:pin],
[self percentEscapeString:password]];
NSData * postBody = [postString dataUsingEncoding:NSUTF8StringEncoding];
[request setHTTPBody:postBody];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
// do whatever with the data...and errors
if ([data length] > 0 && error == nil) {
NSError *parseError;
NSDictionary *responseJSON = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
if (responseJSON) {
// the response was JSON and we successfully decoded it
NSLog(#"Response was = %#", responseJSON);
} else {
// the response was not JSON, so let's see what it was so we can diagnose the issue
NSString *loggedInPage = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"Response was not JSON (from login), it was = %#", loggedInPage);
}
}
else {
NSLog(#"error: %#", error);
}
}];
//Now redirect to assignments page
NSURL *homeURL = [NSURL URLWithString:#"https://mistar.oakland.k12.mi.us/novi/StudentPortal/Home/PortalMainPage"];
NSMutableURLRequest *requestHome = [[NSMutableURLRequest alloc] initWithURL:homeURL];
[request setHTTPMethod:#"POST"];
[NSURLConnection sendAsynchronousRequest:requestHome queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *homeResponse, NSData *homeData, NSError *homeError)
{
// do whatever with the data...and errors
if ([homeData length] > 0 && homeError == nil) {
NSError *parseError;
NSDictionary *responseJSON = [NSJSONSerialization JSONObjectWithData:homeData options:0 error:&parseError];
if (responseJSON) {
// the response was JSON and we successfully decoded it
NSLog(#"Response was = %#", responseJSON);
} else {
// the response was not JSON, so let's see what it was so we can diagnose the issue
NSString *homePage = [[NSString alloc] initWithData:homeData encoding:NSUTF8StringEncoding];
NSLog(#"Response was not JSON (from home), it was = %#", homePage);
}
}
else {
NSLog(#"error: %#", homeError);
}
}];
}
- (NSString *)percentEscapeString:(NSString *)string
{
NSString *result = CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
(CFStringRef)string,
(CFStringRef)#" ",
(CFStringRef)#":/?#!$&'()*+,;=",
kCFStringEncodingUTF8));
return [result stringByReplacingOccurrencesOfString:#" " withString:#"+"];
}
So, it's two NSURLConnection's that are added to the [NSOperationQueue mainQueue]. What my output is showing me is that the second NSURLConnection is running before the first one. So it tries to go to the page where I download data before I'm logged in, so it (obviously) returns a "You're not logged in" error.
How do I schedule them one after another?
The issue, as I suspect you have realized, is that you're doing asynchronous network requests (which is good; you don't want to block the main queue), so there's no assurance of the order they'll finish.
The quickest and easiest answer is to simply put the call to the second request inside the completion block of the first one, not after it. You don't want to be making that second one unless the first one succeeded anyway.
To keep your code from getting unwieldy, separate the login from the request for main page. And you can use the completion block pattern which is common with asynchronous methods. You add a parameter to loginToMistarWithPin that specifies what it should do when the request finishes. You might have one completion block handler for success, and one for failure:
- (void)loginToMistarWithPin:(NSString *)pin password:(NSString *)password success:(void (^)(void))successHandler failure:(void (^)(void))failureHandler {
NSURL *url = [NSURL URLWithString:#"https://mistar.oakland.k12.mi.us/novi/StudentPortal/Home/Login"];
//Create and send request
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setHTTPMethod:#"POST"];
NSString *postString = [NSString stringWithFormat:#"Pin=%#&Password=%#",
[self percentEscapeString:pin],
[self percentEscapeString:password]];
NSData * postBody = [postString dataUsingEncoding:NSUTF8StringEncoding];
[request setHTTPBody:postBody];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
// do whatever with the data...and errors
if ([data length] > 0 && error == nil) {
NSError *parseError;
NSDictionary *responseJSON = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
if (responseJSON) {
// the response was JSON and we successfully decoded it
NSLog(#"Response was = %#", responseJSON);
// assuming you validated that everything was successful, call the success block
if (successHandler)
successHandler();
} else {
// the response was not JSON, so let's see what it was so we can diagnose the issue
NSString *loggedInPage = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"Response was not JSON (from login), it was = %#", loggedInPage);
if (failureHandler)
failureHandler();
}
}
else {
NSLog(#"error: %#", error);
if (failureHandler)
failureHandler();
}
}];
}
- (void)requestMainPage {
//Now redirect to assignments page
NSURL *homeURL = [NSURL URLWithString:#"https://mistar.oakland.k12.mi.us/novi/StudentPortal/Home/PortalMainPage"];
NSMutableURLRequest *requestHome = [[NSMutableURLRequest alloc] initWithURL:homeURL];
[requestHome setHTTPMethod:#"GET"]; // this looks like GET request, not POST
[NSURLConnection sendAsynchronousRequest:requestHome queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *homeResponse, NSData *homeData, NSError *homeError)
{
// do whatever with the data...and errors
if ([homeData length] > 0 && homeError == nil) {
NSError *parseError;
NSDictionary *responseJSON = [NSJSONSerialization JSONObjectWithData:homeData options:0 error:&parseError];
if (responseJSON) {
// the response was JSON and we successfully decoded it
NSLog(#"Response was = %#", responseJSON);
} else {
// the response was not JSON, so let's see what it was so we can diagnose the issue
NSString *homePage = [[NSString alloc] initWithData:homeData encoding:NSUTF8StringEncoding];
NSLog(#"Response was not JSON (from home), it was = %#", homePage);
}
}
else {
NSLog(#"error: %#", homeError);
}
}];
}
Then, when you want to login, you can do something like:
[self loginToMistarWithPin:#"1234" password:#"pass" success:^{
[self requestMainPage];
} failure:^{
NSLog(#"login failed");
}];
Now, change those successHandler and failureHandler block parameters to include whatever data you need to pass back, but hopefully it illustrates the idea. Keep your methods short and tight, and use completion block parameters to specify what an asynchronous method should do when it's done.
Can you check the below link. It is about forcing one operation to wait for another.
NSOperation - Forcing an operation to wait others dynamically
Hope this helps.
I get an self.usersArray with 2 elements in the format:
(
{
userCreated = "2012-01-05 12:27:22";
username = Simulator;
},
{
userCreated = "2013-01-01 14:27:22";
username = "joey ";
}
)
This is gotten in a completion block after which I call another method to fetch points for these 2 users through a helper class:
-(void)getPoints{
self.usersPointsArray = [[NSMutableArray alloc] init];
for (NSDictionary *usersDictionary in self.usersArray) {
[SantiappsHelper fetchPointsForUser:[usersDictionary objectForKey:#"username"] WithCompletionHandler:^(NSArray *points){
if ([points count] > 0) {
[self.usersPointsArray addObject:[points objectAtIndex:0]];
}
NSLog(#"self.usersPointsArray %#", self.usersPointsArray);
}];
}
}
The final self.usersPointsArray log looks like:
(
{
PUNTOS = 5;
username = Simulator;
},
{
PUNTOS = 2;
username = joey;
}
)
But the problem is that the way the call for points is structured, the self.usersPointsArray is returned twice, each time with an additional object, due to the for loop, I know.
Here is the Helper class method:
+(void)fetchPointsForUser:(NSString*)usuario WithCompletionHandler:(Handler2)handler{
NSURL *url = [NSURL URLWithString:#"http://myserver.com/myapp/readpoints.php"];
NSDictionary *postDict = [NSDictionary dictionaryWithObjectsAndKeys:usuario, #"userNa", nil];
NSData *postData = [self encodeDictionary:postDict];
// Create the request
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"POST"];
[request setValue:[NSString stringWithFormat:#"%d", postData.length] forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/x-www-form-urlencoded charset=utf-8" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:postData];
__block NSArray *pointsArray = [[NSArray alloc] init];
dispatch_async(dispatch_get_main_queue(), ^{
// Peform the request
NSURLResponse *response;
NSError *error = nil;
NSData *receivedData = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
if (error) {
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
NSLog(#"HTTP Error: %d %#", httpResponse.statusCode, error);
return;
}
return;
}
NSString *responseString = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
pointsArray = [NSJSONSerialization JSONObjectWithData:[responseString dataUsingEncoding:NSASCIIStringEncoding] options:0 error:nil];
if (handler)
handler(pointsArray);
});
}
I cannot use the self.usersPointsArray with the initial objects, only with the finalized object. It wont always be 2 elements, i actually dont know how many it will be.
What would be the way to structure it so I get a final call when the self.usersPointsArray is complete and then I reload my tableview?
I think of your problem as a standard consumer-producer problem. You can create a queue count for the amount of items that will be processed (int totalToProcess=self.usersArray.count). Each time the completion handler is hit, it will do totalToProcess--. When totalToProcess reaches 0 you have processed all of the elements in your queue and can refresh your table.
If I understand your question correctly I believe this solves your problem. If not, hopefully I can with a bit more information.
EDIT: The highlighted row in the screenshot is what I have a problem with, why is NSURLConnection running on [NSThread main] when I'm not calling it, AFNetworking is.
I'm using AFNetworking for my project, but when running Time Profiler in Instruments I'm seeing a lot of activity on the main thread for NSURLConnection, I have a feeling this is not what I want.
My method is
- (void)parseArticles {
NSMutableArray *itemsToParse = [[FMDBDataAccess sharedDatabase] getItemsToParse];
NSMutableArray *operations = [[NSMutableArray alloc] init];
for (Post *p in itemsToParse) {
NSMutableString *strURL = [NSMutableString new];
[strURL appendString:#"http://xxxxxxxxxxxxxxxxxxxxxx.php?url="];
[strURL appendString:[p href]];
NSURL *url = [NSURL URLWithString:strURL];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[[ParserClient sharedInstance] registerHTTPOperationClass:[AFHTTPRequestOperation class]];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
dispatch_async(loginParseQueue, ^{
Parser *parse = [[Parser alloc] init];
[parse parseLink:responseObject rowID:[p rowID]];
});
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%#",error);
}];
[operations addObject:operation];
}
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
[operationQueue setMaxConcurrentOperationCount:3];
[operationQueue addOperations:operations waitUntilFinished:NO];
}
Why would AFNetworking be using the main thread? and how do I fix it.
AFNetworking is running on a child thread not in main thread, but every thread has a main method, which is on the image you post. This is not the main thread.Now tell me What do you want to fix?
It's because AFNetworking uses "successCallbackQueue" to route the completion block :
AFHTTPRequestOperation.m :
self.completionBlock = ^{
if (self.error) {
if (failure) {
dispatch_async(self.failureCallbackQueue ?: dispatch_get_main_queue(), ^{
failure(self, self.error);
});
}
} else {
if (success) {
dispatch_async(self.successCallbackQueue ?: dispatch_get_main_queue(), ^{
success(self, self.responseData);
});
}
}
};
You can simply assign a different thread to success and failure completion blocks :
dispatch_queue_t backgroundQueue = dispatch_queue_create("com.name.bgqueue", NULL);
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.successCallbackQueue = backgroundQueue;
operation.failureCallbackQueue = backgroundQueue;
EDIT:
Here is some code to run operations in a background thread. Use of any function called from the UI thread will run on on the UI thread. You can use a technique similar to the one specified below to run your operation on a background thread, and then dispatch the result back to the UI thread for later use.
Here is the technique I used, you may replace my sendSynchronousRequest call with your AFHTTPRequestOperation :
Specify a special type (a block) so you can pass blocks of code around.
typedef void (^NetworkingBlock)(NSString* stringOut);
Then, you need to dispatch to a background thread, so as not to freeze your UI thread.
Here's a function to call stuff in a background thread, and then wait for a response, and then call a block when done without using the UI thread to do it:
- (void) sendString:(NSString*)stringIn url:(NSString*)url method:(NSString*)method completion:(NetworkingBlock)completion {
//build up a request.
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
NSData *postData = [stringIn dataUsingEncoding:NSUTF8StringEncoding];
[request setHTTPMethod:method];
[request setValue:[NSString stringWithFormat:#"%d", postData.length] forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"]; //or whatever
[request setHTTPBody:postData];
//dispatch a block to a background thread using GCD (grand central dispatch)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURLResponse *response;
NSError* error;
//request is sent synchronously here, but doesn't block UI thread because it is dispatched to another thread.
NSData* responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
//call complete, so now handle the completion block.
if (completion) {
//dispatch back to the UI thread again
dispatch_async(dispatch_get_main_queue(), ^{
if (responseData == nil) {
//no response data, so pass nil to the stringOut so you know there was an error.
completion(nil);
} else {
//response received, get the content.
NSString *content = [[NSString alloc] initWithBytes:[responseData bytes] length:responseData.length encoding:NSUTF8StringEncoding];
NSLog(#"String received: %#", content);
//call your completion handler with the result of your call.
completion(content);
}
});
}
});
}
Use it like this:
- (void) myFunction {
[self sendString:#"some text in the body" url:#"http://website.com" method:#"POST" completion:^(NSString *stringOut) {
//stringOut is the text i got back
}];
}
I currently have a screen with 2 tables. I'm getting the data synchronously and putting it on the screen. Code looks something like:
viewController.m
DBAccess_Error_T = [getList:a byCompanyID:1];
DBAccess_Error_T = [getList:b byCompanyID:2];
[self putListAOnScreen];
[self putListBOnScreen];
DBAccess.m
+ (DBAccess_Error_T)getList:(NSMutableArray*)a byCompanyID:(NSInteger)cID
{
// Pack this up in JSON form
[self queryDB:postData];
// Unpack and put it into variable a
}
+ (id)queryDB:(id)post
{
// Send request
// Get back data
}
I'm now trying to switch this over to async and I'm struggling. It's been hard even with website tutorials and documentations.
Since all of my database utilities are in separate files from the viewControllers, I'm not sure how I can use the didReceiveData and didReceiveResponse handlers. Also, since I have 2 arrays to fill for my 2 tables, how do I distinguish the difference in didReceiveData?
Instead, what I'm trying to do now is use sendAsynchronousRequest, but it seems I need to create an unpack function for every send function...let me know if I'm way off here...it looks something like:
viewController.m stays the same
DBAccess.m
+ (DBAccess_Error_T)getList:(NSMutableArray*)a byCompanyID:(NSInteger)cID
{
NSDictionary *post = /*blah blah*/
[self queryDB:post output:(a)];
}
+ (id)queryDB:(id)post output:(id)output
{
NSError *error;
NSData *jsonPayload = [NSJSONSerialization dataWithJSONObject:post options:NSJSONWritingPrettyPrinted error:&error];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
[theRequest setHTTPMethod:#"POST"];
[theRequest setHTTPBody:jsonPayload];
[NSURLConnection sendAsynchronousRequest:request
queue:[[NSOperationQueue alloc] init]
completionHandler:^(NSURLResponse *response,
NSData *data,
NSError *error)
{
if ([data length] >0 && error == nil)
{
[self unpackDataForList:output data:data]; // This function needs to be different depending on which function called queryDB...the data will be unpacked in a different way
}
}
}
+ (void)unpackDataForList:(id)output data:(NSData*)data
{
// Do my unpacking here and stick it into 'output'.
}
How can I call a different unpackData function? are function pointers the right way to do this? Is this approach way off? Any tips would be greatly appreciated!
Have you ever looked at ASIHTTPRequest? It makes your life a lot easier by allowing you to use blocks. Here's an example of how to make an asynchronous request:
- (IBAction)grabURLInBackground:(id)sender
{
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setCompletionBlock:^{
// Use when fetching text data
NSString *responseString = [request responseString];
// Use when fetching binary data
NSData *responseData = [request responseData];
}];
[request setFailedBlock:^{
NSError *error = [request error];
}];
[request startAsynchronous];
}
You can find more information here:
http://allseeing-i.com/ASIHTTPRequest/