Potion Factory Blog

F#$% Nil Outlets

This is for you cocoa developers out there.

A common error I keep making is forgetting to set an outlet in Interface Builder. Usually I catch the problem pretty quickly but once in a blue moon it can leave me scratching my head for too long a period of time to confess here.

I love problems that can be engineered away and after making this mistake yet again, I decided to solve it once and for all. I'm sharing the solution because it has worked out splendidly.

The following code parses your header file for outlets and makes sure that they are set at runtime.

In a header file:

#ifndef RELEASE
#define PFValidateOutlets(obj) PFValidateOutlets_(obj, [NSString stringWithFormat:@"%s", __FILE__], nil)
#define PFValidateOutletsInFile(obj, header) PFValidateOutlets_(obj, [NSString stringWithFormat:@"%s", __FILE__], header)
#else
#define PFValidateOutlets(obj)
#define PFValidateOutletsInFile(obj, header)
#endif

In a .m file:

#import <objc/runtime.h>

void PFValidateOutlets_(id obj, NSString *filepath, NSString *headerFilename)
{
#ifndef RELEASE
    if (headerFilename) {
        filepath = [[filepath stringByDeletingLastPathComponent] stringByAppendingPathComponent:headerFilename];
    }
    if (![filepath hasSuffix:@".h"]) {
        filepath = [NSString stringWithFormat:@"%@.h", [filepath stringByDeletingPathExtension]];
    }

    // Parse the outlets
    if ([[NSFileManager defaultManager] fileExistsAtPath:filepath]) {
        NSString *source = [NSString stringWithContentsOfFile:filepath];
        NSArray *classes = [source componentsSeparatedByString:@"@interface"];

        NSCharacterSet *spacesSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
        NSCharacterSet *wordTokenizingSet = [NSCharacterSet characterSetWithCharactersInString:@" \t\n\r{}*:;"];
        NSCharacterSet *statementTokenizingSet = [NSCharacterSet characterSetWithCharactersInString:@"{}:;/"];
        NSCharacterSet *lineSeparatingSet = [NSCharacterSet characterSetWithCharactersInString:@"\n\r"];

        for (NSString *classSource in classes) {
            NSScanner *scanner = [NSScanner scannerWithString:classSource];
            [scanner scanCharactersFromSet:spacesSet intoString:nil];

            NSString *className;

            // Proceed if the parsed class matches the object's class
            if ([scanner scanUpToCharactersFromSet:wordTokenizingSet intoString:&className] &&
                [className isEqualToString:NSStringFromClass([obj class])]) {

                NSArray *lines = [classSource componentsSeparatedByCharactersInSet:lineSeparatingSet];

                for (NSString *line in lines) {
                    // Skip comment lines
                    if ([[line stringByTrimmingCharactersInSet:spacesSet] hasPrefix:@"//"]) continue;

                    NSArray *statements = [line componentsSeparatedByCharactersInSet:statementTokenizingSet];
                    for (NSString *statement in statements) {
                        if ([[statement stringByTrimmingCharactersInSet:spacesSet] hasPrefix:@"IBOutlet"]) {
                            NSString *outletName = [[statement componentsSeparatedByCharactersInSet:wordTokenizingSet] lastObject];
                            Ivar outletIvar = class_getInstanceVariable([obj class], [outletName UTF8String]);
                            id outletValue = object_getIvar(obj, outletIvar);
                            if (outletValue == nil)
                                NSLog(@"WARNING -- %@'s outlet %@ is nil", className, outletName, outletValue);
                        }
                    }
                }
            }
        }
    }
#endif
}

Usage:

- (void)awakeFromNib
{
    PFValidateOutlets(self);
    // Rest of the code
}

As you can see, I'm invoking the validation manually in awakeFromNib of classes that have outlets. It would be awesome if there were way to make the check fully automatic but I stopped investigating because this is good enough for me. Let me know if you think of something though.

Interview of Yours Truly

Will Holmes was kind enough to interview me on his blog. I talk about square watermelons and Don Norman. I hear they're giving away licenses of Tangerine! and Voice Candy too.

Link to Interview

I Love Stars

I Love Stars Icon

"I Love Stars" is my new freebie application that shows the rating of iTunes' currently playing song in the menu bar. Before I say anything else, here's the download link.

I spent a good part of yesterday solving a problem that's been bugging me. That is, how to rate songs in iTunes with as few brain cycles as possible.

You tend to get distracted switching back and forth from iTunes and even rating songs with iTunes' dock menu is not fast enough when your hands are on the keyboard. Apps like QuickSilver and CoverSutra already solve a big chunk of the problem by letting you assign keyboard shortcuts for raising and lowering ratings—I have command-option-control-up and down arrow set to raise and lower the rating, for example—but I needed a way to quickly see what the rating actually is before changing it.

If you cringe at the thought of having unrated songs in the library like I do, you'll enjoy I Love Stars. Mainly, it's there to show the rating, but you can adjust it with the mouse as well.

Here is what you'll get once you launch I Love Stars:

Screenshot 1

To quit, right click for the menu:

Screenshot 2

If you want I Love Stars to launch at boot up, go to System Preferences, Accounts, then Login Items and add it to the list.

I Love Stars is completely free (as in beer), but if you are a music lover, check out my commercial application Tangerine! too.

Thanks to Steve Harris for sharing the rating control code.


2.0 UPDATE:

I think I'm having too much fun working on this little app. I found myself giggling like a schoolgirl a few times today. In what might go down in the history books as the fastest release of a 2.0 version, a day after the initial launch, the sequel is ready already.

The new version features 150% more features:

  • I Love Stars hides itself when iTunes is not playing
  • It does so with a nifty animation
  • You can assign half a star rating by clicking on the last star
  • It is smaller and requires less memory
  • 1 bug has been fixed

Here is a short demo of version 2:

Thanks to people who sent in suggestions. Enjoy.


2.1 UPDATE:

  • Hide when playing podcasts
  • Correctly show album ratings
  • Accept input from scroll wheel for rating songs
  • Slow-mo animation when shift key is held down

Download I Love Stars

Best Crash Report Ever

I was chatting with a developer friend about crash reports and as one thing led to another, after a quick Gmail search, I ended up showing him this one particular crash report I got sometime last year:

A flash of lightning could be seen through the north-facing window, the squak of the dark night's crow taunted me from a distance as I just reloaded the library in Tangerine. Shortly after a masked butcher, knife and all, broke through my thrice-locked front door barking threats of murder, Tangerine! unexpectedly quit. As a father feels when he loses his son, I felt an overwhelming sadness until I was warmly greeted by Apple's "YOUR PROGRAM CRASHED, BROTHA!!" window suggesting I relaunch Tangerine. That's what brings me here, composing this message to you. I have personally attached the Crash Long Contents for your reviewal. I can only hope, pray, that you will use it to correct the wrong that I have experienced. Now I must bid you adieu.

I cringe whenever I get a crash report, but if they were all like this, I wouldn't mind so much.

It's from a Mr. Eric L. Pheterson. I hope he doesn't mind my publishing it here. Oh, and the bug was fixed a long time ago.

Will the Real Techie Please Stand Up?

Happy new year! I had my ups and downs in 2007, but overall it was a great year for me and for Apple users in general.

As I was doing my year end review, one thing that stuck out was that 2007 was the year that I started taking workplace ergonomics seriously. I made some drastic changes and I would like to share how it made me a happier and more productive person.

The Problem

As I neared the end of my indestructible 20s, I began experiencing minor pains in my hands and wrists. It seemed pretty innocuous at first as I would experience it for a short while only for it to go away for long periods of time, but in 2007 it got more frequent and more painful. It got to a point that if things got worse, it would endanger my career. A little frightened, I turned to the Internet for some research and discovered that I was getting RSI because, well... I was doing pretty much everything wrong.

I was:

  • Not taking frequent breaks
  • Staying in a single body position for too long
  • Putting a lot of weight on my wrists
  • Bending the wrists too much
  • Leaning my head forward too much, forcing the neck joints to support the weight of my head. This is quite bad for the nerves, according to my research

The Solution

The first thing I started doing was taking 30 second breaks every 10 minutes. Then a 5 to 10 minute break every hour. This was the easiest part and I saw immediate benefits.

Next was the single body position. For this I got a height-adjustable desk that lets me work sitting down or standing up. I was skeptical about the merits of working standing up, but after trying it out on an improvised standing desk, I saw that it was a no brainer.

The main benefits are that:

  • It makes it much easier to walk away from the desk while you have to think
  • I now do little stretching exercises many times an hour without even consciously thinking about it
  • The little stretching sessions and walking gets the blood-flow going
  • I burn more calories while working.

As any working position, standing up has its own problems if you overdo it. It's also not easy at first to stand up for even an hour. I now find it pretty relaxing to work standing up though. I feel more active even though programming is the least physically demanding thing you could do. And you know how you can easiily spend half an hour following Wikipedia links or reading reddit or digg? That just never happens when I'm in front of the desk standing up. I seem to get into a more committed mode of working in this position.

There aren't that many height-adjustable desks on the market and they all tend to be pretty expensive for the quality of wood, but it was money well spent in my case. It goes from sitting to standing position in about 10 seconds. I can adjust it to the exact height that I want.

To fix the wrist bending problem, I got the new Apple Wireless Keyboard and a Microsoft Natural Wireless Laser Mouse 6000. I'm not sure if ergonomics was one of the main goals of the Apple keyboard, but it's a godsend. The keyboard has three things going for it. It lies almost flat on the surface making it unnecessary to bend your wrist even if you lay your forearm on the desk surface for comfort. Next, the keys require much less pressure, making it easier on your finger joints and hand, in addition to making it a pleasure to use. I don't get Emacs Pinky anymore thanks to this keyboard. The third feature, which I like the best, is the lack of the number pad. I would have gotten its wired sibling if it wasn't for this because I hate wireless input devices. However, the fact that there is less distance between the keys I use the most and the mouse, makes it not only efficient to go back and forth between the two devices, but also ergonomic since I don't have to keep my mouse hand out there in a weird position.

The Apple keyboard isn't perfect though, and I'm proven right once again about wireless input devices. For one thing, it's too smart for its own good, ignoring the caps lock key when it's released too fast. This is a problem because I remap the caps lock key to control, making it one of the most used keys. It also sometimes performs a keystroke 3 times even though I only pressed it once. This gets quite annoying especially with the delete key. The positives are too good to give up this little gem though.

The Microsoft mouse was also effective in curing my hand pain. It's much higher off the ground than a typical mouse and it's designed so that you put the weight of your hand on the blade of your hand, instead of the lower palm and wrist. You get a straight wrist while holding this mouse naturally because of the design.

Thanks to all these changes, I'm happy to report that I haven't experienced the slightest bit of RSI for a while now. I don't even take mandatory breaks anymore since I tend to get that anyway stretching or walking about the room while in standing mode.

Here are some pictures of the entire setup:

DSC_0274DSC_0272DSC_0270DSC_0268DSC_0264DSC_0255DSC_0234DSC_0240DSC_0283

Bonus:

The cable and power brick hanger in photos 4 and 5 were indispensable for a height-adjustable desk, but I would recommend it to anyone. It's nice not having a monstrosity of intertwining cables down there.

And finally, the last picture shows a mini-stepper exercise machine that I sometimes use in front of the computer. It's a little sturdy machine that makes me look quite ridiculous, but it gets the job done. It'll have to do until I can figure out how to smash together a desk and a treadmill or maybe an exercise bike.