Uli's Web Site |
|
blog |
Look-up tables for fun and profit
No time for blogging, so I thought I'd re-post a short programming trick I use a lot that I mentioned in a forum a while ago. This is a fairly fundamental trick, but it works slightly differently in many languages, so even if you know it already, you might appreciate seeing what Cocoa classes are best suited for this. Look-up tables and selectorsImagine you're writing your own programming language, and you want to let the user use WebView to display a browser in a window. All the IBAction functions on a web view are of the same form: -(void) methodName: (id)sender where the sender is generally ignored. So you can take the strings your programming language gets from the user and use NSSelectorFromString() to get a SEL, (the data type that @selector() returns) and then you can use [myWebView performSelector: theSel withObject: nil] or whatever to actually make the call. That way, you only need to write code for each kind of command, not for each and every command individually. Of course, since Objective C is case-sensitive, and your programming language might be case-insensitive, you may want to use an NSDictionary to look up the right spelling for the commands. I.e.: NSDictionary *possibleCommands = [NSDictionary dictionaryWithObjectsAndKeys: @"goBack:", @"goback", @"reload:", @"reload", nil]; NSString* lowercasedCommand = [myParameter lowerString]; NSString* caseCorrectedCommand = [possibleCommands objectForKey: lowercasedCommand]; if( caseCorrectedCommand == nil ) // unknown command passed to XCMD. return; // TODO: Emit error message. SEL theCommand = NSSelectorFromString(caseCorrectedCommand); if( theCommand == nil ) // No selector of that name? Shouldn't be possible, except if our dictionary has a typo. return; // TODO: Emit error message. [myWebView performSelector: theCommand withObject: nil]; To add a new command, all you have to do is add two simple list items to the dictionary declaration: @"makeTextLarger:", @"maketextlarger",for example. One trick in the code above is the lowerString thing: NSDictionary is case-sensitive, too, so what I do is simply specify all the keys in the dictionary in lowercase. When I get a string from the user of my programming language, I turn it into lowercase, which ensures that no matter whether the user wrote "GOBACK", "GoBack", "goBack" or "goback", I always get the dictionary entry with the key "goback" (in this case "goBack:"). Another advantage of the dictionary approach is that you verify that whatever string is passed in is one that you actually support. So if the user accidentally specifies "release" (e.g. passes in the wrong variable), your code won't end up prematurely releasing your myWebView object and cause the whole app to crash. It's a security advantage. Look-up tables and numbersThe same dictionary approach works for other stuff, too. A dictionary can contain any kind of object, so if you wrap a number in an NSNumber, you can have a dictionary that maps strings to numbers, and even numeric constants. A sample dictionary would look like: NSDictionary *possibleInterpolations = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt: NSImageInterpolationDefault], @"default", [NSNumber numberWithInt: NSImageInterpolationNone], @"none", [NSNumber numberWithInt: NSImageInterpolationLow], @"low", [NSNumber numberWithInt: NSImageInterpolationHigh], @"high", nil]; NSNumber* interpolationNumObject = [possibleInterpolations objectForKey: [myParameter lowerString]]; int myInterpolationNumber = NSImageInterpolationDefault; if( const ) myInterpolationNumber = [interpolationNumObject intValue]; // use myInterpolationNumber here... Such a dictionary is what is referred to in computer science texts as a "look-up table". Singleton for more performanceIf you have a look-up table that you use a lot, you can also keep it around between method calls. E.g.: -(void) myLookupTablePoweredMagic { static NSDictionary* sLocallyAccessibleGlobal = nil; if( sLocallyAccessibleGlobal == nil ) sLocallyAccessibleGlobal = [[NSDictionary alloc] initWithObjectsAndKeys: ..., nil]; // singleton, intentional leak. // use it here. } Note that we use alloc/init here, because sLocallyAccessibleGlobal is effectively a global variable (that's what static means in this context), and will maintain its value the second time myLookupTablePoweredMagic is called (hence the == nil test). Since the only one who can work with sLocallyAccessibleGlobal is the myLookupTablePoweredMagic method, this is also a leak, because nobody ever releases the object we created. However, this is an intentional, one-time leak in this case. If this method is really used very frequently, we want the dictionary to be kept around while our application is running. And when our application is quit, the memory will be cleaned up anyway, so why bother explicitly releasing it?
|
Created: 2009-01-23 @323 Last change: 2024-12-26 @251 | Home | Admin | Edit © Copyright 2003-2024 by M. Uli Kusterer, all rights reserved. |