Why action is perform multiple time after loading is finish and why it queuing all the events? - ios

While loading when we click on back button or any cell of the table the action is called multiple times after loading is finish.Here the the code snippet that what i'm doing when I start the loading and stop the loading.
+(void)showLoader_OnView{
APP_DELEGATE.window.userInteractionEnabled = NO;
[MBProgressHUD showHUDAddedTo:APP_DELEGATE.window animated:YES];
}
To stop the loading:-
+(void)hideLoader {
APP_DELEGATE.window.userInteractionEnabled =YES;
[MBProgressHUD hideAllHUDsForView:APP_DELEGATE.window animated:YES];
}
please help me.
Update
actually i'm taking data from server. whenever user will go to next window then in viewWillAppear function i call a function which will hit the api to get the data.
-(void)performAutoSync
{
#try
{
if(self.shouldPerformAutoSync)//Necessary conditions to check the auto sync
{
[AppConstants showLoader_OnView]; //here i call the loader.
self.shouldPerformAutoSync = NO;
if(!self.isSyncing)
{
if(!syncBl)
{
syncBl = [[SyncBL alloc] init];
syncBl.delegate = self;
}
if(!syncDl)
syncDl = [[SyncDL alloc] init];
// [self saveModifiedDataForCurrentViewController];
[self delayToAutoSync];
NSMutableDictionary *dictMainData = [NSMutableDictionary new];
[dictMainData setObject:[syncDl fetchCompleteDataAndPrepareDictionary:YES] forKey:#"data"];//#"MainData"];
[syncBl performAutoSync:dictMainData];
}
}
}
#catch (NSException *exception) {
BILog(#"%#",exception);
}
}

Don't block the main thread.
Seeing that you invoke [AppConstants showLoader_OnView] from performAutoSync, and that showLoader_OnView in turn executes:
[MBProgressHUD showHUDAddedTo:APP_DELEGATE.window animated:YES]
I can only assume that performAutoSync is executed in the main thread. This, of course, blocks the UI until your operations are completed.
You should redesign so that you won't need all your state variables, globals, global calls, and take advantage of multi-threading.
Also, remove this, as it qualifies as a kludge;
APP_DELEGATE.window.userInteractionEnabled = NO

Related

Pushing view controller within block not working

What is the correct way to fire methods within a completion block (if this is even recommended)? Right now, I have an IBAction that calls a method that downloads information with a completion block signifying if the info was retrieved successfully or not. If it was, I want to push a view controller that will display that information, but at the moment, nothing is happening. I'm guessing it has something to do with main thread, gcd, etc...
__weak YTTMSetupViewController *weakSelf = self;
[mc downloadJson:^(BOOL success) {
if(success){
NSLog(#"sucess. metric count - %i",(int)mc.collection.count);
//info was downloaded. Push new view controller with info
YTTMMetricTableViewController *mtvc = [self.storyboard instantiateViewControllerWithIdentifier:#"YTTMMetricTableViewController"];
mtvc.group = (WAGroup*)[[WAMetricCollection sharedInstance].collection lastObject];
mtvc.hidesBottomBarWhenPushed = YES;
[weakSelf.navigationController pushViewController:mtvc animated:YES];
}
else{
NSLog(#"failure");
//display failure UI
}
NSLog(#"end of downloading");
[HUD dismissAfterDelay:0.5f animated:YES];
}];
Not sure if this is the right way to do it, but it worked.
I added a method that will push the vc on the main thread as so:
[weakSelf performSelectorOnMainThread:#selector(pushDetail) withObject:nil waitUntilDone:YES];
Completed Code:
__weak YTTMSetupViewController *weakSelf = self;
[mc downloadJson:^(BOOL success) {
if(success){
NSLog(#"sucess. metric count - %i",(int)mc.collection.count);
//info was downloaded. Push new view controller with info
[weakSelf performSelectorOnMainThread:#selector(pushDetail) withObject:nil waitUntilDone:YES];
}
else{
NSLog(#"failure");
//display failure UI
}
NSLog(#"end of downloading");
}];
}
-(void)pushDetail{
__weak YTTMSetupViewController *weakSelf = self;
YTTMMetricTableViewController *mtvc = [self.storyboard instantiateViewControllerWithIdentifier:#"YTTMMetricTableViewController"];
mtvc.group = (WAGroup*)[[WAMetricCollection sharedInstance].collection lastObject];
mtvc.hidesBottomBarWhenPushed = YES;
[weakSelf.navigationController pushViewController:mtvc animated:YES];
}
You could simply try wrapping the call with a dispatch_asynch block...
__weak YTTMSetupViewController *weakSelf = self;
[mc downloadJson:^(BOOL success) {
if(success){
NSLog(#"sucess. metric count - %i",(int)mc.collection.count);
dispatch_async(dispatch_get_main_queue(), ^{
//info was downloaded. Push new view controller with info
YTTMMetricTableViewController *mtvc = [self.storyboard instantiateViewControllerWithIdentifier:#"YTTMMetricTableViewController"];
mtvc.group = (WAGroup*)[[WAMetricCollection sharedInstance].collection lastObject];
mtvc.hidesBottomBarWhenPushed = YES;
[weakSelf.navigationController pushViewController:mtvc animated:YES];
});
}
else{
NSLog(#"failure");
//display failure UI
}
NSLog(#"end of downloading");
[HUD dismissAfterDelay:0.5f animated:YES];
}];
All UI updates must be performed on the main thread. Personally I prefer to do this through GCD as it produces more readable code than performSelectorOnMainThread. However, there's nothing wrong with performSelectorOnMainThread aside from personal preference in the case of calling a single UI update on the main thread following the execution of some completion block. Do note that, whichever one you choose, you should be consistent with what you use to guarantee that blocks are enqueued in the order you specified.
Working code aside, however, the convention Apple's frameworks seem to use is to perform all completion blocks on the main thread unless a queue is specified as a method parameter, in which case the completion block should be performed on that queue. So in this case I would recommend you edit your download handler class's downloadJson method to automatically perform the completion block on the main queue.

What is a nice way to queue calls for refreshing data?

I've gotten in a few cases when something receives multiple refresh calls in quick succession, eg:
- ViewController receives multiple KVO notifications.
- Datamanger class that is called from setters to refresh when multiple settings change.
Ideally I would like to execute only the last refresh call from a series (drop all the intermediate ones).
Right now I'm using an isRefreshing property and a needRefresh to block excessive refreshes, eg:
- (id)init {
...
[self observeValueForKeyPath:#"isRefreshing" ....];
}
- (void)setParameter:(NSInteger)parameter {
....
[self refresh];
}
/* and many more kinds of updates require a refresh */
- (void)setAnotherProperty:(NSArray*)array {
....
[self refresh];
}
- (void)refresh {
if (self.isRefreshing) {
self.needRefresh = YES;
return;
}
self.isRefreshing = YES;
...
self.isRefreshing = NO;
}
- observeValueForKeyPath..... {
if (!self.isRefreshing && self.needsRefresh) {
self.needsRefresh = NO;
[self refresh];
}
}
Is there a better solution for this kind of problem?
You can create a NSOperationQueue with concurrency set to one and only submit a new operation to it when its operation count is zero. (Or use cancellation logic to remove pending jobs so that only one new one is queued if there's a job in progress.)
What you're doing is reasonable for a single-threaded system but would become fairly complicated for multiple threads.
Looks like you should delay refreshing for a while.
You can use different techniques to do so. It is enough only one flag.
For example you may use async block to make a delay for a one main run-loop cycle
- (void)setParameter:(NSInteger)parameter {
....
[self requestRefrhesh];
}
- (void)setAnotherProperty:(NSArray*)array {
....
[self requestRefrhesh];
}
...
-(void) requestRefrhesh {
if (self.refreshRequested) {
return;
} else {
self.refreshRequested = YES;
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run in main UI thread
//make your UI changes here
self.refreshRequested = NO;
});
}
}

Objective C- Trouble updating UI on main thread

I am having some trouble updating my UI using performSelectorOnMainThread. Here is my situation. In my viewDidLoad I set up an activity indicator and a label. Then I call a selector to retrieve some data from a server. Then I call a selector to update the UI after a delay. Here's the code:
- (void)viewDidLoad
{
[super viewDidLoad];
self.reloadSchools = [[UIAlertView alloc] init];
self.reloadSchools.message = #"There was an error loading the schools. Please try again.";
self.reloadSchools.title = #"We're Sorry";
self.schoolPickerLabel = [[UILabel alloc]init];
self.schoolPicker = [[UIPickerView alloc] init];
self.schoolPicker.delegate = self;
self.schoolPicker.dataSource = self;
self.server = [[Server alloc]init];
schoolList = NO;
_activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[self.view addSubview:_activityIndicator];
[self.view bringSubviewToFront:_activityIndicator];
[_activityIndicator startAnimating];
[NSThread detachNewThreadSelector: #selector(getSchoolList) toTarget: self withObject: nil];
[self performSelector:#selector(updateUI) withObject:nil afterDelay:20.0];
}
The selector updateUI checks to see if the data was retrieved, and calls a selector on the main thread to update the UI accordingly. Here is the code for these parts:
-(void)updateUI
{
self.schools = [_server returnData];
if(!(self.schools == nil)) {
[self performSelectorOnMainThread:#selector(fillPickerView) withObject:nil waitUntilDone:YES];
}
else {
[self performSelectorOnMainThread:#selector(showError) withObject:nil waitUntilDone:YES];
}
}
-(void)showError {
NSLog(#"show error");
[_activityIndicator stopAnimating];
[self.reloadSchools show];
}
-(void)fillPickerView {
NSLog(#"fill picker view");
schoolList = YES;
NSString *schoolString = [[NSString alloc] initWithData:self.schools encoding:NSUTF8StringEncoding];
self.schoolPickerLabel.text = #"Please select your school:";
self.shoolArray = [[schoolString componentsSeparatedByString:#"#"] mutableCopy];
[self.schoolPicker reloadAllComponents];
[_activityIndicator stopAnimating];
}
When the selector fillPickerView is called the activity indicator keeps spinning, the label text doesn't change, and the picker view doesn't reload its content. Can someone explain to me why the method I am using isn't working to update my ui on the main thread?
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//load your data here.
dispatch_async(dispatch_get_main_queue(), ^{
//update UI in main thread.
});
});
First of all you should not be using detachNewThreadSelector. You should use GCD and submit your background task to an async queue. Threads are costly to create. GCD does a much better job of managing system resources.
Ignoring that, your code doesn't make a lot of sense to me. You submit a method, getSchoolList, to run on a background thread. You don't show the code that you are running in the background.
Then use performSelector:withObject:afterDelay to run the method updateUI on the main thread after a fixed delay of 20 seconds.
updateUI checks for self.schools, which presumably was set up by your background thread, and may or may not be done. If self.schools IS nil, you call fillPickerView using performSelectorOnMainThread. That doesn't make sense because if self.schools is nil, there is no data to fill the picker.
If self.schools is not nil, you display an error, again using performSelectorOnMainThread.
It seems to me that the logic on your check of self.schools is backwards. If it is nil you should display an error and if it is NOT nil you should fill the picker.
Next problem: In both cases you're calling performSelectorOnMainThread:withObject:waitUntilDone: from the main thread. Calling that method from the main thread doesn't make sense.
Third problem: It doesn't make sense to wait an arbitrary amount of time for a background task to run to completion, and then either succeed or fail. You won't have any idea what's going on for the full 20 seconds. If the background task finishes sooner, you'll never know.
Instead, you should have your background task notify the main thread once the task is done. That would be a valid use of performSelectorOnMainThread:withObject:waitUntilDone:, while calling it from the main thread is not. (Again, though, you should refactor this code to use GCD, not using threads directly.
It seems pretty clear that you are in over your head. The code you posted needs to be rewritten completely.

UITableView not being displayed after reloading data

I have a strange problem with my tableView.
I load data via JSON into my tableView. While the JSON is being requested from the web in another class, I show an activity indicator view in my current view and the tableView is hidden.
I ve got a delegate method, which is called as soon as the json is ready.
-(void)didReceivePlayers:(NSArray *)players {
[activityIndicator stopAnimating];
tableViewPlayers.hidden = false;
startButton.hidden = false;
playersData = [[NSMutableArray alloc] initWithArray:players];
[tableViewPlayers reloadData];
NSLog(#"done reloading");
}
The method is being called perfectly.
The code is pretty straight forward. I hide my activity indicator and show my tableView.
Then I call reloadData. It takes only a few milliseconds. BUT after reloadData, my activityIndicator is still shown and it takes several seconds to show my tableview, although the nslog is being called right away.
I also tried calling reload data in mainThread, but this did not change a thing.
Thanks for your help!
Be sure that the code is being executed on the main thread. You can use the main operation queue like this:
-(void)didReceivePlayers:(NSArray *)players {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[activityIndicator stopAnimating];
tableViewPlayers.hidden = false;
startButton.hidden = false;
playersData = [[NSMutableArray alloc] initWithArray:players];
[tableViewPlayers reloadData];
NSLog(#"done reloading");
}];
}

Make a chat for iOS with 2 UITableViews and a NSTimer to refresh them

I'm trying to make a chat. I've googled for samples and I followed several of them without success in this post I've asked for the first troubles I've found and I've applied every answer without success.
Which I'm trying is in an UIViewController I load to 2 subclasses of UIView with one UITableView each one. In one of the views I'll load the users list and in other the messages sended with a selected user from the first class.
First I've tried to use threading within each of the classes with the content but was crashing because memory warnings.
Now I'm using a NSTimer in the UIViewController that calls to update both classes, but still crashing.
This is the code:
-(void)chatStartUpdating
{
chatIsUpdating = YES;
chatLoop = [NSTimer scheduledTimerWithTimeInterval:5.0f target:self selector:#selector(backgroundChatUpdate) userInfo:nil repeats:YES];
}
-(void)chatStopUpdating
{
if (chatLoop == nil) return;
[chatLoop invalidate];
chatLoop = nil;
chatIsUpdating = NO;
}
-(void)updateChat
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[chatBar updateList:nil];
[chat messagesMustBeUpdated:nil];
[pool release];
}
-(void)backgroundChatUpdate
{
[self performSelectorOnMainThread:#selector(updateChat) withObject:nil waitUntilDone:NO];
//[self performSelectorInBackground:#selector(updateChat) withObject:nil];
}
If I run in background the list of messages become slow in scrolling, and after a few updates I start receiving mem warnings and the app crashes.
the methods Start and Stop updating are called from Main Thread when user pushes the chat button or some occurrence of the users list.
Anyone knows a good example of code to do something similar? Or point me to the right way?
Thanks in advance.
EDIT----
Those are the classes inside the 2 UIViews that retrieves data from remote API, parse the results in a class to contain it and populates the tableview with results:
-(void)updateList:(id)sender
{
isReading = YES;
users = [OTChatDataManager readUsers];
[list reloadData];
NSLog(#"ChatBar Was updated");
isReading = NO;
}
-(void)messagesMustBeUpdated:(id)sender
{
isReading = YES;
iSQLResult *newMessages = [OTChatDataManager readMessagesFromUser:fromUserId toUser:toUserId sinceLastMessageId:lastMessageId];
[self mergeMessages:newMessages];
[list reloadData];
[newMessages release];
isReading = NO;
}
All the properties of the 2 lists are declared as atomic, and I tried each solution proposed in the link of this post, GDC, Locks, etc...

Resources