macdevcenter.com
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Animating Graphics in Cocoa, Part 1
Pages: 1, 2, 3, 4, 5

Multithreading

This annoying behavior is a side effect of our application having only one thread of execution. That is, only one thing can happen at a time, and the consequence is that when the control is being held and moved, the animation timer stops temporarily until we release the control. To fix this, we make our application multithreaded. The way this works is that when AnimationView is initialized, we detach a new thread from the main thread of execution. In the new thread, we handle the animation loop, and in the main thread we handle any user interaction. By doing this, we can create a much nicer animation and user interaction experience.

A new thread is created using the class method of NSThread +detachNewThreadSelector:toTarget:withObject:. This method takes as its arguments a selector to be invoked -— the method we want to execute in the new thread -- the target of the method indicated by the selector, and some object that contains arbitrary information we might want to access in the new thread. Note that threads are set up to execute a single method.

We’ll detach a new thread in the initialization code of initWithFrame:. The line creating a new thread will replace the code where we previously created our timer.

[NSThread detachNewThreadSelector:@selector(animate:) toTarget:self withObject:nil];

The thread we are creating here will execute a method called animate:, of the class AnimationView. The withObject: argument is nil since there is nothing we want to communicate with the new thread. The method -animate: is new to us; it will contain the code necessary to kick off and time the animation.

When we make a new thread, the first thing that must happen is the creation of an autorelease pool, and the last thing that the thread does before exiting is to release this pool. Until now, we’ve never had a need to create our own autorelease pool. Cocoa has always supplied one for us. Note that if you are using a thread to execute code that doesn’t use the Cocoa frameworks, then you don’t need to create and autorelease pool.

For example, if you had a method that ran some lengthy calculation using only the standard C math functions, then you wouldn’t need an autorelease pool. With this new context for using autorelease pools, you might want to review that documentation. With this, let's set up the framework for the method animate::


- (void)animate:(id)anObject
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    // execute our code here

    [pool release];
}

The argument to animate: is type id; this is the way a method must be set up for use by a thread. The object we specify in withObject: when we create a new thread is passed as an argument to animate:, and we can access it thusly. For more information about NSThread, take a gander through the class reference; it’s pretty short and straight to the point, with many useful insights about threads.

Our animation code is sandwiched between the two lines dealing with the autorelease pool. Lets take a look at that now.

Multithreaded Animation

When we use a separate thread for the animation timing, we’re going to do things a bit differently than above. Rather than use a timer that fires whenever we want a new frame, we will set up a continuous while statement that steps the animation. Within the while loop we will do two things: we will call stepAnimation, and we will tell the display to update itself. –animate: looks like this:


- (void)animate:(id)anObject
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    while (YES) {
        [self stepAnimation:nil];
        [self setNeedsDisplay:YES];
    }

    [pool release];
}

Note that -stepAnimation: also calls setNeedsDisplay:. We want to delete that line from stepAnimation:, since it is now called in the while loop of animate:. The reason we moved it is purely a matter of that message “fitting” in better with the function of –animate: as opposed to –stepAnimation:.

If you run this code as is, you will notice another disturbing behavior: the ball is just jumping all over the place —- nothing nice about that. The reason for this is that when we call [self setNeedsDisplay:YES] the display is not immediately updated. The main thread of execution deals with drawing via drawRect: when it gets some processor time; all we do is let the view know we want an update.

However, while the main thread wants to draw, the animation thread is looping through that while statement as fast as it can, and the main thread tries to update as soon as it can, but it simply can’t keep up. As a result, the animation progresses many more steps between each redraw. To remedy this problem, we tell the animation thread to take a break for a short while and let the other thread take care of business. This is accomplished using the NSThread method sleepUntilDate: after we call for an update to the display:


- (void)animate:(id)anObject
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    while (YES) {
        [self stepAnimation:nil];
        [self setNeedsDisplay:YES];
        [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.04]];
    }

    [pool release];
    [NSThread exit];
}

Also in Programming with Cocoa

Understanding the NSTableView Class

Inside StYNCies, Part 2

Inside StYNCies

Build an eDoc Reader for Your iPod, Part 3

Notice we choose the time interval to be the same as the NSTimer interval we had earlier. With this, you can run the animation and everything will look good. Notice that you can now move the sliders and the speed of the ball will change in real time -- no annoying pause. And that is one of the uses of multithreading: sending some code off to do its thing without locking the user out of the application.

As you’ll read in the NSThread reference, be careful about your multithreading, because when you get down to it, the processor really can only do one thing at a time. The real benefit of multithreading comes when you have multiple processors. Then you can really execute two or more things simultaneously.

The End

We have seen how we can do some pretty good multithreaded animation that lets the user interact with what's going on. The link to download the first version of this application was given above. The project folder for the second version can be downloaded here and finally, here is a link to a third project that works the animation in a slightly different way, allowing the user to scale the size of the ball. I leave you with that to ponder.

Michael Beam is a software engineer in the energy industry specializing in seismic application development on Linux with C++ and Qt. He lives in Houston, Texas with his wife and son.


Return to the Mac DevCenter.