performSelector:withObject:afterDelay: within NSOperation - ios

I'm executing some code within some NSOperation objects managed by an NSOperationQueue. The code also contains a delayed method call using performSelector:withObject:afterDelay:.
The problem is, that the corresponding selector which should be called delayed, is not called at all.
Having read this answer to a StackOverflow question, I guess it's due to the fact that the NSOperation already has finished and its thread doesn't even exist anymore, "forgetting" the scheduled call to the selector.
How can I work around this?
How can I do a delayed call to a method within an NSOperation?

One possibility would be to use Grand Central Dispatch, namely dispatch_after():
double delayInSeconds = 2.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, ^{
...
});
Instead of dispatch_get_global_queue(), you can of course also create your own dispatch queue or use the main queue with dispatch_get_main_queue().

Related

performSelectorWithDelay in serialQueue

I've a serial queue and I use that queue to call a performSelectorWithDelay like below
dispatch_async(serialQueue, ^(void) {
[self performSelector:#selector(fetchConfigFromNetwork) withObject:nil afterDelay:rootConfig.waitTime];
});
However, the method fetchConfigFromNetwork never gets called. However, if instead of serialQueue, I use mainQueue - it starts working.
Cannot understand what's happening here and how to fix it?
The explanation why your code doesn't work is in the documentation: https://developer.apple.com/documentation/objectivec/nsobject/1416176-performselector?language=occ
This method registers with the runloop of its current context, and
depends on that runloop being run on a regular basis to perform
correctly. One common context where you might call this method and end
up registering with a runloop that is not automatically run on a
regular basis is when being invoked by a dispatch queue. If you need
this type of functionality when running on a dispatch queue, you
should use dispatch_after and related methods to get the behavior you
want.
I'm assuming you want that method to be called on the serial queue with a delay. The most straight forward (and recommended way) is to use dispatch_after:
__weak typeof(self) wself = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(rootConfig.waitTime * NSEC_PER_SEC)), serialQueue, ^{
[wself fetchConfigFromNetwork];
});
This method sets up a timer to perform the aSelector message on the current thread’s run loop. The timer is configured to run in the default mode (NSDefaultRunLoopMode). When the timer fires, the thread attempts to dequeue the message from the run loop and perform the selector. It succeeds if the run loop is running and in the default mode; otherwise, the timer waits until the run loop is in the default mode.
This is the discussion about the method performSelector:withObject:afterDelay:, I think the block of dispatch_async will execute on a new thread (not main thread), but you would not know which thread it is, so you can not new a runloop and open it and assign it to this thread. because the runloop of thread is close in default except the main thread, the timer will wait forever.
On my opinion, you should use NSThread instead of dispatch_async, and create a runloop for the thread that you use, then specified the mode of runloop with NSDefaultRunLoopMode, if you actually want to cancelPreviousPerformRequestsWithTarget, otherwise use dispatch_after instead of performSelector.
That's my understanding. I can't promise it is right.

Delay in executing method after dispatch group operations are complete in iOS

I am calling a method after after the queues in my dispatch group complete executing. However, there is a significant delay in executing the final method even after all the queues have been executed. Can anyone explain any probable reasons?
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue,^{
//some code
}
dispatch_group_notify(group, queue,
^{
[self allTasksDone];
});
What I meant was that the method allTasksDone is executed after some delay even when the operation in the async queue has completed.
How does -allTasksDone work? If it's communicating with the user by updating user interface elements, it need to run on in the main thread's context, or else it'll appear that the UI elements in question are "delayed" -- they won't update until the main run loop happens to make them update.
Try this instead:
dispatch_group_notify(group, dispatch_get_main_queue(),
^{
[self allTasksDone];
});
As it is, you're running -allTasksDone on the default background queue, which doesn't play nice with AppKit or UIKit.
I suggest an alternative approach although you can most certainly accomplish this using dispatch groups.
// Important note: This does not work with global queues, but you can use target queues to direct your custom queue to one of your global queues if you need priorities.
dispatch_queue_t queue = dispatch_queue_create("com.mycompany.myqueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue,^{
//some code
}
dispatch_barrier_async(queue,
^{
// this executes when all previously dispatched blocks have finished.
[self allTasksDone];
});

`[NSThread isMainThread]` always returns YES

This code
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(#"Main Thread? %d", [NSThread isMainThread]);
});
shows that I'm in the main thread. Even doing this:
queue = dispatch_queue_create("nonMainQueue", NULL);
still reports that I'm in the main queue. This is, it seems, because I'm using dispatch sync.
Does this mean that my code is the same as not using dispatch_sync at all? Also: what's the point of dispatch_sync if it does nothing at all, then?
Because queues are not threads, in order to check if you are on the main 'queue', you must use different code, something similar to this:
if (dispatch_get_current_queue() == dispatch_get_main_queue()) {
NSLog(#"On Main Thread!");
}
Just note that dispatch_get_current_queue is deprecated, and is subject to be completely removed in a later iOS/Mac OS version.
This is documented behavior. As an optimization the blocks passed to dispatch_sync are executed on the current thread if possible (which is almost always).
My understanding from Apple's GCD guide, there is no guarantee that dispatch queues will execute on a separate thread. GCD will determine which thread, and if necessary create a new one.
Part of the point is now you do not have to think about threads.
The only thing to keep in mind, is to make sure you are updating UI elements on the main queue, for example:
// on background queue
dispatch_async( dispatch_get_main_queue(), ^(void){
someLabel.text = #"My Text";
});

iOS Ensuring a Function Is Called Only Once Per Session

I have several threads and once they are all finished working, I need to call a myMergeBlock method exactly once per action. I can't use dispatch_once because I want to be able to call myMergeBlock at a later time.
Some pseudo code looks like this but is not yet thread safe:
BOOL worker1Finished, worker2Finished, worker3Finished;
void (^mergeBlock)(void) = ^{
if (worker1Finished && worker2Finished && worker3Finished)
dispatch_async(queue, myMergeBlock); // Must dispatch this only once
}
void (^worker1)(void) = ^{
...
worker1Finished = YES;
mergeBlock();
}
void (^worker2)(void) = ^{
...
worker2Finished = YES;
mergeBlock();
}
void (^worker3)(void) = ^{
...
worker3Finished = YES;
mergeBlock();
}
Also, based on the way the workers are called, I do not call them directly, but instead pass them into a function as arguments.
You want to use dispatch groups. First you create a group, schedule the three workers in the group, then add a notification block to the group.
It should look something like this:
//create dispatch group
dispatch_group_t myWorkGroup = dispatch_group_create();
//get one of the global concurrent queues
dispatch_queue_t myQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL);
//submit your work blocks
dispatch_group_async(myWorkGroup, myQueue, worker1);
dispatch_group_async(myWorkGroup, myQueue, worker2);
dispatch_group_async(myWorkGroup, myQueue, worker3);
//set the mergeBlock to be submitted when all the blocks in the group are completed
dispatch_group_notify(myWorkGroup, myQueue, mergeBlock);
//release the group as you no longer need it
dispatch_release(myWorkGroup);
You could hang on the the group and reuse it later if you prefer. Be sure to schedule the work before the notification. If you try to schedule the notification first it will be dispatched immediately.
I haven't tested this code but I do use dispatch_groups in my projects.
This sounds very messy and low level. Have you looked at Operation Queues and Dispatch Groups and semaphores as discussed in the Concurrency Programming Guide. I think they may offer simpler solutions to your problem.
If you're targeting Lion or iOS 5 and up, you can use barrier blocks as long as the blocks are dispatched on a non-global, concurrent queue. For example:
dispatch_queue_t customConcurrentQueue = dispatch_queue_create("customQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(customConcurrentQueue, worker1);
dispatch_async(customConcurrentQueue, worker2);
dispatch_async(customConcurrentQueue, worker3);
dispatch_barrier_async(customConcurrentQueue, mergeBlock);
//Some time later, after you're sure all of the blocks have executed.
dispatch_queue_release(customConcurrentQueue);
A barrier block executes after all previously submitted blocks have finished executing, and any blocks submitted after the barrier block will be forced to wait until the barrier block has finished. Again, for reasons which should be obvious, you can't use barrier blocks on the global queues. You must create your own concurrent queue.

Call a method after 5 sec. without NSTimer

I want to call any method OR event after 5 second in iphone without using NStimer.
Use performSelector:withObject:afterDelay: method.
[self performSelector:#selector(methodName) withObject:nil afterDelay:5];
Or use GCD:
double delayInSeconds = 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
/* code to be executed on the main queue after delay */
});
The question sounds like a very pointless assignment. That calls for an equally useless answer:
sleep(5);
[self myMethodToCallAfter5Seconds];
Edit: Since people don't seem to like my answer, here are some thoughts:
The OP asked for a way to send a message after a delay. He explicitly asked for a way to do this without using an NSTimer yet he didn't specify why. As there are many ways to send a message after a delay, picking one depends upon the reason why to avoid any specific method. Not knowing the reason for refusing an obvious approach there's only speculation.
One possible situation could be a background thread without a runloop that should await the delay. Here my (not totally serious) proposal could even be a proper solution when blocking the thread is not an issue. In this scenario neither NSTimer nor performSelector:withObject:afterDelay: could be used.
I added this answer to highlight the fact that the question makes little sense without giving a reason. As my answer fulfills the specification it shows the pointlessness of the question.

Resources