Showing posts with label Gotcha. Show all posts
Showing posts with label Gotcha. Show all posts

Wednesday, October 22, 2008

Encodings Can Byte You

It is 2008, right? I mean, in this day and age, it's not totally unreasonable to expect that NSString to default to using an NSUTF8StringEncoding as the default encoding when loading a file if it can't determine the encoding, right? I mean, since an ASCII file will load correctly using UTF-8, but not vice versa, UTF-8 seems like the best assumption or, at least, a better assumption than ASCII.

Well, it might be a "reasonable" assumption, but it's an incorrect one. NSString's initWithContentsOfFile: apparently assumes an encoding of NSASCIIStringEncoding when it can't determine the real encoding.

This, I'm embarrassed to say, bit me today. I had the following code that I was using to load a UTF-8 file with non-ASCII characters:

NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"act texts" ofType:@"txt"];
NSString *sourceData = [[NSString alloc] initWithContentsOfFile:sourcePath];

But the resulting strings didn't contain the correct non-ASCII characters that were in the file; every time there was supposed to be a diacritical or other non-ASCII character, the string contained two high-order (>128) ASCII characters. That's an indication that you've got UTF-8 data being loaded as ASCII (UTF-16 looks even weirder when it misses the encoding, making it easier to catch).

The solution is simple enough. Just explicitly tell it the encoding to use by calling initWithContentsOfFile:encoding:error: instead :

NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"act texts" ofType:@"txt"];
NSString *sourceData = [[NSString alloc] initWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil];

Monday, August 18, 2008

Dynamically adding class objects

Okay, folks, the first draft of the book is done, and I have some breathing room until the chapters start coming back from the tech reviewer and copy editor. Mostly, I'm using that time to catch up on sleep and house jobs and spend some time with the kids, but I've also been working on an idea I mentioned earlier about creating a way for NSObjects to automatically persist themselves into a SQLite database. I just finished my first draft of the code to do that, and once I've done some more thorough testing, I'll do a blog post about it.

Today, I'm going to post one of the little gotchas that bit me while writing that code, and which is really, really hard to figure out from Apple's documentation: how to dynamically add a class method at runtime. What I did in my persistence classes was to create class methods to help you retrieve objects. So, if you wanted to retrieve all the objects in the database where the name property equaled "John", you could do this


NSArray *johns = [Person findByName:@"John"];


Anybody who has worked with Ruby's excellent ActiveRecord implementation will recognize this pattern as the one used there. The way to implement this is to override resolveClassMethod: (you would use resolveInstanceMethod: to do the same thing for instance methods, but that is documented well and easy to implement so I won't discuss it here).

The resolveClassMethod: documentation refers you to resolveInstanceMethod: for an example implementation, which looks like this:


+ (BOOL) resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically))
{
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSel];
}


If you use code like that in your resolveClassMethod:, however, you'll get an unrecognized selector error at runtime. Why? Because that code adds a method to the class object, which means you added an instance method to that class. That's not what you want. What you want is to add a method to your class' metaclass object instance, which means going to the runtime to get the Class object that represents the class' metaclass object (say that five times fast!). So, the example above, when turned into a class method override, would look like this:


+ (BOOL) resolveClassMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically))
{
Class selfMetaClass = objc_getMetaClass([[self className] UTF8String]);
class_addMethod([selfMetaClass, aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveClassMethod aSEL];
}


And, in the immortal words of Bugs Bunny: Viola! You've added a class method to your class at runtime. Of course, you have to create the function dynamicMethodIMP in order for this to work and do all the other assorted steps necessary to make dynamic methods work, but this isn't a tutorial on dynamic method creation, just a solution to one gotcha you may encounter while doing it.