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.
Related
I must be misunderstanding dispatch_group because my dispatch_group_notify call is running before the end of the async calls made within individual dispatch_group_async blocks. Here's my code:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t dispatchGroup = dispatch_group_create();
// create operation for each HKTypeIdentifier for which we want to retrieve information
for( NSString *hkType in typesToRetrieve){
dispatch_group_async(dispatchGroup, queue, ^{
// this method runs several HK queries each with a completion block as indicated below
[self getDataForHKQuantity: hkType withCompletion:^(NSArray *results) {
// this completion blocks runs asynchronously as HK query completion block
// I want to runCompletionBlock only after
// all these processResultsArray calls have finished
[self processResultsArray:results];
}];
});
}
dispatch_group_notify(dispatchGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self runCompletionCheck];
});
The method getDataForHKQuantity in turn runs an asynchronous query to HealthKit with a completion block. I need to run runCompletionCheck after all these completion blocks for the HealthKit queries have run, but what is happening now is that runCompletionCheck is running before the code in the queries' completion blocks has run. To me that means that dispatch_group_notify along with dispatch_group_async don't work the way I need, so what am I doing wrong or what's the best way to handle this?
Overall goal: make a bunch of concurrent queries to HealthKit, run their completion blocks, then when all those completion blocks run, run a final method.
The problem is two fold. First, the health kit queries don't always run their completion blocks. I started by using a counter system, with a counter in the health kit queries' completion blocks. That's what told me that these completion blocks don't always run. Second, I don't know how many queries I am trying to run because it depends on what data sources the user has.
So, question, how can I wait until all the completion blocks from a series of health kit queries have run before running a final method?
Your -getDataForHKQuantity:withCompletion: method is asynchronous. So, through your dispatch groups you are syncing the calls to these methods, but not the work done in the methods themselves.
In other words, you are nesting two asynchronous calls, but syncing only the first level through you dispatch groups.
You'll need to come up with a different strategy for controlling your program flow.
Two examples:
1. Using Semaphores (blocking)
Some time ago, I used semaphores for a similar task, not sure it's the best strategy, but in your case it would go sth like:
semaphore = dispatch_semaphore_create(0);
for( NSString *hkType in typesToRetrieve)
{
[self getDataForHKQuantity: hkType withCompletion:^(NSArray *results) {
// register running method here
[self processResultsArray:results];
if (isLastMethod) // need to keep track of concurrent methods running
{
dispatch_semaphore_signal(semaphore);
}
}];
}
// your program will wait here until all calls to getDataForHKQuantity complete
// so you could run the whole thing in a background thread and wait for it to finish
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
2. Using dispatch_group
dispatch_group_t serviceGroup = dispatch_group_create();
for( NSString *hkType in typesToRetrieve)
{
dispatch_group_enter(serviceGroup);
[self getDataForHKQuantity: hkType withCompletion:^(NSArray *results) {
[self processResultsArray:results];
dispatch_group_leave(serviceGroup);
}];
}
dispatch_group_notify(serviceGroup,dispatch_get_main_queue(),^{
// Won't get here until everything has finished
});
Also check this link for further info.
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 have a method that I add to a GCD queue that I have created (so it's a serial queue) and then run it async. From within that block of code I make a dispatch to the main queue, when that block of code dispatched to the main queue is complete I set a BOOL flag to YES, so that I further down in my code can check if this condition is YES then I can continue to the next method. Here is the code in short:
dispatch_queue_t queue = dispatch_queue_create("ProcessSerialQueue", 0);
dispatch_async(queue, ^{
Singleton *s = [Singleton sharedInstance];
dispatch_sync(dispatch_get_main_queue(), ^{
[s processWithCompletionBlock:^{
// Process is complete
processComplete = YES;
}];
});
});
while (!processComplete) {
NSLog(#"Waiting");
}
NSLog(#"Ready for next step");
However this does not work, because dispatch_sync is never able to run the code on the main queue. Is this because I'm running a while loop on the main queue (rendering it busy)?
However if I change the implementation of the while loop to this:
while (!processComplete) {
NSLog(#"Waiting")
NSDate *date = [NSDate distantFuture];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:date];
}
It works without a glitch. Is this an acceptable solution for this scenario? Can I do it any other preferred way? What kind of magic stuff does NSRunLoop do? I need to understand this better.
Part of the main thread's NSRunLoop job is to run any blocks queued on the main thread. By spinning in a while-loop, you're preventing the runloop from progressing, so the queued blocks are never run unless you explicitly make the loop run yourself.
Runloops are a fundemental part of Cocoa, and the documentation is pretty good, so I'd reccommend reading it.
As a rule, I'd avoid manually invoking the runloop as you're doing. You'll waste memory and make make things complicated very quickly if you have multiple manual invocations running on top of one another.
However, there is a much better way of doing this. Split your method into a -process and a -didProcess method. Start the async operation with your -process method, and when it completes, call -didProcess from the completion block. If you need to pass variables from one method to the other, you can pass them as arguments to your -didProcess method.
Eg:
dispatch_queue_t queue = dispatch_queue_create("ProcessSerialQueue", 0);
dispatch_async(queue, ^{
Singleton *s = [Singleton sharedInstance];
dispatch_sync(dispatch_get_main_queue(), ^{
[s processWithCompletionBlock:^{
[self didProcess];
}];
});
});
You might also consider making your singleton own the dispatch queue and make it responsible for handling the dispatch_async stuff, as it'll save on all those nasty embedded blocks if you're always using it asynchronously.
Eg:
[[Singleton sharedInstance] processAyncWithCompletionBlock:^{
NSLog(#"Ready for next step...");
[self didProcess];
}];
Doing something like what you posted will most likely freeze the UI. Rather than freezing up everything, call your "next step" code in a completion block.
Example:
dispatch_queue_t queue = dispatch_queue_create("ProcessSerialQueue", 0);
dispatch_queue_t main = dispatch_get_main_queue();
dispatch_async(queue, ^{
Singleton *s = [Singleton sharedInstance];
dispatch_async(dispatch_get_main_queue(), ^{
[s processWithCompletionBlock:^{
// Next step code
}];
});
});
Don't go creating a loop like that waiting for a value inside a block, variables in blocks are read only, instead call your completion code from inside the block.
dispatch_async(queue, ^{
Singleton *s = [Singelton sharedInstance];
[s processWithCompletionBlock:^{
//process is complete
dispatch_sync(dispatch_get_main_queue(), ^{
//do something on main queue....
NSLog(#"Ready for next step");
});
}];
});
NSLog(#"waiting");
I've got a non-concurrent NSOperation running, which has a part in which a lot of network calls are made and the results processed. This seemed like an easy target for parallelization, so I did the following:
NSOperationQueue *downloadOperationQueue = [[NSOperationQueue alloc] init];
downloadOperationQueue.maxConcurrentOperationCount = 5;
self.operationThread = [NSThread currentThread];
//prevent the operation queue from starting until we're ready to receive events
[downloadOperationQueue setSuspended:YES];
for (FooInfo *fooInfo in foos)
{
//FooDownloadOperation is a non-concurrent operation.
FooDownloadOperation *downloadOp = [[FooDownloadOperation alloc] initWithFoo:fooInfo];
downloadOp.delegate = self;
[downloadOperationQueue addOperation:downloadOp];
}
//unsuspend the queue and spin a run loop until the local operation count hits zero
[downloadOperationQueue setSuspended:NO];
while (self.isCancelled == NO && [downloadOperationQueue operationCount] > 0)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1.0f]];
}
// ... do other things
and there's another method that -main in FooDownloadOperation calls, which does a performSelector:onThread: to jump back to self.operationThread to process the results:
- (void)downloadOperation:(FooDownloadOperation *)downloadOp didSucceed:(NSArray *)results
{
if ([[NSThread currentThread] isEqual:self.operationThread] == NO)
{
//too many arguments for -performSelector:onThread:withObject:waitUntilDone:
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:_cmd]];
[inv setTarget:self];
[inv setSelector:_cmd];
[inv setArgument:&downloadOp atIndex:2];
[inv setArgument:&results atIndex:3];
[inv retainArguments];
[inv performSelector:#selector(invoke) onThread:self.operationThread withObject:nil waitUntilDone:YES];
return;
}
//... process the results
}
No problem. In theory.
In fact, about 70% of the time, worse after waking the device up from sleep and running the operation, it just sits in the while loop, -runMode:untilDate: returning NO. Strangely, after anywhere from 20 seconds to 6 minutes, the FooDownloadOperations finally start.
To make matters more bizarre, if I add logging inside the while loop, it starts working as expected. I could just add some logging, but I'd really rather know why that seems to fix the issue.
Your mixing metaphors so to speak. If your NSOpeations are not concurrent you don't have to worry about a runloop - you cannot message them while they run (if you need that feature, you need concurrent operations and should look on github for a number of examples on how to use those).
So, you don't need to know the current thread (which is the current thread of your main app, not the operations (in your code). So you create a queue, you suspend it, you add a whole bunch of operations to run, then when you want you tell the queue to run. You can then wait on the queue to complete, or you can poll it, etc.
I am testing some code that does asynchronous processing using Grand Central Dispatch. The testing code looks like this:
[object runSomeLongOperationAndDo:^{
STAssert…
}];
The tests have to wait for the operation to finish. My current solution looks like this:
__block BOOL finished = NO;
[object runSomeLongOperationAndDo:^{
STAssert…
finished = YES;
}];
while (!finished);
Which looks a bit crude, do you know a better way? I could expose the queue and then block by calling dispatch_sync:
[object runSomeLongOperationAndDo:^{
STAssert…
}];
dispatch_sync(object.queue, ^{});
…but that’s maybe exposing too much on the object.
Trying to use a dispatch_semaphore. It should look something like this:
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[object runSomeLongOperationAndDo:^{
STAssert…
dispatch_semaphore_signal(sema);
}];
if (![NSThread isMainThread]) {
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
} else {
while (dispatch_semaphore_wait(sema, DISPATCH_TIME_NOW)) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0]];
}
}
This should behave correctly even if runSomeLongOperationAndDo: decides that the operation isn't actually long enough to merit threading and runs synchronously instead.
In addition to the semaphore technique covered exhaustively in other answers, we can now use XCTest in Xcode 6 to perform asynchronous tests via XCTestExpectation. This eliminates the need for semaphores when testing asynchronous code. For example:
- (void)testDataTask
{
XCTestExpectation *expectation = [self expectationWithDescription:#"asynchronous request"];
NSURL *url = [NSURL URLWithString:#"http://www.apple.com"];
NSURLSessionTask *task = [self.session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
XCTAssertNil(error, #"dataTaskWithURL error %#", error);
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger statusCode = [(NSHTTPURLResponse *) response statusCode];
XCTAssertEqual(statusCode, 200, #"status code was not 200; was %d", statusCode);
}
XCTAssert(data, #"data nil");
// do additional tests on the contents of the `data` object here, if you want
// when all done, Fulfill the expectation
[expectation fulfill];
}];
[task resume];
[self waitForExpectationsWithTimeout:10.0 handler:nil];
}
For the sake of future readers, while the dispatch semaphore technique is a wonderful technique when absolutely needed, I must confess that I see too many new developers, unfamiliar with good asynchronous programming patterns, gravitate too quickly to semaphores as a general mechanism for making asynchronous routines behave synchronously. Worse I've seen many of them use this semaphore technique from the main queue (and we should never block the main queue in production apps).
I know this isn't the case here (when this question was posted, there wasn't a nice tool like XCTestExpectation; also, in these testing suites, we must ensure the test does not finish until the asynchronous call is done). This is one of those rare situations where the semaphore technique for blocking the main thread might be necessary.
So with my apologies to the author of this original question, for whom the semaphore technique is sound, I write this warning to all of those new developers who see this semaphore technique and consider applying it in their code as a general approach for dealing with asynchronous methods: Be forewarned that nine times out of ten, the semaphore technique is not the best approach when encounting asynchronous operations. Instead, familiarize yourself with completion block/closure patterns, as well as delegate-protocol patterns and notifications. These are often much better ways of dealing with asynchronous tasks, rather than using semaphores to make them behave synchronously. Usually there are good reasons that asynchronous tasks were designed to behave asynchronously, so use the right asynchronous pattern rather than trying to make them behave synchronously.
I’ve recently come to this issue again and wrote the following category on NSObject:
#implementation NSObject (Testing)
- (void) performSelector: (SEL) selector
withBlockingCallback: (dispatch_block_t) block
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self performSelector:selector withObject:^{
if (block) block();
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_release(semaphore);
}
#end
This way I can easily turn asynchronous call with a callback into a synchronous one in tests:
[testedObject performSelector:#selector(longAsyncOpWithCallback:)
withBlockingCallback:^{
STAssert…
}];
Generally don't use any of these answers, they often won't scale (there's exceptions here and there, sure)
These approaches are incompatible with how GCD is intended to work and will end up either causing deadlocks and/or killing the battery by nonstop polling.
In other words, rearrange your code so that there is no synchronous waiting for a result, but instead deal with a result being notified of change of state (eg callbacks/delegate protocols, being available, going away, errors, etc.). (These can be refactored into blocks if you don't like callback hell.) Because this is how to expose real behavior to the rest of the app than hide it behind a false façade.
Instead, use NSNotificationCenter, define a custom delegate protocol with callbacks for your class. And if you don't like mucking with delegate callbacks all over, wrap them into a concrete proxy class that implements the custom protocol and saves the various block in properties. Probably also provide convenience constructors as well.
The initial work is slightly more but it will reduce the number of awful race-conditions and battery-murdering polling in the long-run.
(Don't ask for an example, because it's trivial and we had to invest the time to learn objective-c basics too.)
Here's a nifty trick that doesn't use a semaphore:
dispatch_queue_t serialQ = dispatch_queue_create("serialQ", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQ, ^
{
[object doSomething];
});
dispatch_sync(serialQ, ^{ });
What you do is wait using dispatch_sync with an empty block to Synchronously wait on a serial dispatch queue until the A-Synchronous block has completed.
- (void)performAndWait:(void (^)(dispatch_semaphore_t semaphore))perform;
{
NSParameterAssert(perform);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
perform(semaphore);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_release(semaphore);
}
Example usage:
[self performAndWait:^(dispatch_semaphore_t semaphore) {
[self someLongOperationWithSuccess:^{
dispatch_semaphore_signal(semaphore);
}];
}];
There’s also SenTestingKitAsync that lets you write code like this:
- (void)testAdditionAsync {
[Calculator add:2 to:2 block^(int result) {
STAssertEquals(result, 4, nil);
STSuccess();
}];
STFailAfter(2.0, #"Timeout");
}
(See objc.io article for details.) And since Xcode 6 there’s an AsynchronousTesting category on XCTest that lets you write code like this:
XCTestExpectation *somethingHappened = [self expectationWithDescription:#"something happened"];
[testedObject doSomethigAsyncWithCompletion:^(BOOL succeeded, NSError *error) {
[somethingHappened fulfill];
}];
[self waitForExpectationsWithTimeout:1 handler:NULL];
Here is an alternative from one of my tests:
__block BOOL success;
NSCondition *completed = NSCondition.new;
[completed lock];
STAssertNoThrow([self.client asyncSomethingWithCompletionHandler:^(id value) {
success = value != nil;
[completed lock];
[completed signal];
[completed unlock];
}], nil);
[completed waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
[completed unlock];
STAssertTrue(success, nil);
Swift 4:
Use synchronousRemoteObjectProxyWithErrorHandler instead of remoteObjectProxy when creating the remote object. No more need for a semaphore.
Below example will return the version received from the proxy. Without the synchronousRemoteObjectProxyWithErrorHandler it will crash (trying to access non accessible memory):
func getVersion(xpc: NSXPCConnection) -> String
{
var version = ""
if let helper = xpc.synchronousRemoteObjectProxyWithErrorHandler({ error in NSLog(error.localizedDescription) }) as? HelperProtocol
{
helper.getVersion(reply: {
installedVersion in
print("Helper: Installed Version => \(installedVersion)")
version = installedVersion
})
}
return version
}
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[object blockToExecute:^{
// ... your code to execute
dispatch_semaphore_signal(sema);
}];
while (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW)) {
[[NSRunLoop currentRunLoop]
runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0]];
}
This did it for me.
Sometimes, Timeout loops are also helpful. May you wait until you get some (may be BOOL) signal from async callback method, but what if no response ever, and you want to break out of that loop?
Here below is solution, mostly answered above, but with an addition of Timeout.
#define CONNECTION_TIMEOUT_SECONDS 10.0
#define CONNECTION_CHECK_INTERVAL 1
NSTimer * timer;
BOOL timeout;
CCSensorRead * sensorRead ;
- (void)testSensorReadConnection
{
[self startTimeoutTimer];
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
while (dispatch_semaphore_wait(sema, DISPATCH_TIME_NOW)) {
/* Either you get some signal from async callback or timeout, whichever occurs first will break the loop */
if (sensorRead.isConnected || timeout)
dispatch_semaphore_signal(sema);
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate dateWithTimeIntervalSinceNow:CONNECTION_CHECK_INTERVAL]];
};
[self stopTimeoutTimer];
if (timeout)
NSLog(#"No Sensor device found in %f seconds", CONNECTION_TIMEOUT_SECONDS);
}
-(void) startTimeoutTimer {
timeout = NO;
[timer invalidate];
timer = [NSTimer timerWithTimeInterval:CONNECTION_TIMEOUT_SECONDS target:self selector:#selector(connectionTimeout) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}
-(void) stopTimeoutTimer {
[timer invalidate];
timer = nil;
}
-(void) connectionTimeout {
timeout = YES;
[self stopTimeoutTimer];
}
Very primitive solution to the problem:
void (^nextOperationAfterLongOperationBlock)(void) = ^{
};
[object runSomeLongOperationAndDo:^{
STAssert…
nextOperationAfterLongOperationBlock();
}];
I have to wait until a UIWebView is loaded before running my method, I was able to get this working by performing UIWebView ready checks on main thread using GCD in combination with semaphore methods mentioned in this thread. Final code looks like this:
-(void)myMethod {
if (![self isWebViewLoaded]) {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block BOOL isWebViewLoaded = NO;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (!isWebViewLoaded) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((0.0) * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
isWebViewLoaded = [self isWebViewLoaded];
});
[NSThread sleepForTimeInterval:0.1];//check again if it's loaded every 0.1s
}
dispatch_sync(dispatch_get_main_queue(), ^{
dispatch_semaphore_signal(semaphore);
});
});
while (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW)) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0]];
}
}
}
//Run rest of method here after web view is loaded
}