I would like to show the progress bar in my app as determinate rather than indeterminate. It doesn't work though when setting it up as determinate (works just fine for indeterminate). I've read some of the other answers to this, although they haven't worked. Any help would be appreciated - thanks!
#interface AppDelegate : NSObject <NSApplicationDelegate> {
IBOutlet NSProgressIndicator *showProgress;
}
- (IBAction)someMethod:(id)sender {
[showProgress setUsesThreadedAnimation:YES]; // This works
[showProgress startAnimation:self]; // This works
[showProgress setDoubleValue:(0.1)]; // This does not work
[showProgress setIndeterminate:NO]; // This does not work
[self doSomething];
[self doSomethingElse];
[self doSomethingMore];
....
[barProgress setDoubleValue:(1.0)]; // This does not work
[barProgress stopAnimation:self]; // This works
}
Updated code [working]:
- (IBAction)someMethod:(id)sender {
[showProgress setUsesThreadedAnimation:YES];
[showProgress startAnimation:self];
[showProgress setIndeterminate:NO];
[showProgress setDoubleValue:(0.1)];
[showProgress startAnimation:nil];
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(backgroundQueue, ^{
for (NSUInteger i = 0; i < 1; i++) {
dispatch_async(dispatch_get_main_queue(), ^{
[barProgress incrementBy:10.0];
});
}
[self doSomething];
[showProgress incrementBy:...];
dispatch_async(dispatch_get_main_queue(), ^{
[showProgress stopAnimation:nil];
});
});
[showProgress setDoubleValue:(1.0)];
}
Your doSomething method is blocking the main thread, which causes the run loop not to cycle, which in turn causes UI redraw to be blocked. The fix is to do the long running work in doSomething on a background queue, with periodic callbacks to the main queue to update the progress bar.
I have no idea what your doSomething method does, but for the sake of explanation, let's assume it runs a for loop with 100 steps. You'd implement it something like this:
- (void)doSomething
{
[showProgress startAnimation:nil];
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(backgroundQueue, ^{
for (NSUInteger i = 0; i < 100; i++) {
// Do whatever it is you need to do
dispatch_async(dispatch_get_main_queue(), ^{
[showProgress incrementBy:1.0];
});
}
// Done with long running task
dispatch_async(dispatch_get_main_queue(), ^{
[showProgress stopAnimation:nil];
});
});
}
Keep in mind, you still need to set the progress indicator up to be determinate, initialize its value and set an appropriate minValue and maxValue.
If you must do the work in doSomething on the main thread, it's possible to schedule small chunks of that work to be done on each run loop cycle, or to manually spin the run loop periodically as you're doing the work, but Grand Central Dispatch (GCD) would be my first choice if you can use it.
Related
I have an Objective C MacOS project In Xcode 12.3 with a loop containing code that writes to user interface controls and may display alerts. When the loop runs, the cursor becomes a rotating rainbow disc. Clicking on a toolbar item (or any user interface control) has no effect until the loop has terminated.
I would like to have a toolbar item accept user clicks during loop execution. Whilst running the loop in a separate thread would allow this, substantial recoding would be required to remove the interface references and alerts from the loop code.
Is there a way of pausing the loop execution to check for input from user controls such as toolbar items? Adding [[NSRunloop mainRunLoop] runUntilDate:[NSDate datewithTimeIntervalSinceNow:0.5]];at the start of the loop code does not achieve this.
I've tried running the loop code (runBatch) in a separate thread using
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
[self runBatch];
dispatch_sync(dispatch_get_main_queue(), ^{
});
});
The loop code is contained in runBatch, which sets and reads various UI controls and these are are flagged as only being accessible from the main thread at run time. The project builds OK. Placing these UI interactions on the main thread after async queue completion would be difficult.
An example of code showing the problem is below. The project consists of a window with an NSTextField (outlet textData) and three buttons, two of which run a loop and the third (Stop) sets a stop flag. The runMain shows the index in textData, but when it runs only the final value appears and the Stop button is not responsive. The cursor becomes a coloured wheel after about 3 seconds when it is moved off the Start button.
When the loop is run on the background thread, the Stop button is responsive but textData cannot be updated from the background thread.
What I would like is for textData to show the index value while the loop is running.
AppDelegate.h
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate>
#property (weak) IBOutlet NSTextField *textData;
#end
AppDelegate.m
#import "AppDelegate.h"
#interface AppDelegate ()
#property (strong) IBOutlet NSWindow *window;
#end
#implementation AppDelegate
#synthesize textData;
static bool stopBatch = false;
- (IBAction)runMain:(id)sender {
stopBatch = false;
[self runMain];
}
- (IBAction)stopClick:(id)sender {
stopBatch = true;
}
- (IBAction)runBackground:(id)sender {
stopBatch = false;
[self runBatchBackground];
}
-(void) runMain{
[textData setStringValue:#"Start"];
[textData displayIfNeeded];
NSString * iString = #"0";
for (int i=0;i<=10000 ;i++)
{
iString= [NSString stringWithFormat: #"%d",i];
[textData setStringValue:iString];
[textData displayIfNeeded];
if(stopBatch)
{
break;
}
}
NSString *iStringFinal = iString;
}
-(void)runBatchBackground{
[textData setStringValue:#""];
NSString * __block iString = #"0";
dispatch_queue_t backgroundQueue = dispatch_queue_create("Network",nil);
dispatch_async(backgroundQueue, ^(void){
for (int i=0;i<=10000000 ;i++)
{
iString= [NSString stringWithFormat: #"%d",i];
//[self->_textData setStringValue:iString];
//[self->_textData displayIfNeeded];
if(stopBatch)
{
break;
}
}
NSString *iStringFinal = iString;
});
}
#end
After some experimentation I found a simpler solution than that kindly provided by #willeke. Using runMain code as shown below, adding a timerCalled method and adding a class variable iVal allowed the Stop button action to be executed while the loop was running. It appears that the 10000 timer requests are queued and then executed without blocking the main loop (and access to user controls) until timerCalled is exited using a return statement as shown. Is there anything wrong with this approach?
-(void) runMain{
for (int i=0;i<10000 ;i++)
{
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(timerCalled) userInfo:nil repeats:NO];
}
}
-(void)timerCalled{
if(stopBatch) return;
for (int i=0;i<10;i++)
{
iVal++;
iString= [NSString stringWithFormat: #"%ld",iVal];
[textData setStringValue:iString];
}
}
Here you go
- (void)runBatchBackground {
[self.textData setStringValue:#""];
NSString * __block iString = #"0";
dispatch_queue_t backgroundQueue = dispatch_queue_create("Network",nil);
dispatch_async(backgroundQueue, ^(void){
for (int i = 0; i <= 10000000; i++)
{
// Simulate some processing
// If the code on the background thread runs faster than the code
// on the main thread, then the main thread is lagging behind and doesn't
// have time to process events.
[NSThread sleepForTimeInterval:0.25];
iString = [NSString stringWithFormat: #"%d",i];
// Execute UI code on the main thread.
dispatch_async(dispatch_get_main_queue(), ^{
[self.textData setStringValue:iString];
//[self.textData displayIfNeeded]; displayIfNeeded is not needed
});
if (self->stopBatch)
{
break;
}
}
});
}
I'd like to init a model, let the model do some async stuff and present a new viewcontroller once completed. But how do i wait for the two async methods to be completed and how do I setup the callback method?
Pseudocode
In my StartViewController.m:
-(void)openArticle
{
article = [Article initWithObject:someObject];
article.callback = changeView;
}
-(void)changeView
{
[self presentViewController:someController];
}
In my ArticleModel.m:
-(void)initWithObject:someObject
{
[self loadImage]
[self geoCode]
}
-(void)loadImage
{
runAsyncMethod: success:^() // This one is actually a AFNetworking setImageWithURLRequest
}
-(void)geoCode
{
runAnotherAsyncMethod: success:^() // This one is actually a geocodeAddressString operation
}
You can achieve this using dispatch_groups
- (void)initWithObject:(id)someObject
{
self = [super init];
if (self) {
self.dispatch_group = dispatch_group_create();
[self loadImage]
[self geoCode]
dispatch_group_notify(self.dispatch_group, dispatch_get_main_queue(), ^{
NSLog(#"Push new view controller");
});
}
return self;
}
- (void)loadImage
{
dispatch_group_enter(self.dispatch_group);
__weak __typeof(self) weakSelf = self;
runAsyncMethod: success:^{
__typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf.dispatch_group) {
dispatch_group_leave(strongSelf.dispatch_group); // You need to ensure that this is called in both success and failure
}
}
}
- (void)geoCode
{
dispatch_group_enter(self.dispatch_group);
__weak __typeof(self) weakSelf = self;
runAnotherAsyncMethod: success:^{
__typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf.dispatch_group) {
dispatch_group_leave(strongSelf.dispatch_group);
}
}
}
You do not wait. If you wait, it isn't asynchronous! You would be losing the entire point of asynchronous if you were to wait.
What you do is, when your success handler is called, you step out to the main thread (just in case you got called back on a background thread) and now do whatever you need to do. In other words, you just let your success handler get called whenever it happens to get called.
In your case, you might like to chain the things you want to do:
Call loadImage
In its callback, call geoCode
In its callback, step out to the main thread and present the new view controller.
You can use dispatch_group so that when a method is over, it just leaves the group. I use a similar code myself and it works like a charm.
- (void)initWithObject:someObject {
// Create a dispatch group
dispatch_group_t group = dispatch_group_create();
[self loadImageWithDispatchGroup:group];
[self geoCodeWithDispatchGroup:group];
// Here we wait for all the requests to finish
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// Do whatever you need to do when all requests are finished
});
}
- (void)loadImageWithDispatchGroup:(dispatch_group_t)group {
dispatch_group_enter(group);
runAsyncMethod: success:^() // This one is actually a AFNetworking setImageWithURLRequest
// In your success or failure AFNetworking method, call this as soon as the request ended
dispatch_group_leave(group);
}
- (void)geoCodeWithDispatchGroup:(dispatch_group_t)group {
dispatch_group_enter(group);
runAnotherAsyncMethod: success:^() // This one is actually a geocodeAddressString operation
// In your success async geocode callback method, call this as soon as the request ended
dispatch_group_leave(group);
}
I do not known your needs but native GCD way to wait several asynch tasks is
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html#//apple_ref/c/func/dispatch_barrier_async
I am implementing a Cocoa Application which is just a simple progress bar that starts when I press a button.
The situation is: I can see Animation is Start and Stop when I press the button, but the progress bar will not update the value.
I had also tried the solution mentioned here but it doesn't work:
How do I update a progress bar in Cocoa during a long running loop?
Can someone help to see where is the problem in my source code?
Here is my source.
SimpleProgressBar.m
#import "SimpleProgressBar.h"
#implementation SimpleProgressBar
#synthesize progressBar;
int flag=0;
-(IBAction)startProgressBar:(id)sender{
if(flag ==0){
[self.progressBar startAnimation:sender];
flag=1;
}else{
[self.progressBar stopAnimation:sender];
flag=0;
}
[self.progressBar displayIfNeeded];
[self.progressBar setDoubleValue:10.0];
int i=0;
for(i=0;i<100;i++){
NSLog(#"progr: %f",(double)i);
[self.progressBar setDoubleValue:(double)i];
[self.progressBar setNeedsDisplay:YES];
}
}
#end
SimpleProgressBar.h
#import < Foundation/Foundation.h >
#interface SimpleProgressBar : NSObject{
__weak NSProgressIndicator *progressBar;
}
#property (weak) IBOutlet NSProgressIndicator *progressBar;
-(IBAction)startProgressBar:(id)sender;
#end
Thank you very much for any helpful answer.
Update:
Here is my porting from the solution and it doesn't work:
SimpleProgressBar.m
#import "SimpleProgressBar.h"
#implementation SimpleProgressBar
#synthesize progressBar;
int flag=0;
-(IBAction)startProgressBar:(id)sender{
if(flag ==0){
[self.progressBar startAnimation:sender];
flag=1;
}else{
[self.progressBar stopAnimation:sender];
flag=0;
}
[self.progressBar displayIfNeeded];
[self.progressBar setDoubleValue:0.0];
void(^progressBlock)(void);
progressBlock = ^{
[self.progressBar setDoubleValue:0.0];
int i=0;
for(i=0;i<100;i++){
//double progr = (double) i / (double)100.0;
double progr = (double) i;
NSLog(#"progr: %f",progr);
dispatch_async(dispatch_get_main_queue(),^{
[self.progressBar setDoubleValue:progr];
[self.progressBar setNeedsDisplay:YES];
});
}
};
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue,progressBlock);
}
Update:
A couple of observations:
It strikes me that if you want to watch the NSProgressIndicator advance, you need to add a sleepForTimeInterval or else the for loop iterates so quickly that you won't see the progress indicator advance, but rather you'll just see it quickly end up in its final state. If you insert sleepForTimeInterval, you should see it progress:
self.progressIndicator.minValue = 0.0;
self.progressIndicator.maxValue = 5.0;
[self.progressIndicator setIndeterminate:NO];
self.progressIndicator.doubleValue = 0.001; // if you want to see it animate the first iteration, you need to start it at some small, non-zero value
for (NSInteger i = 1; i <= self.progressIndicator.maxValue; i++)
{
[NSThread sleepForTimeInterval:1.0];
[self.progressIndicator setDoubleValue:(double)i];
[self.progressIndicator displayIfNeeded];
}
Or, if you wanted to do the for loop on a background thread, and dispatch the updates back to the main queue:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (NSInteger i = 1; i <= self.progressIndicator.maxValue; i++)
{
[NSThread sleepForTimeInterval:1.0];
dispatch_async(dispatch_get_main_queue(), ^{
[self.progressIndicator setDoubleValue:(double)i];
[self.progressIndicator displayIfNeeded];
});
}
});
You are using startAnimation and stopAnimation, but according to the documentation each of these "does nothing for a determinate progress indicator," so these calls seem inappropriate for this situation.
My original answer, below, was predicated on the comment in the Threads and Your User Interface in the Threading Programming Guide, which says:
If your application has a graphical user interface, it is recommended that you receive user-related events and initiate interface updates from your application’s main thread. This approach helps avoid synchronization issues associated with handling user events and drawing window content. Some frameworks, such as Cocoa, generally require this behavior, but even for those that do not, keeping this behavior on the main thread has the advantage of simplifying the logic for managing your user interface.
But the answer below is (incorrectly) an iOS answer, so is not applicable.
Original answer:
Your for loop is running on the main thread, and thus UI updates won't appear until you yield back to the runloop. You're also going through that loop so quickly that even if you properly dispatched that to a background queue, you wouldn't experience the progress view changing as you iterate through your loop.
So, perform the loop on a secondary thread (e.g. via GCD or operation queue) and then dispatch UI updates back to the main thread, which is now free to do UI updates. So, using your theoretical example, you could do something like:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 100; i++)
{
[NSThread sleepForTimeInterval:0.1];
dispatch_async(dispatch_get_main_queue(), ^{
[self.progressView setProgress: (CGFloat) (i + 1.0) / 100.0 animated:YES];
});
}
});
Note, having a loop that updates the progress view only makes sense if you're doing something slow enough for you to see the progress view change. In your original example, you're just looping from 0 to 99, updating the progress view. But that happens so quickly, that there's no point in a progress view in that case. That's why my above example not only employs a background queue for the loop, but also added a slight delay (via sleepForTimeInterval).
Let's consider a more realistic application of the progress view. For example, let's say I had an array, urls, of NSURL objects that represent items to be downloaded from the server. Then I might do something like:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < [urls count]; i++)
{
// perform synchronous network request (on main queue, you should only do asynchronous network requests, but on background queue, synchronous is fine, and in this case, needed)
NSError *error = nil;
NSURLResponse *response = nil;
NSURLRequest *request = [NSURLRequest requestWithURL:urls[i]];
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:response error:error];
// got it; now update my model and UI on the basis of what I just downloaded
dispatch_async(dispatch_get_main_queue(), ^{
[self.progressView setProgress: (CGFloat) (i + 1.0) / [array count] animated:YES];
// do additional UI/model updates here
});
}
});
I'm trying to write unit tests for some gui components that use grand central dispatch. I'd like to call threaded code from the test, wait for it to finish, and then check the results on the gui object.
dispatch_queue_t myQueue = dispatch_queue_create();
- (void)refreshGui {
[self.button setEnabled:NO];
dispatch_async(myQueue, ^{
//operation of undetermined length
sleep(1);
dispatch_sync(dispatch_get_main_queue(), ^{
// GUI stuff that must be on the main thread,
// I want this to be done before I check results in my tests.
[self.button setEnabled:YES];
});
});
}
In my tests, I want to do something like this:
-(void)testRefreshGui {
[object refreshGui];
[object blockUntilThreadedOperationIsDone];
STAssertTrue([object isRefreshedProperly], #"did not refresh");
}
My first idea was to call something synchronously on the relevant queue, like this. Unfortunately, this results in deadlock when called from the main queue (because there is a dispatch_sync() to the main queue in the gui code, and the test is also running on the main thread):
-(void)blockOnQueue:(dispatch_queue_t)q {
dispatch_sync(q, ^{});
}
Using a dispatch group with dispatch_group_wait(group, DISPATCH_TIME_FOREVER) also results in deadlock for the same reason.
A hack solution I came up with was this:
- (void)waitOnQueue:(dispatch_queue_t)q {
__block BOOL blocking = YES;
while (blocking) {
[NSRunLoop.mainRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
dispatch_sync(q, ^{});
blocking = NO;
});
}
}
Unfortunately, this 'solution' has the problem of pumping the main run loop and causing other tests to run, which breaks a number of things for me.
I also do not want to change the GUI code's dispatch_sync() to dispatch_async() because that's not the right behavior for this queue, and in the tests, the GUI code wouldn't be guaranteed to complete before the test checks the result either.
Thanks for any ideas!
You should decouple your need for the test to wait for GUI updates to run from how the main code path runs. In the first code block you posted, dispatch_sync is almost certainly the wrong approach (vs. dispatch_async) because you're going to block a background thread waiting on the main thread for no reason (there's no code after the dispatch_sync) this can lead to thread starvation (in deployment that is). I'm guessing that you made it dispatch_sync in an attempt to use the queue itself to interlock the two parallel tasks. If you are really committed to using that somewhat sub-optimal approach, you could do something like this:
- (void)testOne
{
SOAltUpdateView* view = [[SOAltUpdateView alloc] initWithFrame: NSMakeRect(0, 0, 100, 100)];
STAssertNotNil(view, #"View was nil");
STAssertEqualObjects(view.color, [NSColor redColor] , #"Initial color was wrong");
dispatch_queue_t q = dispatch_queue_create("test", 0);
dispatch_group_t group = dispatch_group_create();
view.queue = q;
// Run the operation
[view update];
// An operation we can wait on
dispatch_group_async(group, q, ^{ });
while (dispatch_group_wait(group, DISPATCH_TIME_NOW))
{
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, YES);
}
STAssertEqualObjects(view.color, [NSColor greenColor] , #"Updated color was wrong");
view.queue = nil;
[view release];
dispatch_release(group);
dispatch_release(q);
}
That was the approach that seemed closest to what you already had, but I came up with something that might be a little better/cleaner: A semaphore can do this interlocking for you, and with a little effort, you can make the intrusion on your actual GUI code pretty minimal. (Note: it will be effectively impossible to have no intrusion at all, because in order for two parallel tasks to interlock, they have to share something to interlock on -- something shared -- in your existing code it was the queue, here I'm using a semaphore.) Consider this contrived example: I've added a generic means for the test harness to push in a semaphore that can be used to notify it when the background operation completes. The "intrusion" on the code to be tested is limited to two macros.
NSObject+AsyncGUITestSupport.h:
#interface NSObject (AsyncGUITestSupport)
#property (nonatomic, readwrite, assign) dispatch_semaphore_t testCompletionSemaphore;
#end
#define OPERATION_BEGIN(...) do { dispatch_semaphore_t s = self.testCompletionSemaphore; if (s) dispatch_semaphore_wait(s, DISPATCH_TIME_NOW); } while(0)
#define OPERATION_END(...) do { dispatch_semaphore_t s = self.testCompletionSemaphore; if (s) dispatch_semaphore_signal(s); } while(0)
NSObject+AsyncGUITestSupport.m:
#import "NSObject+AsyncGUITestSupport.h"
#import <objc/runtime.h>
#implementation NSObject (AsyncGUITestSupport)
static void * const kTestingSemaphoreAssociatedStorageKey = (void*)&kTestingSemaphoreAssociatedStorageKey;
- (void)setTestCompletionSemaphore:(dispatch_semaphore_t)myProperty
{
objc_setAssociatedObject(self, kTestingSemaphoreAssociatedStorageKey, myProperty, OBJC_ASSOCIATION_ASSIGN);
}
- (dispatch_semaphore_t)testCompletionSemaphore
{
return objc_getAssociatedObject(self, kTestingSemaphoreAssociatedStorageKey);
}
#end
SOUpdateView.h
#interface SOUpdateView : NSView
#property (nonatomic, readonly, retain) NSColor* color;
- (void)update;
#end
SOUpdateView.m
#import "SOUpdateView.h"
#import "NSObject+AsyncGUITestSupport.h"
#implementation SOUpdateView
{
NSUInteger _count;
}
- (NSColor *)color
{
NSArray* colors = #[ [NSColor redColor], [NSColor greenColor], [NSColor blueColor] ];
#synchronized(self)
{
return colors[_count % colors.count];
}
}
- (void)drawRect:(NSRect)dirtyRect
{
[self.color set];
NSRectFill(dirtyRect);
}
- (void)update
{
OPERATION_BEGIN();
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
#synchronized(self)
{
_count++;
}
dispatch_async(dispatch_get_main_queue(), ^{
[self setNeedsDisplay: YES];
OPERATION_END();
});
});
}
#end
And then the test harness:
#import "TestSOTestGUI.h"
#import "SOUpdateView.h"
#import "NSObject+AsyncGUITestSupport.h"
#implementation TestSOTestGUI
- (void)testOne
{
SOUpdateView* view = [[SOUpdateView alloc] initWithFrame: NSMakeRect(0, 0, 100, 100)];
STAssertNotNil(view, #"View was nil");
STAssertEqualObjects(view.color, [NSColor redColor] , #"Initial color was wrong");
// Push in a semaphore...
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
view.testCompletionSemaphore = sem;
// Run the operation
[view update];
// Wait for the operation to finish.
while (dispatch_semaphore_wait(sem, DISPATCH_TIME_NOW))
{
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, YES);
}
// Clear out the semaphore
view.testCompletionSemaphore = nil;
STAssertEqualObjects(view.color, [NSColor greenColor] , #"Updated color was wrong");
}
#end
Hope this helps.
I'm trying to use sleep in the below code when the NSStream Connection fails or if there's a Stream Error and tries to reconnect after sleep. The Sleep is working but it puts the Whole Application to Sleep.
I have started NSStream as a Thread, but when the NSStreamEvent is received, the handleEvent seems to be working as a Synchronous method.
Any ideas on using Sleep for this piece of Code ..? I just want the sleep to work for the Stream Thread alone.
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)event
{
case NSStreamEventErrorOccurred:
{
NSError *streamErr = [stream streamError];
NSString *strErr = [streamErr localizedFailureReason];
[self CloseStream];
NSLog(#"Stream Error ::: %#",strErr);
//[NSThread sleepForTimeInterval : 15];
sleep(15);
[self Initialize];
[self OpenStream];
break;
}
case NSStreamEventEndEncountered:
{
NSLog(#"Connection Closed by the Server");
[self CloseStream];
usleep(15000);
[self Initialize];
[self OpenStream];
break;
}
}
You should use GCD (Grand Central Dispatch). Your code is being executed in the Background and your application doesn't freeze.
Read this: GCD Reference
Basically you create a queue and add a block of code, which is being executed in the background. Here's my code example
dispatch_queue_t backgroundQueue = dispatch_queue_create("some_identifier", NULL);
dispatch_async(backgroundQueue, ^(void) {
//do your background stuff
dispatch_sync(dispatch_get_main_queue(), ^{
//update the gui (if needed)
});
});