July 19, 2010

When I need a string-constant, I #define it, instead of doing the “right” thing and using an extern const NSString * variable.

UPDATE 2010-07-20

Thanks to Elfred Pagen for pointing out that you should always put () around your macros. Wrong: #define A_STRING @"hello"

instead use (), even when you don’t think you have to:

#define A_STRING (@"hello")

This prevents accidental string concatenation. In C, string-literals separated only by whitespace are implicitly concatenated. It’s the same with Objective-C string literals. This feature lets you break long strings up into several lines, so NSString *x = @"A long string!" can be rewritten:

NSString *x =
@"A long"
@" string!";

Unfortunately, this seldom-used feature can backfire in unexpected ways. Consider making an array of two strings:

That looks right, but I forgot a “,” after X, so after string-concatenation, a is ['explain'], not ['ex','plain'].

Moral of the story: you can never have too many ()’s in macros.

And, now, back to why I use #define…

It’s less code

Using an extern variable means declaring it in a header, and defining it in some implementation file. But a macro is just one line in a header.

It’s faster to lookup

Because there’s only the definition of a macro, Open Quickly/command-double-clicking a macro always jumps to the definition, so you can see what it’s value is in one step. Generally Xcode jumps to a symbol’s declaration first, and then it’s definition, making it slower to lookup the value of a const symbol.

Xcode can catch errors like “[NSString stringWithFormat:@"reading garbage since there's no argument: %s"]“, if you let it. Unfortunately, the Objective-C compiler isn’t smart enough to check [NSString stringWithFormat:externConstString,x,y,z]; because it doesn’t know what an extern variable contains until link-time. But preprocessor macros are evaluated early enough in the build process that that the compiler can check their values.

It can’t be changed at runtime

It’s possible to change the value of const variables through pointers, like so:

Yes this is pathological code, but I’ve seen it happen (I’m looking at you AddressBook.framework!)

Of course, you can re-#define a preprocessor-symbol, so macros aren’t a panacea for pathological constant-changing code. (Nothing is!) But they push the pathology into compile time, and common wisdom is that it’s easier to debug compile-time problems, so that’s a Good Thing. You may disagree there, and you may be right! All I can say for sure is that in my experience, I’ve had bugs from const values changing at runtime, but no bugs from re-#define-ed constants (yet).

Conclusion

Preprocessor macros are damnably dangerous in C. Generally you should avoid them. But for NSString* constants in applications, I think they’re easier, and arguably less error prone. So go ahead and #define YOUR_STRING_CONSTANTS (@"like this").

April 22, 2009

BREAKING UPDATE: Actually comparing the -absoluteURL or -absoluteString of two NSURLs that represent a file is not good enough. One may start file:///, and the other file://localhost/, and they will not be isEqual:! A work around is to compare the path of each NSURL. I’m still looking into the issue, but for now I am using the following method to compare NSURLs.

[a isEqual:b] may report NO for two NSURLs that both resolve to the same resource (website, file, whatever). So compare NSURLs like [[a absoluteString] isEqual:[b absoluteString]]. It’s important to be aware of this gotcha, because URLs are Apple’s preferred way to represent file paths, and APIs are starting to require them. Equality tests that worked for NSString file-paths may fail with NSURL file-paths.

An NSURL object is composed of two parts—a potentially nil base URL and a string that is resolved relative to the base URL. An NSURL object whose string is fully resolved without a base is considered absolute; all others are considered relative.

In other words, two NSURL objects can resolve to the same absolute URL, but have a different base URL, and be considered !isEqual:.

I choose to define [aString hasSubstring:@""] as NO. You might prefer to throw an exception, or differentiate between @"" and nil. But I don’t think a nil string is enough error to throw an exception. And even though technically any string contains the empty string, I generally treat @"" as semantically equivalent to nil.

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. That way you can give it a default value of your choice, and won’t have undefined values if an object is unexpectedly nil.
Bad:- (struct CStructure) evaluateThings;
Good:- (void) resultOfEvaluatingThings:(struct CStructure*)result;.

It’s not a bad idea to wrap up all the rangeOf methods in functions or categories that play safer with nil.

I thought I was programming defensively by eagerly adding percent-escapes to any string that would become part of a URL. But this caused some annoying bugs resulting form a string being percent-escaped more then once. My solution was to create an indempotent replacement for stringByAddingPercentEscapesUsingEncoding: (I also simplified things a little by removing the encoding parameter, because I never used any encoding other then NSUTF8StringEncoding),

The paranoid have probably noticed that [aBadlyEncodedString stringByReplacingPercentEscapesOnce] will return aBadlyEncodedString not nil, This could make it harder to detect an error.

But it’s not something that I’m worried about for my application. Since I only ever use a UTF8 encoding, and it can represent any unicode character, it’s not possible to have an invalid string. But it’s certainly something to be aware of in situations where you might have strings with different encodings.

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 niland{}. Say with code like:

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.)

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.

But if you try to NSLog your own object, you get pretty useless output, like

myObject = <MyObject: 0x53f330>

Fortunately, it’s easy to fix! Just implement the method -(NSString*) description; and whatever it returns will be printed by NSLog and GDB (po object, will print object in GDB and the Xcode debugging console).

Whenever you make a new object, I strongly recommended immediately implementing a description method, and religiously keeping it up to date (it’s not hard, honest!). This won’t fix bugs, but it will make finding some of them much easier.

“Foreign” characters, like the ï in “naïve”, will break your code, if you use NSASCIIStringEncoding. Such characters are more common then you might expect, even if you do not have an internationalized application. “Smart quotes”, and most well-rendered punctuation marks, are not 7-bit ASCII. For example, that last sentence can’t be encoded into ASCII, because my blog uses smart-quotes. (Seriously, [thatSentence cStringUsingEncoding:NSASCIIStringEncoding] will return nil!)

Here are some simple alternatives:

C-String Paths
Use - (const char *)fileSystemRepresentation; to get a C-string that you can pass to POSIX functions. The C-string will be freed when the NSString it came from is freed.

An Alternate EncodingNSUTF8StringEncoding is the closest safe alternative to NSASCIIStringEncoding. ASCII characters have the same representation in UTF-8 as in ASCII. UTF-8 strings will printf correctly, but will look wrong (‘fancy’ characters will be garbage) if you use NSLog(%s).

Native Foundation (NSLog) Encoding
Generally, Foundation uses UTF-16. It is my understanding that this is what NSStrings are by default under the hood. UTF-16 strings will look right if you print them with NSLog(%s), but will not print correctly using printf. In my experience printf truncates UTF-16 strings in an unpredictable way. Do not mix UTF-16 and printf.

Convenience C-Ctrings[someNSString UTF8String] will give you a const char * to a NULL-terminated UTF8-string. ASCII characters have the same representation in UTF-8 as in ASCII.

Take a minute to search all your projects for NSASCIIStringEncoding, and replace it with a more robust option.

July 5, 2008

As I have written before, the best way to convert a FourCharCode to an NSString* for NSLog()ing is to use the NSFileTypeForHFSTypeCode() function. But for the life of me I can’t remember that name, even though I use it about once a month. It’s too long, and it has too little to do with what I’m using it for.

So I have added the line:#define FourCharCode2NSString(err) NSFileTypeForHFSTypeCode(err)
To my prefix-files, because I can remember FourCharCode2NSString().

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.

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 NSRangestruct 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.