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];
});
});
});
});
}
Related
I have a TableView with customCells, when user press Start button on some cell the loading starts. There are many such cells, so I need to implement this downloading in parallel (asynchronously).
For image downloading and updating the cell in Table view I use next code:
#define myAsyncQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
I include this method into the async queue, that I supposed should enable parallel downloading of images.
- (void)didClickStartAtIndex:(NSInteger)cellIndex withData:
(CustomTableViewCell*)data
{
dispatch_async(myAsyncQueue, ^{
self.customCell = data;
self.selectedCell = cellIndex;
ObjectForTableCell* tmp =[self.dataDictionary objectForKey:self.names[cellIndex]];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:tmp.imeageURL]
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:60.0];
self.connectionManager = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
});
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
self.urlResponse = response;
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
NSDictionary *dict = httpResponse.allHeaderFields;
NSString *lengthString = [dict valueForKey:#"Content-Length"];
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
NSNumber *length = [formatter numberFromString:lengthString];
self.totalBytes = length.unsignedIntegerValue;
self.imageData = [[NSMutableData alloc] initWithCapacity:self.totalBytes];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.imageData appendData:data];
self.customCell.progressView.progress = ((100.0/self.urlResponse.expectedContentLength)*self.imageData.length)/100;
float per = ((100.0/self.urlResponse.expectedContentLength)*self.imageData.length);
self.customCell.realProgressStatus.text = [NSString stringWithFormat:#"%0.f%%", per];
}
I tried to set this block to queue - main queue - cause its the place where image is already downloaded,
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
dispatch_async(dispatch_get_main_queue(), ^{
self.customCell.realProgressStatus.text = #"Downloaded";
UIImage *img = [UIImage imageWithData:self.imageData];
self.customCell.image.image = img;
self.customCell.tag = self.selectedCell;
});
[self.savedImages setObject:img forKey:self.customCell.nameOfImage.text];
NSNumber *myNum = [NSNumber numberWithInteger:self.selectedCell];
[self.tagsOfCells addObject:myNum];
}
Without all queues(when I comment it)all works properly - but just 1 downloading at a ones.
But when I tried to implement code with queues as a result it doesn't download anything. I understand that I did smh wrong but I can't define it.
Thanks a lot for any help in advance.
If your looking out for starting it form basics I guess you should start with NSURLSession as NSURLConnection most of implementation had been deprecated and won't be available after iOS 9. For complete reference URL Session Programming Guide and tutorial
Coming back to your question you should do something similar to this took it from tutorial
// 1
NSURLSessionDownloadTask *getImageTask =
[session downloadTaskWithURL:[NSURL URLWithString:imageUrl]
completionHandler:^(NSURL *location,
NSURLResponse *response,
NSError *error) {
// 2
UIImage *downloadedImage =
[UIImage imageWithData:
[NSData dataWithContentsOfURL:location]];
//3
dispatch_async(dispatch_get_main_queue(), ^{
// do stuff with image
_imageWithBlock.image = downloadedImage;
});
}];
// 4
[getImageTask resume];
But my personal recommendation is go for AFNetworking which is best for iOS networking and widely used/tested in iOS app world.
For image download using AFNetworking
[_imageView setImageWithURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://i.imgur.com/fVhhR.png"]]
placeholderImage:nil
success:^(NSURLRequest *request , NSHTTPURLResponse *response , UIImage *image ){
NSLog(#"Loaded successfully: %d", [response statusCode]);
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error){
NSLog(#"failed loading: %#", error);
}
];
EDIT : Async downloading using concurrency
// get main dispact queue
dispatch_queue_t queue = dispatch_get_main_queue();
// adding downloading task in queue using block
dispatch_async(queue, ^{
NSData* imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]];
UIImage* image = [[UIImage alloc] initWithData:imageData];
// after download compeletes geting main queue again as there can a possible crash if we assign directly
dispatch_async(dispatch_get_main_queue(), ^{
_imageWithBlock.image = image;
});
});
Use this sample code from Apple to solve your problem of lazy loading.
I have a function that gets called recursively to get new data from the database depending on the latest date. My problem is that this recursive call made causes memory usage to increase. I have commented out code to see what line is cause 300kb to be Allocated every time, so whats happening is that every time the NSURLConnection is hit i get memory usage to be increased by 300kb every 10 seconds.
This is the method that gets called recursively:
-(NSString*)setupPhpCall:(NSString*)requestString :(NSString*)sciptPage{
#autoreleasepool {
//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]; //THIS LINE CAUSES THE MEMORY USAGE TO INCREASE
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;
}
This is what i do with the response:
-(void)recurseForumActivity{
#autoreleasepool {
__block __weak dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
myRequestStringForum = [NSString stringWithFormat:#"lastDate=%#&threadTitle=%#&threadCountry=%#&threadCategory=%#&threadSubCategory=%#&getData=0",lastDateForumActivity,searchThreadTitle, searchThreadCountry, searchThreadCategory, searchThreadSubCategory];
responseForum = [self setupPhpCall:myRequestStringForum :#"xxx.php"];
[NSThread sleepForTimeInterval:2.0f];
dispatch_async(dispatch_get_main_queue(), ^{
if(responseForum.length > 0 && ![responseForum isEqualToString:#"[]"]){
labelNewForumThreads.text = [NSString stringWithFormat:#"%# new threads...", responseForum];
if(imageviewForumAlert == NULL){
UIImage *image = [UIImage imageNamed:#"alert.png"];
imageviewForumAlert = [UIImageView new];
[viewNav1 addSubview:imageviewForumAlert];
imageviewForumAlert.translatesAutoresizingMaskIntoConstraints = NO;
imageviewForumAlert.image = image;
NSDictionary *viewsDictionary = #{#"imageviewForumAlert":imageviewForumAlert};
NSArray *constraint_H = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-19-[imageviewForumAlert(12)]-19-|"
options:0
metrics:nil
views:viewsDictionary];
NSArray *constraint_V = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-19-[imageviewForumAlert(12)]-19-|"
options:0
metrics:nil
views:viewsDictionary];
[self.view addConstraints:constraint_H];
[self.view addConstraints:constraint_V];
}else{
imageviewForumAlert.hidden = NO;
}
/**NSDictionary *dic = [response JSONValue];
if((NSNull*)dic != [NSNull null]){
labelNewForumThreads.text = [NSString stringWithFormat:#"%d new threads...", dic.count];
}**/
}else{
imageviewForumAlert.hidden = YES;
labelNewForumThreads.text = [NSString stringWithFormat:#"%d new threads...", 0];
}
/**else{
labelNewForumThreads.text = [NSString stringWithFormat:#"%d new threads...", 0];
}**/
myRequestStringForum = #"";
responseForum = #"";
concurrentQueue = nil;
[self recurseForumActivity];
});
});
}
}
I have an app that has a tableviewcontroller with this viewDidLoad:
- (void)viewDidLoad{
[super viewDidLoad];
// begin animating the spinner
[self.spinner startAnimating];
[SantiappsHelper fetchUsersWithCompletionHandler:^(NSArray *users) {
self.usersArray = [NSMutableArray array];
for (NSDictionary *userDict in users) {
[self.usersArray addObject:[userDict objectForKey:#"username"]];
}
//Reload tableview
[self.tableView reloadData];
}];
}
The Helper Class method is this:
+(void)fetchUsersWithCompletionHandler:(Handler)handler {
NSString *urlString = [NSString stringWithFormat:#"http://www.myserver.com/myApp/fetchusers.php"];
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:10];
[request setHTTPMethod: #"GET"];
__block NSArray *usersArray = [[NSArray alloc] init];
//A
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// Peform the request
NSURLResponse *response;
NSError *error = nil;
NSData *receivedData = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
if (error) {
// Deal with your error
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
NSLog(#"HTTP Error: %d %#", httpResponse.statusCode, error);
return;
}
NSLog(#"Error %#", error);
return;
}
NSString *responseString = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];
usersArray = [NSJSONSerialization JSONObjectWithData:[responseString dataUsingEncoding:NSASCIIStringEncoding] options:0 error:nil];
if (handler){
dispatch_async(dispatch_get_main_queue(), ^{
handler(usersArray);
});
}
});
}
The above code was suggested to me and it makes sense from what I know about GCD. Everything runs on the main queue, but before it dispatches to a background queue before the NSURLConnection synchronous call. After it gets the data it fills the usersArray and should return it to the main queue. The usersArray is populated and when it tests for if handler, it moves to the dispatch_asynch(dispatch_get_main_queue () line. But when it returns to the main queue to process the array dictionaries, the NSArray *users is empty. The app crashes with this error:
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
If I comment out the dispatch_async(dispatch_get_main_queue() code to look like this:
if (handler){
//dispatch_async(dispatch_get_main_queue(), ^{
handler(usersArray);
//});
}
It works fine...well kinda, its a little sluggish. Why is this failing?
Replacing
dispatch_async(dispatch_get_main_queue(),
With:
dispatch_sync(dispatch_get_main_queue(),
REASON:
dispatch_sync will wait for the block to complete before execution
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!
When I start the simulator and the application starts and I click on the UI I get EXC_BAD_ACCESS for NSString *strLength = [NSString stringWithFormat:#"%d", [postStr length]]; and for [req setHTTPBody:[_postStr dataUsingEncoding:NSUTF8StringEncoding. I dont know why this happens. If I uninstall the app but keep the simulator open and run it again I get no errors. Any help would be great. Code is below.
#import "LocavoreRetroAPIAdapter.h"
//Class extention declares a method that is private to the class
#interface LocavoreRetroAPIAdapter ()
-(NSMutableURLRequest *)initRequest:(NSURL *)url method:(NSString *)method;
#end
#implementation LocavoreRetroAPIAdapter
//Called when this class is first initialized
-(id) initWithName:(NSString *)postStr webService:(NSString *)webService spinner: (UIActivityIndicatorView *)spinner{
if(self = [super init]){
_postStr = postStr;
_baseURL = #"http://base/api/";
_webService = webService;
_spinner = spinner;
_result = nil;
}
return self;
}
//Request to Locavore API restful web services
-(void) conn:(NSString *)method{
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
__block NSDictionary *resultBlock = nil;
dispatch_sync(concurrentQueue, ^{
/* Download the json here */
//Create webservice address
NSString *webService = [_baseURL stringByAppendingString:_webService];
//Create the url
NSURL *url = [NSURL URLWithString:webService];
//Create error object
NSError *downloadError = nil;
//Create the request
NSMutableURLRequest *req = [self initRequest:url method:method];
if(req != nil){
//Request the json data from the server
NSData *jsonData = [NSURLConnection
sendSynchronousRequest:req
returningResponse:nil
error:&downloadError];
NSError *error = nil;
id jsonObject = nil;
if(jsonData !=nil){
/* Now try to deserialize the JSON object into a dictionary */
jsonObject = [NSJSONSerialization
JSONObjectWithData:jsonData
options:NSJSONReadingAllowFragments
error:&error];
}
//Handel the deserialized object data
if (jsonObject != nil && error == nil){
NSLog(#"Successfully deserialized...");
if ([jsonObject isKindOfClass:[NSDictionary class]]){
resultBlock = (NSDictionary *)jsonObject;
//NSLog(#"Deserialized JSON Dictionary = %#", resultBlock);
}
else if ([jsonObject isKindOfClass:[NSArray class]]){
NSArray *deserializedArray = (NSArray *)jsonObject;
NSLog(#"Deserialized JSON Array = %#", deserializedArray);
} else {
/* Some other object was returned. We don't know how to deal
with this situation, as the deserializer returns only dictionaries
or arrays */
}
}
else if (error != nil){
NSLog(#"An error happened while deserializing the JSON data.");
}else{
NSLog(#"No data could get downloaded from the URL.");
[self conn:method];
}
}
});
dispatch_sync(dispatch_get_main_queue(), ^{
/* Check if the resultBlock is not nil*/
if(resultBlock != nil){
/*Set the value of result. This will notify the observer*/
[self setResult:resultBlock];
[_spinner stopAnimating];
}
});
});
}
//Configure the request for a post/get method
- (NSMutableURLRequest *)initRequest:(NSURL *)url method:(NSString *)method{
//Create the request
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
//Get the string length
NSString *strLength = [NSString stringWithFormat:#"%d", [_postStr length]];
//Specific to requests that use method post/get
//Configure the request
if([method isEqualToString:#"POST"]){
[req addValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content- Type"];
[req addValue:strLength forHTTPHeaderField:#"Content-Length"];
[req setHTTPMethod:#"POST"];
}else if([method isEqualToString:#"GET"]){
[req addValue:#"text/xml; charset=utf-8" forHTTPHeaderField:#"Content-Type"];
[req addValue:strLength forHTTPHeaderField:#"Content-Length"];
[req setHTTPMethod:#"GET"];
}else{
return nil;
}
//Set the HTTP Body
[req setHTTPBody:[_postStr dataUsingEncoding:NSUTF8StringEncoding]];
//Return the request
return req;
}
//Called when this object is destroyed
- (void)dealloc {
NSLog(#"DEALLOC LocavoreRetroAPIAdapter");
[super dealloc];
[_baseURL release];
[_result release];
}
#end
Familiarize yourself the with memory management and object lifetime rules. Your code is crashing because you do not retain (or copy) the arguments within your init... method, and they are being deallocated. Change your init... method to:
-(id) initWithName:(NSString *)postStr webService:(NSString *)webService spinner: (UIActivityIndicatorView *)spinner{
if(self = [super init]){
_postStr = [postStr copy];
_baseURL = #"http://base/api/";
_webService = [webService copy];
_spinner = [spinner retain];
}
return self;
}
Be sure to release the three instance variables you are now copying or retaining in your dealloc method. Also call [super dealloc] as the last step in that method but that's not the source of your problem right now.
//Called when this object is destroyed
- (void)dealloc {
NSLog(#"DEALLOC LocavoreRetroAPIAdapter");
[_postStr release];
[_webService release];
[_spinner release];
[_result release];
[super dealloc];
}
Notice I removed the call to [_baseURL release] from your dealloc as you did not retain it so you do not own the object. If you didn't create an object with alloc or new, and didn't call retain or copy on it, then you don't own the object, so you must not release it.