The code is shown as below, I have a button once you click it activates timer and timer calls method every 4sec. However, sometimes 4 sec is not enough for server to return the data. However, increasing the timer value is not also good solution if server returns data in 1 sec and would not good for user to wait longer. I do not know what is best/optimal solution in this case.
-(IBAction)play:(id)sender{
timer=[NSTimer scheculedWith TimerInterval(4.0) target:(self)selector:#selector(httpRequest) userinfo:nil repeats:YES]
}
-(void)httpRequest{
_weak ASIHTTPRequest *request1 = [ASIHTTPRequest requestWithURL:url1];
[request1 setCompletionBlock:^{
NSString *responseString1 = [request1 responseString];
//dispatch_async(backgroundProcess1,^(void){
[self plotOverlay1:responseString1];
//});
}];
[request1 setFailedBlock:^{
NSError *error=[request1 error];
NSLog(#"Error: %#", error.localizedDescription);
}];
[request1 startAsynchronous];
}
If you just want the data to be updating continuously, consider calling -httpRequest again from within the completion block of the first request (and removing the timer). That way, you can be assured that the request will get performed again, but only after the first request finishes - and you can introduce a delay there, so you get something like "check again two seconds after the first check finishes."
This might look something like:
- (void)httpRequest {
__weak ASIHTTPRequest *req = [ASIHTTPRequest requestWithURL:url1];
[req setCompletionBlock:^{
NSString *resp = [req responseString];
[self plotOverlay1:resp];
[self httpRequest];
// or...
[self performSelector:#selector(httpRequest) withObject:nil afterDelay:2.0];
}];
/* snip fail block */
[req startAsynchronous];
}
Related
Hi I have been sending a login with asynchronous request (as it is commonly advised to use asynchronous where possible) but I now want to make it synchronous to better control when I receive the response.
Can someone suggest how to alter the asynchronous code below to synchronous?
Thanks for any suggestions:
NSURL *url = [NSURL URLWithString:#"http://~/login.php"];
NSMutableURLRequest *rq = [NSMutableURLRequest requestWithURL:url];
[rq setHTTPMethod:#"POST"];
NSData *jsonData = data;
[rq setHTTPBody:jsonData];
[rq setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[rq setValue:[NSString stringWithFormat:#"%ld", (long)[jsonData length]] forHTTPHeaderField:#"Content-Length"];
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
//Want to change to a synchronous request
[NSURLConnection sendAsynchronousRequest:rq queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *rsp, NSData *data, NSError *err) {
if (err) {
NSLog(#"Error%#",err);
} else {
NSDictionary *jsonResults = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
NSNumber *idResponse = jsonResults[#"response"][#"userid"];
if (![idResponse isKindOfClass:[NSNull class]]) {
NSInteger userid = [idResponse integerValue];
}
}
dispatch_async(dispatch_get_main_queue(), ^{
//back in main queue to use results of login.
});
}
}];
}
I think you're out of luck.
Apple has deprecated just about all the methods in NSURLConnection. We're supposed to start using NSURLSession instead, and that is async only.
The take-away is "if you're using synchronous networking, you're doing it wrong."
I think you should probably bite the bullet and do that refactoring. What I do is to create my own methods that take a completion block, and the completion block in NSURLSession calls my methods completion block (from the main thread to make things simple.)
According to Apple, there is a Sync Request function called
+ sendSynchronousRequest:returningResponse:error:
But also, According to Apple, you should never use it from the main thread of a GUI application(explained in the following link)
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLConnection_Class/index.html#//apple_ref/occ/clm/NSURLConnection/sendSynchronousRequest:returningResponse:error:
The Async you posted, did finally get back to the Main Thread when the call is finished using
dispatch_get_main_queue()
I would suggest to do some researches on how iOS manage multi-thread using GCD
Ok, this seems like it should be very simple - All I want to do is call my ServerConnect.m (NSObject), NSURL Connection Request Method, from my SignIn.m (ViewController) and stop the UIActivityIndicatorView after the NSURL Request has completed. Of course, if I do it all on the main thread:
- (IBAction)forgotPassword:(id)sender {
[activityIndicator startAnimating];
connection = [[ServerConnect alloc] init];
[connection sendUserPassword:email withSecurity:securityID];
[activityIndicator stopAnimating];
}
Then, everything will then execute concurrently, and the activity indicator will start and stop before the connection method finishes...
Thus, I attempted to place the connection request on a secondary thread:
- (IBAction)forgotPassword:(id)sender {
[NSThread detachNewThreadSelector: #selector(requestNewPassword:) toTarget:self withObject:userEmail.text];
}
- (void) requestNewPassword:(NSString *)email
{
[self->thinkingIndicator performSelectorOnMainThread:#selector(startAnimating) withObject:nil waitUntilDone:NO];
//Make NSURL Connection to server on secondary thread
NSString *securityID = [[NSString alloc] init];
securityID = #"security";
connection = [[ServerConnect alloc] init];
[connection sendUserPassword:email withSecurity:securityID];
[self->thinkingIndicator performSelectorOnMainThread:#selector(stopAnimating) withObject:nil waitUntilDone:NO];
}
But, I don't see the activity indicators here either, which may be due the NSURL Request not functioning properly on the secondary thread (i.e. for some reason, it does not gather an xml string as it does when requested on the main thread).
What is the proper way to architecture my code to make this work? I am surprised at how much work has been involved in trying to figure out how to get my activity indicator to simply stop after a method from another file has finished executing. Is there a way to run the code in series (one after another) and not concurrently? Any help would be appreciated.
Updated to Show: sendUserPassword:(NSString *)withSecurity:(NSString *)
- (void)sendUserPassword:(NSString *)emailString
withSecurity:(NSString *)passCode;
{
NSLog(#"Making request for user's password");
newUser = NO;
fbUser = NO;
forgotPassword = YES;
NSString *post = [NSString stringWithFormat: #"email=%#&s=%#", emailString, passCode];
NSData *postData = [post dataUsingEncoding:NSUTF8StringEncoding];
//Construct the web service URL
NSURL *url = [NSURL URLWithString:#"http://www.someurl.php"];
//Create a request object with that URL
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:90];
[request setURL:url];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:postData];
//Clear out the existing connection if there is one
if(connectionInProgress) {
[connectionInProgress cancel];
}
//Instantiate the object to hold all incoming data
xmlData = [[NSMutableData alloc] init];
//Create and initiate the conection - non-blocking
connectionInProgress = [[NSURLConnection alloc] initWithRequest: request
delegate:self
startImmediately:YES];
}
One suggestion try like this:
- (IBAction)forgotPassword:(id)sender
{
[self->thinkingIndicator startAnimating];
[NSThread detachNewThreadSelector: #selector(requestNewPassword:) toTarget:self withObject:userEmail.text];
}
- (void) requestNewPassword:(NSString *)email
{
//Make NSURL Connection to server on secondary thread
NSString *securityID = [[NSString alloc] init];
securityID = #"security";
connection = [[ServerConnect alloc] init];
[connection sendUserPassword:email withSecurity:securityID];
[self->thinkingIndicator performSelectorOnMainThread:#selector(stopAnimating) withObject:nil waitUntilDone:NO];
}
I ended up incorporating the NSNotification system (see Multithreading for iOS) to solve my problem. Any reason why this would be frowned upon:
"One easy way to send updates from one part of your code to another is Apple’s built-in NSNotification system.
It’s quite simple. You get the NSNotificationCenter singleton (via [NSNotificationCenter defaultCenter]) and:
1.) If you have an update you want to send, you call postNotificationName. You just give it a unique string you make up (such as “com.razeware.imagegrabber.imageupdated”) and an object (such as the ImageInfo that just finished downloading its image).
2.) If you want to find out when this update happens, you call addObserver:selector:name:object. In our case the ImageListViewController will want to know when this happens so it can reload the appropriate table view cell. A good spot to put this is in viewDidLoad.
3.) Don’t forget to call removeObserver:name:object when the view gets unloaded. Otherwise, the notification system might try to call a method on an unloaded view (or worse an unallocated object), which would be a bad thing!"
You could try something this, it uses a block when it is finished. I had similar thing right here.
// Turn indicator on
// Setup the request
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
[request setTimeoutInterval: 90.0];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:postData];
request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue currentQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
// Its has finished but sort out the result (test for data and HTTP 200 i.e. not 404)
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (data != nil && error == nil && [httpResponse statusCode] == 200)
{
// Connection finished a gooden
// Do whatever you like with data
// Stop indicator
}
else
{
// There was an error, alert the user
// Do whatever you like with data
// Stop indicator
}
}];
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 am using performSelector to call URLRequest every couple of second with different timetstamp. However, data processing may take longer than the time I have defined.
[self performSelector:#selector(process) withObject:nil afterDelay:1.6];
below part shows the method is called
-(void)process
{
timestamp=[NSString stringWithFormat:#"%1.f",progressValue];
NSString *contour=#"&bandschema=4";
NSString *url6=[NSString stringWithFormat:#"http://contour.php? callback=contourData%#&type=json×tamp=%#%#",timestamp,timestamp,contour];
NSURL *url1=[NSURL URLWithString:url6];
__weak ASIHTTPRequest *request1 = [ASIHTTPRequest requestWithURL:url1];
[request1 setCompletionBlock:^{
responseString = [request1 responseString];
[self plotPoint:self.responseString];
}];
[request1 setFailedBlock:^{
NSError *error=[request1 error];
NSLog(#"Error: %#", error.localizedDescription);
}];
[request1 startAsynchronous];
}
this part is start point of analyzing data.
-(void)plotPoint:(NSString *)request
{
NSArray *polygonArray = [[dict objectForKey:#"data"]valueForKey:#"polygon"];
NSArray *valleyPolygonArray = [[dict objectForKey:#"valley"]valueForKey:#"polygon"];
CLLocationCoordinate2D *coords;
}
However sometimes time interval is not enough to get new data especially when internet connection is not good.
Could you guide me please? How could I handle the problem? What is the optimal solution?
Why dont you call process after you get the response as follows,
-(void)process
{
timestamp=[NSString stringWithFormat:#"%1.f",progressValue];
NSString *contour=#"&bandschema=4";
NSString *url6=[NSString stringWithFormat:#"http://contour.php? callback=contourData%#&type=json×tamp=%#%#",timestamp,timestamp,contour];
NSURL *url1=[NSURL URLWithString:url6];
__weak ASIHTTPRequest *request1 = [ASIHTTPRequest requestWithURL:url1];
[request1 setCompletionBlock:^{
responseString = [request1 responseString];
[self plotPoint:self.responseString];
if (somecondition)//based on some condition to break the chain when needed
[self process];
}];
[request1 setFailedBlock:^{
NSError *error=[request1 error];
NSLog(#"Error: %#", error.localizedDescription);
if (somecondition)//based on some condition to break the chain when needed
[self process];
}];
[request1 startAsynchronous];
}
This way you can keep 1.6 as the exact time interval after getting a response to creating a new request.
Here i am trying to call my ASIHTTPRequest in a GCD. Sometimes the comletion blocks and failed blocks are not executing. What i want to do is, after this request finished, i have to use the returned data in a another ASIHTTPRequest. So how to improve this code:
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:url]];
[request setCompletionBlock:^{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
NSData *_responseData = [request responseData];
NSString *response = [[NSString alloc] initWithData:_responseData encoding:NSASCIIStringEncoding] ;
self.albumDic = [response JSONValue];
[response release];
dispatch_async(dispatch_get_main_queue(), ^{
[self GetDictionary:self.albumDic];
});
});
}];
[request setFailedBlock:^{
NSError *error = [request error];
NSLog(#"Error : %#", error.localizedDescription);
}];
[request startSynchronous];
Don't go that way. You're doing threading on threading (GCD uses threading and so does ASIHTTPRequest when used asynchronously).
Use ASINetworkQueue instead - read about it here
Here is a simple way you could use it:
- (void)addRequestsToNetworkQueue:(NSArray *)requests {
// Stop anything already in the queue before removing it
[[self networkQueue] cancelAllOperations];
// Creating a new queue each time we use it means we don't have to worry about clearing delegates or resetting progress tracking
[self setNetworkQueue:[ASINetworkQueue queue]];
[[self networkQueue] setDelegate:self];
[[self networkQueue] setRequestDidFinishSelector:#selector(requestFinished:)];
[[self networkQueue] setRequestDidFailSelector:#selector(requestFailed:)];
[[self networkQueue] setQueueDidFinishSelector:#selector(queueFinished:)];
//Add all requests to queue
for (ASIHTTPRequest *req in requests) {
[[self networkQueue] addOperation:req];
}
//Start queue
[[self networkQueue] go];
}
ASINetworkQueue provides many delegate methods (most are also customizable), so you can update the GUI when a request is finished and so forth. It is asynchronous, so GCD is unnecessary.