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.
Related
Can anybody see a reason why this code would work fine to update UI:
__block NSDictionary *result = nil;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[[SomeService sharedInstance] doSomethingGreatWithReplyBlock:^(NSDictionary * response) {
result = response;
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
[self updateDisplay:result];
});
But this one won't?
__block NSDictionary *result = nil;
[[SomeService sharedInstance] doSomethingGreatWithReplyBlock:^(NSDictionary * response) {
dispatch_async(dispatch_get_main_queue(), ^{
[self updateDisplay:response];
});
}];
Isn't this exactly the same? In the first example I'm waiting for the async operation to finish using a semaphore. Then dispatch_async on the main queue.
In the second one I'm calling dispatch_async (also on the main queue) directly from within the other block (which runs on some background queue). This one still calls the updateDisplay method fine - however it doesn't actually update the UI. It feels like some main thread update issue however [NSThread isMainThread] still returns true...
Is there any obvious difference I'm missing here? I'm pretty lost here and would appreciate any explanation. I have never observed such weird behavior before.
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.
I am developing an app that contacts a RESTful server to get some data and then with the returned JSON response to display that data.
Using UniRest calls and all is working well. The main call is 'runUnirestRequest'
The uni rest call is an async GCD dispatch call. My problem is that because I am testing locally the call is so quick I can't see the activity indicator rolling. It simply disappears before I can see it.
The GCD block occur within the viewController viewDidLoad call.
What I need to achieve: Have the async unirest call take several seconds to simulate a server response that is slow (Dont want to actually stop the iOS app in its tracks).
Please excuse any coding errors/bad habits, only been doing objective c for a week but am happy for any additional constructive crit. :)
I have tried
sleep(5); // But bad idea as far as I can see.
Also tried
[NSThread sleepForTimeInterval:5.0]; // but this doesn't seem to do anything.
viewDidLoad
- (void)viewDidLoad
{
[super viewDidLoad];
[self createActivityIndicator];
NSLog(#"viewDidLoad");
NSLog(#"viewDidLoad->thread: %#", [NSThread currentThread]);
[messageLabel setText:#""];
unirestQueue = dispatch_queue_create("com.simpleweb.pbs.dayDataUnirestRequest", NULL);
// Do any additional setup after loading the view from its nib.
daySalesFigures = [[PBSDaySales alloc] init];
responseVal = [[HttpJsonResponse alloc] init];
// Use Grand Central Dispatch to run async task to server
dispatch_async(unirestQueue, ^{
[self runUnirestRequest:self.requestUrl];
});
dispatch_after(unirestQueue, dispatch_get_main_queue(), ^(void){
[activityIndicator stopAnimating];
});
}
runUniRestRequest function
- (void) runUnirestRequest:(NSString*)urlToGet
{
[NSThread sleepForTimeInterval:5.0];
NSLog(#"runUnirestRequest called");
HttpJsonResponse* response = [[Unirest get:^(SimpleRequest* request) {
[request setUrl:#"http://x.x.x.x:9000/Sales/Day/2013-02-14"];
}] asString];
NSString *jsonStr = [response body];
SBJsonParser *jsonParser = [SBJsonParser new];
id response2 = [jsonParser objectWithString:jsonStr];
[self deserializeJsonPacket:(NSDictionary*)response2];
}
dispatch_after's first parameter is time. You are passing in unirestQueue, which is dispatch_queue_t queue according to
unirestQueue = dispatch_queue_create("com.simpleweb.pbs.dayDataUnirestRequest", NULL);
proper code for dispatch_after, i.e. performing block after some delay, is like this:
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){
// Do whatever you want
});
Edit: Oh, I probably see what you are trying to accomplish :-) You thought the dispatch_after means "do something after this queue" right? Nope, it's "do something after some time"
Edit 2: You can use code like below to do something time consuming in background and update UI when its done
// Start block on background queue so the main thread is not frozen
// which prevents apps UI freeze
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do something taking a long time in background
// Here we just freeze current (background) thread for 5s
[NSThread sleepForTimeInterval:5.0];
// Everything in background thread is done
// Call another block on main thread to do UI stuff
dispatch_async(dispatch_get_main_queue(), ^{
// Here you are in the main thread again
// You can do whatever you want
// This example just stops UIActivityIndicatorView
[activityIndicator stopAnimating];
});
});
Edit 3: I recommend this great article about GCD at raywenderlich.com for more detailed info
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.
I have an object that fetches XML or JSON over a network. Once this fetching is complete it calls a selector, passing in the returned data. So, for example I'd have something like:
-(void)testResponseWas200
{
[MyObject get:#"foo.xml" withTarget:self selector:#selector(dataFinishedLoading:)];
}
I tried the route of implementing dataFinishedLoading in the Test class and attempting to test inside that method, but the test suite is just locking up. This seems like it's a case for mocking, but I'm wondering if others have encountered this and how they handled it.
FYI: I'm using gh-unit for testing and any method prefixed with test* is executed automatically.
Three ways that come to mind are: NSRunLoop, semaphores, and groups.
NSRunLoop
__block bool finished = false;
// For testing purposes we create this asynchronous task
// that starts after 3 seconds and takes 1 second to execute.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0UL);
dispatch_time_t threeSeconds = dispatch_time(DISPATCH_TIME_NOW, 3LL * NSEC_PER_SEC);
dispatch_after(threeSeconds, queue, ^{
sleep(1); // replace this with your task
finished = true;
});
// loop until the flag is set from inside the task
while (!finished) {
// spend 1 second processing events on each loop
NSDate *oneSecond = [NSDate dateWithTimeIntervalSinceNow:1];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:oneSecond];
}
A NSRunLoop is a loop that processes events like network ports, keyboard, or any other input source you plug in, and returns after processing those events, or after a time limit. When there are no events to process, the run loop puts the thread to sleep. All Cocoa and Core Foundation applications have a run loop underneath. You can read more about run loops in Apple's Threading Programming Guide: Run Loops, or in Mike Ash Friday Q&A 2010-01-01: NSRunLoop Internals.
In this test, I'm just using the NSRunLoop to sleep the thread for a second. Without it, the constant looping in the while would consume 100% of a CPU core.
If the block and the boolean flag are created in the same lexical scope (eg: both inside a method), then the flag needs the __block storage qualifier to be mutable. Had the flag been a global variable, it wouldn't need it.
If the test crashes before setting the flag, the thread is stuck waiting forever. Add a time limit to avoid that:
NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:2];
while (!finished && [timeout timeIntervalSinceNow]>0) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
}
if (!finished) NSLog(#"test failed with timeout");
If you are using this code for unit testing, an alternative way to insert a timeout is to dispatch a block with an assert:
// taken from https://github.com/JaviSoto/JSBarrierOperationQueue/blob/master/JSBarrierOperationQueueTests/JSBarrierOperationQueueTests.m#L118
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 2LL * NSEC_PER_SEC);
dispatch_after(timeout, dispatch_get_main_queue(), ^(void){
STAssertTrue(done, #"Should have finished by now");
});
Semaphore
Similar idea but sleeping until a semaphore changes, or until a time limit:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
// signal the semaphore after 3 seconds using a global queue
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0UL);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3LL*NSEC_PER_SEC), queue, ^{
sleep(1);
dispatch_semaphore_signal(semaphore);
});
// wait with a time limit of 5 seconds
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 5LL*NSEC_PER_SEC);
if (dispatch_semaphore_wait(semaphore, timeout)==0) {
NSLog(#"success, semaphore signaled in time");
} else {
NSLog(#"failure, semaphore didn't signal in time");
}
dispatch_release(semaphore);
If instead we waited forever with dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); we would be stuck until getting a signal from the task, which keeps running on the background queue.
Group
Now imagine you have to wait for several blocks. You can use an int as flag, or create a semaphore that starts with a higher number, or you can group the blocks and wait until the group is finished. In this example I do the later with just one block:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0UL);
// dispatch work to the given group and queue
dispatch_group_async(group,queue,^{
sleep(1); // replace this with your task
});
// wait two seconds for the group to finish
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 2LL*NSEC_PER_SEC);
if (dispatch_group_wait(group, timeout)==0) {
NSLog(#"success, dispatch group completed in time");
} else {
NSLog(#"failure, dispatch group did not complete in time");
}
dispatch_release(group);
If for some reason (to clean up resources?) you want to run a block after the group is finished, use dispatch_group_notify(group,queue, ^{/*...*/});
Asynchronous callbacks often require a message loop to run. It is a frequent pattern to stop the message loop after callback was called in the test code. Otherwise the loop is just waiting for next tasks, and there will be none.
#jano Thank you I made of this little util from your post
In PYTestsUtils.m
+ (void)waitForBOOL:(BOOL*)finished forSeconds:(int)seconds {
NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:seconds];
while (!*finished && [timeout timeIntervalSinceNow]>0) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
}
}
in my test file
- (void)testSynchronizeTime
{
__block BOOL finished = NO;
[self.connection synchronizeTimeWithSuccessHandler:^(NSTimeInterval serverTime) {
NSLog(#"ServerTime %f", serverTime);
finished = YES;
} errorHandler:^(NSError *error) {
STFail(#"Cannot get ServerTime %#", error);
finished = YES;
}];
[PYTestsUtils waitForBOOL:&finished forSeconds:10];
if (! finished)
STFail(#"Cannot get ServerTime within 10 seconds");
}
variation
add in PYTestsUtils.m
+ (void)execute:(PYTestExecutionBlock)block ifNotTrue:(BOOL*)finished afterSeconds:(int)seconds {
[self waitForBOOL:finished forSeconds:seconds];
if (! *finished) block();
}
usage:
- (void)testSynchronizeTime
{
__block BOOL finished = NO;
[self.connection synchronizeTimeWithSuccessHandler:^(NSTimeInterval serverTime) {
NSLog(#"ServerTime %f", serverTime);
finished = YES;
} errorHandler:^(NSError *error) {
STFail(#"Cannot get ServerTime %#", error);
finished = YES;
}];
[PYTestsUtils execute:^{
STFail(#"Cannot get ServerTime within 10 seconds");
} ifNotTrue:&finished afterSeconds:10];
}
One of the best ways to test asynchronous and multi-threaded code is with event logging. Your code should log events at interesting or useful times. Often an event alone is enough information to prove that logic is working correctly. Somtimes events will need payloads, or other meta information so they can be paired or chained.
This is most useful when the run-time or the operating system supports an efficient and robust eventing mechanism. This enables your product to ship with events in the 'retail' version. In this scenario, your events are only enabled when you need to debug a problem, or run a unit test to prove thins are working correctly.
Having the events in the retail (production) code lets you test and debug on any platform. This is huge benefit over debug or 'checked' code.
Note, like asserts, be careful where you put events - they can be expensive if logged to often. But the good news is that modern OSes and some application frameworks support eventing mechanisms that support 10's of thousands of events easily. Some support taking a stack trace on selected events. This can be very powerful, but usually requires that symbols are available at some point in time - either at logging, or trace post processing time on the target system.