Vincent Gable’s Blog

May 17, 2010

Don’t Check For nil in Your dealloc Methods

Filed under: Cocoa,Programming | , ,
― Vincent Gable on May 17, 2010

There’s no need to check if a variable is nil before release-ing it, because calling a method on a NULL variable is a no-op in Objective-C. And the runtime already has a super-fast check for this case.  So adding an extra if test into your code will probably slow things down.

Doing a pointless ” ==?nil”  test is bad for two reasons.

Firstly, it’s more code for no good reason. Even if it’s just one more line, It’s one more line to debug and test. It’s one more place where something could go wrong.

Secondly, it’s not idiomatic Cocoa-code, so it signals that something strange is going on. That’s a problem for whoever is reading the code, because they have to stop and look around more carefully to figure out why the pointless tests are happening.

In summary, do NOT do this:

– (void)dealloc;

{

if(baseImage) {

[baseImage release];

baseImage = nil;

}

[super dealloc];

}?

Do this:

– (void)dealloc;

{

[baseImage release];

baseImage = nil;

[super dealloc];

}

December 16, 2008

isEmpty?

Checking if a Cocoa object is empty is a little harder then in other languages, say C++, (but easier in some ways). Because every object in Objective-C is actually a pointer to an object, there are two ways, obj, can be empty.

obj = {}

obj points to an object that is empty. Say an array with 0 items, or the string "", etc..

obj = nil

obj, the pointer obj, is NULL, nil, 0, or whatever you want to call it. You might argue that obj isn’t really an object, but it is empty, because there’s nothing in it.

Bug:

When I first started writing Objective-C, I made the mistake of writing code like: if([name isEqualToString:@""]){ ... }, to test for empty strings. And this code would work for a while until I used it in a situation where name was nil, and then, because sending any method called on nil “returns” NO, I would have mysterious errors. (Worse then a crash, because it’s harder to track down.)

Bug:

It’s tempting to avoid the previous bug, by explicitly testing for nil and {}. Say with code like:

if (email == nil || ![email isEqualTo:@""] )
   email = @"An email address is required";

But generally this is a bad idea. It means more code, which means more places for a bug. I know it’s only one trivial test, but I’m serious, when I say it’s asking for a bug — like the bug in the example above, which sets email to @"An email address is required", whenever it is not the empty string, rather then when it is empty. (Values have been changed tho protect the innocent but it’s a bug I’ve seen.)

Solutions:

Wil Shipley suggests using the global function:

static inline BOOL IsEmpty(id thing) {
    return thing == nil
        || ([thing respondsToSelector:@selector(length)]
        && [(NSData *)thing length] == 0)
        || ([thing respondsToSelector:@selector(count)]
        && [(NSArray *)thing count] == 0);
}

I’ve been using his IsEmpty() for about a year. I’ve had zero problems with it, while it’s made my code more readable and concise.

Another solution is to take advantage of what happens when you send a message to nil. (To over-simplify, you get back 0 or NO.) So you can just say “if ([obj count] == 0) then obj is empty.” This often means reversing your thinking, and testing “IsNotEmpty()” instead of “IsEmpty()”. I don’t think it’s as clear is IsEmpty() in general, but in cases where it is, there you have it.

December 12, 2008

Catching nil Member Variables

Filed under: MacOSX,Objective-C,Sample Code | , , ,
― Vincent Gable on December 12, 2008

When I wrote this method it seemed like it would be helpful,

//log a warning for any member variable that nil
- (void) warnOfNilIVars;
{
  unsigned int ivarCount = 0;
  Ivar * ivarList = class_copyIvarList([self class], &ivarCount);
  if(ivarList) {
    for(int i = 0; i < ivarCount; i++){
      const char *typeString = ivar_getTypeEncoding(ivarList[i]);
      if(typeString && typeString[0] == '@')
        if(!object_getIvar(self, ivarList[i]))
          NSLog(@"***WARNING: ivar %s of %@ is nil", ivar_getName(ivarList[i]), [self className]);
    }
    free(ivarList);
  }
}

But in practice I haven’t really used it (and when I have there were quite a few false-positives). Still, I think it’s pretty neat that you can do something like this in Objective-C.

If you find a use for it, please let me know!

August 31, 2008

Better List Termination

Filed under: MacOSX,Objective-C,Programming,Usability | , ,
― Vincent Gable on August 31, 2008

As I’ve written before, using nil to terminate a variable-length list (eg [NSArray arrayWithObject:foo,bar,nil]) is a bad choice because of how easy it is to get a nil value in Objective-C without knowing it. (Unlike, say C++, doing stuff with nil won’t cause an exception, leading to some subtle bugs). Well, here is a solution.

A Better Sentinel

#define END_OF_LIST ((id) -1)
is my current choice of a good sentinel-value. It is an invalid pointer, so unlike nil, you should never get it back from some library. Sending it any message will fail fast, and recognizably (you will see 0xFF...FF as an illegal address somewhere in the back-trace).

Example:


id aNilObject = nil;
NSArray *standard = [NSArray arrayWithObjects:@"1", @"2", aNilObject, @"4", nil];
LOG_ID(standard);
NSArray *better = [NSArray arrayWithSentinelTerminatedObjects:@"1", @"2", aNilObject, @"4", END_OF_LIST];
LOG_ID(better);


standard = (
   1,
   2
)
better = (
   1,
   2,
   <null>,
   4
)

The Code:


#define END_OF_LIST ((id) -1)

@implementation NSArray (SentinelTerminatedConstructors)

+ (NSArray*) arrayWithSentinelTerminatedObjects:(id) firstObject, ...
{
   NSMutableArray *array = [NSMutableArray array];
   
   if (firstObject != END_OF_LIST)
   {
      if(!firstObject)
         firstObject = [NSNull null];

      [array addObject:firstObject];
      
      id eachObject;
      va_list argumentList;
      va_start(argumentList, firstObject);
      while (END_OF_LIST != (eachObject = va_arg(argumentList, id)))
      {
         if(!eachObject)
            eachObject = [NSNull null];

         [array addObject:eachObject];
      }
      va_end(argumentList);
   }
   
   return array;
}

@end

Some Downsides

arrayWithSentinelTerminatedObjects” is too long of a name. If you’ve got a better one, please share!

You won’t get a compiler-warning if you leave out END_OF_LIST, but if you compile with -Wformat, you will get a warning if you leave out nil. The full story

There are six collection creation methods in Foundation which require nil termination to function properly. They are:

+[NSArray arrayWithObjects:]
-[NSArray initWithObject:]
+[NSDictionary dictionaryWithObjectsAndKeys:]
-[NSDictionary initWithObjectsAndKeys:]
+[NSSet setWithObjects:]
-[NSSet initWithObjects:]

These methods have been decorated with the NS_REQUIRES_NIL_TERMINATION macro, which adds an additional check to invocations of those methods to make sure the nil has been included at the end of the argument list. This warning is only available when compiling with -Wformat.

What do you think?

Frankly, I haven’t used END_OF_LIST-terminated collection creation enough yet to have a final verdict. So please let me know what you think! Was this code useful? Did it cause problems, or preempt them? As always, if you have a better solution, please share!

July 17, 2008

Null-Terminated Argument Lists

Filed under: Bug Bite,C++,Cocoa,Design,Objective-C,Programming,Usability | , , , , ,
― Vincent Gable on July 17, 2008

I was using +[NSDictionary dictionaryWithObjectsAndKeys:] to make a new dictionary, but one of the objects in the dictionary was the result of a call to a method that was returning nil, so the dictionary was incomplete.

This got me thinking about NULL/nil terminated argument lists. I don’t think they are a great idea (the compiler should be able to handle the list-termination for you!), but I think they are an especially bad idea in Objective-C.

The problem that it’s very common to have a nil object in Objective-C, relative to, say C++. Many Cocoa methods return nil on error. Since doing stuff with nil (generally) won’t cause an exception, these nils stick around much longer then in other languages. As you can see, nil is a pretty poor choice of a sentinel value.

It’s the 21st century! The compiler could tell an Obj-C method using a variable-argument-list how many arguments are in the list. This is trivial when all arguments are of type id. Since Obj-C methods use a radically different syntax from C functions, it shouldn’t effect existing C-code. Unfortunately, I don’t see this being added, because Objective-C is already so mature.

In the meantime. Be a little more suspicious of any objective-C methods taking a NULL-terminated list. I wish I had a perfect solution to avoid them, but I don’t! Sometimes they are the best way to do something. If you have a great work-around for constructing, say an NSDictionary with a variable number of key/values please let me know!

May 31, 2008

Messages to Nowhere

Filed under: Bug Bite,Cocoa,iPhone,MacOSX,Objective-C,Programming | , , ,
― Vincent Gable on May 31, 2008

If you send a message to (call a method of) an object that is nil (NULL) in Objective-C, nothing happens, and the result of the message is nil (aka 0, aka 0.0, aka NO aka false). At least most of the time. There is an important exception if sizeof(return_type) > sizeof(void*); then the return-value is undefined under PowerPC/iPhone, and thus all Macs for the next several years. So watch out if you are using a return value that is a struct, double, long double, or long long.

The fully story:

In Objective-C, it is valid to send a message to a nil object. The Objective-C runtime assumes that the return value of a message sent to a nil object is nil, as long as the message returns an object or any integer scalar of size less than or equal to sizeof(void*).
On Intel-based Macintosh computers, messages to a nil object always return 0.0 for methods whose return type is float, double, long double, or long long. Methods whose return value is a struct, as defined by the Mac OS X ABI Function Call Guide to be returned in registers, will return 0.0 for every field in the data structure. Other struct data types will not be filled with zeros. This is also true under Rosetta. On PowerPC Macintosh computers, the behavior is undefined.

I was recently bitten by this exceptional behavior. I was using an NSRange struct describing a substring; but the string was nil, so the substring was garbage. But only on a PPC machine! Even running under Rosetta wouldn’t have reproduced the bug on my MacBook Pro.Undefined values can be a hard bug to detect, because they may be reasonable values when tests are run.

Code running in the iPhone simulator will return all-zero stucts when messaging nil, but the same code running on an actual device will return undefined structs. Be aware that testing in the simulator isn’t enough to catch these bugs.

A few simple guidelines can help you avoid my misfortune:

  • Be especially careful using of any objective-C method that returns a double, struct, or long long
  • Don’t write methods that return a double, struct, orlong long. Return an object instead of a struct; an NSNumber* or float instead of a double or long long. If you must return a dangerous data type, then see if you can avoid it. There really isn’t a good reason to return a struct, except for efficiency. And when micro-optimizations like that matter, it makes more sense to write that procedure in straight-C, which avoids the overhead of Objective-C message-passing, and solves the undefined-return-value problem.
  • But if you absolutely must return a dangerous data type, then return it in a parameter.
    Bad:
    - (double) crazyMath;
    Good:
    - (void) crazyMathResult:(double*)result;.

I love Objective-C’s “nil messaging” behavior, even though it is rough around the edges. It’s usefulness is beyond the scope of this article, but it can simplify your code if you don’t return a data-type that is larger then sizeof(void*). With time, when the intel-style return behavior can be universally relied on, things will be even better.

Powered by WordPress