I have a method what I want to call after -viewDidLoad and in background thread. Is there way to combine this two methods:
[self performSelector:(SEL) withObject:(id) afterDelay:(NSTimeInterval)]
and
[self performSelectorInBackground:(SEL) withObject:(id)]?
Grand Central Dispatch has dispatch_after() which will execute a block after a specified time on a specified queue. If you create a background queue, you will have the functionality you desire.
dispatch_queue_t myBackgroundQ = dispatch_queue_create("com.romanHouse.backgroundDelay", NULL);
// Could also get a global queue; in this case, don't release it below.
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, seconds * NSEC_PER_SEC);
dispatch_after(delay, myBackgroundQ, ^(void){
[self delayedMethodWithObject:someObject];
});
dispatch_release(myBackgroundQ);
Try the following:
// Run in the background, on the default priority queue
dispatch_async( dispatch_get_global_queue(0, 0), ^{
[self performSelector:(SEL) withObject:(id) afterDelay:(NSTimeInterval)]
});
Code not tested
Be aware that your selector/method must not use UIKit (so don't update the UI) or access UIKit properties (like frame) so your selector may need to kick off work back to the main thread. e.g.
(id)SomeMethod:UsingParams: {
// Do some work but the results
// Run in the background, on the main queue
dispatch_async(dispatch_get_main_queue(), ^{
// Do something UIKit related
});
}
[self performSelector:(SEL) withObject:(id) afterDelay:(NSTimeInterval)]
Performs a selector on the thread that it is being called. So when you call it from a background thread it will run there...
You can do that per example:
dispatch_time_t delay = dispatch_time( DISPATCH_TIME_NOW, <delay in seconds> * NSEC_PER_SEC );
dispatch_after( delay, dispatch_get_main_queue(), ^{
[self performSelectorInBackground: <sel> withObject: <obj>]
});
Somehow a mixed solution. It would be better to stick with a full GCD approach tho.
Related
Is there a way to do multiple actions respectively using the : performSelector:withObject:afterDelay code ?
Sample code will be appreciated ,
Thanks in advance.
Or use blocks. If you start to type dispatch_after, you'll see code completion that will pop up the following snippet of code, and then you can put however many actions you want in that block. In this example, I'm showing it being used inside an IBAction:
- (IBAction)pushedSomeButton:(id)sender
{
// anything you want to do immediate, do here
[self doingSomethingRightNow];
// anything you want to defer for some time, do inside the dispatch_after block
// in this example, calling callAnotherMethod and whyNotCallAnotherMethod
int64_t delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self callAnotherMethod];
[self whyNotCallAnotherMethod];
});
}
Setup a method that gets fired with the performSelector:withObject:afterDelay: call:
-(void)performTheseAction {
// do something
// do something else
[self callAnotherMethod];
[self whyNotCallAnotherMethod];
}
I have a method that takes several params, I need to delay a portion of that method. I DO NOT want to split it into several methods and use [self performSelectorAfterDelay] because the delay requires params already in that method. I need something like the following
-(void)someMethod{
.....
delay {
more code but not a separate self method
}
... finish method
}
The dispatch_after function seems to line up with what you need:
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
// this code is going to be executed, on the main queue (or thread) after 2.0 seconds.
});
Of course, the time is configureable, and it's a bit confusing to read at first, but once you get used to how blocks work in conjunction with objective-c code, you should be good to go.
One word of caution:
NEVER, NEVER, NEVER! Block the main thread of an iPhone app using sleep(). Just don't do it!
Looks like an overkill.
-(void)someMethod{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSLog(#"Start code");
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_sync(backgroundQueue, ^{
sleep(5);
// delayed code
NSLog(#"Delayed code");
});
dispatch_sync(backgroundQueue, ^{
// finishing code
NSLog(#"Finishing code");
});
});
}
backgroundQueue might be user at external dispatch call. It looks really bad though :)
I'm having a hard time figuring out how to put this all together.
I have a puzzle solving app on the mac.
You enter the puzzle, press a button, and while it's trying to find the number of solutions,
min moves and such I would like to keep the UI updated.
Then once it's finished calculating, re-enable the button and change the title.
Below is some sample code from the button selector, and the solving function:
( Please keep in mind I copy/paste from Xcode so there might be some missing {} or
some other typos.. but it should give you an idea what I'm trying to do.
Basicly, user presses a button, that button is ENABLED=NO, Function called to calculate puzzle. While it's calculating, keep the UI Labels updated with moves/solution data.
Then once it's finished calculating the puzzle, Button is ENABLED=YES;
Called when button is pressed:
- (void) solvePuzzle:(id)sender{
solveButton.enabled = NO;
solveButton.title = #"Working . . . .";
// I've tried using this as a Background thread, but I can't get the code to waitTilDone before continuing and changing the button state.
[self performSelectorInBackground:#selector(createTreeFromNode:) withObject:rootNode];
// I've tried to use GCD but similar issue and can't get UI updated.
//dispatch_queue_t queue = dispatch_queue_create("com.gamesbychris.createTree", 0);
//dispatch_sync(queue, ^{[self createTreeFromNode:rootNode];});
}
// Need to wait here until createTreeFromNode is finished.
solveButton.enabled=YES;
if (numSolutions == 0) {
solveButton.title = #"Not Solvable";
} else {
solveButton.title = #"Solve Puzzle";
}
}
Needs to run in background so UI can be updated:
-(void)createTreeFromNode:(TreeNode *)node
{
// Tried using GCD
dispatch_queue_t main_queue = dispatch_get_main_queue();
...Create Tree Node and find Children Code...
if (!solutionFound){
// Solution not found yet so check other children by recursion.
[self createTreeFromNode:newChild];
} else {
// Solution found.
numSolutions ++;
if (maxMoves < newChild.numberOfMoves) {
maxMoves = newChild.numberOfMoves;
}
if (minMoves < 1 || minMoves > newChild.numberOfMoves) {
solutionNode = newChild;
minMoves = newChild.numberOfMoves;
// Update UI on main Thread
dispatch_async(main_queue, ^{
minMovesLabel.stringValue = [NSString stringWithFormat:#"%d",minMoves];
numSolutionsLabel.stringValue = [NSString stringWithFormat:#"%d",numSolutions];
maxMovesLabel.stringValue = [NSString stringWithFormat:#"%d",maxMoves];
});
}
GCD and performSelectorInBackground samples below. But first, let's look at your code.
You cannot wait where you want to in the code above.
Here's the code you had. Where you say wait in the comment is incorrect. See where I added NO.
- (void) solvePuzzle:(id)sender{
solveButton.enabled = NO;
solveButton.title = #"Working . . . .";
// I've tried using this as a Background thread, but I can't get the code to waitTilDone before continuing and changing the button state.
[self performSelectorInBackground:#selector(createTreeFromNode:) withObject:rootNode];
// NO - do not wait or enable here.
// Need to wait here until createTreeFromNode is finished.
solveButton.enabled=YES;
}
A UI message loop is running on the main thread which keeps the UI running. solvePuzzle is getting called on the main thread so you can't wait - it will block the UI. It also can't set the button back to enabled - the work hasn't been done yet.
It is the worker function's job on the background thread to do the work and then when it's done to then update the UI. But you cannot update the UI from a background thread. If you're not using blocks and using performSelectInBackground, then when you're done, call performSelectorOnMainThread which calls a selector to update your UI.
performSelectorInBackground Sample:
In this snippet, I have a button which invokes the long running work, a status label, and I added a slider to show I can move the slider while the bg work is done.
// on click of button
- (IBAction)doWork:(id)sender
{
[[self feedbackLabel] setText:#"Working ..."];
[[self doWorkButton] setEnabled:NO];
[self performSelectorInBackground:#selector(performLongRunningWork:) withObject:nil];
}
- (void)performLongRunningWork:(id)obj
{
// simulate 5 seconds of work
// I added a slider to the form - I can slide it back and forth during the 5 sec.
sleep(5);
[self performSelectorOnMainThread:#selector(workDone:) withObject:nil waitUntilDone:YES];
}
- (void)workDone:(id)obj
{
[[self feedbackLabel] setText:#"Done ..."];
[[self doWorkButton] setEnabled:YES];
}
GCD Sample:
// on click of button
- (IBAction)doWork:(id)sender
{
[[self feedbackLabel] setText:#"Working ..."];
[[self doWorkButton] setEnabled:NO];
// async queue for bg work
// main queue for updating ui on main thread
dispatch_queue_t queue = dispatch_queue_create("com.sample", 0);
dispatch_queue_t main = dispatch_get_main_queue();
// do the long running work in bg async queue
// within that, call to update UI on main thread.
dispatch_async(queue,
^{
[self performLongRunningWork];
dispatch_async(main, ^{ [self workDone]; });
});
}
- (void)performLongRunningWork
{
// simulate 5 seconds of work
// I added a slider to the form - I can slide it back and forth during the 5 sec.
sleep(5);
}
- (void)workDone
{
[[self feedbackLabel] setText:#"Done ..."];
[[self doWorkButton] setEnabled:YES];
}
dispatch_queue_t backgroundQueue;
backgroundQueue = dispatch_queue_create("com.images.bgqueue", NULL);
- (void)process {
dispatch_async(backgroundQueue, ^(void){
//background task
[self processHtml];
dispatch_async(main, ^{
// UI updates in main queue
[self workDone];
});
});
});
}
By and large, any work to be submitted to a background queue needs to follow this pattern of code:
dispatch_queue_t queue = dispatch_queue_create("com.myappname", 0);
__weak MyClass *weakSelf = self; //must be weak to avoid retain cycle
//Assign async work
dispatch_async(queue,
^{
[weakSelf doWork];
dispatch_async(dispatch_get_main_queue(),
^{
[weakSelf workDone];
});
});
queue = nil; //Using ARC, we nil out. Block always retains the queue.
Never Forget:
1 - queue variable above is a reference counted object, because it is a private queue, not a global one. So it is retained by the block which is executing inside that queue. Until this task is complete, it is not released.
2 - Every queue got its own stack which will be allocated / deallocated as part of recursive operation. You only need to worry about class member variables which are reference counted (strong, retain etc.) which are accessed as part of doWork above.
3 - While accessing those reference counted vars inside background queue operation, you need to make them thread-safe, depending on use cases in your app. Examples include writes to objects such as strings, arrays etc. Those writes should be encapsulated inside #synchronized keyword to ensure thread-safe access.
#synchronized ensures no another thread can get access to the resource it protects, during the time the block it encapsulates gets executed.
#synchronized(myMutableArray)
{
//operation
}
In the above code block, no alterations are allowed to myMutableArray inside the #synchronized block by any other thread.
I would like to call a method every 10 seconds, but I want to use something other than NSTimer. What could I use to do this?
I know you said you didn't want to use timers, but just to make sure you know how simple it would be with a timer...
[NSTimer scheduledTimerWithTimeInterval:10.0
target:self
selector:#selector(someMethod)
userInfo:nil
repeats:YES];
If you dont want to use the timer, you can use GCD which internally will make use of NSOperationQueue, nevertheless will work in all cases. For eg: i had a class which was inherited from NSOperation so the above methods didn't work so i had go go with GCD:
double delayInSeconds = 3.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_after(popTime, queue, ^{
[self methodYouWantToCall];
});
The above code calls the method methodYouWantToCall after every three seconds.
You can create a loop with performSelector:withObject:afterDelay: setting afterDelay to 10.0.
I don't recommend this though, use an NSTimer.
- (void)callMeEvery10Seconds
{
[self performSelector:#selector(callMeEvery10Seconds)
withObject:nil
afterDelay:10.0];
// ... code comes here ...
}
If you are not using Cocos2D, you have to use a NSTimer to do this....
If you are using Cocos2D, use the schedule method
here's a link below that shows both :
How can I create a count down timer for cocos2d?
The easiest way to do so is:
- (void)scheduleLoopInSeconds:(NSTimeInterval)delayInSeconds
{
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_after(popTime, queue, ^{
[self callWhatEverMethodYouWant];
[self shceduleLoopcaInSeconds:delayInSeconds];//set next iteration
});
}
// now whenever you like call this, and it will be triggering "callWhatEverMethodYouWant" every 10 secs.
[self shceduleLoopcaInSeconds:10.0];
We are using dispatch queues to generate timer events. Following is the code which does the task:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
if (!timer) return self;
dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval * NSEC_PER_SEC, 5 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer,
^{
//Some work…
});
This works very well except that when we run the profiler, we see a lot of memory leaks from these methods:
dispatch_source_create
dispatch_source_set_timer
dispatch_source_set_event_handler
We had made sure that timer is released using dispatch_release() method.
Can someone please let us know if there is any mistake we are doing in the code above? And also if you can point out any example of timer event generation, it would be helpful.
dispatch_source_set_timer(3) Mac OS X Manual Page
All timers will repeat indefinitely until
dispatch_source_cancel() is called.
How do you call dispatch_source_cancel() and dispatch_release() for the timer?
Dispatch source timer example:
dispatch_source_t timer = dispatch_source_create(
DISPATCH_SOURCE_TYPE_TIMER, 0, 0,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
dispatch_source_set_timer(timer,
dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC),
DISPATCH_TIME_FOREVER, 1ull * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
NSLog(#"wakeup!");
dispatch_source_cancel(timer);
});
dispatch_source_set_cancel_handler(timer, ^{
NSLog(#"canceled");
dispatch_release(timer);
});
dispatch_resume(timer);