Sunday, October 16, 2011

How to wait while an NSTimer runs in a unit test

Suppose you were a lowly noob to iOS development. Further suppose you had a Cool Idea (tm) which involved timers. The internets might rapidly guide you to NSTimer and you might decide to try to get it to log to the console in a unit test. The most obvious approach seems to be to setup a timer to tick frequently, lets say every 0.1 seconds, and setup a timer callback that logs something, then make a test that sleeps for a couple of seconds. Presumably during the sleep period we'll see a bunch of timer output. The code might look like this (inside an XCode 4.2 test implementation class):

- (void)onTimerTick:(NSTimer*)timer
{
    NSLog(@"MY TIMER TICKED");
}

- (void)testTimerBasics
{
    NSLog(@"timer time");
    
    [NSTimer scheduledTimerWithTimeInterval:0.1
                   target:self
                   selector:@selector(onTimerTick:)
                   userInfo:nil
                   repeats:YES];
    
    [timer fire]; //manually calling fire DOES log 'MY TIMER TICKED'
    
    NSLog(@"about to wait");    
    [NSThread sleepForTimeInterval:2.0]; //absolutely no logs of 'MY TIMER TICKED' occur; somehow the time doesn't fire during a thread sleep :(
    NSLog(@"wait time is over");    
}

Sadly absolutely no log messages are printed during our two second sleep ([NSThread sleepForTimeInterval:2.0]) ; WTF?!

After much Google and literally in the midst of typing a Stack Overflow question I came across a question involving waiting for something else that mentioned NSRunLoop in passing. The very existence of a run loop class suggests an answer: our tests run on the same thread as the run loop. This means if we put the run loop to sleep nothing gets processed. Instead of sleep we need some sort of "run the run loop for a while" approach. Luckily it turns out that NSRunLoop provides a runUntilDate API so we can re-write the test above as follows:

- (void)onTimerTick:(NSTimer*)timer
{
    NSLog(@"MY TIMER TICKED");
}

- (void)testTimerBasics
{
    NSLog(@"timer time");
    
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.1
                              target:self
                              selector:@selector(onTimerTick:)
                              userInfo:nil
                              repeats:YES];
    
    //[timer fire];
    
    NSDate *runUntil = [NSDate dateWithTimeIntervalSinceNow: 3.0 ];
    
    NSLog(@"about to wait");    
    [[NSRunLoop currentRunLoop] runUntilDate:runUntil];
    NSLog(@"wait time is over");    
}
We've found the right magic incantation! Knuth would be proud.

Speaking of magic incantations, I am using the SyntaxHighlighter libraries hosted @ http://syntaxhighlighter.googlecode.com/svn/trunk/Scripts/. However, there is no Objectionable-C brush there so I took the one posted @ http://www.undermyhat.org/blog/wp-content/uploads/2009/09/shBrushObjectiveC.js and updated it the casing and namespace names to the newer highlighter standard. The updated brush looks like this:


dp.sh.Brushes.ObjC = function()
{
 var datatypes = 'char bool BOOL double float int long short id void';
 
 var keywords = 'IBAction IBOutlet SEL YES NO readwrite readonly nonatomic nil NULL ';
 keywords += 'super self copy ';
 keywords += 'break case catch class const copy __finally __exception __try ';
 keywords += 'const_cast continue private public protected __declspec ';
 keywords += 'default delete deprecated dllexport dllimport do dynamic_cast ';
 keywords += 'else enum explicit extern if for friend goto inline ';
 keywords += 'mutable naked namespace new noinline noreturn nothrow ';
 keywords += 'register reinterpret_cast return selectany ';
 keywords += 'sizeof static static_cast struct switch template this ';
 keywords += 'thread throw true false try typedef typeid typename union ';
 keywords += 'using uuid virtual volatile whcar_t while';
 // keywords += '@property @selector @interface @end @implementation @synthesize ';
 
  
 this.regexList = [
  { regex: dp.sh.RegexLib.SingleLineCComments,  css: 'comments' },  // one line comments
  { regex: dp.sh.RegexLib.MultiLineCComments,  css: 'comments' },  // multiline comments
  { regex: dp.sh.RegexLib.DoubleQuotedString,  css: 'string' },   // double quoted strings
  { regex: dp.sh.RegexLib.SingleQuotedString,  css: 'string' },   // single quoted strings
  { regex: new RegExp('^ *#.*', 'gm'),      css: 'preprocessor' },  // preprocessor
  { regex: new RegExp(this.GetKeywords(datatypes), 'gm'),  css: 'datatypes' },  // datatypes
  { regex: new RegExp(this.GetKeywords(keywords), 'gm'),  css: 'keyword' },   // keyword
  { regex: new RegExp('\\bNS\\w+\\b', 'g'),     css: 'keyword' },   // keyword
  { regex: new RegExp('@\\w+\\b', 'g'),      css: 'keyword' },   // keyword
  ];
 this.CssClass = 'dp-objc';
 this.Style = '.dp-objc .datatypes { color: #2E8B57; font-weight: bold; }'; 
}
dp.sh.Brushes.ObjC.prototype = new dp.sh.Highlighter();
dp.sh.Brushes.ObjC.Aliases  = ['objc'];

3 comments:

Naviya Nair said...

I have read your blog its very attractive and impressive. I like it your blog.

Java Online Training Java EE Online Training Java EE Online Training Java 8 online training Core Java 8 online training

Java Online Training from India Java Online Training from India Core Java Training Online Core Java Training Online Java Training InstitutesJava Training Institutes

Priya Kannan said...

It's interesting that many of the bloggers to helped clarify a few things for me as well as giving.Most of ideas can be nice content.The people to give them a good shake to get your point and across the command .
IOS Training in Chennai

Swathi Samala said...

very valuable information ios Online Training Bangalore

Post a Comment