![]() |
| |||||||
|
Tutorials
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| int main (int argc, const char * argv[]) { | |||||
| while (1 == 1) { |
|||||
| sleep(5); | |||||
| } return 0; |
|||||
| } | |||||
This will work fine for a background task, but there's two problems with it. For one, it's not good for a GUI-based application, since the app will appear frozen for the time it's sleeping, and we will eventually be working with GUIs. The second problem is that it really doesn't teach us anything about Cocoa. Cocoa also provides a way to put the application into a loop while allowing the periodic execution of code - the RunLoop. In GUI-based applications, the app will enter a RunLoop automatically - for this utility, we've got to handle that ourselves. If we look over the NSRunLoop documentation, there's really only one way to create/access one:
+ (NSRunLoop *) currentRunLoop
Returns the NSRunLoop for the current thread. If a run loop does not exist in the thread, one is created and returned.
The "+" before the method definition indicates that it's a class method - you send the message to NSRunLoop itself, rather than an instance of the run loop. Instance methods, those you send to an individual object, are preceded by a "-" in the documentation. The text within the parentheses indicates the type of the returned value - a pointer to an NSRunLoop. Note that this will either return the existing one, or create one if necessary, so it's guaranteed to work. For now, all we really want to do is start the RunLoop going, which is accomplished by the "run" method (as you can see from the documentation, there are other, more elaborate methods, but we don't need them for now). So, in order to enter a RunLoop, we could do:
NSRunLoop *myRunLoop = [NSRunLoop currentRunLoop];
[myRunLoop run];
Or, to simplify things, combine them in a single line:
[[NSRunLoop currentRunLoop] run];
If you put this line in main.m in place of the while loop, however, the program will quickly exit without looping. That's because the RunLoop has nothing to do - no code to execute. So, let's give it something to do.
Getting the RunLoop to do Something:
So, we want to get the RunLoop to do something every few seconds. Let's try to reason through where to find such a thing. Such a capability clearly doesn't require using GUI elements, so it's probably going to be in Foundation -NSRunLoop itself probably isn't a bad place to look. The list of methods does contain something that sounds promising - a Timer, as in the "addTimer:forMode:" method. Looking at that method's description, however, it appears that it requires an NSTimer to have been created. Going to the NSTimer documentation, we find the following line:
A timer waits until a certain time interval has elapsed and then fires, sending a specified message to a specified object.
Sounds exactly like what we need. So, it's time to read over the NSTimer documentation, which conveniently lets you know that there are three ways to create one. Although you may need to wander through the NSInvocation documentation to get all the info you need, it should become clear that all three of these methods need an object as a target. Given that, we're going to have to make one.
From the "File" menu, select "New File", and choose Objective-C Class. Name the file "OutLauncher", and Project Builder will place a header (.h) and implementation (.m) file with that name in your project. The files that are created are already hooked up to Foundation, contain the appropriate structure of Obj-C classes, and are subclasses of NSObject by default. It's worth explaining a couple of aspects of the structure - the .h file should look like this:
@interface OutLauncher : NSObject {
}
@end
You can place any variables that are components of the class within the brackets; definitions of the methods should go between the closing bracket and the "@end".
You can put the code for the methods in the .m file after the "@implementation" line. In our case, we want to make a method to call with the timer. We'll just call it "doStuff " - it'll be an intance method, and we'll put the definition in the .h file, just before the "@end":
- (void)doStuff;
We'll put the same line right after "@implementation" in the .m file, replacing the ";" with a "{". For now, we'll just note the fact that we got to this stage using NSLog:
| - (void)doStuff { | ||||
| NSLog( @"Doing Stuff"); | ||||
| } | ||||
Okay, we've got an object and a method in place, we're ready to use it to get an NSTimer to execute.
Making an NSTimer:
Although there are several ways to make a timer, I personally recommend having the object that's going to be the target of the timer do the actual creation. This way, should you change the code for the object itself, updating the handling of the timer to accommodate these changes can be done by editing the same file. So, we need another method in the OutLauncher.h file:
- (NSTimer *)makeTimer {
Looking over NSTimer's documentation, the simplest way to create one in a way that contains all the necessary information appears to be:
+ (NSTimer *) scheduledTimerWithTimeInterval: (NSTimeInterval) seconds target: (id) target selector: (SEL) aSelector userInfo: (id) userInfo repeats: (BOOL) repeats
The two tricky parts here are the NSTimeInterval and the Selector (userInfo is just a way of handing information to the target of the timer). Both of these are hidden in the Foundation "Functions" and "Types and Constants" documentation, buried near the bottom of the Foundation class list documentation page. NSTimeIntervals are pretty straightforward - they're simply floating point values, and we'll use 5, which gives us execution every 5 seconds. Selectors are a bit trickier - they are method signatures rendered as text. The one function for this that's listed is:
SEL NSSelectorFromString(NSString *aSelectorName)
Although this would work, you'll rarely see this function used. Instead, selectors are mostly created using a literal expression started with the "@" symbol:
@selector(selectorName)
So, pulling this information together, we can have our method make a timer targeted at itself with the following code:
| - (NSTimer *)getTimer{ | ||||
| NSTimer *theTimer; theTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector: @selector(doStuff) userInfo:nil repeats:YES]; return [theTimer autorelease]; |
||||
| } | ||||
Go ahead and type that in. Now, let's use it. First, to give main.m access to OutLauncher, type the following right below the import of Foundation:
#import <OutLauncher.h>
In the main function, right after the creation of the AutoRelease Pool, type the following in order to create our an instance of our class and get a timer targeting it:
OutLauncher *theLauncher = [[OutLauncher alloc] init];
NSTimer *theTimer = [theLauncher getTimer];
[theTimer retain];
Now, we just have to attach the timer to the RunLoop, using the method we'd already noted in the NSRunLoop documentation: addTimer: forMode: We already have a timer, and at the top of the NSRunLoop documentation, they indicate that there are several constants to handle the mode. We'll use NSDefaultRunLoopMode, since that's the most straightforward. Now, before telling the RunLoop to run, type:
[[NSRunLoop currentRunLoop] addTimer: theTimer forMode: NSDefaultRunLoopMode];
This will leave main.m looking like the following:
| #import <Foundation/Foundation.h> #import <OutLauncher.h> int main (int argc, const char * argv[]) { |
||||
| NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; OutLauncher *theLauncher = [[OutLauncher alloc] init]; NSTimer *theTimer = [theLauncher getTimer]; [theTimer retain]; [[NSRunLoop currentRunLoop] addTimer: theTimer forMode: NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop] run]; [pool release]; return 0; |
||||
| } | ||||
Now, when you build and run, the RunLoop has something to do - fire the timer every five seconds. As a result, the program won't exit, and about every five seconds until you stop it, the run window will log that your program is "doing stuff". Since we've made a bunch of changes, it's probably worth letting it run for a while and checking for leaks (though you won't find any). Congratulations, we're on our way to something useful.
If you have any questions or comments about this article, feel free to e-mail me at john_timmer@osxfaq.com