Update UILabel in a "while loop" - objective-c

I'm trying to update a UILabel within a "while loop" but it is not changing the UILabel text. I know that it is shown at the end of the current run loop cycle at the main thread as usual for iOS. But how can solve this problem ( the ftp.AsyncFinished function is provided by external chilkat ftp module) :
The data is updated every second. I searched this forum and in Apple's documentation, but I could not figure out the correct way to update a UILabel within a "while loop" on the main thread. What can I use instead of a while loop, that allows the main thread to update the UILabel.
while (ftp.AsyncFinished != YES) {
// here is the code getting data from the cilkat FTP module stored in PercentOfFile and MinutesRemaining
NSString *content =[[NSString alloc] initWithFormat:#"%.0f%% done%.0fmin left",PercentOfFile,MinutesRemaining ];
[LabelProgress setText:content];
}

In order for the UI to update at all, the main thread's run loop has to complete. That means your method (if called on the main thread), must return before any UI updates occur. All updates are then coalesced and the UI is redrawn.
See What is the most robust way to force a UIView to redraw? for more background.
Your while loop is incorrectly designed for an asynchronous update. You should not be fetching async data in the middle of a while loop. You should be registering for a callback of some kind (generally by setting yourself as delegate) and then just waiting for the system to call you.

You will need to call your upload progress in another thread and register for call backs. Create a custom class that inherits from CkoFTP2Progress which has this method:
- (void)PercentDone: (NSNumber *)pctDone abort:(BOOL *)abort;
Assuming you have a CkoFTp2 object named "ftp" and your custom class named "UploadProgress", register for callbacks:
[self.ftp setEventCallbackObject:self.uploadProgress];

You could move the while loop to a background thread and call setText from there with performSelectorOnMainThread.

// update your UILabel here
// ...
// Delay execution of long-running task so that update of UILabel can be performed
dispatch_after( dispatch_time( DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC ), dispatch_get_main_queue(), ^{
// long-running task
// ..
});

Related

How to switch between background and main threads

I've never used background threads before. I have a time consuming computation currently running on the main thread which appends the data output to a TERecord. My workflow essentially goes:
run long process…
update GUI…
run long process…
update GUI…
and so on.
At several places where the code produces (string) output I update the UI by calling my 'addToRecord' method shown here:
-(void)addToRecord:(NSString*)passedStr:(BOOL)updateUI
{
NSRange endRange;
// add the passed text...
endRange.location = [[theOutputView textStorage] length];
endRange.length = 0;
[theOutputView replaceCharactersInRange:endRange withString:passedStr];
if(updateUI) // immediate GUI update needed...
{
// scroll window contents to BOTTOM of page...
endRange = NSMakeRange([[theOutputView string] length],0);
[theOutputView scrollRangeToVisible:endRange];
[theOutputView display];
}
}
While it does the job, my entire UI remains unresponsive until the process completes, of course. I know I should be doing the heavy lifting on a background thread which I've never used before. I've figured out part of the problem in creating a background thread like below:
-(IBAction)readUserInput:(id)sender
{
// irrelevant code snipped for brevity
if([self checkForErrors] == NO)
{
[runButton setEnabled:NO];
[self performSelectorInBackground:#selector(runWorkThread) withObject:nil];
}
}
-(void)runWorkThread
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
[self runLongProcess];
[pool drain];
}
but i just don't understand how to call the main thread every time the code encounters my 'addToRecord' method, then how to return control to the background thread?
Another possibility might be to remove the updateUI code from my 'addToRecord' method and just have have the main thread calling this code every second or so on a timer?
Any advice and sample code would be greatly appreciated. Thanks!
Instead of using performSelectorInBackground you can use the Dispatch framework (also called GCD), which is the preferred way of handling concurrent work. The Dispatch already has a pool of background threads set up that you can use. To switch thread you call dispatch_async() like this:
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
// :
// Do your background work here
// :
dispatch_async(dispatch_get_main_queue(), ^{
// :
// Now you are back in the main thread
// :
});
});
The first parameter is the queue identifier which is supplied to you by either dispatch_get_global_queue() which returns one of the "worker" queues, or dispatch_get_main_queue() which returns the main queue. The last parameter is a code block that is executed on the selected queue.
When requesting a concurrent queue using dispatch_get_global_queue() you specify a Quality of Service, which determines the priority your code will have in relation to other work. See the documentation for more information and possible values.
Read more on the Dispatch

Stopping the NSApplication main event loop

I have an application consisting of the following single .m file:
#import <Cocoa/Cocoa.h>
int main(int argc, char* argv[]) {
[[[NSThread alloc] initWithBlock: ^{
sleep(2);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Stop");
[[NSApplication sharedApplication] stop:nil];
});
}] start];
[[NSApplication sharedApplication] run];
NSLog(#"Run finished");
return 0;
}
According to the developer documentation, stop should stop the main loop (run), but it doesn't (at least not on OS X 10.12 and 10.13). There's also terminate, but this exits the program too soon. I also tried setting an NSApplicationDelegate that implements applicationShouldTerminate, but this is never called.
How can I make sure the main run loop is (cleanly) exited?
Note: The shared application main loop is necessary because there is UI work being done elsewhere. More concretely, this is giving problems in the Go WDE UI library, which uses Cocoa to provide a window to a Go application.
The documentation for -stop: says:
[C]alling this method from a timer or run-loop observer routine would not stop the run loop because they do not result in the posting of an NSEvent object.
A block dispatched to the main queue is similar in that it doesn't post an event. You can try posting an NSEventTypeApplicationDefined event after calling -stop:.
After investigating this further, it seems that the UI loop stop request is only processed after a UI event (so not just after a main loop event). So, it works in response to a UI event, but not in a thread like I did in my example.
Triggering a UI event after a stop request (e.g. a programmatic resize works for me) causes the loop to end.

How to not crash your app on quit when using concurrency

I'm using NSOperationQueue and a subclass of NSOperation for a part in my app that is generating a lot of data and therefore is very calculation-heavy.
When the app is closed by the user processingQueue.cancelAllOperations() is called. Also in my NSOperation subclass I overwrote cancel() to let it forward a cancel request to the class that does the actual heavy lifting ...
override func cancel() {
AppDelegate.instance.cancelDataGeneration()
super.cancel()
}
But this is still not enough. When I close the app while the data generation is ongoing it will crash in Xcode.
What can I do to prevent the crashing (which might result in data loss)? Is it OK to let the app wait for closing until all concurrent operations are canceled and how is this done (if it's even possible)? Or what other methods are generally used to address this issue?
UPDATE:
After more investigation I found that cancel() on my NSOperation subclass is never called, even after calling processingQueue.cancelAllOperations() in applicationShouldTerminate. So I added a method to manually call cancel on it:
func cancelDataGeneration() {
if let op = AppDelegate.instance._operation {
op.cancel();
}
}
And I call this from inside applicationShouldTerminate (since applicationShouldTerminate is called earlier than applicationWillTerminate. Interestingly, since my AppDelegate is a Singleton I have to use AppDelegate.instance._operation. If I only check for _operation it results in being nil when called from applicationShouldTerminate. Would be interesting to know why this is the case.
In any case, canceling now works properly: When the app is quit, it will cancel the data generation class and exits without crashing ... mostly anyway. But I still wonder why my NSOperation subclass' cancel() isn't called when I use processingQueue.cancelAllOperations()!
From Apple's documentation.
Canceling the operations does not automatically remove them from the queue or stop those that are currently executing. For operations that are queued and waiting execution, the queue must still attempt to execute the operation before recognizing that it is canceled and moving it to the finished state.
I would block the App's mainthread until the NSOperationQueue finishes all of its work.
I would call [NSOperationQueue cancelAllOperations] first.
Then in the 'Application will terminate' method I will call
[NSOperationQueue waitUntilAllOperationsAreFinished]. This will make sure the currently executing block(all the other queued tasks will be cancelled) will complete before the application quits.
Now if you are not comfortable with the main thread blocking until the currently executing block finishes, then you need to check for a flag(or an NSApplicationDelegate could be set on that class) which signals if the application is still active in order to continue. If application is to be terminated, then fall out of the block voluntarily, this is the cleanest way to do it.
Something roughly like the below.
void ^(aBlock)(void) = ^void(void)
{
for(NSUInteger i = 0;i < 1000; i++)
{
// heavy processing code. Taking several iterations each second
// At the start of each iteration, check for a flag, to see if to quit
if(_applicationIsShuttingDown) break;
// perform block operation
}
};
and your class is an NSApplicationDelegate and implements
-applicationWillTerminate:(NSNotification *)aNotification
{
_applicationIsShuttingDown = YES;
}

UI does not update when main thread is blocked in Cocoa app

I am using a NSProgressIndicator in my main thread to update on progress as I run through my entire method. Now when I end up calling an object from a different class file, and wait for that object to return to a value to my main thread, I notice that the NSProgressIndicator will disappear. I understand that this is because the main thread is blocked until I get the return value from the other object.
So my questions is what is the recommended way for updating UI in the main thread without blocking it and having other objects run in the background and return values to the main thread as needed. I know how to use blocks but blockoperations are not allowed to return values.
What I need is something that helps this pseudo code:
-(IBAction) main {
//Update progress indicator UI to show progress
//perform an call to another object from another class.
// wait till i get its return value.
//Update progress indicator UI to show progress
// Use this return value to do something.
//Update progress indicator UI to show progress
}
When the call to the other object is made, I notice that the determinate NSProgressIndicator I have completely disappears since the main thread is blocked. Thanks.
Your above code is not the correct approach. Since main never returns, the progress indicator will never update. You must return quickly on the main thread.
Instead, what you want to do is set up a background block that at various points updates the progress indicator on the main thread. So, for instance:
- (IBAction)start:(id)sender {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{[self.progress setProgress:0];});
// Doing some stuff
dispatch_async(dispatch_get_main_queue(), ^{[self.progress setProgress:.25];});
// Doing more stuff
dispatch_async(dispatch_get_main_queue(), ^{[self.progress setProgress:.75];});
});
}
(Yes, this causes the queue to retain self, but that's ok here because self is not retaining the queue.)
You can achieve what you are looking for with GCD (Grand Central Dispatch).
Here is an example to get you started:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
// Perform async operation
dispatch_sync(dispatch_get_main_queue(), ^{
// Update UI
});
});
It sounds like your operation should be run in a separate thread which can be done several ways but is probably most easily achieved using NSOperationQueue and either custom NSOperation classes (it's easier than it sounds to set these up) or use of the NSInvokeOperation class.
Then you can send messages back to your class in the main thread using the NSNotificationCenter or set up as an observer using Key-Value Observing (KVO).
Bottom line, you have a variety of choices and to make the best one should have an understanding of the underlying technologies. I'd start with Apple's Threaded Programming Guide personally, then read it a second time to be sure you extracted all the goodness before building out your solution.

How to update UI in a task completion block?

In my application, I let a progress indicator starts animation before I send a HTTP request.
The completion handler is defined in a block. After I get the response data, I hide the progress indicator from inside the block. My question is, as I know, UI updates must be performed in the main thread. How can I make sure it?
If I define a method in the window controller which updates UI, and let the block calls the method instead of updating UI directly, is it a solution?
Also, if your app targets iOS >= 4 you can use Grand Central Dispatch:
dispatch_async(dispatch_get_main_queue(), ^{
// This block will be executed asynchronously on the main thread.
});
This is useful when your custom logic cannot easily be expressed with the single selector and object arguments that the performSelect… methods take.
To execute a block synchronously, use dispatch_sync() – but make sure you’re not currently executing on the main queue or GCD will deadlock.
__block NSInteger alertResult; // The __block modifier makes alertResult writable
// from a referencing block.
void (^ getResponse)() = ^{
NSAlert *alert = …;
alertResult = [NSAlert runModal];
};
if ([NSThread isMainThread]) {
// We're currently executing on the main thread.
// We can execute the block directly.
getResponse();
} else {
dispatch_sync(dispatch_get_main_queue(), getResponse);
}
// Check the user response.
if (alertResult == …) {
…
}
You probably misunderstood something. Using blocks doesn't mean that your code is running in a background thread. There are many plugins that work asynchronously (in another thread) and use blocks.
There are a few options to solve your problem.
You can check if your code is running in the main thread my using [NSThread isMainThread]. That helps you to make sure that you're not in the background.
You can also perform actions in the main or background by using performSelectorInMainThread:SEL or performSelectorInBackground:SEL.
The app immediately crashes when you're trying to call the UI from a bakcground thread so it's quite easy to find a bug.