NSOperationQueue setSuspended:NO not starting queue, NSLog does - objective-c

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.

Related

Barrier operations in NSOperationQueue

How can we implement dispatch_barrier_async's equivalent behavior using NSOperationQueue or any user-defined data-structure based on NSOperationQueue?
The requirement is, whenever a barrier operation is submitted it should wait until all non-barrier operations submitted earlier finish their execution and blocks other operations submitted after that.
Non-barrier operations should be able to perform concurrently.
Barrier operations should execute serially.
NB: Not using GCD,as it doesn't provide(or atleast difficult) much access over the operations, like cancelling single operation, etc.
This is more or less what jeffamaphone was saying, but I put up a gist that should, in rough outline, do what you ask.
I create a NSMutableArray of NSOperationQueues, which serves as a "queue of queues". Every time you add a BarrierOperation object, you tack a fresh suspended op queue on the end. That becomes the addingQueue, to which you add subsequent operations.
- (void)addOperation:(NSOperation *)op {
#synchronized (self) {
if ([op isKindOfClass:[BarrierOperation class]]) {
[self addBarrierOperation:(id)op];
} else {
[[self addingQueue] addOperation:op];
}
}
}
// call only from #synchronized block in -addOperation:
- (void)addBarrierOperation:(BarrierOperation *)barrierOp {
[[self addingQueue] setSuspended:YES];
for (NSOperation *op in [[self addingQueue] operations]) {
[barrierOp addDependency:op];
}
[[self addingQueue] addOperation:barrierOp];
// if you are free to set barrierOp.completionBlock, you could skip popCallback and do that
__block typeof(self) weakSelf = self;
NSOperation *popCallback = [NSBlockOperation blockOperationWithBlock:^{
[weakSelf popQueue];
}];
[popCallback addDependency:barrierOp];
[[self addingQueue] addOperation:popCallback];
[[self addingQueue] setSuspended:NO];
NSOperationQueue *opQueue = [[NSOperationQueue alloc] init];
[opQueue setSuspended:YES];
[_queueOfQueues addObject:opQueue]; // fresh empty queue to add to
}
When one NSOperationQueue finishes, it gets popped and the next one starts running.
- (void)popQueue
{
#synchronized (self) {
NSAssert([_queueOfQueues count], #"should always be one to pop");
[_queueOfQueues removeObjectAtIndex:0];
if ([_queueOfQueues count]) {
// first queue is always running, all others suspended
[(NSOperationQueue *)_queueOfQueues[0] setSuspended:NO];
}
}
}
I might have missed something crucial. The devil's in the details.
This smells a bit like a homework assignment to me. If so, tell me what grade I get. :)
Addendum: Via abhilash1912's comment, a different but similar approach. That code is tested, so it already wins. But it is a bit stale (2 years or so as of today; some deprecated method usage). Moreover, I question whether inheriting from NSOperationQueue is the best path, though it has the virtue of retaining familiarity. Regardless, if you've read this far, it's probably worth looking over.
If you create or find the world's greatest BarrierQueue class, please let us know in the comments or otherwise, so it can be linked up.
Create an NSOperation that is your barrier, then use:
- (void)addDependency:(NSOperation *)operation
To make that barrier operation dependent on all the ones you want to come before it.
I don't think it's possible to create an NSOperation object that's gives you the same sort of functionality, barriers have more to do with the way the queue operates.
The main difference between using a barrier and the dependency mechanism of NSOperations is, in the case of a barrier, the thread queue waits until all running concurrent operations have completed, and then it runs your barrier block, while making sure that any new blocks submitted and any blocks waiting do not run until the critical block has passed.
With an NSOperationQueue, it's impossible to set up the queue in such a way that it'll enforce a proper barrier: all NSOperations added to the queue before your critical NSOperation must be explicitly registered as a dependency with the critical job, and once the critical job has started, you must explicitly guard the NSOperationQueue to make sure no other clients push jobs onto it before the critical job has finished; you guard the queue by adding the critical job as a dependency for the subsequent operations.
(In the case where you know there's only one critical job at a time, this sounds sorta easy, but there will probably be n critical jobs waiting at any one time, which means keeping track of the order jobs are submitted, managing the relative dependency of critical jobs relative to their dependent jobs -- some critical jobs can wait for others, some must be executed in a particular order relative to others... yikes.)
It might be possible to get this level of functionality by setting up an NSOperationQueue with a concurrent job max of one, but that sorta defeats the purpose of doing this, I think. You might also be able to make this work by wrapping an NSOperationQueue in a facade object that protects NSOperations that are submitted "critically."
Just another way... don't hurt me.
Todo: save origin completion and self.maxConcurrentOperationCount = 1 sets queue to serial on adding. But should before execution.
#import "NSOperationQueue+BarrierOperation.h"
#implementation NSOperationQueue (BarrierOperation)
- (void)addOperationAsBarrier:(NSOperation *)op
{
//TODO: needs to save origin completion
// if (op.completionBlock)
// {
// originBlock = op.completionBlock;
// }
NSOperationQueue* qInternal = [NSOperationQueue new];
NSInteger oldMaxConcurrentOperationCount = self.maxConcurrentOperationCount;
op.completionBlock = ^{
self.maxConcurrentOperationCount = oldMaxConcurrentOperationCount;
NSLog(#"addOperationAsBarrier maxConcurrentOperationCount restored");
};
[self addOperationWithBlock:^{
self.maxConcurrentOperationCount = 1;
NSLog(#"addOperationAsBarrier maxConcurrentOperationCount = 1");
}];
[qInternal addOperationWithBlock:^{
NSLog(#"waitUntilAllOperationsAreFinished...");
[self waitUntilAllOperationsAreFinished];
}];
NSLog(#"added OperationAsBarrier");
[self addOperation:op];
}
#end

Locked up waiting for #synchronized

I have this (rare) odd case where my objective-c iOS program is locking up. When I break into the debugger, there are two threads and both of them are stuck at a #synchronized().
Unless I am completely misunderstanding #synchronized, I didn't think that was possible and the whole point of the command.
I have a main thread and worker thread that both need access to a sqlite database, so I wrap the chunks of code that are accessing the db in #synchronized(myDatabase) blocks. Not much else happens in these blocks except the db access.
I'm also using the FMDatabase framework to access sqlite, I don't know if that matters.
The myDatabase is a global variable that contains the FMDatabase object. It is created once at the start of the program.
I know I'm late to the party with this, but I've found a strange combination of circumstances that #synchronized handles poorly and is probably responsible for your problem. I don't have a solution to it, besides to change the code to eliminate the cause once you know what it is.
I will be using this code below to demonstrate.
- (int)getNumberEight {
#synchronized(_lockObject) {
// Point A
return 8;
}
}
- (void)printEight {
#synchronized(_lockObject) {
// Point B
NSLog(#"%d", [self getNumberEight]);
}
}
- (void)printSomethingElse {
#synchronized(_lockObject) {
// Point C
NSLog(#"Something Else.");
}
}
Generally, #synchronized is a recursively-safe lock. As such, calling [self printEight] is ok and will not cause deadlocks. What I've found is an exception to that rule. The following series of events will cause deadlock and is extremely difficult to track down.
Thread 1 enters -printEight and acquires the lock.
Thread 2 enters -printSomethingElse and attempts to acquire the lock. The lock is held by Thread 1, so it is enqueued to wait until the lock is available and blocks.
Thread 1 enter -getNumberEight and attempts to acquire the lock. The lock is held already and someone else is in the queue to get it next, so Thread 1 blocks. Deadlock.
It appears that this functionality is an unintended consequence of the desire to bound starvation when using #synchronized. The lock is only recursively safe when no other thread is waiting for it.
The next time you hit deadlock in your code, examine the call stacks on each thread to see if either of the deadlocked threads already holds the lock. In the sample code above, by adding long sleeps at Point A, B, and C, the deadlock can be recreated with almost 100% consistency.
EDIT:
I'm no longer able to demonstrate the previous issue, but there is a related situation that still causes issues. It has to do with the dynamic behavior of dispatch_sync.
In this code, there are two attempts to acquire the lock recursively. The first calls from the main queue into a background queue. The second calls from the background queue into the main queue.
What causes the difference in behavior is the distinction between dispatch queues and threads. The first example calls onto a different queue, but never changes threads, so the recursive mutex is acquired. The second changes threads when it changes queues, so the recursive mutex cannot be acquired.
To emphasize, this functionality is by design, but it behavior may be unexpected to some that do not understand GCD as well as they could.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSObject *lock = [[NSObject alloc] init];
NSTimeInterval delay = 5;
NSLog(#"Example 1:");
dispatch_async(queue, ^{
NSLog(#"Starting %d seconds of runloop for example 1.", (int)delay);
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:delay]];
NSLog(#"Finished executing runloop for example 1.");
});
NSLog(#"Acquiring initial Lock.");
#synchronized(lock) {
NSLog(#"Acquiring recursive Lock.");
dispatch_sync(queue, ^{
NSLog(#"Deadlock?");
#synchronized(lock) {
NSLog(#"No Deadlock!");
}
});
}
NSLog(#"\n\nSleeping to clean up.\n\n");
sleep(delay);
NSLog(#"Example 2:");
dispatch_async(queue, ^{
NSLog(#"Acquiring initial Lock.");
#synchronized(lock) {
NSLog(#"Acquiring recursive Lock.");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(#"Deadlock?");
#synchronized(lock) {
NSLog(#"Deadlock!");
}
});
}
});
NSLog(#"Starting %d seconds of runloop for example 2.", (int)delay);
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:delay]];
NSLog(#"Finished executing runloop for example 2.");
I stumbled into this recently, assuming that #synchronized(_dataLock) does what it's supposed to do, since it is such a fundamental thing after all.
I went on investigating the _dataLock object, in my design I have several Database objects that will do their locking independently so I was simply creating _dataLock = [[NSNumber numberWithInt:1] retain] for each instance of Database.
However the [NSNumber numberWithInt:1] returns the same object, as in same pointer!!!
Which means what I thought was a localized lock for only one instance of Database is not a global lock for all instances of Database.
Of course this was never the intended design and I am sure this was the cause of issues.
I will change the
_dataLock = [[NSNumber numberWithInt:1] retain]
with
_dataLock = [[NSUUID UUID] UUIDString] retain]

Problems Getting Multiple NSURLConnections to Run in Parallel

I am am trying to get multiple NSURLConnections to run in parallel (synchronously), however if it is not running on the main thread (block of code commented out below) the URL connection doesn't seem to work at all (none of the NSURLConnection delegate methods are triggered). Here is the code I have (implementation file of an NSOperation subclass):
- (void)start
{
NSLog(#"DataRetriever.m start");
if ([self.DRDelegate respondsToSelector:#selector(dataRetrieverBeganExecuting:)])
[self.DRDelegate dataRetrieverBeganExecuting:identifier];
if ([self isCancelled]) {
[self finish];
} else {
/*
//If this block is not commented out NSURLConnection works, but not otherwise
if (![NSThread isMainThread])
{
[self performSelectorOnMainThread:#selector(start) withObject:nil waitUntilDone:NO];
return;
}*/
SJLog(#"operation for <%#> started.", _url);
[self willChangeValueForKey:#"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:#"isExecuting"];
NSURLRequest * request = [NSURLRequest requestWithURL:_url];
_connection = [[NSURLConnection alloc] initWithRequest:request
delegate:self];
if (_connection == nil)
[self finish];
} //not cancelled
}//start
Ran through it with a debugger, and after the end of this start method none of the NSURLConnection delegates trigger (I set breakpoints there). But on the main thread it works just fine. Any ideas of what's up? Thanks!
Background threads don't automatically have an active run loop on them. You need to start up the run loop after you create the NSURLConnection in order to get any input from it. Fortunately, this is quite simple:
[[NSRunLoop currentRunLoop] run];
When you say that you are running the connections synchronously, you are incorrect. The default mode of NSURLConnection is asynchronous -- it creates and manages a new background thread for you, and calls back to the delegate on the original thread. You therefore don't need to worry about blocking the main thread.
If you do actually want to perform a synchronous connection, you would use sendSynchronousRequest:returningResponse:error:, which will directly return the data. See "Downloading Data Synchronously" for details.
NSURLConnection needs an active run loop to actually work; the easiest way to ensure this is to just run it from the main thread.
Note that NSURLConnection normally runs asynchronously (and if you run one synchronously, what it really does is run one asynchronously on another thread and then block until that completes), so except for whatever processing you do in your delegate methods it shouldn't have much of an effect on UI responsiveness.

How can I make a method stall for a fixed amount of time?

I have an app that calls a sometimes-fast, sometimes-slow method. I know an upper bound for how long it will take (2 seconds). I'd like to set a timer to start when the method is called, run the code, but then not produce the output until 2 seconds has passed, no matter how long it actually takes. That way the user perceives the action as always taking the same amount of time. How can I implement this?
What I would like is something along the lines of this:
-(IBAction)doStuff {
// START A TIMER, LOOK BUSY
[activityIndicator startAnimating];
... real work happens here ...
... NSString *coolString gets assigned ...
// WHEN TIMER == 2 SECONDS, REVEAL COOLNESS
[activityIndicator stopAnimating];
[textField setText:coolString];
}
There are a couple of ways to delay an action in Cocoa. The easiest may be to use performSelector:withObject:afterDelay:. This method sets up a timer for you and calls the specified method when the time comes. It's an NSObject method, so your objects all get it for free.
The tricky part here is that the first method will block the main thread, so you need get it onto a background thread, and then get back to the main thread in order to update the UI. Here's a stab at it:
// Put the method which will take a while onto another thread
[self performSelectorInBackground:#selector(doWorkForUnknownTime)
withObject:nil];
// Delay the display for exactly two seconds, on the main thread
[self performSelector:#selector(displayResults)
withObject:nil
afterDelay:2.0];
- (void)doWorkForUnknownTime {
// results is an ivar
results = ...; // Perform calculations
}
- (void)displayResults {
if( !results ){
// Make sure that we really got results
[self performSelector:#selector(displayResults:)
withObject:nil
afterDelay:0.5];
return;
}
// Do the display!
}
The only other thing I can think of is to store the time that the "work" method is called in an NSDate, and check how long it took when you get the results. If it isn't two seconds yet, sleep the background thread, then call back to the main thread when you're done.
[self performSelectorInBackground:#selector(doWorkForUnknownTime:)
withObject:[NSDate date]];
- (void)doWorkForUnknownTime:(NSDate *)startTime {
// All threads must have an autorelease pool in place for Cocoa.
#autoreleasepool{
// This will take some time
NSString * results = ...; // Perform calculations
NSTimeInterval elapsedTime = [[NSDate date] timeIntervalSinceDate:startTime];
if( elapsedTime < 2.0 ){
// Okay to do this to wait since we're on a background thread,
// although not ideal; sleeping threads are kind of wasteful.
// Try not to do this a lot.
sleep(2.0 - elapsedTime);
}
// Don't forget to retain results on the main thread!
[self performSelectorOnMainThread:#selector(displayResults:)
withObject:results
waitUntilDone:YES];
// [results release]; // if necessary
}
}
[self performSelector:#selector(myfunc) withObject: afterDelay:];
should help.
-(IBAction)doStuff {
// START A TIMER, LOOK BUSY
[activityIndicator startAnimating];
... real work happens here ...
... NSString *coolString gets assigned ...
// WHEN TIMER == 2 SECONDS, REVEAL COOLNESS
[self performSelector:#selector(revealCoolnessWithString:) withObject:coolString afterDelay:2];
}
- (void)revealCoolnessWithString:(NSString *)coolString
{
[activityIndicator stopAnimating];
[textField setText:coolString];
}
Hope this helps

How do you test an asynchronous 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.