so I want my app not to lock the GUI while sending an http request and getting the response, I made an attempt, but it complains that i use uikit outside of the mainthread, can someone please tell me the proper way of separating the http and the gui?
-(void)parseCode:(NSString*)title{
UIActivityIndicatorView *spinner;
spinner.center = theDelegate.window.center;
spinner.tag = 12;
[theDelegate.window addSubview:spinner];
[spinner startAnimating];
dispatch_queue_t netQueue = dispatch_queue_create("com.david.netqueue", 0);
dispatch_async(netQueue, ^{
NSString *url =[NSString stringWithFormat:#"http://myWebService.org/"];
// Setup request
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:url]];
[request setHTTPMethod:#"POST"];
NSString *contentType = [NSString stringWithFormat:#"application/x-www-form-urlencoded"];
[request addValue:contentType forHTTPHeaderField:#"Content-Type"];
NSMutableString *data = [[NSMutableString alloc] init];
[data appendFormat:#"lang=%#", #"English"];
[data appendFormat:#"&code=%#", theDelegate.myView.text ];
[data appendFormat:#"&private=True" ];
[request setHTTPBody:[data dataUsingEncoding:NSUTF8StringEncoding]];
NSHTTPURLResponse *urlResponse = nil;
NSError *error = [[NSError alloc] init];
NSData *responseData = [NSURLConnection sendSynchronousRequest:request
returningResponse:&urlResponse
error:&error];
NSString *result = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
dispatch_async(dispatch_get_main_queue(), ^{
[spinner stopAnimating];
[spinner removeFromSuperview];
[self presentResults:result];
});
});
}
Instead of using NSURLConnection:sendSynchronousRequest, use NSURLConnection:initWithRequest:delegate:startImmediately:, which sends the request asynchronously. Use the NSURLConnection:connectionDidFinishLoading delegate method to handle the response.
Apple provides an example in the URL Loading System Programming Guide.
If you set startImmediately to YES, the delegate method will be called on the same run loop as the one where the request is called from. Most likely, this will be your main run loop, so you can modify the UI all you want in the delegate method, without worrying about threading issues.
i didn't look to much into it but you can always try,
[self performSelectorInBackground:#selector(parseCode:) withObject: title];
that method causes the function to run on a separate thread in the back ground, and takes little effort to implement, i use it for when i'm doing simple downloads like
[NSData dataWithContentsOfURL:url]; but if you are doing something bigger you may need to do a little more work.
if you need to call the method out side of a class, then you will have to make a method with in the class that makes the call above that will then invoke the selector
I don't think the problem is with your HTTP code - it's with your access of the UI from within the background thread. Specifically this line:
[data appendFormat:#"&code=%#", theDelegate.myView.text ];
You're assumedly accessing a UITextView or something similar there. You need to do that outside the background thread. Move it into a local NSString variable and then you can safely access that variable from within the background thread.
Related
I am a beginer in iOS programming. I have some problem with NSURLConnection: I have installed SWRevealViewController https://github.com/John-Lluch/SWRevealViewController and when my app is loading Data from server, I can't use interaction with screen. I can't open my SWR-menu while Data is loading.
Here is my SWR in viewDidLoad:
SWRevealViewController *revealViewController = self.revealViewController;
if ( revealViewController ) {
[self.openMenyItmet setTarget: self.revealViewController];
[self.openMenyItmet setAction: #selector( revealToggle: )];
[self.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
}
After that, I called Get method in viewDidLoad:
[self GetQUIZ];
Method detail:
- (void)GetQUIZ {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
NSString *url = [NSString stringWithFormat:#"http://stringlearning.com/api/v1/user-quiz?token=%#",[[NSUserDefaults standardUserDefaults] stringForKey:#"token"]];
[request setURL:[NSURL URLWithString: url]];
[request setHTTPMethod:#"GET"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setValue:[UIDevice currentDevice].name forHTTPHeaderField:#"device"];
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
NSLog(#"Left menu, User details: %#", [[NSString alloc] initWithData:[request HTTPBody] encoding:NSUTF8StringEncoding]);
NSLog(#"%#", [request allHTTPHeaderFields]);
if(conn) {
NSLog(#"Connection Successful");
} else
NSLog(#"Connection could not be made");
And then I use data in connectionDidFinishLoading:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSError *deserr = nil;
NSDictionary *responseDict = [NSJSONSerialization JSONObjectWithData:responseData options: 0 error: &deserr];
I read that i should use async methods, but I never use it before. Would you write some detail solution ?
Maybe, does have different path?
I would be very grateful for the help!
I'd suggest starting with NSURLSession, which is a modern API that will accomplish the same thing, asynchronously.
To use NSURLSession, you need a few piece of the puzzle:
A web address to reach, and optionally any payload or custom headers.
An instance of NSURL: where you're downloading from and an NSURLRequest to wrap it in.
An NSURLSessionConfiguration, which handles things like caching, credentials and timeouts.
The session itself.
You need an NSURLSessionTask instance. This is the closest object to your NSURLConnection. It has callbacks via delegate or a completion block, if you just need to know when it finishes.
Here's how this would look in code:
// 1. The web address & headers
NSString *webAddress = [NSString stringWithFormat:#"http://stringlearning.com/api/v1/user-quiz?token=%#",[[NSUserDefaults standardUserDefaults] stringForKey:#"token"]];
NSDictionary <NSString *, NSString *> *headers = #{
#"device" : [UIDevice currentDevice].name,
#"Content-Type" : #"application/x-www-form-urlencoded"
};
// 2. An NSURL wrapped in an NSURLRequest
NSURL* url = [NSURL URLWithString:webAddress];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// 3. An NSURLSession Configuration
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
[sessionConfiguration setHTTPAdditionalHeaders:headers];
// 4. The URLSession itself.
NSURLSession *urlSession = [NSURLSession sessionWithConfiguration:sessionConfiguration];
// 5. A session task: NSURLSessionDataTask or NSURLSessionDownloadTask
NSURLSessionDataTask *dataTask = [urlSession dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
}];
// 5b. Set the delegate if you did not use the completion handler initializer
// urlSession.delegate = self;
// 6. Finally, call resume on your task.
[dataTask resume];
This will run asynchronously, allowing your UI to remain responsive as your app loads data.
When you send a request on the main thread, like you are doing now, your UI, which is always performed on the main thread, is blocked, waiting for the request to finish and process. So you should perform all your network on a background thread, asynchronously. I would recommend first to check the networking library AFNetworking , it could simplify most of your networking problems.
Welcome to SO. You should know that NSURLConnection was deprecated in iOS 9. You should be using NSURLSession instead. The approach is very similar. You can take the NSURLRequest you've created and pass it to the sharedSession object, which is set up for async requests. The simplest way to deal with it is to use the call dataTaskWithRequest:completionHandler:, which takes a completion block. In your completion block you provide code that handles both success and failure.
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
I have a problem with my application.It freeze for several second when I tap the sidebar menu.
What happen when I tapped menu is I pass string that gonna be url for json data fetch in my mainviewcontroller.Then it freeze because I fetch the data and populating data in tableview.
However I really new to ios programming,I wonder how can I remove the freeze?.
thanks in advance
here is my code snippet for the mainviewcontroller:
Don't use dataWiyhContentsOfURL:, or at least not directly on the main thread. If you block the main thread then the whole app stops working (as you see).
You need to learn about background threads and callback blocks, and look at using NSURLSession to download your data and then process it.
Instead of using dataWithContentsOfURL (which will block the main thread and so the UI) you need to start an asynchronous connection. In the IF ELSE change the two requests to something like below. The completionHandler (Block) is executed when done, the data parsed, HUD removed and table Updated.
You can even (and in fact must) do this within your cellForRowAtIndexPath for each of the images, however, I would use SDWebImage as it has a cache and is very easy to use.
There are also other methods if this is not right for you such as NSURLSession.
Some other points;
I have also noted that the HUD is stopped on every iteration of the FOR and probably should be outside.
I also can not see how your data is being loaded so I added a [myTable reloadData];
I can not see that the "dictionary" object is needed as it can be added directly to the array (see code)
// If you have the status bar showing
// [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[HUD showUIBlockingIndicatorWithText:#"Please wait. . ."];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:kategori]];
[request setTimeoutInterval: 10.0];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue currentQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
// [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
if (data != nil && error == nil)
{
//All Worked
id jsonObjects = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
for (NSDictionary *dataDict in jsonObjects)
{
NSString *title_data = [dataDict objectForKey:#"title"];
NSString *thumbnail_data = [dataDict objectForKey:#"thumb"];
NSString *author_data = [dataDict objectForKey:#"creator"];
NSString *link_data = [dataDict objectForKey:#"link"];
[myObject addObject:[[NSDictionary alloc]initWithObjectsAndKeys:
title_data, title,
thumbnail_data, thumbnail,
author_data,author,
link_data,link,
nil]];
}
[HUD hideUIBlockingIndicator];
[myTableView reloadData];
}
else
{
// There was an error
}
}];
For the images something like (this is not tested). I am not sure what format your images are in but you should be able to just add it, this may need tweeking;
cell.imageView.frame = CGRectMake(0, 0, 80, 70);
__block UIImageView *cellImage = cell.imageView;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[tmpDict objectForKey:thumbnail]]];
[request setTimeoutInterval: 10.0];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue currentQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if (data != nil && error == nil)
{
//All Worked
cellImage.image = [[UIImage alloc]initWithData:data];
[cellImage layoutIfNeeded];
}
else
{
// There was an error
}
}];
You can start activity indicator and call fetch data method after few time...
- (void)viewDidLoad{
[activityIndicator startAnimating];
[self performSelector:#selector(fetchData) withObject:nil afterDelay:0.5];
}
- (void)fetchData{
Fetch your data over here
}
Or ideally you have to load data Asynchronous
For loading data Asynchronously check out the following link-
iphone-synchronous-and-asynchronous-json-parse
I Prefer MBProgressHUD.
Here is the link for 3rd Party API.
https://github.com/jdg/MBProgressHUD
Just copy these two files in your app.
MBProgressHUD.h
MBProgressHUD.m
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
}
}];
I am using ASIHTTPRequest framework, in the document, what are the differences between the 2nd and 3rd example, in usage, advantage and disadvantage?
2nd example (Creating an asynchronous request):
- (IBAction)grabURLInBackground:(id)sender
{
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request startAsynchronous];
}
- (void)requestFinished:(ASIHTTPRequest *)request
{
// Use when fetching text data
NSString *responseString = [request responseString];
// Use when fetching binary data
NSData *responseData = [request responseData];
}
- (void)requestFailed:(ASIHTTPRequest *)request
{
NSError *error = [request error];
}
3rd example (Using blocks)
- (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];
}
The blocks in iOS are a part of Concurrent Programming
You use a block when you want to create units of work (that is, code segments) that can be passed around as though they are values. Blocks are usually used for writting a callbacks.
Usually, using blocks do not reflect in different applicatino behaviour. The syntactical difference is that, when using blocks you do not need to define a request delegate or implement delegate methods (such as -requestFinished: and -requestFailed:) for async requests.
One of the advantages is in accessing local method variables in completion block, bacause the function expression in block can reference and can preserve access to local variables (like variable url in your method -grabURLInBackground: or any other local variable defined in your method).
The second adventage is in using nested request calls. For example, you may need to perform a few requests in sequence, and without blocks you will need to define a separate delegate method callback for each request, which may result in reduced readability of your code.
Blocks allow you to write code at the point of invocation that is executed later in the context of the method implementation, which may be very usefull, when you get used to using them.
Some patterns to avoid when using blocks are mentioned in Apple Blocks Programming Topis