Put a boolean False inside a runloop while - ios

i'm trying to get directory file listing from my ftp server using Chilkat library.
In this case, i want to animating a UIActivityIndicatorView when the process is running. But the problem is, the UIActivityIndicatorView never start to animate. The code I use is :
[self.activityIndicator startAnimating];
[selfgetListFromPath:ftpPath withConnection:ftpConnect];
[self.activityIndicator stopAnimating];
activityIndicator is an object of UIActivityIndicatorView, ftpPath is a NSString of my file path in FTP server, and getListFromPath is the method for getting list from FTP server using Chilkat algorithm, ftpConnect is an object of FTP Connection Class.
I was try to use NSRunLoop before called getListFromPath function, so I changed my code into :
[self.activityIndicator startAnimating];
BOOL waitingOnProcessing = YES;
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
while (waitingOnProcessing && [currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) {
}
[self getListFromPath:ftpPath withConnection:ftpConnect];
[self.activityIndicator stopAnimating];
this make the activityIndicator animating, but the getListFromPath never fired. After a trial, i choosed to changed again my code into :
[self.activityIndicator startAnimating];
BOOL waitingOnProcessing = YES;
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
while (waitingOnProcessing && [currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) {
waitingOnProcessing = NO;
}
[self getListFromPath:ftpPath withConnection:ftpConnect];
[self.activityIndicator stopAnimating];
it make the activityIndicator animating, and also fired the getListFromPath function. But i'm doubt with this code, am i right with this code? or maybe there is a bad practice for using NSRunLoop?
can somebody tell me
Thank you

This code runs as in a "black box" :
[self.activityIndicator startAnimating];
[self getListFromPath:ftpPath];
[self.activityIndicator stopAnimating];
So UI updates occur when your method returns, and you won't see the update.
What you need to do is startAnimating your activity indicator, then start your getListFromPath in another thread. When that method terminates, you call back your main thread telling it to stopAnimating the indicator.
Use this methods:
[NSObject performSelectorInBackground:withObject:]
to start your getListFromPath thread, then when it's done, call
[NSObject performSelectorOnMainThread:withObject:waitUntilDone:]
to give the control back to the main thread, which will stop the spinner animation.

I dont know the Chilkat library, but it must have some way of telling you that you receive an answer from your ftp server. When you got it, you should use a NSNotification or a protocol to tell your view controller that you got an answer. When that happen you stop the spinner.

Related

better way of using GCD for threading

Ok. So i am trying to use GCD To Handle all the heavy loading before transition to next view controller. I am opening large archive Files and extracting them which takes some time.
The entire Process is like this:
Click a UICollectionViewCell>Display activity indicator>Let GCD take care of heavy loading>call transition selector using performSelector: onThread:.....
The problem is when i use mainThread, the transition occurs too fast and all the heaving loading don't come to effect until after some time and the transition looks awful and while using currentThread, well it just takes so much time, it seems plain awful of a app.
-(void)someMethod
{
//activity Indicator before transition begins
UIActivityIndicatorView *activity=[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[activity setFrame:self.view.bounds];
[self.view addSubview:activity];
[self.view bringSubviewToFront:activity];
activity.hidesWhenStopped=YES;
[activity startAnimating];
dispatch_queue_t transitionQueue;
transitionQueue = dispatch_queue_create("com.app.transitionQueue", NULL);
dispatch_async(transitionQueue,^{
//heavy lifting code
viewerPVC=.....
dispatch_async(dispatch_get_main_queue(),^{
[activity stopAnimating];
[self transitionToMangaViewer:mReaderPVC];
});
};
}
-(void)transitionToViewer:(ViewerPVC*)viewerPVC
{
[self.navigationController pushViewController:mReaderPVC animated:YES];
}
So Tried The First Suggestion, but the transition still seems buggy since the CollectionViewController still remain On Background For Some Time after the transition
you shouldnt need to use NSThread when you are using gcd, try something like this instead
dispatch_async(transitionQueue,^{
//heavy lifting code
viewerPVC=..... //this should block here otherwise will not work
dispatch_async(dispatch_get_main_queue(), ^{
[activity stopAnimating];
[self transitionToAnotherViewer:viewerPVC];
});
});
UI Update should be done on Main Thread, no need to create New Thread for performing UI Transition. Try below code:
dispatch_async(transitionQueue,^{
//heavy lifting code
viewerPVC=.....
dispatch_async(dispatch_get_main_queue(), ^{
[activity stopAnimating];
[self performSelector:#selector(transitionToAnotherViewer:) withObject:viewerPVC waitUntilDone:YES];
});
};

Dropbox DBRestClient/DBRestClientDelegate execute on a thread

I am using the dropbox API with DBRESTClient and DBRestClientDelegate in Dropbox-iOS-SDK
My issue is that I need these to run on a background thread.
When I call the [restClient loadMetadata] I do not get a response to - restClient:loadedMetadata: unless I begin call from the main thread.
Is there a simple workaround/library that I can use which will allow a delegate response on a thread ? I have tried Dropblocks which uses blocks but no luck.
I noticed "Make sure you call DBRestClient methods from the main thread or a thread that has a run loop. Otherwise the delegate methods won't be called." on the Dropbox Page
https://www.dropbox.com/developers-v1/core/start/ios
I used a runloop and it functions on a thread now
This is using a completion block Similarly you could use the delegate to set the flag to NO
self.inQuery = YES;
[self loadMetaDataWithPath:rootFolder mediaType:#(mediaType) completion:^(BOOL complete) {
self.inQuery = NO;
}];
#autoreleasepool {
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
DDLogError(#"currentRunLoop %#",[NSDate date]);
}while(self.inQuery);
}

iOS: Processing UI events in NSRunLoop

I'm working on an iPad app that needs to spawn a dialog mid-function for some user interaction. In order to wait for the dialog, I run an NSRunLoop, however, this is preventing events on the dialog from being processed. This is how I spawn the dialog:
NSArray* listOfCompatibleTypes = [[NSArray alloc] initWithArray:[listOfCompatibleTypesAndSizesAsSet allObjects]];
[secondaryImplantChooserDialog setModalPresentationStyle:UIModalPresentationFormSheet];
[secondaryImplantChooserDialog setDefinesPresentationContext:YES];
[self presentViewController:secondaryImplantChooserDialog animated:NO completion:nil];
And the runloop is like this:
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
while (secondaryImplantChooserDialog.fDialogDone != YES)
{
[runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
}
The dialog box has a popover that contains a tableview. When I comment out the runloop, the table view's didSelectRowAtIndexPath(…) method gets called. When the loop is active, however, that method doesn't get called until the dialog gets dismissed.
I realize this isn't exactly an iOS-kosher design, so I should probably refactor it, but I was wondering if there was any way to process UI events while the runloop is active.
Polling is very rarely a good solution, particularly in a user-interactive environment like an iOS app. The functionality you are looking for is a semaphore - where you can block execution in one part of your app until another has completed. You can do this with Grand Central Dispatch but this is probably adding complexity when a better solution is to re-factor.
From what I understand you have a method which is performing some calculation or business-logic and it determines that additional information is needed. You could refactor something like:
if ([self haveEnoughInfo]) {
[self performFinalCalc];
} else {
[self gatherMoreInformation]; // Use a delegate or completion block to invoke [self performFinalCalc] once more information is gathered
}

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.

Effective way to use NSThread and autorealease pools in iOS

I'm using the MBProgressHUD library in my app, but there are times that the progress hud doesn't even show when i query extensive amount of data, or show right after the processing of data is finished (by that time i don't need the hud to be displayed anymore).
In another post i found out that sometimes UI run cycles are so busy that they don't get to refresh completely, so i used a solution that partially solved my problem: Now every request rises the HUD but pretty much half the times the app crashes. Why? That's where I need some help.
I have a table view, in the delegate method didSelectRowAtIndexPath i have this code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[NSThread detachNewThreadSelector:#selector(showHUD) toTarget:self withObject:nil];
...
}
Then, I have this method:
- (void)showHUD {
#autoreleasepool {
[HUD show:YES];
}
}
At some other point I just call:
[HUD hide:YES];
And well, when it works it works, hud shows, stays and then disappear as expected, and sometimes it just crashes the application. The error: EXC_BAD_ACCESS . Why?
By the way, the HUD object is already allocated in the viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
...
// Allocating HUD
HUD = [[MBProgressHUD alloc] initWithView:self.navigationController.view];
[self.navigationController.view addSubview:HUD];
HUD.labelText = #"Checking";
HUD.detailsLabelText = #"Products";
HUD.dimBackground = YES;
}
You need to perform your processing on another thread, otherwise the processing is blocking MBProgressHud drawing until it completes, at which point MBProgressHud is hidden again.
NSThread is a bit too low-level for just offloading processing. I'd suggest either Grand Central Dispatch or NSOperationQueue.
http://jeffreysambells.com/2013/03/01/asynchronous-operations-in-ios-with-grand-central-dispatch
http://www.raywenderlich.com/19788/how-to-use-nsoperations-and-nsoperationqueues
/* Prepare the UI before the processing starts (i.e. show MBProgressHud) */
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
/* Processing here */
dispatch_async(dispatch_get_main_queue(), ^{
/* Update the UI here (i.e. hide MBProgressHud, etc..) */
});
});
This snippet will let you do any UI work on the main thread, before dispatching the processing to another thread. It then returns to the main thread once the processing is done, to allow you to update the UI.

Resources