« Debugging, Omni-Style

Posted by Submission on July 07, 2002 [Feedback (0) & TrackBack (0)]

by H. Lally Singh

(website | email)

OmniBase provides a simple set of extensions to help debug your application. It also has alot of functionality for OpenStep portability, but its relevence in that respect has shrunk in recent times. OmniBase is a pretty simple framework that provides a good introduction to the OmniFrameworks. I'll cover the most important parts of OmniBase here, but it's got lots of small bits and peices of useful code, so I recommend that you go through and discover what parts you like for yourself.

To download and install OmniBase, check out our article on Installing the OmniFrameworks.

 

// OBObject

OBObject is a simple subclass of NSObject with a few additions to help aid debugging. All of the other classes in the OmniFrameworks derive from OBObject, and thusly they will all have the same debugging benefits found here. Let's look through the methods OBObject provides:

  • +allocWithZone:(NSZone *)zone - If you define either DEBUG_INITIALIZE or DEBUG_ALLOC, allocWithZone: will be compiled in, which will do two things. Firstly, it will make sure that you've called [super initialize], which according to the nice big comment above allocWithZone:'s implementation in OBObject.m, is necessary. Read the comment yourself. Also, if you set the OBObjectDebug flag to YES, then allocWithZone: will NSLog() every time an allocation occurs.
  • -dealloc - This works the same way allocWithZone: works in respect to tracking allocations. When dealloc is called, it will NSLog() a message to let you know.
  • -(NSMutableDictionary *)debugDictionary - Override this and return an NSDictionary containing a description of the object (e.g. one key-value pair for each member variable). Also, call the superclass's implementation so that you can stack on the descriptions as you do everything else when you inherit. Generally this is only to be called when you're in a gdb session.
  • -(NSString *)descriptionWithLocale:(NSDictionary *)locale indent:(unsigned int)level - Returns a stringified [self debugDictionary], unless indent > 2, in which case it returns [self shortDescription].
  • -(NSString *)description - What's normally called when you try to print the value of an object in gdb. Returns [self descriptionWithLocale:nil indent:0].
  • -(NSString *)shortDescription - Returns the result of NSObject's description method.

Most of these methods are here to support better exploration of the system state while running the program in gdb. The easiest way to access gdb is to set a breakpoint in Project Builder and then look in the "Console" tab of the debugging pane. Some good documentation on using the console of gdb can be found here and here.

 

// Assertion Support

A really useful technique to help catch bugs early is the assertion. An assertion is a statement that states that some condition must be true. One very nice feature of most assertion mechanisms is that there's a way to completely disable the assertion checking altogether when you're ready to build the final executable. The normal way is with some sort of preprocessor macro.

The C standard library provides the ever-useful assert() macro, which for the most part gets the job done. OmniBase extends the mechanism with a bit more flexibility. To use them, you should define the DEBUG macro in your build environment. You can do that in the inspector for your Project's target, in Development build phase. Under the "Build Settings" tab, there will be a section called, uniquely enough, "Build Settings." There will be a set of macros already set, so just add DEBUG to the list. Define it to whatever you want, as it's value isn't important, just the fact that it's set.

Going back to OmniBase's assertion mechanism, it provides four types:

  • PRECONDITION - something that has to be true before you start a procedure
  • POSTCONDITION - something that has to be true after you finish a procedure
  • INVARIANT - something that must always be true
  • ASSERT - a catch-all for everything else

Each one of these types of assertions has a macro that lets you make the appropriate assertion: OBPRECONDITION(ex), OBPOSTCONDITION(ex), OBINVARIANT(ex), and OBASSERT(ex). For each of these, just put in an expression for the state you want to assert. For example:

- (int) useDict:(NSDictionary*) dict
{
   int result = -1;
   OBPRECONDITION(dict != nil);
   
   // do something here that should increase the value of result.
   
   OBPOSTCONDITION(result >= 0);
   return result;
}

The routine above asserts that the input dictionary cannot be nil, and that it cannot return a negative value. So, the natural question appears: "So Lally, what happens when I call [someObject useDict:nil]? Does the universe implode? How about just you? Wouldn't you like that? Hmm? I thought so tough guy! Who's the man now?" Well, no I won't implode, nor will the universe. Stop being such a punk.

Now that you've calmed down a bit, we can talk about what all these macros do when the expression evaluates to false. The normal action is for the macro to just print out a message to the log, like NSLog does. However, if you set OBShouldAbortOnAssertFailureEnabled in the program's user defaults to true, the program will abort when an assertion fails. If you were debugging the program at the time, you'll get a stack trace and the values of all the local variables, which will usually provide a good handle on what's going on.


// The Rest

I'm just covering some of the stuff I've found most useful in OmniBase. There's lots of other stuff in it, like some nice RCS macros, runtime environment modifiers, and even a few memory allocation tweaks. Look around, use what you like, and leave the rest be. Once we start looking into OmniFoundation and OmniAppKit, you'll want to add OmniBase to your apps just so that you can use these more advanced toolkits.

Remember, read the source! It's instructional and has all kinds of cool goodies inside!


Comments
Post a comment