December 20, 2006

Pimp My Code, Part 13: The Pimp Before Christmas, Redux

Well, it's been awhile since I've pimped anything (except other developers' moms), and frankly I feel my pimp muscles starting to atrophy. A reader was nice enough to send me some code today with the following note:

I'm sure there's a better way to do this, and there's probably some entertaining mockery to be had as well. The code's from a motion-sensing alarm thing I wrote, along the lines of iAlertU.

Mockery? I believe sir may have me confused with someone else? At any rate, let's view our victim:

Original: -awakeFromNib
- (void)awakeFromNib
{
NSRect screenRect = [[NSScreen mainScreen] frame];
NSRect boxRect = [lockBox frame];
[lockBox setFrame:NSMakeRect((screenRect.size.width - boxRect.size.width)/2,
(screenRect.size.height - boxRect.size.height)/2,boxRect.size.width, boxRect.size.height)];

NSShadow *lockShad = [NSShadow alloc];
[lockShad setShadowOffset:NSMakeSize(0,-4)];
[lockShad setShadowBlurRadius:8.0];
[lockShad setShadowColor:[NSColor colorWithDeviceWhite:0 alpha:0.75]];
NSMutableParagraphStyle *paraSty = [NSMutableParagraphStyle alloc];
[paraSty setAlignment:NSCenterTextAlignment];
shadAttrs = [[NSDictionary dictionaryWithObjectsAndKeys:[NSFont fontWithName:
@"Lucida Grande Bold" size:72],NSFontAttributeName,lockShad,NSShadowAttributeName,
[NSColor whiteColor],NSForegroundColorAttributeName,paraSty,NSParagraphStyleAttributeName,
nil] retain];
[lockShad release];
[paraSty release];
def = [NSUserDefaults standardUserDefaults];
[def registerDefaults:[NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:
[NSNumber numberWithInt:0],[NSNumber numberWithInt:5],[NSNumber numberWithFloat:0.8],
@"System locked.\nDo not touch.",nil] forKeys:[NSArray arrayWithObjects:@"compType",
@"sensitivity",@"lockedOpacity",@"lockedText",nil]]];
[self getPrefs];

// Menu icon thing
menuItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:24] retain];
menuItemIcon = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle]
pathForImageResource:@"lockicon"]];
menuItemSelIcon = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle]
pathForImageResource:@"lockiconsel"]];
[menuItem setImage:menuItemIcon];
[menuItem setAlternateImage:menuItemSelIcon];
[menuItem setHighlightMode:YES];
[menuItem setMenu:menuItemMenu];
}

There's some general clean-up to be done here, plus some of this code needs to move. First off, let's rip that defaults registration out of the middle of -awakeFromNib and into +initialize, because the latter is automatically called before any method can be called on this class, and "before everything" is a damn fine time to set up preferences.

Pimped: New +initialize method
+ (void)initialize;
{
[[NSUserDefaults standardUserDefaults] registerDefaults:[NSDictionary
dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:0], @"compType",
[NSNumber numberWithInt:5], @"sensitivity", [NSNumber numberWithFloat:0.8],
@"lockedOpacity", NSLocalizedString(@"System locked.\nDo not touch.", @"panel message"),
@"lockedText", nil]];
}

Note I also don't store off +standardUserDefaults in a temporary variable, because it's really fast to call this method and very self-documenting, and more variables == more clutter. And I've made the English text for the locked message localizable, which you should always do as a matter of course.

Let me say this again in slow motion: NEVER type in ANY English string without typing NSLocalizedString() around it! This will save you SO MUCH HASSLE later on when your app is popular. Remember that enterprising polyglots can localize your code from just the binary you ship if you follow a few rules of localization, so you may wake up one day and find that someone from across the world has mailed you a your app in another language. It's a fuzzy feeling and it gets you instant market-share.

Also note I'm using +dictionaryWithObjectsAndKeys: instead of +dictionaryWithObjects:forKeys:, the latter of which is obviously wordier AND harder to read and edit.

Next off, we're going to move the text attributes code out of -awakeFromNib as well, and put it into his code right before it gets used. Since I don't actually have this code, I'm just going to pretend, but imagine there's a method in which he's about to set the attributed string value of a text field... this code would be inserted right before that happened:

Pimped: Insert right before attributes are used
[...some code...]
static NSDictionary *shadowedTextAttributes = nil;
if (!shadowedTextAttributes) {
NSShadow *lockShadow = [[[NSShadow alloc] init] autorelease];
[lockShadow setShadowOffset:NSMakeSize(0,-4)];
[lockShadow setShadowBlurRadius:8.0];
[lockShadow setShadowColor:[NSColor colorWithDeviceWhite:0 alpha:0.75]];

NSMutableParagraphStyle *paragraphStyle = [[[NSMutableParagraphStyle alloc] init]
autorelease];
[paragraphStyle setAlignment:NSCenterTextAlignment];

shadowedTextAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:[NSFont
boldSystemFontOfSize:72], NSFontAttributeName, lockShadow, NSShadowAttributeName,
[NSColor whiteColor], NSForegroundColorAttributeName, paragraphStyle,
NSParagraphStyleAttributeName, nil];
}
[...actually use shadowedTextAttributes here...]
[...more code...]

Why do we do this in the main code instead of in one of the class initialization methods? First, because this offers us better locality of code -- when we see "shadowedTextAttributes" being use we don't wonder what the heck it is, and have to scroll to the top of the file to find out. It's right there. Similarly, if we want to, say, change the size of the text, we just find where we use it and the size is right there. Second, and related, we don't forget that we set up "shadowedTextAttributes" this way -- if we delete the code that uses it, we'll quickly see that we should delete the "shadowedTextAttributes" setup code as well. You would be amazed how many times I've seen classes that set up variables in their +initialize or -awakeFromNib methods that they never use any more. There's no compiler warning if the variable is declared at the top level, but there is if it's declared inline like this. Finally, our program launches faster, because we are even more lazy about initializing (or not initializing) our state until the very second we need it. The biggest performance wins you will find are always to not do any work until you absolutely need to.

I also use +boldSystemFontOfSize: instead of typing in "Lucida Grande Bold" by hand, which reduces the chances of a typo and also means our font will automatically change when Apple redoes their fonts for Leopard. (Right, Apple? Right?... Apple? Anyone?)

I'm going to remove this code, because if the NIB is set up correctly the 'lockBox' view should auto-center itself anyways. If for some reason it didn't, this is what the code would look like pimped out a bit -- note that we get the screenRect of the screen our view is actually on, instead of arbitrarily picking the main screen, and that we don't assume a screen's rect starts with 0.

Pimped: Delete this
NSRect screenRect = [[[lockBox window] screen] frame];
NSRect boxRect = [lockBox frame];
[lockBox setFrame:NSMakeRect(NSMinX(screenRect) + (NSWidth(screenRect) - NSWidth(boxRect))/2,
NSMinY(screenRect) + (NSHeight(screenRect) - NSHeight(boxRect))/2, NSWidth(boxRect),
NSWidth(boxRect)];

Finally, that leaves us with the now-gutted -awakeFromNib, which I have rewritten to not use as many temporary variables (or any at all, actually), to be less wordy when looking up images. Also, I don't know what -getPrefs does, but I'm guessing it can at least be made private:

Pimped: Final -awakeFromNib
- (void)awakeFromNib;
{
[self __getPreferences];

// Menu icon thing
menuItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:24] retain];
[menuItem setImage:[NSImage imageNamed:@"lockicon"]];
[menuItem setAlternateImage:[NSImage imageNamed:@"lockiconsel"]];
[menuItem setHighlightMode:YES];
[menuItem setMenu:menuItemMenu];
}

And that's how we pimp it up in my house, nerds and nerdettes. So, happy holidays, and don't spend all your savings snorting coke off of hoo... oh, wait, I have One More Thing!


No, it's not an Apple Phone, it's... MORE CODE. Yes, our friend actually mailed me TWO snippets of code, so we get to double our fun today.

Also, a method from another application, this one a game cache manager:
Original: -applicationWillFinishLaunching:
-(void)applicationWillFinishLaunching:(NSNotification *)aNotification
{
// Get cache-file contents
NSString *fileCont = [NSString stringWithContentsOfFile:
[@"~/Library/Application Support/Unreal Tournament 2004/Cache/cache.ini"
stringByStandardizingPath]];
NSArray *lines = [fileCont componentsSeparatedByString:@"\n"]; // Split it up
NSEnumerator *lineEnum = [lines objectEnumerator]; // Enumerator to iterate through all
the lines
NSScanner *scanner; // String-parser scanny thingy
NSString *tmpStr; // set to full line in loop
NSString *idStr = nil; // ID string
NSString *nameStr = nil; // Name string
NSString *fileExt; // File extension
NSString *typeStr; // Type string, also folder to copy to
NSFileManager *fileMan = [NSFileManager defaultManager]; // File manager - used to get
the file attributes
NSDictionary *fattrs; // File attributes holder
cacheArray = [[NSMutableArray alloc] initWithCapacity:([lines count]-1)]; // Initialize
the main array
[lineEnum nextObject]; // Skip the "[Cache]" header
while(tmpStr = [lineEnum nextObject])
{
scanner = [NSScanner scannerWithString:tmpStr];
[scanner scanUpToString:@"=" intoString:&idStr]; // get ID
[scanner scanString:@"=" intoString:nil]; // skip the =
[scanner scanUpToString:@"" intoString:&nameStr]; // get name
fileExt = [nameStr pathExtension]; // get extension
//Figure out where it goes
if([fileExt isEqualToString:@"ut2"])
typeStr = @"Maps";
else if([fileExt isEqualToString:@"ogg"])
typeStr = @"Music";
else if([fileExt isEqualToString:@"utx"])
typeStr = @"Textures";
else if([fileExt isEqualToString:@"usx"])
typeStr = @"StaticMeshes";
else
typeStr = @"System";
nameStr = [nameStr stringByDeletingPathExtension];
fattrs = [fileMan fileAttributesAtPath:[self pathToCacheFile:idStr] traverseLink:YES];
// get file attributes
// make the final dictionary
[cacheArray addObject:[NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:
nameStr,idStr,typeStr,[[fattrs fileModificationDate] descriptionWithCalendarFormat:
@"%b %e, %Y" timeZone:nil locale:nil],fileExt,nil] forKeys:[NSArray
arrayWithObjects:@"name",@"id",@"type",@"date",@"ext",nil]]];
}
maxIndex = [cacheArray count]-1; // set the index properly
[table setDataSource:self];
[table setDelegate:self];
[table display]; // make the table update
}

Ok, let's start with the code we're going to cut, because (sing it along with me) the line of code we cut is the line of code we don't ever debug. I'm going to delete the "maxIndex" ivar (towards the end of the method), since asking an array for its count is O(1) and it's MUCH less fragile and much more readable to just use "[cacheArray count]" whenever you need that count, instead of caching an int at launch time.

Let me stress that I hate instance variables: they add needless complexity to code most of the time. Objective-C is mostly self-documenting; it's much clearer to me and any casual observer what "[cacheArray count]" is (the count of the cacheArray, maybe?) than what "maxIndex" is. DO NOT CACHE VALUES THAT ARE O(1) TO RECOMPUTE.

Also, there's a problem here, in that if there are NO lines in the file we read in, maxIndex will be set to -1 or NSNotFound (I don't know if it's unsigned or signed), and either value is likely to cause subsequent code to act anomolously (which is why we normally use 'count' instead of 'max index' for arrays and sets -- 'count' works for empty collections, and 'max index' is undefined). Not caching this count makes us face up to "cacheArray"'s possible emptiness every time we use it.

Pimped: delete this
maxIndex = [cacheArray count]-1; // set the index properly

And while I'm in a deleting mood, let's change our tableView to use bindings in NIB, so I can delete these three lines, which shouldn't exist anyways because the dataSource and delegate should be set up in NIB even if you AREN'T using bindings, which you should:

Pimped: delete this
[table setDataSource:self];
[table setDelegate:self];
[table display]; // make the table update

Ah, that's getting better.

Now let's rewrite the main method, shall we?"

Pimped: -applicationWillFinishLaunching:
-(void)applicationWillFinishLaunching:(NSNotification *)notification;
{
NSString *fileContents = [NSString stringWithContentsOfFile:[[NSString pathWithComponents:
[NSArray arrayWithObjects:[NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory,
NSUserDomainMask, YES) lastObject], @"Unreal Tournament 2004", @"Cache", @"cache.ini",
nil]] stringByStandardizingPath]]; // Get cache-file contents

cacheArray = [[NSMutableArray alloc] init]; // Initialize the main array

NSEnumerator *lineEnumerator = [[fileContents componentsSeparatedByString:@"\n"]
objectEnumerator];
[lineEnumerator nextObject]; // Skip the "[Cache]" header
NSString *lineString; // set to full line in loop
while (lineString = [lineEnumerator nextObject]) {
// parse the line
NSScanner *scanner = [NSScanner scannerWithString:lineString];
NSString *idString = nil;
if (![scanner scanUpToString:@"=" intoString:&idString]) // get ID
break;
if (![scanner scanString:@"=" intoString:nil]) // skip the =
break;
NSString *nameString = nil;
if (![scanner scanUpToString:@"" intoString:&nameString]) // get name
break;

// figure out where it goes
NSString *pathExtension = [nameString pathExtension];
static NSDictionary *pathExtensionsToDirectoryNames = nil;
if (!pathExtensionsToDirectoryNames)
pathExtensionsToDirectoryNames = [[NSDictionary alloc] initWithObjectsAndKeys:@"Maps",
@"ut2", @"Music", @"ogg", @"Textures", @"utx", @"StaticMeshes", @"usx", nil];
NSString *typeString = [pathExtensionsToDirectoryNames objectForKey:pathExtension]; //
Type string, also folder to copy to
if (!typeString)
typeString = @"System";

NSDictionary *fileAttributes = [[NSFileManager defaultManager] fileAttributesAtPath:[self
pathToCacheFile:idString] traverseLink:YES];

NSDictionary *cacheDictionary = [NSDictionary dictionaryWithObjectsAndKeys:[nameString
stringByDeletingPathExtension], @"name", idString, @"id", typeString, @"type",
[[fileAttributes fileModificationDate] descriptionWithCalendarFormat:@"%b %e, %Y"
timeZone:nil locale:nil], @"date", pathExtension, @"ext", nil];
[cacheArray addObject:cacheDictionary];
}
}

Starting at the end, I've added "cacheDictionary" as a temporary ivar because I really want for there to be a first-class object here instead of an NSDictionary. Approximately 100% of the time that I've used NSDictionaries as a cheapie way to store data structures I've discovered myself adding code to that data in some other class, and then 100% of the time I hit my head at some point and say, "Duh, code + data = objects" and I write a class instead of using an NSDictionary and my life is a lot better. Don't make the same mistakes I did!

Also, it bothers me that we're storing NSDates in this dictionary as strings -- we're losing data in our model layer for no good reason, and forcing a particular style of internationalization in the model as well. I'd much rather we store this as an NSDate and then apply formatting in a (-gasp-) NSDateFormatter when we actually display the value. This has the double-advantage of letting us set it up in NIB. Hey, this NIB thing is like a theme with me, isn't it?

I didn't change either of those things because I don't have all the relevant code, but I'm calling them out here as something I'd want fixed from my team.

Other changes I've made include moving variable declarations down to where they are used, which we've talked about before -- it reduces the scope of variables, and having a short-lived variable is a nice step towards not having a variable, and you know how I hate variables. It also increases readability by not introducing a bunch of extraneous concepts in different places in the code.

I've also renamed variables to remove all but approved abbreviations, and after that I removed a bunch of comments which I found entirely self-evident, because I find this kind of comment worse than no comment. "nameString" is "// Name string"? No shit? Well, what's "idString"? Oh, it's the "// ID string"? I'll be damned. Wait, wait, don't tell me what "fileExtension" is, I want to guess...

I replaced a bunch of if/elses with a single dictionary lookup, which happens to be more efficient, but also makes it a lot clearer at a glance -- we're going to translate "pathExtension" into "typeString", and that happens on one line now instead of over eight or so. Now there's no chance for me to have a typo on one of the lines that I miss forever ("oops, there's a ! in one of the if statements, that condition doesn't work!").

I've added some trivial error-checking in our parsing code, so if the file is corrupted we'll bail early and not try to copy something invalid to someplace unknown. Failing nicely is always a good thing to do -- I don't know exactly what the later code does, so there may be more error-checking to be done here, but I'd like to note you don't have to always, like, create an NSError and recovery code and localized error panels to handle every stupid error condition. But you SHOULD always fail gracefully. When you are parsing anything from the disk, assume the disk hates you and wants to kill you. We have enough customers with Delicious Library now that we've actually had more than a couple of plain old cosmic-ray-zapped data files show up (eg, every 3,000th character in the file was turned into "~" or some such).

Finally, working our way back to the start of the method, I've changed the way we create the path to be more verbose. I've seen NEXTSTEP switch filesystems TOO many times to EVER type a path separator, EVER. Sure, sure, you say, Mac OS X will use "/" forever -- except I've seen this same OS run on Windows FAT32 ("\") and I've seen it run on UFS ("/") and I've seen it run on HFS (":", now translated to "/"), so I'm not buying it. Don't use "~" for the home directory, either -- don't use UNIXisms at this layer, and don't assume the application support directory is where you think it is in the directory structure. Give the Apple guys the chance to move stuff around between releases, and always use "NSSearchPathForDirectoriesInDomains()" instead.

Well, that's it for now. Have a reasonably suicide-free holiday season, if you can. Seriously, we're all miserable here -- just survive the season, and you're ahead of the game. If you get too sad, try doing something nice for somebody -- it'll make you feel good inside and, hey, you might get laid. (My personal plan is to stay drunk and happy until the sun comes out again in Feburary.)

Labels:

29 Comments:

Blogger Tyler said...

Haha... was that you in the background right at the beginning of the vid?... oh and an interview! Good for you Wil, you're a good example to all the other rich kids in Seattle.

December 21, 2006 5:57 PM

 
Anonymous Seth Willits said...

Indeed, that is the famous Mr. Shipley.

December 21, 2006 6:48 PM

 
Blogger mullzk said...

Good stuff as usual, thanks for some new thoughts.
But I can't understand why you want to go with the nib-files all the time. In my experience, stupid I-was-absolutely-sure-i-connected-this-darn-thing-bugs take more time to fix in Interface Builder than in source code (another app, no debugger, too much stuff hidden behind tabs etc). Do you have any clever golden rules for deciding between code and nib?

December 21, 2006 6:56 PM

 
Blogger Chris said...

Wil: You're god reincarned. Wait... what I just said? lowering yourself to god level... tsk tsk tsk... :D

Do you have any books or something I should consider to increase the level of my code? I can program anything I want, it's just that I'm not sure it's 100% perfect.

Thanks for the children! You're doing great... except for the tie. :P

December 21, 2006 8:02 PM

 
Blogger The Nog said...

Can't you do "[aWindow center]" to auto-center a window?

December 22, 2006 9:58 AM

 
Blogger Wil Shipley said...

Can't you do "[aWindow center]" to auto-center a window?

Yes, but I guessed it was a view and not a window, because windows don't respond to -setFrame:

December 22, 2006 2:35 PM

 
Anonymous Mark Stultz said...

Do you EVER use local variables for the like:

[ NSUserDefaults standardUserDefaults ]
[ NSScreen mainScreen ]
[ NSFileManager defaultManager ]
[ NSBundle mainBundle ]

If not, what if you call reference these objects two or three times in the function? Still no? And when do you draw the line between variable clutter [ and [ object [ message [ cluttering [ of [ the [ line(s) ]]]]]]]

Do you prefer one over the other?

Mark

December 22, 2006 3:09 PM

 
Anonymous Anonymous said...

One thing that I would have done differently is to put the code that generated the NSShadow into a method of its own, probably in a category of NSShadow.

This is also what I do whenever I have more than about ten lines of code that are generating an NSBezierPath.

Before I started using this approach, I had a bad habit of repeating drawing code in several places in an app. Keeping it synced when I decided to change it became a major nuisance.

-jcr

December 24, 2006 1:38 AM

 
Anonymous Anonymous said...

Mark,

I will often use a macro to replicate verbose code like changing

mouseDownPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil];

to

mouseDownPoint = WHERE;

in cases where I'm tracking the mouse in -mouseDown: mouseMoved: and mouseDragged: methods.

For an example, see my "Cropped Image" sample on the ADC web site.

The preprocessor works just as well for Objective-C as it does for plain old C code.

-jcr

December 24, 2006 1:52 AM

 
Blogger John Gustafsson said...

Want to know the weirdest thing about Pimp my code? I would seriously consider sending this maniac my first ObjC code ever, and feel happy if he wrecks it. Yet I don't feel quite comfortable letting people at work review my code. What makes it even more weird is that I've got some serious experience with what I do for a living.

Wil, first real ObjC app I create will come your way, promise.

December 25, 2006 11:36 AM

 
Blogger Kevin Hoctor said...

Okay, I'm a youngling when it comes to Objective-C, but it seems like your use of NSLocalizedString(...) is backwards.

Mr. Hillegass taught me (okay, his book taught me) to do NSLocalizedString(@"SystemLocked", @"System locked.\nDo not touch.") and then use genstrings to generate the string table from the comment (the second argument). Has something changed since this second edition book?

As always, thanks for sharing your vast knowledge Master Shipley. ;->

December 27, 2006 5:32 PM

 
Blogger Wil Shipley said...

Kevin:

In this case "System locked.\nDo not touch." was the actual primary message, not the comment. Since genstrings creates a file with the original string AND the comment, I've found it's much more efficient to NOT have the comment re-state what I just said in the original string -- instead, I just use it to provide some context for localizers, so they know where the string is going to be used.

December 27, 2006 6:40 PM

 
Blogger Wil Shipley said...

Mark:

Do you EVER use local variables for the like: [
NSUserDefaults standardUserDefaults ]...


Programming is a bit like writing English. You don't want to write the exact same phrase over and over again if you can help it, but you also don't want to use extra words.

Just as you wouldn't say, "William Jon Shipley is a programmer. William Jon Shipley likes eggs, and William Jon Shipley has a hot girlfriend." You wouldn't want to use the same (lengthy) method call over and over again.

It's a judgment call, really. Use whatever reads best to you, NOT what you think is fastest, because accessors are hella fast.

My rule of thumb would be, if you've got accessors nested in accessors, you might use a variable. I'd type [NSBundle mainBundle] 20x in the same method if it came up, because I think it reads better than "myMainBundle" or whatever. There's always the element of doubt when you see an ivar -- was it iniatilized correctly? Is it REALLY what you think it is, or is it badly named?

December 27, 2006 6:44 PM

 
Blogger Wil Shipley said...

Mullzk:

But I can't understand why you want to go with the nib-files all the time.

Nib files are not very self-documenting as of 10.4, I agree. The bigger issue for me is splitting functionality across two files, though. If you create an object in NIB and set up some state for it (or leave it in its default state, even), then set up MORE state in a .m file when you load it, you run into several issues:

1) People just looking at the NIB have no idea about how the object is truly used and configured, because they don't know to look in the code.

2) If the NIB is loaded elsewhere or the object is copied/pasted into another NIB, it'll work differently, because it won't get the magic .m config code.

3) If you change the initial state of an object in NIB and it's overridden in code, you'll wonder why the hell your changes aren't taking.

So, since we're forced to use NIB, I say, for any object in NIB, set it up as much as possible entirely in NIB.

If you want to create an object entirely by hand, I'd understand that, but I don't encourage splitting between the two.

December 27, 2006 6:47 PM

 
Blogger Wil Shipley said...

jcr,

We'll have to disagree on categories and MACROS -- I've used both in the past, but decided they both just add a ton of complexity. I avoid both like the plague now, except in the most dire circumstances.

December 27, 2006 6:48 PM

 
Anonymous Anonymous said...

...ironically, NSLocalizedString and variants are Macros.

December 29, 2006 10:20 AM

 
Blogger Wil Shipley said...

I should have been more clear: I avoid writing my own macros because their behavior is kind of strange compared to methods, and they aren't particularly self-documenting.

When it's a system macro that we're all told to use, I feel it's safe to assume any programmer coming after me will already be familiar with it (or should be, so it's safe to ask her to learn in via my code). When it's a macro I wrote, it's essentially asking the next programmer to learn something bizarre I wrote that does NOT apply to any other project they are using, and thus adds complexity.

December 29, 2006 12:08 PM

 
Anonymous Anonymous said...

Dear Wil, you are great guy.
We all enjoy your pimping series and look forward to the new year pimps.
Happy New Year!

December 30, 2006 11:09 AM

 
Anonymous Anonymous said...

I use categories because to my eye, they make the right object do the work. When I need a rounded rect, for example, I'd much rather have a -appendRoundRect:radius:.. method on NSBezierPath than a block of twenty lines of moveto/lineto/arcto everywhere I might need to draw a roundRect.

One of the well-known smalltalkers (could have been Alan Kay) said that most messages should be to self, and in my experience following that guideline makes for less code.

-jcr

December 30, 2006 6:18 PM

 
Blogger Wil Shipley said...

JCR:

I agree with you in the example you gave, and in fact wrote that category for Omni and for Delicious Monster. Categories that add completely general functionality that Apple SHOULD have done themselves are a great thing, and easily deleted if, say, Apple adds the functionality in Leopard.

December 30, 2006 6:21 PM

 
Anonymous Anonymous said...

I hadn't thought about that WHERE macro in quite a while, but I decided just now that it makes more sense to add a method either to NSEvent or to NSView to do the same thing. So now, I can extract the location of a mouse event in my local coordinate space by sending a message like

NSPoint where = [self locationOfEvent:theEvent];

or

NSPoint where = [theEvent locationInView:self];

-jcr

December 31, 2006 6:19 PM

 
Anonymous Anonymous said...

Two absolutely nitpicky little things:

One is that apparently, the suicide rate in the US is actually at a minimum during November and December. There's a big myth that people are more likely to do themselves in around the holidays, but that's not actually the case. Now, you didn't say anything to the effect that the rate was higher during the holidays, but just mentioning suicide and holidays together sort of perpetuates the myth. NPR had a short piece on this topic recently, and the Annenberg Public Policy Center's press release on the topic is here:

http://www.annenbergpublicpolicycenter.org/07_adolescent_risk/suicide/dec14%20suicide%20report.htm

The other really nitpicky thing is that you seem to use the term "ivar" no matter whether you're referring to a local variable or a variable that's part of a class. For example, you talk about adding a "temporary ivar" to -applicationWillFinishLaunching. I've always thought of "ivar" as being short for "instance variable" and thus referring specifically to variables that are part of an instance of a class. I only mention it because it had me scratching my head for a bit and may cause some confusion for others as well, and also because I'd hate to see the term lose some of its meaning.

Great post, though. Thanks.

-CS.

January 04, 2007 11:28 AM

 
Anonymous Anonymous said...

Oops... I should have previewed my post after all. Sorry. Here's a complete URL for the Annenberg Public Policy Center's press release:

http://tinyurl.com/y4xqzj

cheers,

-CS

January 04, 2007 11:32 AM

 
Blogger Alejandro said...

Oh Wil, you should definitely use that leaf (http://i16.tinypic.com/3z0vgjs.png) as a favicon for your site and RSS feed.

January 04, 2007 4:10 PM

 
Blogger CoralPoetry said...

Ha! I found a Dick-tater. New comments disabled, indeed. You should be so lucky !!! Hmmm. Think I should follow suit? NO! That defeats the object of a blog.

I was intending to post in response to a responder called Greg, but I shall save my bits and bytes and breath. Here it is anyway.

Because I haven’t appeared on "Who Wants to be a Millionaire", I KNOW the meaning of kerfuffle: Hey, wait - THAT's the reason for the suspension of visitors to this blog?


noun : a disorderly outburst or tumult; "they were amazed by the
furious disturbance they had caused" syn: disturbance,
disruption, commotion, stir, flutter, hurly
burly, to-do, hoo-ha, hoo-hah


Antonym: a viridescent platitudinous calm

Regards,
Coral

January 14, 2007 3:25 PM

 
Blogger Word Imp said...

Wow. I had no idea! I knew I didn't understand computer language but now I realise that I don't understand it a lot! Thanks, I think.

January 15, 2007 2:29 AM

 
Anonymous Anonymous said...

you're really cool

January 15, 2007 8:12 AM

 
Anonymous Anonymous said...

I'm wondering, Wil, why you have such a problem caching the count of an array rather than using [array count] each time (especially if you have a tight loop over a large array). Considering that [array count] has to go through the Objective-C messaging mechanism, you'd think you'd only want to send a message that gives the same answers over and over only *once*.

Asking an array for its count once then storing that count in an integer takes *far* less overhead than constantly sending a message to an object.

November 06, 2007 10:27 AM

 
Blogger Wil Shipley said...

I'm wondering, Wil, why you have such a problem caching the count...

I thought I covered this. I mean, there's a paragraph on it.

Anyhow, message-send is just really not that expensive. You need to stop thinking in terms of what's expensive for the machine, and instead think in terms of what's the least code. You're on a machine that can process several BILLION operations a second, and a message-send on average takes, like, 8 of them. So, wah, you can only do, say, a hundred-million message-sends a second.

AFTER your program is done, if it's slow, you can go back and make sections of code uglier, based on doing timing tests with Shark or dTrace. But I promise that you will not ever find that calling -count is your bottleneck.

-W

November 06, 2007 10:38 AM

 

Post a Comment

<< Home