Delaying a method on a separate thread in objective C - objective-c

Is there a way to delay a method call before it changes a key value in the user defaults?
For instance; I have method A which is an IBAction and Method B. If a key "keyOne" is false; method A sets "keyOne" to true via the -[NSUserDefaults setBool: forKey:] and then calls method B with an integer input of for time delay. Method B then needs to wait for whatever the delay was input to be in seconds and then change the "keyOne" back to true with the same NSUserDefaults.

Use GCD's dispatch_after() to delay the operation. But, instead of using the main queue as generated by the Xcode code snippet, create your own queue, or utilize the background queue.
dispatch_queue_t myQueue = dispatch_queue_create("com.my.cool.new.queue", DISPATCH_QUEUE_SERIAL);
double delayInSeconds = 10.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, myQueue, ^(void){
// Reset stuff here (this happens on a non-main thead)
dispatch_async(dispatch_get_main_queue(), ^{
// Make UI updates here. (UI updates must be done on the main thread)
});
});
You can find more information on the difference between using performSelector: and dispatch_after() in the answer in this post: What are the tradeoffs between performSelector:withObject:afterDelay: and dispatch_after

You can use perform selector from method A to call method B:
[self performSelector:#selector(methodName) withObject:nil afterDelay:1.0];
If your method B needs to know the delay you can use withObject: to pass parameter, it needs to be NSNumber, not integer.

Related

Overwrite dispatch command

I have a function (sendError) that sends an error message. And then after 5 seconds removes it like this:
-(void)sendError:(NSString*)message{
_errorMessage.stringValue = message;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
_errorMessage.stringValue = #"";
});
}
The problem is that if the button is ran once and then again 3 seconds later the second time it is ran the message will remove 2 seconds later rather than 5.
How do I go about cancelling the previous dispatch and writing a new one (overwriting).
There are a couple of ways to do this. First, you can stop using GCD and go back to an earlier mechanism, -performSelector:withObject:afterDelay:. The advantage here is that there's a method +cancelPreviousPerformRequestsWithTarget:selector:object: to cancel a still-pending perform request.
-(void)sendError:(NSString*)message{
_errorMessage.stringValue = message;
[NSObject cancelPreviousPerformRequestsWithTarget:_errorMessage selector:#selector(setStringValue:) object:#""];
[_errorMessage performSelector:#selector(setStringValue:) withObject:#"" afterDelay:5];
}
If you want to keep using GCD, then you need to track the cancellation yourself. One way is to keep a "generation" count. So, assume the existence of an NSUInteger instance variable called _errorGeneration.
-(void)sendError:(NSString*)message{
_errorMessage.stringValue = message;
_errorGeneration++;
NSUInteger capturedGeneration = _errorGeneration;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (_errorGeneration == capturedGeneration)
_errorMessage.stringValue = #"";
});
}
Basically, you don't cancel the dispatched task (because you can't), but you make it do nothing if it's obsolete.

Retry Objective-C method until a condition is met

I have a method which is called, and the first thing it does is ascertain when the network is reachable. If it's not, I'd like to wait 10s, then run the same method with the arguments/params that were initially passed in..
This is my disastrous attempt (I'm more a JS dev, relatively new to Objective-C):
- (void)sendRequestToURL:(NSString *)url withPostData:(NSString *)postData withPage:(int)page sortBy:(NSString *)sort completionBlock:(void (^)(NSDictionary *))completion {
if(![FooNetworkManager isReachable]){
self.lastRequest = ??? // lastRequest is an NSDictionary
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 10);
dispatch_after(delay, dispatch_get_main_queue(), ^(void){
[self sendRequestToURL:self.lastRequest]; // this is almost definitely wrong
});
}
}
In JavaScript, inside a method we have access to the arguments object that contains all the params that were passed into the method, not sure how to replicate this in Objective-C.
Also, self.lastRequest is defined further up in this same class:
#property(nonatomic, strong) NSDictionary *lastRequest;
In its simplest form you can dispense with lastRequest and do:
- (void)sendRequestToURL:(NSString *)url withPostData:(NSString *)postData withPage:(int)page sortBy:(NSString *)sort completionBlock:(void (^)(NSDictionary *))completion {
if(![FooNetworkManager isReachable]){
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 10);
dispatch_after(delay, dispatch_get_main_queue(), ^(void){
[self sendRequestToURL:url withPostData:postData withPage:page sortBy:sort completionBlock:completion];
});
}
}
in this way you're simply capturing the passed parameters in the block. So you need to be careful that capturing mutable objects, if you have any, is acceptable...
You should also have some way to cancel the retry, or perhaps a maximum number of attempts, or even an exponential back off time in between attempts.

How to wait past dispatch_async before proceeding?

I have a series of dispatch_async that I am performing and I would like to only update the UI when they are all done. Problem is the method within dispatch_async calls something in a separate thread so it returns before the data is fully loaded and dispatch_group_notify is called before everything is loaded.
So I introduce a infinite loop to make it wait until a flag is set.
Is this the best way? See code below.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_group_t group = dispatch_group_create();
for (...) {
dispatch_group_async(group, queue, ^{
__block BOOL dataLoaded = NO;
[thirdPartyCodeCallWithCompletion:^{
dataLoaded = YES;
}];
// prevent infinite loop
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)),
queue, ^{
dataLoaded = YES;
});
// infinite loop to wait until data is loaded
while (1) {
if (dataLoaded) break;
}
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//update UI
});
}
You're already aware of dispatch groups. Why not just use dispatch_group_wait(), which includes support for a timeout? You can use dispatch_group_enter() and dispatch_group_leave() rather than dispatch_group_async() to make the group not done until the internal block for the third-party call with completion is finished.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_group_t group = dispatch_group_create();
for (...) {
dispatch_group_enter(group);
dispatch_async(queue, ^{
[thirdPartyCodeCallWithCompletion:^{
dispatch_group_leave(group);
}];
}
}
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, NSECS_PER_SEC));
dispatch_async(dispatch_get_main_queue(), ^{
//update UI
});
The use of dispatch_group_wait() does make this code synchronous, which is bad if run on the main thread. Depending on what exactly is supposed to happen if it times out, you could use dispatch_group_notify() as you were and use dispatch_after() to just updates the UI rather than trying to pretend the block completed.
Update: I tweaked my code to make sure that "update UI" happens on the main queue, just in case this code isn't already on the main thread.
By the way, I only used dispatch_async() for the block which calls thirdPartyCodeCallWithCompletion: because your original used dispatch_group_async() and I wasn't sure that the hypothetical method was asynchronous. Most APIs which take a completion block are asynchronous, though. If that one is, then you can just invoke it directly.
Another method is to use semaphore and the dispatch_semaphore_wait:
// Create your semaphore, 0 is specifying the initial pool size
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
#autoreleasepool {
// Your code goes here
}
// Release the resource and signal the semaphore
dispatch_semaphore_signal(semaphore);
});
// Wait for the above block execution, AKA Waits for (decrements) a semaphore.
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// After this line you can now safely assert anything you want regarding the async operation since it is done.

Adding delay between execution of two following lines

I need to add a delay between the execution of two lines in a(same) function. Is there any favorable option to do this?
Note: I don't need two different functions to do this, and the delay must not affect other functions' execution.
eg:
line 1: [executing first operation];
line 2: Delay /* I need to introduce delay here */
line 3: [executing second operation];
You can use gcd to do this without having to create another method
// ObjC
NSTimeInterval delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(#"Do some work");
});
// Swift
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
print("Do some work)
}
You should still ask yourself "do I really need to add a delay" as it can often complicate code and cause race conditions
You can use the NSThread method:
[NSThread sleepForTimeInterval: delay];
However, if you do this on the main thread you'll block the app, so only do this on a background thread.
or in Swift
NSThread.sleepForTimeInterval(delay)
in Swift 3
Thread.sleep(forTimeInterval: delay)
This line calls the selector secondMethod after 3 seconds:
[self performSelector:#selector(secondMethod) withObject:nil afterDelay:3.0 ];
Use it on your second operation with your desired delay. If you have a lot of code, place it in its own method and call that method with performSelector:. It wont block the UI like sleep
Edit: If you do not want a second method you could add a category to be able to use blocks with performSelector:
#implementation NSObject (PerformBlockAfterDelay)
- (void)performBlock:(void (^)(void))block
afterDelay:(NSTimeInterval)delay
{
block = [block copy];
[self performSelector:#selector(fireBlockAfterDelay:)
withObject:block
afterDelay:delay];
}
- (void)fireBlockAfterDelay:(void (^)(void))block
{
block();
}
#end
Or perhaps even cleaner:
void RunBlockAfterDelay(NSTimeInterval delay, void (^block)(void))
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC*delay),
dispatch_get_current_queue(), block);
}
I have a couple of turn-based games where I need the AI to pause before taking its turn (and between steps in its turn). I'm sure there are other, more useful, situations where a delay is the best solution. In Swift:
let delay = 2.0 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue()) { self.playerTapped(aiPlayView) }
I just came back here to see if the Objective-C calls were different.(I need to add this to that one, too.)
[checked 27 Nov 2020 and confirmed to be still accurate with Xcode 12.1]
The most convenient way these days: Xcode provides a code snippet to do this where you just have to enter the delay value and the code you wish to run after the delay.
click on the + button at the top right of Xcode.
search for after
It will return only 1 search result, which is the desired snippet (see screenshot). Double click it and you're good to go.
If you're targeting iOS 4.0+, you can do the following:
[executing first operation];
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[executing second operation];
});
Like #Sunkas wrote, performSelector:withObject:afterDelay: is the pendant to the dispatch_after just that it is shorter and you have the normal objective-c syntax. If you need to pass arguments to the block you want to delay, you can just pass them through the parameter withObject and you will receive it in the selector you call:
[self performSelector:#selector(testStringMethod:)
withObject:#"Test Test"
afterDelay:0.5];
- (void)testStringMethod:(NSString *)string{
NSLog(#"string >>> %#", string);
}
If you still want to choose yourself if you execute it on the main thread or on the current thread, there are specific methods which allow you to specify this. Apples Documentation tells this:
If you want the message to be dequeued when the run loop is in a mode
other than the default mode, use the
performSelector:withObject:afterDelay:inModes: method instead. If you
are not sure whether the current thread is the main thread, you can
use the performSelectorOnMainThread:withObject:waitUntilDone: or
performSelectorOnMainThread:withObject:waitUntilDone:modes: method to
guarantee that your selector executes on the main thread. To cancel a
queued message, use the cancelPreviousPerformRequestsWithTarget: or
cancelPreviousPerformRequestsWithTarget:selector:object: method.

What would be the equivalent of doing this with NSOperation?

[self performSelector:#selector(stopPulling) withObject:nil afterDelay:0.01];
The code is fine. I just think that using NSOperation and block should be the way to go for the future.
I am familiar with NSOperation. I just want to do the same thing with block and NSOperation.
I can do this with GCD already:
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){
<#code to be executed on the main queue after delay#>
});
C'mon. There is something that can be done in GCD that can't be done more easily in NSOperation?
NSOperationQueue does not provide a mechanism for delayed execution. Use GCD or NSTimer.
I ended up making this:
#import "BGPerformDelayedBlock.h"
#implementation BGPerformDelayedBlock
+ (void)performDelayedBlock:(void (^)(void))block afterDelay:(NSTimeInterval)delay
{
int64_t delta = (int64_t)(1.0e9 * delay);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delta), dispatch_get_main_queue(), block);
}
+(void)performSlightlyDelayedBlock:(void (^)(void))block
{
[self performDelayedBlock:block afterDelay:.1];
}
#end
It's based on an answer in How do you trigger a block after a delay, like -performSelector:withObject:afterDelay:?
I think it shouldn't be a category.
Strange that I ended up using GCD.
However, using it is simple. I just do:
[BGPerformDelayedBlock performSlightlyDelayedBlock:^{
[UIView animateWithDuration:.3 animations:^{
[self snapToTheTopOfTheNonHeaderView];
}];
}];
Your code is similar to, using a NSTimer setting a selector after 0.01sec with no repeats. This will be called on the main thread.
NSOperation or blocks are used to perform operations in background. These you can use instead of performSelectorInBackground.
If your need is to work in background then go for it. There are many tutorials available to learn 'NSOperationusing 'NSOperationQueue and blocks.