Archive for the ‘Cocoa’ Category

Favorite Feeds updated

Sunday, February 11th, 2007

I’ve updated my list of Favorite Feeds, which is available from the Downloads category in the sidebar of my blog’s front page. It’s in .opml format for easy import into Vienna and other feed readers. The list includes a rather large collection of Cocoa developer blogs, if you’re interested in that kind of thing, and I assume that you are, because you can’t be here just for the jokes.

Speaking of Cocoa developer blogs, I do have a backlog of content that I plan to publish. Really. I’ve confronted a number of coding issues over the past few months that nearly (you be the judge) drove me insane, so I feel an obligation to write about them in order to preserve, or at least not worsen, your mental health. Stay tuned! Don’t take me off your speed dial.

Now back to our regularly scheduled static…

WordPress Bug Fix!

Saturday, January 20th, 2007

This is the first post in what I hope is a series, which I’ll call “WordPress Bug Fix Near Saturday”. And while I’m naming things, I’ll call this first post “Episode IV: A New Hope” (to be followed, no doubt, by “Episode V: All Hope Dashed”). I’m very happy to report that the ETag parsing bug has been fixed in WordPress 2.0.7. Thus, if you’re running WordPress 2.0.7 on your site, you no longer have to comment out the following line in the file wp-includes/classes.php:

@header("ETag: $wp_etag");

My logs confirm that WordPress is now correctly parsing its own ETags and sending out HTTP 200 and 304 responses as appropriate. The Penultimate Warrior is victorious and undefeated! Note, however, that none of the bugs that I reported before the Warrior started Running Wild® (cp. Going Wild®) have been fixed yet.

Speaking of my web site logs, they contain a list of phrases that were used to find my site from internet search engines. I’d like to share a few of them that have caught my eye:

  1. instructions+for+using+the+thighmaster

    Squeeze, release, repeat. You’re welcome.

  2. how+do+u+declare+a+pointer+to+an+array+of+pointers+to+int%3f%3f+in+c+language

    int * (* ptr)[];

  3. talk+to+cat+software

    That doesn’t exist, Dr. Doolittle. Try meowing.

  4. cat+lederhosen

    I’ve given your IP to the SPCA.

  5. betamax+the+sausage+and+the+mouse

    These aren’t the droids we’re looking for.

  6. what+does+ns+stand+for+i+cocoa

    NeXTSTEP. Next!

  7. circle+k+employee+uniforms

    I’m so very sorry, dude.

  8. if+jeff+s+usual+is+a+hint+for+a+password+what+is+the+password

    Stop trying to hack into my account, you scoundrel!

A real developer contest: Win a MacBook

Saturday, December 23rd, 2006

A Holiday Cocoa Duel and IronCoder are interesting, but what’s the incentive? Charity? The people’s ovation and fame forever? Please! Everyone knows that the true meaning of the Holiday Season™ is the hope of getting cool electronic stuff. In that spirit, check out the competition sponsored by Marko Karppinen & Co. Today’s theme ingredient: BaseTen, their new open source Cocoa database framework. Contestants have until January 31 to write an open source application based on BaseTen, and the developer of the app that best demonstrates the power of BaseTen will receive a new Core 2 Duo MacBook. How cool is that? Allez Cocoa!

The WebView: Reloaded

Sunday, November 19th, 2006

I know, I’ve been neglecting my blog again. My excuse is that I’ve been working on a secret project, so secret that if I told you, I’d have to buy you a pitcher of margaritas and hope you forgot. It’s not quite Top Secret: more like Middle Secret. (No, it wasn’t TomKat’s wedding.)

As promised, I’ll talk about Cocoa programming, specifically, WebKit programming. (Technically, WebKit is not Cocoa — at least, it’s not included by Cocoa.h — but you can use it in your Cocoa projects, sopleasedonotsueme!) As usual, the story begins with a bug report from a Vienna user. I swear, Vienna is no more buggy than your average app, or ant hill. At any rate, I don’t think that this bug was really our fault. The user reported that after a lost internet connection was restored, Vienna’s web browser couldn’t reload a page that had initially failed to load. However, Vienna’s reload button just uses the WebKit action -[WebView reload:], and Safari, which also uses WebKit, can reload a page once the internet connection is restored.

The class reference for WebView says that its reload: method Reloads the current page. That’s helpful. Luckily, WebKit is open source. In the file WebView.m, we find the implementation for the action:


- (IBAction)reload:(id)sender
{
    [[self mainFrame] reload];
}

Ok, that makes sense. Yet the class reference for WebFrame, which is slightly more helpful, says that its reload method Reloads the initial request passed as an argument to loadRequest:. Vienna does pass an NSURLRequest as an argument when calling loadRequest: on the mainFrame of the WebView. Why doesn’t the page get reloaded? Returning to the source, the file WebFrame.m contains the implementation:


- (void)reload
{
    [_private->frameLoader reload];
}

In WebFrame.h, we see that _private is an instance variable:


@interface WebFrame : NSObject
{
@private
    WebFramePrivate *_private;
}

Back in WebFrame.h, we can see that frameLoader is, in turn, an ivar of the class WebFramePrivate:


@interface WebFramePrivate : NSObject
{
@public
    WebFrameView *webFrameView;
    WebFrameLoader *frameLoader;

Finally, in WebFrameLoader.m we see the problem:


- (void)reload
{
    WebDataSource *ds = [self dataSource];
    if (ds == nil)
        return;

    NSMutableURLRequest *initialRequest = [ds request];

The WebFrame does not keep a reference to the NSURLRequest! The initial request comes from the data source, but the documentation for -[WebFrame loadRequest:] indicates that that there is no (committed) data source until data has been received. Obviously, no data has been received if there was no internet connection. When there is no data source, reloading the page does, well, nothing. In other words, the description of -[WebFrame reload] should be amended with, Now, where did I put that initial request? I thought I had it around here somewhere. Check under the bed.

The solution to this problem is to keep your own reference to the NSURLRequest and override -[WebView reload:], as follows.


-(IBAction)reload:(id)sender {
    if ([[self mainFrame] dataSource] == nil) {
        [[self mainFrame] loadRequest:myRequest];
    } else {
        [super reload:sender];
    }
}

I’ve created a demonstration project that contrasts this with the default reload: method. To see how they work (or don’t, as the case may be), build and run the application, turn off your net connection, press the Load button, turn your connection back on, and then experiment with the Reload buttons. You can see in the run log that both dataSource and provisionalDataSource are nil.

Die Sauerkraut ist in mein Lederhosen.

Modifier keys for opening external links

Thursday, November 2nd, 2006

This is the first of a series of posts (two) on the method -[NSWorkspace openURLs: withAppBundleIdentifier: options: additionalEventParamDescriptor: launchIdentifiers:], described in the NSWorkspace Class Reference. The first post will consist entirely of naming the method, because just typing it has given me carpal tunnel syndrome.

My cat graciously offered to type, so we’ll press ahead. Vienna has a built-in WebKit browser that allows you to follow links in a feed without switching to another app. There is a preference to choose whether to open links in Vienna or in your default web browser (in my case, Safari). Opening an array of URLs in your default web browser can be accomplished by calling the above mentioned method with the parameter withAppBundleIdentifier: set to nil.

Occasionally I want to override Vienna’s link opening preference. For example, I might want to bookmark some pages in Safari while still doing most of my web browsing in Vienna. (Note to self: implement feature in Vienna to add page to bookmarks.) My very first contribution to the Vienna project was a patch to override the preferred link opening behavior with a keyboard shortcut. I used the shift key, because at that point the option key was reserved for another purpose. The patch would do the opposite of your preference when you shift-clicked a link in the article pane or typed shift-return in the articles list. In other words, it would give you chicken salad, on rye, untoasted, and a cup of tea.

Using the shift key for this feature was a mistake, I later discovered. I received a bug report in the forum from a user who had minimized a Safari window to the Dock. When the user pressed the shift key in Vienna to open a link, the page did indeed open in Safari … extremely … slowly. My new Vienna feature had inadvertently activated the Mac OS X super-slow-motion effect for opening minimized windows. (The only purpose of this effect, as far as I can tell, is to mildly impress Windows users.) I faced another apparent example of relativistic weirdness, as if the shift key created time dilation by accelerating the rest of the system to near light speed, leaving Safari to lumber along like a gorilla. You maniacs! You blew it up! Super slowly!

To make a long story even longer, I fixed the bug in Vienna by switching the modifier key from shift to option. All recent builds of Vienna use the option key to override the browsing preference. My advice to you is to avoid using the shift key in your application as a modifier to open external links. Thank you very little.

Mouse tracking by Lap Cat

Thursday, October 26th, 2006

How do you call among you the little mouse, the mouse that jumps? Oh, that’s right, we call that one Mighty Mouse. Actually, my cat prefers the keyboard. He must be a hacker at heart. (A hacker and a slicer.)

In the post Single-click renaming in NSTableView, I mentioned that Cocoa gives the appearance of being able to see into the future. Now I’ll describe another example of this phenomenon. Strange things are afoot at the Circle K! Our story begins with a Vienna bug: mouse-clicking in another browser tab didn’t always bring the other tab forward. The bug seemed to occur at random, and that, as you know, is the worst kind of bug. (Though yellow jackets are pretty bad too, I’ve discovered.) So how do we debug de, err, the bug?

Here I come to save the day! Perhaps I’m old school (the Academy), but I find old school methods to be useful, and this looks like the perfect situation for NSLog(). (It’s a little known fact, however, that NS stands for New School.) I’ve uploaded a simple demo application project, TrackingRects, that simulates mouse tracking for Vienna browser tabs with close buttons. The application presents a window containing two larger boxes, or ‘tabs’, each of which contains a smaller box, or ‘close button’. Every box has a mouse-tracking rectangle, or ‘cat’, within its borders, and all of the resulting mouseEntered: and mouseExited: messages are logged.

If you move the mouse in and out of the boxes slowly, then everything works as expected. Weirdness begins to happen, though, if you zip the mouse through the boxes. According to the logs, the mouse has entered the second box before it exited the first box. You can’t be serious! You cannot be serious! Yet logs don’t lie, folks. (Although they do sometimes commit sins of omission.) Apparently, Cocoa likes to send mouseEntered: messages before mouseExited: messages when they’re close enough together. It’s almost as if your mouse were approaching the speed of light, and events that appear in sequence from the mouse’s perspective appear simultaneous from the perspective of the stationary window, because of the principles of special relativity. Wow, you didn’t think you needed to be a rocket scientist to write Cocoa apps, did you?

Moral of the story: Do not rely on the runtime to send event messages to your code in a particular order. And most important, do not do your homework without wearing headphones.

The post is over, thought I’d something more to say.

A matter of style

Tuesday, October 17th, 2006

I was dreaming when I wrote this. Forgive me if it goes astray. Like many programmers, I’ve experimented with several coding styles. (But I didn’t inhale.) The point of this experimentation was not simply aesthetic, although I do believe that eye-appeal is important when you’re staring at something for hours a day. Good coding style makes it easier for you and others to understand, debug, and modify your code. It can also prevent bugs from occurring the first place.

In essence, coding styles offer alternative presentations of code that the compiler treats as the same. Compilers, it turns out, have no taste. A compiler wouldn’t care if your entire application appeared all smushed together on one line. That, by the way, is one of the reasons we prefer not to talk to compilers directly; the preprocessor is a much more civilized conversational partner.

In zoological order below, I express my current opinions on coding style, for whatever they’re worth. (Make me an offer.) I of course encourage a diversity of viewpoints, so in the comments to this post I look forward to hearing from both sides on these issues: those who agree with me and those who vehemently agree with me.

Making it explicit

I know what you’re thinking, but get your mind out of the gutter! The headline refers to giving clear indications in your code of things that would otherwise be handled automatically according to the specifications of the language. For example, in expressions with multiple operators, I put parentheses around the sub-expressions, 1 + (2 * 3), instead of relying on the implicit order of operations, although I do make an exception when the main operator is an assignment, sum = 1 + 2. When checking for 0 values, I put the 0 values in my code: if (pointer != NULL), if (pointer == nil), if ([array count] > 0), as opposed to if (pointer), if (!pointer), if ([array count]). I use casts, (int)count, and constant suffixes, 0u, instead of allowing implicit type conversions. This applies to function and method arguments and variable assignments too!

Even if you know the operator precedence and type conversion rules like the back of your hand — actually, I can’t say that I give much thought to the back of my hand — others who read your code might not. When these are left implicit, bugs are destined to arise. In fact, I recently fixed a bug in Vienna where some rows were too short because the methods took floats but the calculations were done as integers, thus truncating the fractional values. Besides, implicit rules can vary from language to language, so if you’re concerned about code portability, or perhaps the sheer effort of mastering multiple languages, why not just free your code from reliance on the linguistic eccentricities?

By the way, I really like the way Java handles logical expressions, using only boolean values. This is definitely a bias due to my background in logic. I want boolean expressions to be real booleans, not integers! My instinctive reaction when encountering if (expression) is to treat expression as boolean, so I don’t want to do a double-take every time just in case I should really be thinking non-boolean.

Goto jail. Do not pass Go. Do not collect $200.

On the one hand, it’s painful to read function and method implementations that consist entirely of nested if clauses. On the other hand, a single return at the end makes an implementation easier to understand and debug. I’ve come to the conclusion, then, that goto is the way to go. I admit that it seems very BASIC, but give it a chance.


-(BOOL)doSomethingWithArgument:(id)argument {
    BOOL success = NO;

    if (argument == nil) {
        goto end;
    }
    statements
    if (condition) {
        goto end;
    statements

    success = YES;

    end:
    return success;
}

Space: The final frontier

I put a space before before and after binary operators, 1 + 2. The only exceptions would be the operators for structure or union membership, a.b and a->b, which I don’t put space around, but you could argue that they are actually binary postfix operators, while other binary operators are infix.

I don’t have a strong opinion about whether to put space after an opening parenthesis and before a closing parenthesis, such as with expressions or function arguments, (1 + 2) vs. ( 1 + 2 ). I tend not to use space in these cases. However, I do have a strong opinion about whether to put space after the asterisk in a declaration: YES! In some cases, leaving out the space is highly misleading. For example, int *array[50] does not declare a pointer to an array of integers but rather an array of pointers to integers, so it’s better to use int * array[50]. The * character already has too many functions — pointer declaration, pointer dereferencing, multiplication — which is why I think it’s best to distinguish them as much as possible.

In the past, I preferred that the opening brace of a code block appear on its own line.


while (condition)
{
    statements
}

I suppose that the logician in me enjoyed the symmetry of opening and closing braces at the same level. However, I’ve come to appreciate the virtues of the other standard.


while (condition) {
    statements
}

Using this style, it’s just as easy to determine the beginning and ending of the code block, and over the course of many blocks you fill a lot less vertical space, which means that you can see more of your function or method at once (which is a good thing). Moreover, there are instances where you want to put something after the closing brace, such as in a do while loop.


do {
    statements
} while (condition);

Therefore, if you want to be consistent, you shouldn’t be opposed to similar constructs elsewhere.


if (condition) {
    statements
} else {
    statements
}

Speaking of consistency, I like to use the ‘shorter’ style for function or method implementations (and even for Objective-C @interface ivar declarations).


type function(arguments) {
    statements
}

It may be true that functions and methods are ‘special’, but I believe that the same considerations apply to them as to other blocks. I don’t understand why developers would deviate here from the coding style used everywhere else.

Party’s over

Oops, out of time.

Build settings for Xcode projects

Sunday, October 15th, 2006

Here’s an iFAQ: Jeff, how do you choose the topics for your blog posts? Answer: I don’t. All of my topics are actually chosen by manatees. For some reason, those crazy manatees have indicated that today I should talk about build settings for Xcode projects. They’re inscrutable! The manatees, that is, not the build settings…at least not for the most part.

Some developers never dare to change the default build settings. Why? Because the Xcode build settings pane is scary — perhaps not as scary as the Oompa-Loompas, but scary nonetheless. And like the Oompa-Loompas, there is more than one pane. Besides the project build settings, each target in the project has its own build settings too. It’s enough to drive you mad! (That’s what happened to this friend of mine, so he had a lobotomy. Now he’s well again.) Still, you might want to change the build settings in your project, because the default settings were designed for backwards compatibility, not for the latest-and-greatest, state-of-the-art, up-to-the-minute, cutting-and-bleeding-edge technologies (such as Mac OS X 10.2). Even Apple recommends changing the value of ALWAYS_SEARCH_USER_PATHS, for example.

The default build settings for a project, indeed many of the elements of a project, come from a template in the folder Project Templates. When you create a new project in Xcode, it uses the template corresponding to the project type. The template for a Cocoa application project is in the Cocoa Application folder. Like other items in the /Library/ folder, you can override the default template in a particular user account, say, yours, by putting a parallel item in the user’s ~/Library/ folder. To change the template for a Cocoa application, create the folder ~/Library/Application Support/Apple/Developer Tools/Project Templates/Application/, copy the Cocoa Application/ template to that folder, and modify your copy of the template. The build settings are stored in the file CocoaApp.xcodeproj/project.pbxproj. If you’re adventurous, you could edit that file directly. On the other hand, you could just open the CocoaApp project in Xcode and change the build settings there, but afterward you’d want to delete the Xcode-generated files such as build/ in the project folder as well as the .mode1 and .pbxuser files in xcodeproj/. You might also want to delete those pesky Finder-generated .DS_Store files. (Please, please get rid of them in Leopard!) Xcode will use your custom template rather than the factory template whenever you create a new Cocoa application project.

There are many different types of project template, even for a Cocoa application — Cocoa Application, Cocoa Document-based Application, Core Data Application, etc. — so it could become tedious to change every template you need. If you only want to change the default build settings, it would be much easier to create a build settings configuration file that you can use in all of your projects.

Which default build settings should you change? As I mentioned, you should uncheck Always Search User Paths. You should check Enable Objective-C Exceptions (GCC_ENABLE_OBJC_EXCEPTIONS) to use @try, @catch, and @throw exception handling. You’ll probably also want to set the value of Mac OS X Deployment Target (MACOSX_DEPLOYMENT_TARGET) to the oldest version that your application will support. If you want to be cruel, set it to 10.5.

The most confusing collection of build settings, in my opinion, are the compiler warnings. Apple has an interesting article about this, and you can find definitions of the warnings in the GCC manual. If you hate to RTFM, you’re in luck, because I already did. I recommend using the warnings Missing Function Prototypes (GCC_WARN_ABOUT_MISSING_PROTOTYPES), Missing Newline At End Of File (GCC_WARN_ABOUT_MISSING_NEWLINE), Sign Comparison (GCC_WARN_SIGN_COMPARE), and for Other Warning Flags (WARNING_CFLAGS), -Wall. You would think that -Wall would cover all warnings, but noooooooooooo! However, the -Wall option does enable the default warnings in a Cocoa application project, Mismatched Return Type (GCC_WARN_ABOUT_RETURN_TYPE) and Unused Variables (GCC_WARN_UNUSED_VARIABLE), so those settings can be deleted when you use this flag.

If you would like to use my settings, you can copy the text below into an .xcconfig file. The values of SDKROOT and PREBINDING are preserved from the default Cocoa application template. For information on prebinding, see the documentation.

P.S. The manatees have asked me to remind you to use precompiled prefix headers!

// Jeff's project build settings
SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk
ALWAYS_SEARCH_USER_PATHS = NO
PREBINDING = NO
MACOSX_DEPLOYMENT_TARGET = 10.4
GCC_ENABLE_OBJC_EXCEPTIONS = YES
GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES
GCC_WARN_ABOUT_MISSING_NEWLINE = YES
GCC_WARN_SIGN_COMPARE = YES
WARNING_CFLAGS = -Wall

Update: I’ve revised the build settings to remove some warnings. It turns out that the flags -Wall -Wextra already enable GCC_WARN_UNUSED_PARAMETER, and that warning can get annoying anyway, so I removed it and the -Wextra flag. I removed the flag -Wconversion too, because it does far more than I wanted it to. The warning GCC_WARN_SHADOW was ditched because it doesn’t like you to use the variable index. I added GCC_WARN_SIGN_COMPARE, which was enabled by -Wextra.

Single-click renaming in NSTableView

Thursday, October 12th, 2006

Greetings, programs! Welcome to another installment of…um…this blog. I’m going to talk about the very important topic of renaming, because nobody is happy with their given name. Can you imagine Eleanor Gow wearing a Ralph Lifschitz dress? When I was born, I was destined to become Kid Dyn-O-Mite!

In earlier versions of Vienna, double-clicking a feed in the folders list started editing the feed’s name. The folders list is an NSOutlineView, which is a subclass of NSTableView and inherits much of the superclass behavior, including the default behavior of editing when a table cell is double-clicked. Daniel Jalkut suggested to me that double-clicking should open a feed’s home page, because the user will probably want to do that much more often than rename the feed. I agreed, but in order to follow his suggestion, I had to override the default behavior of NSOutlineView. It took me six revisions to finally get this right. (svn commit -m "D'oh!") The same technique works for both NSOutlineView and NSTableView, so I’ll examine the more general case.

According to the class reference for NSTableView, you need to do four things to override the default double-clicking behavior.

  1. Make the table cell uneditable. One way of doing this would be with the delegate method tableView:shouldEditTableColumn:row:.
  2. Set the double action for the table (setDoubleAction:).
  3. Set the action for the table (setAction:).
  4. Set the target for the table (setTarget:).

However, getting the single-click and double-click behaviors you desire is not as simple as setting the action and double action. The catch is that the table’s action method gets called for many different reasons. For example, the action method gets called as a result of the first click of a double-click. (Ted: No way! Bill: Yes way!) The action method also gets called when you click to make the table the first responder or to change the selection in the table. The irony is—isn’t it ironic, don’t you think?—that the change in table selection or first responder status is a result of your click, but the action method gets called after tableViewSelectionDidChange: or becomeFirstResponder. It’s like the runtime is prescient! There must be some spice in that Cocoa.

What you want is for a single-click to trigger editing only when the table is already the first responder and not when changing the cell selection or double-clicking the cell, but you need to take into account that the selection can change via mouse or keyboard, and the table can become first responder in any number of ways:

  1. As the initialFirstResponder of the window
  2. As the recipient of a mouse click
  3. As the nextKeyView of the first responder
  4. As the recipient of a makeFirstResponder: message

Are you exhausted yet? Never fear, because I’ve done the rest of the work for you. The trick to weeding out those ‘retrospective’ single-clicks is to set a short timer that prevents single-click renaming after your non-renaming events.

The following code is adapted from Vienna’s FoldersTree and FoldersView classes. I know it’s bad form to combine your model, view, and controller in one class, but JJTableView does have the virtues of brevity, which is appreciated on the web, and full functionality, when applied to a table in Interface Builder. This code is released under the SHAG license: if you use it in your app and become rich and famous while I linger in poverty and obscurity, I will Silently Hold A Grudge.

#import <Cocoa/Cocoa.h>

@interface JJTableView : NSTableView {
    NSMutableArray * array;
    BOOL canRename;
    IBOutlet NSTextField * textField;
    NSTimer * timer;
}
-(void)doClick:(id)sender;
-(void)doDoubleClick:(id)sender;
-(void)enableClickToRenameAfterDelay;
-(void)enableClickToRenameByTimer:(id)sender;
-(void)renameByTimer:(id)sender;
-(void)startTimerWithTimeInterval:(NSTimeInterval)seconds selector:(SEL)selector;
-(void)stopTimer;
@end

@implementation JJTableView

// NSNibAwaking
-(void)awakeFromNib {
    NSLog(@"awakeFromNib");
    array = [[NSMutableArray alloc] initWithObjects:
        @"1", @"2", @"3", @"4", @"5", nil];
    canRename = NO;
    timer = nil;
    [self setDataSource:self];
    [self setDelegate:self];
    [self setAction:@selector(doClick:)];
    [self setDoubleAction:@selector(doDoubleClick:)];
    [self setTarget:self];
    [self setNextKeyView:textField];
    [textField setNextKeyView:self];
}

// NSResponder (super)
-(BOOL)becomeFirstResponder {
    NSLog(@"becomeFirstResponder");
    BOOL flag = [super becomeFirstResponder];
    if (flag) {
        [self enableClickToRenameAfterDelay];
    }
    return flag;
}

// NSObject (super)
-(void)dealloc {
    [self stopTimer];
    [array release];
    [super dealloc];
}

// Action
-(void)doClick:(id)sender {
    NSLog(@"doClick:");
    if (canRename) {
        int row = [self clickedRow];
        if (row >= 0) {
            [self startTimerWithTimeInterval:0.5 selector:@selector(renameByTimer:)];
        }
    }
}

// DoubleAction
-(void)doDoubleClick:(id)sender {
    NSLog(@"doDoubleClick:");
    [self enableClickToRenameAfterDelay];
}

-(void)enableClickToRenameAfterDelay {
    canRename = NO;
    [self startTimerWithTimeInterval:0.2
        selector:@selector(enableClickToRenameByTimer:)];
}

-(void)enableClickToRenameByTimer:(id)sender {
    NSLog(@"enableClickToRenameByTimer:");
    canRename = YES;
}

-(void)renameByTimer:(id)sender {
    if (canRename) {
        int row = [self selectedRow];
        if (row != -1) {
            [self editColumn:0 row:row withEvent:nil select:YES];
        }
    }
}

-(void)startTimerWithTimeInterval:(NSTimeInterval)seconds selector:(SEL)selector {
    [self stopTimer];
    timer = [[NSTimer scheduledTimerWithTimeInterval:seconds
        target:self
        selector:selector
        userInfo:nil
        repeats:NO] retain];
}

-(void)stopTimer {
    if (timer != nil) {
        if ([timer isValid]) {
            [timer invalidate];
        }
        [timer release];
    }
}

// NSTableDataSource
-(int)numberOfRowsInTableView:(NSTableView *)tableView {
    return (int)[array count];
}

// NSTableDataSource
-(id)tableView:(NSTableView *)tableView
        objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)rowIndex {
    id value = nil;
    if ((rowIndex >= 0) && ((unsigned int)rowIndex < [array count])) {
        value = [array objectAtIndex:(unsigned int)rowIndex];
    }
    return value;
}

// NSTableDataSource
-(void)tableView:(NSTableView *)tableView
        setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn
        row:(int)rowIndex {
    if ((object != nil) && (rowIndex >= 0) && ((unsigned int)rowIndex < [array count])) {
        [array replaceObjectAtIndex:(unsigned int)rowIndex withObject:object];
    }
}

// NSTableView delegate
-(BOOL)tableView:(NSTableView *)tableView
        shouldEditTableColumn:(NSTableColumn *)tableColumn row:(int)rowIndex {
    return NO;
}

// NSTableView delegate
-(void)tableViewSelectionDidChange:(NSNotification *)notification {
    NSLog(@"tableViewSelectionDidChange:");
    [self enableClickToRenameAfterDelay];
}

@end

Filling an NSMutableArray

Tuesday, October 10th, 2006

It is estimated that there are over 6 billion people living on Earth. This staggering number raises many issues. For the Cocoa programmer, one of the issues is, can they all fit in an NSArray?

I’ve always wondered how many objects can fit in an NSArray. My first guess is one fewer than the number of grains in a heap of sand. According to the class reference for NSMutableArray, the method +[NSMutableArray arrayWithCapacity:] takes an unsigned int argument, so what’s the maximum value of an unsigned int? This should be defined in the standard headers by the macro UINT_MAX. We could easily learn the value of UINT_MAX by calling NSLog(@"UINT_MAX: %u", UINT_MAX); but instead let’s go on a wild goose chase! That would be much more fun. The natural place to start would be /usr/include/limits.h.

Nope, no dice. Ok, time to give up.

Wait! Near the top of the file we find #include <machine/limits.h> and #include <sys/syslimits.h>. The next stop on our goose chase is /usr/include/machine/limits.h. This file just tells you where to look depending on your architecture. If you have a PowerPC machine, it’s /usr/include/ppc/limits.h; I have an Intel machine, so I’m going to try /usr/include/i386/limits.h. Bingo!

#define UINT_MAX 0xffffffff /* max value for an unsigned int */

For those of you who don’t count in hexadecimal, that’s decimal 4,294,967,295. For those of you who don’t count in decimal, that’s 4 billion, give or take (actually, give). Unfortunately, an array with capacity UINT_MAX is not big enough to hold every person, or every Person, as the sample code usually goes. On the other hand, who pays attention to the stated capacity? Certainly not elevator riders. Maybe we can just keep stuffing an array with objects if we push really hard. After all, an NSMutableArray is supposed to be able to expand beyond its Capacity: argument.

Before we send our array to the all-you-can-eat object buffet, we should carefully consider the consequences. Will it have to go on an NSDiet afterward? Will we be able to find an object after it has been added to the array? The method -[NSArray indexOfObject:] returns an unsigned int. What index will the (UINT_MAX + 1)th object return? Another worry is that this method returns NSNotFound when the array does not contain the object. The file NSObjCRuntime.h defines NSNotFound:

enum {NSNotFound = 0x7fffffff};

That’s 2,147,483,647 for the hex-impaired. It turns out, then, that NSNotFound < UINT_MAX. (Note to self: link to the sound of a car slamming on its brakes and screeching to a halt.) If we add more than NSNotFound objects to an array, how will we know whether -[NSArray indexOfObject:] has found an object or not?

We’ve accumulated plenty of unanswered questions. It’s time to put up or shut up. (Note to self: stop talking to yourself.) Let’s test what actually happens when we add a large number of objects to an NSMutableArray. I decided to begin with NSNotFound before moving up to UINT_MAX. In the end, it didn’t make a difference.

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
	NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

	unsigned int size = NSNotFound;
	NSMutableArray * array = [NSMutableArray arrayWithCapacity:size];
	NSNumber * number;
	unsigned int index;
	for (index = 0u; index < size; ++index) {
		number = [[NSNumber alloc] initWithUnsignedInt:index];
		[array addObject:number];
		[number release];
		if ((index % 1000000u) == 0u) {
			NSLog(@"Current index: %u", index);
		}
	}

	NSString * string = [[NSString alloc] initWithString:@"MAD_MAX"];
	[array addObject:string];
	NSLog(@"MAD_MAX index: %u", [array indexOfObject:string]);
	[string release];

    [pool release];
    return 0;
}

Can you say “SIGBUS”, children? Good! I knew you could! Yes, my test program crashed hard at somewhere between 115 and 116 million objects.

ArraySize(3780) malloc: *** vm_allocate(size=1069056) failed (error code=3)
ArraySize(3780) malloc: *** error: can't allocate region

Moral of the story: What’s the capacity of an NSMutableArray? I don’t know. Before it reaches any kind of limit, your computer will run out of memory. My iMac has 1 GB RAM; a Mac Pro has a maximum of 16 GB. Therefore, if you’re adding a completely unknown quantity of objects to an array, you might want to place your own limits. Or in other words, be excellent to each other, and party on dudes!