So I normally write apps for android and just use an async task to call methods to run in the background while an alert dialog plays saying "Loading" or something like that. On this app Im trying to translate to iOS, Im parsing data from different websites and displaying a couple web images and I want to have my alart dialog play while all these things are being loaded. Ive been searching for hours and havent found the solution I am looking for. I was hoping someone could point me to a tutorial or somewhere in the right direction.
here is what im working with:
- (void) RSEpic{
NSURL * imageURL = [NSURL URLWithString:RSEimageURL];
NSData * imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage * image = [UIImage imageWithData:imageData];
_RSEImage.image = image;
[self waterTemp];
}
- (void) waterTemp{
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
/* set headers, etc. on request if needed */
[request setURL:[NSURL URLWithString:#"http://waterdata.usgs.gov/usa/nwis/uv?02035000"]];
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:NULL error:NULL];
NSString *html = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSScanner *scanner = [NSScanner scannerWithString:html];
NSString *token = nil;
[scanner scanUpToString:#"<table id=\"table_12_00010\"" intoString:NULL];
[scanner scanUpToString:#" " intoString:&token];
NSArray *words = [token componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#":"]];
double temp = [words[1] doubleValue];
_waterTempC.Text = [NSString stringWithFormat:#"%.2f°C",temp];
_waterTempF.Text = [NSString stringWithFormat:#"%.2f°F",temp*9/5+32];
[self waterDepth];
}
- (void) waterDepth{
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
/* set headers, etc. on request if needed */
[request setURL:[NSURL URLWithString:#"http://waterdata.usgs.gov/va/nwis/uv?02037500"]];
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:NULL error:NULL];
NSString *html = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSScanner *scanner = [NSScanner scannerWithString:html];
NSString *token = nil;
[scanner scanUpToString:#"<table id=\"table_07_00065\"" intoString:NULL];
[scanner scanUpToString:#" " intoString:&token];
NSArray *words = [token componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#":"]];
double temp = [words[1] doubleValue];
_waterLevel.Text = [NSString stringWithFormat:#"%.2fFT",temp];
if (temp >= 9.0) {
_levelAlert.text = #"HIGH WATER PERMIT REQUIRED";
}
else if (temp >= 5.0){
_levelAlert.text = #"LIFE JACKET REQUIRED";
}
else {
_levelAlert.text = #"";
}
[self tempChart];
}
- (void) tempChart{
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
/* set headers, etc. on request if needed */
[request setURL:[NSURL URLWithString:#"http://waterdata.usgs.gov/nwis/uv/?dd_cd=12_00010&format=img_default&site_no=02035000&set_arithscale_y=on&period=7"]];
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:NULL error:NULL];
NSString *html = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSScanner *scanner = [NSScanner scannerWithString:html];
NSString *token = nil;
[scanner scanUpToString:#"http" intoString:NULL];
[scanner scanUpToString:#"\"" intoString:&token];
NSLog(#"%#",token);
NSURL * imageURL = [NSURL URLWithString:token];
NSData * imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage * image = [UIImage imageWithData:imageData];
_chartImage.image = image;
}
- (void) depthChart{
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
/* set headers, etc. on request if needed */
[request setURL:[NSURL URLWithString:#"http://waterdata.usgs.gov/va/nwis/uv/?dd_cd=07_00065&format=img_default&site_no=02037500&set_arithscale_y=on&period=7"]];
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:NULL error:NULL];
NSString *html = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSScanner *scanner = [NSScanner scannerWithString:html];
NSString *token = nil;
[scanner scanUpToString:#"http" intoString:NULL];
[scanner scanUpToString:#"\"" intoString:&token];
NSLog(#"%#",token);
NSURL * imageURL = [NSURL URLWithString:token];
NSData * imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage * image = [UIImage imageWithData:imageData];
_chartImage.image = image;
}
- (void) progressAlert {
// initialize our Alert View window without any buttons
baseAlert=[[UIAlertView alloc]initWithTitle:#"Please wait,\ndownloading updates...." message:nil delegate:self cancelButtonTitle:nil otherButtonTitles:nil];
// Display our Progress Activity view
[baseAlert show];
// create and add the UIActivity Indicator
UIActivityIndicatorView
*activityIndicator=[[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
activityIndicator.center=CGPointMake(baseAlert.bounds.size.width
/ 2.0f,baseAlert.bounds.size.height-40.0f);
// initialize to tell our activity to start animating.
[activityIndicator startAnimating];
[baseAlert addSubview:activityIndicator];
// automatically close our window after 3 seconds has passed.
[self performSelector:#selector(showProgressDismiss)withObject:nil afterDelay:3.0f];
}
- (void) showProgressDismiss
{
[baseAlert dismissWithClickedButtonIndex:0 animated:NO];
}
#end
So can someone tell me how to make my baseAlert Show and Dismiss while all this stuff is loading?
Use a dispatch_group_t, and once all threads have been completed, they can invoke notify, like so:
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[self doAnExpensiveOperation];
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[self doAnotherExpensiveOperation];
});
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
// called when both background threads have finished.
// Update UI elements here
});
});
To prioritize:
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self doAnotherExpensiveOperation];
});
The same concepts are applied on iOS as on Android: if you want to perform heavy computing and you want the app to be responsive do the heavy computing on a background thread and DO NOT ACCES UI FROM BACKGROUND THREADS. The only difference between the iOS and Android is the way you can perform tasks on background threads. Android has AsyncTasks or Loaders, iOS has NSOperations & NSOperationsQueue,GCD (grand central dispatch which is my favorite and the best solution in my opinion) or there are methods like performSelectorInBackground: which I don't like because is harder to return objects after thread is completed.
So my suggestion is, have a look over GCD (there are lots of other tutorials) and after that, change your code accordingly, if you have troubles changing the code or you have unexpected behaviors, come back to SO with other questions
Well, to make it simple and concise, you can tell your OS to execute some code in another thread with this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Insert your code here
});
The global queue means that you will submit a block to GCD, and the OS will execute that block in a convenient thread, except the main thread (where your UI runs). You must take care that if you want to update the UI (for example hide an alert, or stop an activity indicator), you need to do that in the main thread, (main_queue). So, a template we always use is as follows:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Process your heavy code here.
dispatch_async(dispatch_get_main_queue(), ^{
//Update your UI here.
});
});
This is very important to avoid retain cycles if you're using ARC
As Apple says here, you have to use weak references to self inside a block, as so for any iVar you use inside a block whose retain count will be incremented.
I suggest the tutorial proposed by #danypata , it's a good one. In the same site you can find a lot of useful tutorials!!
Have a great day, and hope it helps!
Related
i am developing an application and in this app i am loading xml data in a uitableview and that table view call parse method after every 5 secs and reload uitable to load new data. Everything was working fine but app got stuck when parsing starts after 5 sec so i decided to implement dispatch_async in parse method but after that application is crashing like after 5 sec whenever app reload uitable. here is my code.
- (void) Parse{
previusCount = rssOutputData.count;
rssOutputData = [[NSMutableArray alloc]init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *post =[[NSString alloc] initWithFormat:#"https://messages_%#.xml",[[NSUserDefaults standardUserDefaults] stringForKey:#"xmls_id"]];
NSData *xmlData=[[NSData alloc]initWithContentsOfURL:[NSURL URLWithString:post]];
xmlParserObject =[[NSXMLParser alloc]initWithData:xmlData];
[xmlParserObject setDelegate:self];
dispatch_async(dispatch_get_main_queue(), ^{
[xmlParserObject parse];
[messageList reloadData];
if (previusCount != rssOutputData.count) {
NSInteger bottomRow = [rssOutputData count] - 1; // this is your count's array.
if (bottomRow >= 0) {
///////getting to latest msg/////////////
NSIndexPath *indexPathnew = [NSIndexPath indexPathForRow:bottomRow inSection:0];
[self.messageList scrollToRowAtIndexPath:indexPathnew atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
}
});
});
}
Method through which i am sending a message to the xml data file.
- (IBAction)sendClicked:(id)sender {
[messageText resignFirstResponder];
if ( [messageText.text length] > 0 ) {
NSString *rawStr;
if ([[NSUserDefaults standardUserDefaults] integerForKey:#"userType"] == 1) {
rawStr = [NSString stringWithFormat:#"data=%#&user_id=%#&session_id=%#", messageText.text, [[NSUserDefaults standardUserDefaults] stringForKey:#"therapist_id"],[[NSUserDefaults standardUserDefaults] stringForKey:#"xmls_id"]];
} else{//////In case of Patient
rawStr = [NSString stringWithFormat:#"data=%#&user_id=%#&session_id=%#", messageText.text, [[NSUserDefaults standardUserDefaults] stringForKey:#"patient_id"],[[NSUserDefaults standardUserDefaults] stringForKey:#"xmls_id"]];
}
NSData *data = [rawStr dataUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:#"http://do_add_message.php"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:data];
NSURLResponse *response;
NSError *err;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&err];
NSLog(#"responseData: %#", responseData);
//////////////////////
[self Parse];
}
messageText.text = #"";
}
And the Error which i am getting is:
Print your array. Check it twice. I think you are getting an empty array. Print every object in the console which you get from server and which you parse. So you will get idea.
Update:
Just reload the table data on the main thread and don't parse data on it. like:
dispatch_async(dispatch_get_main_queue(), ^{
[messageList reloadData];
});
Put the other code outside the main thread.
I have a Test App, what i have in this App is a call made via a PHP script, once the data comes back the Recursive call is made again and again PHP Script is called and so on:
Whats happening is that every time [Self recusiveForumActivity]; is called i get 300kb of memory allocated and the memory usage keeps climbing when this recursive method is called. If i remove the method the memory usage stays stable. How can i overcome this so that i don't loose memory allocation at all, when the recursive method is called every time?
This is the code:
//
// ViewController.m
// Test
//
// Created by trikam patel on 30/06/2015.
// Copyright (c) 2015 trikam patel. All rights reserved.
//
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
-(NSString*)setupPhpCall:(NSString*)requestString :(NSString*)sciptPage{
//NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:0];
//[NSURLCache setSharedURLCache:sharedCache];
NSHTTPURLResponse *urlresponse = nil;
NSError *error = nil;
NSString *response = #"";
NSData *myRequestData = nil;
NSMutableURLRequest *request = nil;
NSData *returnData = nil;
myRequestData = [NSData dataWithBytes: [requestString UTF8String] length: [requestString length]];
//Create your request string with parameter name as defined in PHP file
request = [[NSMutableURLRequest alloc] initWithURL: [NSURL URLWithString: [NSString stringWithFormat: #"http://www.hugt.co.uk/%#", sciptPage]]];
// set Request Type
[request setHTTPMethod: #"POST"];
// Set content-type
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"content-type"];
// Set Request Body
[request setHTTPBody: myRequestData];
// Now send a request and get Response
//NSHTTPURLResponse* urlResponse = nil;
//NSError *error = nil;
//if(tmpArray.count == 0){
returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:&urlresponse error: &error];
response = [[NSString alloc] initWithBytes:[returnData bytes] length:[returnData length] encoding:NSUTF8StringEncoding];
//}
// Log Response
urlresponse = nil;
error = nil;
myRequestData = nil;
request = nil;
returnData = nil;
//NSLog(#"%#",response);/****/
//[sharedCache removeAllCachedResponses];
if(response != nil){
return response;
}
return nil;
}
-(void)recurseForumActivity{
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
__block NSString *myRequestStringForum = [NSString stringWithFormat:#"lastDate=%#&threadTitle=%#&threadCountry=%#&threadCategory=%#&threadSubCategory=%#&getData=0",#"",#"", #"", #"", #""];
__block NSString *responseForum = [self setupPhpCall:myRequestStringForum :#"getThreadRecurse.php"];
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
[NSThread sleepForTimeInterval:2.0f];
responseForum = #"";
myRequestStringForum = #"";
[self recurseForumActivity];
});
});
});
});
}
- (void)viewDidLoad {
[super viewDidLoad];
[self recurseForumActivity];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Try using [object performSelector:method] instead of recursive call. A recursive function must always have a base/exit condition and your code doesn't have any such condition. So you must not make any recursive call here.
- (void)recurseForumActivity{
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
__block NSString *myRequestStringForum = [NSString stringWithFormat:#"lastDate=%#&threadTitle=%#&threadCountry=%#&threadCategory=%#&threadSubCategory=%#&getData=0",#"",#"", #"", #"", #""];
__block NSString *responseForum = [self setupPhpCall:myRequestStringForum :#"getThreadRecurse.php"];
YourClass * __weak weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
responseForum = #"";
myRequestStringForum = #"";
[weakSelf performSelector:#selector(recurseForumActivity) withObject:nil afterDelay:2.0f];
});
});
});
});
}
I need to know something about button and web service ,normally I'm use Uicollectionview for show data from web service by indexPath.item but if I don't use Uicollectionview It's possible? to pass and get data from web service.
Here's code
-(IBAction)ttButton:(id)sender
{
bookName = #"test";
bookVersion = [[bookList objectAtIndex:indexPath.row]bookVersion];// when I use this it's will crash.
_bookPosition = [[bookList objectAtIndex:indexPath.row]bookPosition];
bookId = #"1";
bookPath = #"test001";
pageAmount = 2;
mainMenu = #"test";
// downloadURL = [[bookList objectAtIndex:indexPath.row]downloadURL];
// pageAmount = [[bookList objectAtIndex:indexPath.row]pageAmount]; I want to go like this. but indexPath I can use only in collection view
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *stringURL = [NSString stringWithFormat:#"http://tlf/testdata/webservice/book_extract.php?main_menu=test&language=en&id=%#",(_bookPosition)];
NSURLResponse *response = nil;
NSError *error = nil;
NSURL *url = [NSURL URLWithString:stringURL];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
NSURLConnection *urlConnection = [[NSURLConnection alloc]init];
NSData *data = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:&response error:&error];
dispatch_async(dispatch_get_main_queue(), ^{
[self connection:urlConnection didReceiveResponse:response];
[self connection:urlConnection didReceiveData:data];
[db saveAssetVersion:_assetVersion];
if([db isDownloaded:bookId bookVersion:[bookVersion floatValue]]){
[self performSegueWithIdentifier:#"test" sender:self];
}
else{
[self startDownload];
}
});
});
}
Please Advice for any Idea. Thank you very very much.
I dont think you code to get the data is even being called in the case of Button. Anyway, you need to set the delegate of your NSURLConnection class to the class where fetching code is.
Essentially, there is no difference at all using UICollectionView or UIButton. That is just the difference of how user interacts with the system. The code to download and update should be seperate and should be called by both similarly.
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.
I've followed some tutorials, but I'm stuck on doing Post requests. I Just want to send 3 parameters, to a URL and hadle with the response. And it has to be asynchronous, because it will give me some images, that i want to but one by one on the view.
Can you help me guys?
This is well covered here.
But the way I do it I find to be simpler, as I'll show you. Still there are many questions here on SO and other places that provide this knowledge.
First we set up our request with our parameters:
- (NSData *)executePostCall {
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"%#", YOUR_URL]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSString *requestFields = [NSString stringWithString:#""];
requestFields = [requestFields stringByAppendingFormat:#"parameter1=%#&", parameter1];
requestFields = [requestFields stringByAppendingFormat:#"parameter2=%#&", parameter2];
requestFields = [requestFields stringByAppendingFormat:#"parameter3=%#", parameter3];
requestFields = [requestFields stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSData *requestData = [requestFields dataUsingEncoding:NSUTF8StringEncoding];
request.HTTPBody = requestData;
request.HTTPMethod = #"POST";
NSHTTPURLResponse *response = nil;
NSError *error = nil;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (error == nil && response.statusCode == 200) {
NSLog(#"%i", response.statusCode);
} else {
//Error handling
}
return responseData;
}
This has to be wrapped up in a block since we can't execute this on the main thread because it will lock up our application and that is frowned upon, so we do the following to wrap this request up, I'll leave the rest of the details up to you:
dispatch_queue_t downloadQueue = dispatch_queue_create("downloader", NULL);
dispatch_async(downloadQueue, ^{
NSData *result = [self executePostCall];
dispatch_async(dispatch_get_main_queue(), ^{
// Handle your resulting data
});
});
dispatch_release(downloadQueue);
Use NSURLRequest. You can download files in the background and show them once you receive the delegate notification: Downloading to a Predetermined Destination