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!