I have Table View Controller with collection of cells, each cell displays 2 values:
–––––––––––––––––––––––––
| cell1 | val1 | val2 |
–––––––––––––––––––––––––
| cell2 | val1 | val2 |
–––––––––––––––––––––––––
…
I need to perform a serial synchronous operation with these cells with algorithm like this:
setup core data & update tableView UI
iterate cells
2.1 update tableViewCell UI
2.2 iterate values
2.2.1 update value UI
2.2.2 download new value
2.2.2.1 if failed repeat iteration
2.2.3 save & update value UI
2.3 update tableViewCell UI
save & update tableView UI
I’m using NSOperationQueue with NSBlockOperations:
- (void)startOperation:(id)sender {
operationQueue = [NSOperationQueue new];
operationQueue.name = #"Operation Queue";
operationQueue.maxConcurrentOperationCount = 1;
[operationQueue waitUntilAllOperationsAreFinished];
NSBlockOperation * prepareCoreData = [[NSBlockOperation alloc] init];
[prepareCoreData addExecutionBlock:^{
NSLog(#"Prepare Core Data");
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(#"Update tableView UI");
}];
}];
[operationQueue addOperation:prepareCoreData];
NSBlockOperation * iterateCells = [[NSBlockOperation alloc] init];
__weak NSBlockOperation * weakIterateCells = iterateCells;
[iterateCells addExecutionBlock:^{
for (Item * item in self.items.fetchedObjects) {
if ([weakIterateCells isCancelled]) break;
NSBlockOperation * setCellUI = [[NSBlockOperation alloc] init];
[setCellUI addExecutionBlock:^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(#" Set tableViewCell UI");
}];
}];
[operationQueue addOperation:setCellUI];
NSBlockOperation * iterateValues = [[NSBlockOperation alloc] init];
__weak NSBlockOperation * weakIterateValues = iterateValues;
[iterateValues addExecutionBlock:^{
for (SearchEngine * s in self.searchEngines) {
if ([weakIterateValues isCancelled]) break;
NSBlockOperation * setValueUI = [[NSBlockOperation alloc] init];
[setValueUI addExecutionBlock:^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(#" Set Value UI");
}];
}];
[operationQueue addOperation:setValueUI];
NSBlockOperation * getValue = [[NSBlockOperation alloc] init];
[getValue addExecutionBlock:^{
NSLog(#" Download Value");
}];
[getValue addDependency:setCellUI];
[operationQueue addOperation:getValue];
NSBlockOperation * updateValue = [[NSBlockOperation alloc] init];
[updateValue addExecutionBlock:^{
NSLog(#" Save Data");
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(#" Update Value UI");
}];
}];
[updateValue addDependency:getValue];
[operationQueue addOperation:updateValue];
}
}];
[iterateValues addDependency:setCellUI];
[operationQueue addOperation:iterateValues];
NSBlockOperation * updateCellUI = [[NSBlockOperation alloc] init];
[updateCellUI addExecutionBlock:^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(#" Update tableViewCell UI");
}];
}];
[updateCellUI addDependency:iterateValues];
[operationQueue addOperation:updateCellUI];
};
}];
[iterateCells addDependency:prepareCoreData];
[operationQueue addOperation:iterateCells];
NSBlockOperation * completeOperation = [[NSBlockOperation alloc] init];
[completeOperation addExecutionBlock:^{
NSLog(#"Save Data");
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(#"Update tableView UI");
}];
}];
[completeOperation addDependency:iterateCells];
[operationQueue addOperation:completeOperation];
}
With this code, last operation (#3) executes before all iterations are done.
I'm looking for suggestions on how to properly nest operations and queues to maintain serial order.
Related
I am trying to load an image in the background from a url. The code works great if all I pass is the NSUrl. If I try to pass an NSArray with additional variables, it never gets called:
This code works great, LoadImage2 is called which in turn nicely calls ImageLoaded2.
- (void)LoadBackgroundImage2: (char*)pImageURL
{
NSString* pImageURLString = [NSString stringWithFormat:#"%s", pImageURL];
NSLog( #"LoadBackgroundImage2: %#", pImageURLString );
NSOperationQueue *queue = [NSOperationQueue new];
NSInvocationOperation *operation = [[NSInvocationOperation alloc]
initWithTarget:self
selector:#selector(LoadImage2:)
object:pImageURLString];
[queue addOperation:operation];
[operation release];
}
- (void)LoadImage2: (NSString*)pImageURL
{
NSLog( #"LoadImage2: %#", pImageURL );
NSData* imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:pImageURL]];
UIImage* image = [[[UIImage alloc] initWithData:imageData] autorelease];
[imageData release];
[self performSelectorOnMainThread:#selector(ImageLoaded2:) withObject:image waitUntilDone:NO];
}
This code does not work. LoadImage never gets called:
- (void)LoadBackgroundImage: (char*)pImageURL :(int)textureID :(int)textureType
{
printf( "LoadBackgroundImage( %s, %d, %d)\n", pImageURL, textureID, textureType );
NSString* pImageURLString = [NSString stringWithFormat:#"%s", pImageURL];
NSArray* pUrlAndReferences = [[[NSArray alloc] initWithObjects: pImageURLString, textureID, textureType, nil] autorelease];
NSOperationQueue *queue = [[NSOperationQueue new] autorelease];
NSInvocationOperation *operation = [[NSInvocationOperation alloc]
initWithTarget:self
selector:#selector(LoadImage:)
object:pUrlAndReferences];
[queue addOperation:operation];
[operation release];
}
- (void)LoadImage: (NSArray*)pUrlAndReferences
{
NSString* pImageUrl = [pUrlAndReferences objectAtIndex: 0];
int textureId = [ [ pUrlAndReferences objectAtIndex: 1 ] intValue ];
int textureType = [ [ pUrlAndReferences objectAtIndex: 2 ] intValue ];
NSLog( #"\n\nLoadImage: %#, %d, %d\n", pImageUrl, textureId, textureType );
NSData* pImageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:pImageUrl]];
UIImage* pImage = [[[UIImage alloc] initWithData:pImageData] autorelease];
NSArray* pImageAndReferences = [[[NSArray alloc] initWithObjects: pImage, textureId, textureType, nil] autorelease];
[pImageData release];
[self performSelectorOnMainThread:#selector(ImageLoaded:) withObject:pImageAndReferences waitUntilDone:NO];
}
Anyone have any ideas why LoadImage doesn't get called?
Thanks.
My guess is your not retaining your queue. Here's what's happening
Your array (autoreleased) goes into the NSInvocationOperation and it's being retained (no problem)
Your NSInvocationOperation goes into the queue (retained) and then it's released. No problem here since the retain count is still one: 1 (alloc) + 1 (retain) - 1 (release) = 1 = not dealloced.
Your queue is alloced (new = alloc+init) and then autoreleased, but it's not being retained elsewhere. Here comes the problem: since you have autoreleased the queue, once the method LoadBackgroundImage is finished the queue has a retain count of 0 and it's automatically released, therefore, your Invocation won't be executed.
You can try if that's the problem by just removing the autorelease call from the queue. If I'm correct your code should work. But be aware that that is not a good solution because you're loosing memory. It's just to see if it works.
You should definitely make a class, a singleton, an instance variable or whatever you like to retain that instance of the queue. Also, it's a good idea to only have one queue for all the LoadBackgroundImage calls instead of creating a new queue every time.
I having trouble with my threads.
After i segue a couple of times between 2 screen when the thread is busy. The thread don't perform every line.., The breakpoint just disappear when it has to return to the main thread.
Can somebody please help me ?
I release the thread when the view is unload.
Thanks,
- (void)fetchFeedDataIntoDocument
{
NSString * labelString = [NSString stringWithFormat:#"Feed Fetcher %#", self.pageTitle];
const char *label = [labelString UTF8String];
self.fetchF = dispatch_queue_create(label, NULL);
dispatch_async(self.fetchF, ^{
NSArray *feeds = [FeedFetcher getDataForJson:self.pageTitle downloadBy:#"up"];
NSDictionary *lastfeed;
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate getManagedObjectContext];
if ([feeds count] > 0)
{
lastfeed = [feeds objectAtIndex:0];
[FeedFetcher setLastUpdateIdToCatgorie:self.pageTitle WithId:[lastfeed objectForKey:#"id"] AndPulishDate:[lastfeed objectForKey:#"publish_up"]];
}
for (NSDictionary *feedInfo in feeds) {
[Feed FeedWithInfo:feedInfo InManageObject:context];
}
NSError *error = nil;
[context save:&error];
if (error){
NSLog(#"Error save : %#", error);}
dispatch_async(dispatch_get_main_queue(), ^{
[self setupFetchedResultsController];
[self.tableView reloadData];
[self downloadImagesForFeeds:feeds];
});
});
You are accessing the managedObjectContext from a different thread from where it was created. This is Core Data Threading Rule #1.
You are getting the MOC from the app delegate. If it's the normal Xcode-generated MOC, then it is created with thread-confinement concurrency. You can't even call performBlock with it. You can only access that MOC from the main thread. Period. Anything else is playing with fire, at best.
If you want to do all the work in a separate thread, you need a separate MOC as well. Like this (just typed - not compiled)...
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
moc.parentContext = appDelegate.managedObjectContext;
[moc performBlock:^{
// Go get your remote data and whatever you want to do
// Calling save on this MOC will push the data up into the "main" MOC
// (though it is now in the main MOC it has not been saved to the store).
[moc save:&error];
}];
Which would translate into something like this...
- (void)fetchFeedDataIntoDocument
{
NSString * labelString = [NSString stringWithFormat:#"Feed Fetcher %#", self.pageTitle];
const char *label = [labelString UTF8String];
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *mainContext = [appDelegate getManagedObjectContext];
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
context.parentContext = mainContext;
[context performBlock:^{
NSArray *feeds = [FeedFetcher getDataForJson:self.pageTitle downloadBy:#"up"];
NSDictionary *lastfeed;
if ([feeds count] > 0)
{
lastfeed = [feeds objectAtIndex:0];
[FeedFetcher setLastUpdateIdToCatgorie:self.pageTitle WithId:[lastfeed objectForKey:#"id"] AndPulishDate:[lastfeed objectForKey:#"publish_up"]];
}
for (NSDictionary *feedInfo in feeds) {
[Feed FeedWithInfo:feedInfo InManageObject:context];
}
NSError *error = nil;
[context save:&error];
if (error){
NSLog(#"Error save : %#", error);}
DO you really want to continue on error?
dispatch_async(dispatch_get_main_queue(), ^{
// Data has been pushed into main context from the background
// but it still needs to be saved to store...
// Do not forget to perform error handling...
NSError *error = nil;
[mainContext save:&error];
[self setupFetchedResultsController];
[self.tableView reloadData];
[self downloadImagesForFeeds:feeds];
});
});
EDIT
The code generated by Xcode for creating the MOC uses init, which uses NSConfinementConcurrencyType. You can replace it with MainConcurrency, without any problems, but get several benefits.
In your app delegate file, replace...
__managedObjectContext = [[NSManagedObjectContext alloc] init];
with this...
__managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
Now, your main MOC can be "parented" and you can also call performBlock on it as well.
what about doing this...
-(void)functionToCallFetch {
[self performSelectorInBackground:#selector(fetchFeedDataIntoDocument) withObject:nil];
}
- (void)fetchFeedDataIntoDocument
{
NSArray *feeds = [FeedFetcher getDataForJson:self.pageTitle downloadBy:#"up"];
NSDictionary *lastfeed;
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate getManagedObjectContext];
if ([feeds count] > 0)
{
lastfeed = [feeds objectAtIndex:0];
[FeedFetcher setLastUpdateIdToCatgorie:self.pageTitle WithId:[lastfeed objectForKey:#"id"] AndPulishDate:[lastfeed objectForKey:#"publish_up"]];
}
for (NSDictionary *feedInfo in feeds) {
[Feed FeedWithInfo:feedInfo InManageObject:context];
}
NSError *error = nil;
[context save:&error];
if (error){
NSLog(#"Error save : %#", error);}
//dispatch_async(dispatch_get_main_queue(), ^{
// [self setupFetchedResultsController];
// [self.tableView reloadData];
// [self downloadImagesForFeeds:feeds];
//});
[self performSelectorOnMainThread:#selector(setupFetchedResultsController) withObject:nil waitUntilDone:NO];
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
[self performSelectorOnMainThread:#selector(downloadImagesForFeeds:) withObject:feeds waitUntilDone:NO];
}
Maybe that would work better?
Change following code..
dispatch_queue_t queue1 = dispatch_queue_create("com.MyApp.AppTask",NULL);
dispatch_queue_t main = dispatch_get_main_queue();
dispatch_async(queue1,
^{
dispatch_async(main,
^{
[self setupFetchedResultsController];
[self.tableView reloadData];
[self downloadImagesForFeeds:feeds];
});
});
Hope, this will help you
Did you try building a method like :
- (void)methodToBePerformedOnMainThread{
[self setupFetchedResultsController];
[self.tableView reloadData];
[self downloadImagesForFeeds:feeds];
}
and call it with
[self performSelectorOnMainThread:#selector(methodToBePerformedOnMainThread) withObject:nil waitUntilDone:NO];
at the end of fetchFeedDataIntoDocument
Edit :
Did you try to wrap it with NSOperationQueue in place of dispatch_queue ?
Like :
NSOperationQueue *operationQueue = [NSOperationQueue new];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(fetchFeedDataIntoDocument) object:nil];
if(operation != nil){
[operationQueue addOperation:operation];
I am using a Window based application and then loading up my initial navigationview based controller in the appDelegate part - application didFinishLaunchingwithOptions (UPDATED)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
self.window.rootViewController = self.navigationController;
[self.window makeKeyAndVisible];
return YES;
}
The method of getting the data from the webservice is usually triggered in this method of viewdidload .... in the [self getData]; method.
- (void)viewDidLoad
{
[super viewDidLoad];
if (_refreshHeaderView == nil) {
EGORefreshTableHeaderView *view = [[EGORefreshTableHeaderView alloc] initWithFrame:CGRectMake(0.0f, 0.0f - self.tableView.bounds.size.height, self.view.frame.size.width, self.tableView.bounds.size.height)];
view.delegate = self;
[self.tableView addSubview:view];
_refreshHeaderView = view;
[view release];
}
// update the last update date
[_refreshHeaderView refreshLastUpdatedDate];
[self loadImages];
HUDMB = [[MBProgressHUD alloc] initWithView:self.view];
[self.view addSubview:HUDMB];
HUDMB.dimBackground = YES;
// Regiser for HUD callbacks so we can remove it from the window at the right time
HUDMB.delegate = self;
HUDMB.labelText = #"Loading..";
[HUDMB show:TRUE];
[self getData];
[self.tableView reloadData];
}
Before loading it if the user is not registered/ does not have a credentials present then it takes the user to a login view controller .
- (void)loadView {
[super loadView];
if([Preferences isValid]?YES:NO)
{
}
else
{
int r = arc4random() % 5;
switch (r) {
case 0:
{
loginViewController *sampleView = [[loginViewController alloc] initWithNibName:#"loginViewController" bundle:nil];
[self.navigationController presentModalViewController:sampleView animated:YES];
[sampleView release];
}
break;
case 1:
{
loginViewController *sampleView = [[loginViewController alloc] initWithNibName:#"loginViewController" bundle:nil];
[sampleView setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
[self.navigationController presentModalViewController:sampleView animated:YES];
[sampleView release];
}
break;
case 2:
{
loginViewController *sampleView = [[loginViewController alloc] initWithNibName:#"loginViewController" bundle:nil];
[sampleView setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
[self.navigationController presentModalViewController:sampleView animated:YES];
[sampleView release];
}
break;
case 4:
{
loginViewController *sampleView = [[loginViewController alloc] initWithNibName:#"loginViewController" bundle:nil];
[sampleView setModalTransitionStyle:UIModalTransitionStylePartialCurl];
[self.navigationController presentModalViewController:sampleView animated:YES];
[sampleView release];
}
break;
case 3:
{
loginViewController *sampleView = [[loginViewController alloc] initWithNibName:#"loginViewController" bundle:nil];
[sampleView setModalTransitionStyle:UIModalTransitionStyleCoverVertical];
[self.navigationController presentModalViewController:sampleView animated:YES];
[sampleView release];
}
break;
default:
break;
}
}
}
I think the getdata function is making a lot of troubles. so let me add that and also the corresponding functions i use for data retrieval and serialization.
-(void)getData{
NSLog(#"loggin into call sheet page");
[self getCallSheetData];
NSLog(#"after call sheet");
}
- (void)getCallSheetData
{
NSString *postCMD = #"Blah... Blah... Blah...";
NSMutableData *postDataCMD = (NSMutableData *)[postCMD dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSURL *url = [NSURL URLWithString:[Preferences getURL]];
// NSLog(#"get call Sheet");
NSString *postLengthCMD = [NSString stringWithFormat:#"%d", [postDataCMD length]+1];
// NSLog(#"The CS String: %#",[Preferences retriveSession]);
requestCMD = [ASIHTTPRequest requestWithURL:url];
[requestCMD setURL:url];
[requestCMD addRequestHeader:#"Content-Length" value:postLengthCMD];
[requestCMD addRequestHeader:#"Content-Type" value:#"application/x-www-form-urlencoded"];
[requestCMD addRequestHeader:#"user-Agent" value:#"Mobile 1.4" ];
[requestCMD addRequestHeader:#"Content-Language" value:#"en-US"];
[requestCMD addRequestHeader:#"Accept-Encoding" value:#"gzip"];
[requestCMD addRequestHeader:#"Cookie" value:[Preferences retriveSession]];
[requestCMD setPostBody:postDataCMD];
[requestCMD setDelegate:self];
[requestCMD startAsynchronous];
}
- (void)requestFinished:(ASIHTTPRequest *)request
{
NSMutableArray *CSArray = [[NSMutableArray alloc] init];
if( [[request responseString] isEqualToString:#"OK"]){
return;
}
// Use when fetching binary data
NSData *responseData = [request responseData];
NSDateFormatter *formatter1=[[NSDateFormatter alloc]init];
[formatter1 setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss'Z'"];
NSTimeZone *gmt = [NSTimeZone timeZoneWithAbbreviation:#"GMT"];
[formatter1 setTimeZone:gmt];
NSDateFormatter *formatterFinal=[[NSDateFormatter alloc]init];
[formatterFinal setDateStyle:NSDateFormatterMediumStyle];
[formatterFinal setTimeStyle: NSDateFormatterShortStyle];
[formatterFinal setLocale:[NSLocale currentLocale]];
JSONDecoder *jCSArray = [[JSONDecoder alloc]init];
NSMutableArray *theObject = [jCSArray objectWithData:responseData];
// CallSheet= [NSMutableArray arrayWithCapacity:50];
for(id key in theObject)
{
csr = [[CallSheetRecord alloc] init];
// cName,cCompany,cId,cMemberId,crcStatus,crcTarget,cImportance,cLastContact
csr.importance = #"1";
csr.rcstatus = #"1";
csr.rcTarget = #"1";
csr.company = #"";
csr.lastContact= #"";
if([key valueForKey:#"firstName"] != Nil)
{
csr.name = [NSString stringWithFormat:#"%#",[key valueForKey:#"firstName"]] ;
if ([key valueForKey:#"lastName"] != Nil) {
csr.name = [csr.name stringByAppendingString:#" "];
csr.name = [csr.name stringByAppendingString:[NSString stringWithFormat:#"%#",[key valueForKey:#"lastName"]]];
}
}
if([key valueForKey:#"company"] != Nil)
{
csr.company = [NSString stringWithFormat:#"%#",[key valueForKey:#"company"]] ;
}
if([key valueForKey:#"memberId"] != Nil)
{
csr.memberId = [NSString stringWithFormat:#"%#",[key valueForKey:#"memberId"]] ;
}
if([key valueForKey:#"id"] != Nil)
{
csr.id_ = [NSString stringWithFormat:#"%#",[key valueForKey:#"id"]] ;
}
if([key valueForKey:#"lastContact"] != Nil)
{
NSDate *finalDate =[formatter1 dateFromString:[NSString stringWithFormat:#"%#",[key valueForKey:#"lastContact"]]];
//NSString *timeStamp = [formatter1 stringFromDate:[finalDate descriptionWithLocale:[NSLocale currentLocale]]];
//NSLog(#"Time stamp : %#",[finalDate descriptionWithLocale:[NSLocale currentLocale]]);
//NSLog(#"Time stamp : %#",timeStamp);
//csr.lastContact = [key valueForKey:#"lastContact"];
csr.lastContact = [formatterFinal stringFromDate:finalDate];
}
if([key valueForKey:#"importance"] != Nil)
{
csr.importance = [NSString stringWithFormat:#"%#",[key valueForKey:#"importance"]];
}
if([key valueForKey:#"rcStatus"] != Nil)
{
csr.rcstatus= [NSString stringWithFormat:#"%#",[key valueForKey:#"rcStatus"]] ;
}
if([key valueForKey:#"rcTarget"] != Nil)
{
csr.rcTarget = [NSString stringWithFormat:#"%#",[key valueForKey:#"rcTarget"]] ;
}
[CSArray addObject:csr];
}
CSD = [CSArray mutableCopy];
[CSArray release];
[formatter1 release];
[formatterFinal release];
//CallSheetArray = [CSArray mutableCopy];
//[csr release];
[jCSArray release];
[HUDMB hide:TRUE];
[self.tableView reloadData];
}
- (void)requestFailed:(ASIHTTPRequest *)request
{
NSError *error = [request error];
UIAlertView *message = [[[UIAlertView alloc] initWithTitle:#"Hello World!"
message:[NSString stringWithFormat:#"%#",error]
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil]autorelease];
[message show];
}
then right after that i try to load the table with [self getData]; that i get make using webservice call using asiHTTP .. for this question lets say it takes 3 seconds time to get the data and then deserialize it . now... my question is it works out okey in the later runs as I store the username and password in a seure location... but in the first instance.... i am not able to get the data to laod to the tableview... I have tried a lot of things...
1. Initially the data fetch methods was in a diffrent methods.. so i thought that might be the problem as then moved it the same place as the tbleviewController(navigationController)
2. I event put in the Reload data at the end of the functionality for the data parsing and deserialization... nothing happens.
3. The screen is black screen with nothing displayed on it for a good 5 seconds in the consecutive launches of the app.... so could we have something like a MBPorgressHUD implemented for the same.
could any one please help for these scenarios and guidance as to what paths to take from here. ...
Update:
Bounty Just answer 2 of the questions the best you can... The property was a thing i tried but it did not work... but my problem is not that... my problem is my screen is not able to load data from a webservice after i login until i do the "pull to refresh". Next eventualy starting my app takes about 5 seconds to show the screen(till that it shows a black screen)... what is the best way to show the apps Blank screen to the End- user when they make it to the app. I dont want the user to think the app is not working or has adversly affected their phone.
There are a number of potential issues here. The first is related to [self getData] - is this blocking or not?
1. getData is blocking (synchronous)
If this method is blocking (i.e. does not return immediately) then this will explain why you are not seeing anything on the screen for a few seconds. You are currently calling this method in the main thread, so it will stop the UI from being updated until is complete.
You can make it run in the background by doing this:
- (void) getDataInBackgroundThread
{
NSAutoReleasePool *pool = [[NSAutoReleasePool alloc] init];
[self getData];
// force the table to redraw - UI operations must be on the main thread
[tableView performSelectorOnMainThread:#selector( reloadData )];
[pool drain];
}
In your main code, now instead of calling [self getData] call [self getDataInBackgroundThread];
2. getData is not blocking (asynchronous)
Even if getData is blocking, if it runs in the main thread it will still cause the UI to hang. Also, if it uses a lot of processor time it will slow down the UI and give the appearance of not running in the background.
To address this issue, you would need make sure the lengthy operation of getData really is not running in the main thread and also put in sleep() calls every so often to give some time for the UI to update.
You still haven't given enough info for people to really help, we're just guessing.
It is highly likely the delay is caused by getData. Try commenting that out or bracket it with NSLog statements to verify.
You should show us more code, specifically what is happening in getData and any delegate or notification handling implementations related to loading data.
You don't need to explicitly call [tableView reloadData] in viewDidLoad. The tableView automatically loads data the first time it is displayed.
As #dmatt said, if you are loading data asynchronously then getData would return immediately with no delay. You would typically would either respond to delegate messages or notifications (depending on how you are loading data) to reload the table when the data is finished loading and serializing.
There are lots of folks on SO who are happy to try and help you if given enough info. Especially when you offer a bounty.
The thing is i have to make Custom cell of UITable, i have to call large webservice with many data which will take longer tym to load it once. So i want to apply threading in it. now my que is. can we at run -time insert value one by one in custom cell , as user will see data comming one by one at run time..?? if yes how we can do that.
yes you can ... try this
- (void)viewDidLoad
{
myApp = (myAppDelegate *) [[UIApplication sharedApplication] delegate];
[self performSelectorInBackground:#selector(startParsing) withObject:nil];
[super viewDidLoad];
}
- (void) startParsing
{
[self performSelectorOnMainThread:#selector(startIndicator) withObject:nil waitUntilDone:NO];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSURL *url = [[NSURL alloc] initWithString:#"http://abc.com"];
NSData *data = [NSData dataWithContentsOfURL:url];
// 2 -- parsing
parser = [[VolMyParser alloc] init];
[parser parseXML:data];
[data release];
//[parser print];
[self performSelectorOnMainThread:#selector(updateTable) withObject:nil waitUntilDone:NO];
[pool release];
}
- (void) startIndicator
{
av.hidesWhenStopped = YES;
[av startAnimating];
}
- (void) updateTable
{
[av stopAnimating];
[myTable reloadData];
}
Hope its gives you an Idea...
I would like to wait this code to be executed before to continue but as these blocks are called assynchronously I don't know how to do???
NSURL *asseturl;
NSMutableArray *tmpListAsset = [[NSMutableArray alloc] init];
ALAssetsLibrary *library = [[[ALAssetsLibrary alloc] init] autorelease];
NSMutableArray *objectsToRemove = [[NSMutableArray alloc] init];
for (NSDictionary *dico in assetsList) {
asseturl = [NSURL URLWithString:[dico objectForKey:#"assetUrl"]];
NSLog(#"asset url %#", asseturl);
// Try to load asset at mediaURL
[library assetForURL:asseturl resultBlock:^(ALAsset *asset) {
// If asset doesn't exists
if (!asset){
[objectsToRemove addObject:dico];
}else{
[tmpListAsset addObject:[asseturl absoluteString]];
NSLog(#"tmpListAsset : %#", tmpListAsset);
}
} failureBlock:^(NSError *error) {
// Type your code here for failure (when user doesn't allow location in your app)
}];
}
GCD semaphore approach:
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
for (NSURL *url in self.assetUrls) {
dispatch_async(queue, ^{
[library assetForURL:url resultBlock:^(ALAsset *asset) {
[self.assets addObject:asset];
dispatch_semaphore_signal(sema);
} failureBlock:^(NSError *error) {
dispatch_semaphore_signal(sema);
}];
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
dispatch_release(sema);
/* Check out ALAssets */
NSLog(#"%#", self.assets);
The solution is here
http://omegadelta.net/2011/05/10/how-to-wait-for-ios-methods-with-completion-blocks-to-finish/
Note that assetForURL:resultBlock:failureBlock: will stuck if the main thread is waiting without RunLoop running. This is alternative ( cleaner :-) ) solution:
#import <libkern/OSAtomic.h>
...
ALAssetsLibrary *library;
NSMutableArray *assets;
...
__block int32_t counter = 0;
for (NSURL *url in urls) {
OSAtomicIncrement32(&counter);
[library assetForURL:url resultBlock:^(ALAsset *asset) {
if (asset)
[assets addObject:asset];
OSAtomicDecrement32(&counter);
} failureBlock:^(NSError *error) {
OSAtomicDecrement32(&counter);
}];
}
while (counter > 0) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
}
The easiest thing to do is to move your code to inside (at the end of) the resultBlock or the failureBlock. That way, your code will run in the correct order, and you will also retain asynchronous behaviour.
This is an easy way to do it. Maybe not as elegant as using GCD but it should get the job done ... This will make your method blocking instead of non-blocking.
__block BOOL isFinished = NO;
NSURL *asseturl;
NSMutableArray *tmpListAsset = [[NSMutableArray alloc] init];
ALAssetsLibrary *library = [[[ALAssetsLibrary alloc] init];
NSMutableArray *objectsToRemove = [[NSMutableArray alloc] init];
for (NSDictionary *dico in assetsList) {
asseturl = [NSURL URLWithString:[dico objectForKey:#"assetUrl"]];
NSLog(#"asset url %#", asseturl);
// Try to load asset at mediaURL
[library assetForURL:asseturl resultBlock:^(ALAsset *asset) {
// If asset doesn't exists
if (!asset){
[objectsToRemove addObject:dico];
}else{
[tmpListAsset addObject:[asseturl absoluteString]];
NSLog(#"tmpListAsset : %#", tmpListAsset);
}
if (objectsToRemove.count + tmpListAsset.count == assetsList.count) {
isFinished = YES;
}
} failureBlock:^(NSError *error) {
// Type your code here for failure (when user doesn't allow location in your app)
isFinished = YES;
}];
}
while (!isFinished) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01f]];
}