Dan Wood: The Eponymous Weblog

Dan Wood is co-owner of Karelia Software, creating programs for the Macintosh computer. He is the father of two kids, lives in the Bay Area of California USA, and prefers bicycles to cars. This site is his weblog, which mostly covers geeky topics like Macs and Mac Programming.

Useful Tidbits and Egotistical Musings from Dan Wood

Categories: Mac OS X · Cocoa Programming · General · All Categories

Tue, 23 Sep 2008

Earlier this afternoon, Alex Payne announced on Twitter the availability of some new APIs, built by Matt Sanford that would allow programmers to make use of the searches and trends found at search.twitter.com

I checked it out, from curiosity, and immediately thought of an interesting use of this. Why not set up a twitter account that people can follow to hear about the "trending topics" of people's twitter messages? You could follow that account, and learn about the things which people around the twitterverse are talking about.

I was stunned to find that the account "trending" was not yet taken. So I set up an account and got to work on a quick little project.

And I mean quick. After just an hour or two of programming and setting up a simple database, I had my "bot" set up. Every five minutes, it checks the latest topics, and checks to see if anything is new. If it is, it posts each topic, along with a URL to perform that search on the web, as a new tweet.

It took a while to wait around for some new topics to come on the horizon, in order to verify that the automatic aspect was working properly. But, about three hours after the announcement, I announced this new account/service.

So if you use Twitter, give it a whirl — follow trending. It's fascinating to become aware, in real time, of what people are twittering about.

Also, if you are so inclined, you can follow me on twitter, and also Karelia Software. Fellow Karelians Mike and Terrence are also on Twitter as well.

Sat, 30 Aug 2008

Clang's Empirical Demonstration of a Need for Defensive Coding Guidelines

At the job that I had before starting Karelia Software, around the turn of the century, our software engineering department had a fairly intense set of coding guidelines. Not just the formatting rules that people had to follow (e.g. no K&R braces, indentation rules, etc.) but also many guidelines that were about programming defensively. The idea was that you had to write code that was less likely to have errors in it, and was less likely to have errors introduced later on.

Although the guidelines were originally written for C++ (for old-school Mac development), a lot of the rules transferred easily to Java (as did the many of the developers!), and then, with me, when I went "indie," to Objective-C.

I don't have a copy of that document, alas, but I did manage to internalize many of those rules, so that it became habit for me (for the most part), and I've been able to add on to some of those rules to be specific to Objective-C/Cocoa programming.

A few days ago, I found out about the LLVM/Clang Static Analyzer — "Clang" for short — from a number of developers on Twitter (where I am "danwood", BTW, and a lot more active there than on this blog these days). I ran our codebase (Sandvox, along with the iMedia Browser and other bits of code) through the analyzer, and what I found was very interesting: Most of the bugs that it found, if we had better applied the guidelines we've been trying to follow, would not have been there. I consider this proof of the utility of these kinds of guidelines, which I'll list later on here.

See more ...

Help! with some mystery backtraces, or, Airing our Dirty Laundry

I make my living off the evening news
Just give me something-something I can use
People love it when you lose,
They love dirty laundry
— Don Henley, "Dirty Laundry"

So we are close to releasing Sandvox 1.5 over in Karelia-land. But there are a few exceptions and crashes that people are getting, and we're out of ideas on how to diagnose them. (Of course, they don't happen to us!)

So Karl suggested posting the backtraces and seeing if any of our astute readers (of the Developer persuasion) had some ideas. Can those of you not daunted by this prospect take a look and see if anything comes to mind, any thoughts that you might have to help track down the issues at hand? I have some good juicy backtraces with symbols, the trick is figuring out why these crashes or exceptions happened. (For the crashes I am just including the backtraces but I could upload the full crash reports if people want...)

I'd love to hear your comments in the comments section, or you can drop me a line: dwood at the venerable domain of karelia, in the kingdom of Dot Com.

See more ...

Fri, 08 Aug 2008

At Karelia, we will be releasing a couple of new bit of software, and I'm looking for people to try to break things!

Next week, we are planning on releasing a new 1.1.1 version of the standalone Karelia iMedia Browser. The many people working on this code — more on that below — have apparently fixed a lot of "crash" kinds of bugs, but I'd like to really see if that is true!

If you have a few minutes to explore this handy utility, could you download it here and exercise it a bit? See if you find any problems. There is a built-in feedback reporter and problem reporter so if you do encounter any issues, please report them so we can track them down.

While I'm here, I want to gush for a minute about the open-source community that has rallied around the iMedia Browser Framework, the guts of this utility that is being used in a number of applications. There are about a dozen developers who have contributed code to this, but I wanted to give a big shout-out to the folks at Plasq and Boinx for their amazing contributions of late. They are making this a more useful and stable bit of code on an almost daily basis.

In a few weeks, we are planning on releasing a new Sandvox, which we hope will have some amazing stability improvements over the current version. If you have Sandvox and you'd like to see what the new version has to offer, or you don't have Sandvox and you want to give it a spin, this is a good chance to kick its tires. We've had a few reports of "hanging" that we are trying to solve before we can certify it for release. (We've added a lot of extra logging to help us track down some of these issues.) So if you have a few minutes to try it out, I'd appreciate it! Download it here. And send in your feedback using the menu items from the Sandvox menu.

Thanks!

Mon, 04 Aug 2008

A Great Need for Better Localization Solutions for Mac Developers

I've been spending the last few days, on and off, dealing with localizations of the new Sandvox version we are trying to get ready. Each time we have a new version coming out, I have to go through a lot of tedium and hassle to successfully get Sandvox updated and fully functional in several languages. It really could be a lot easier than it is now.

The current version of Sandvox has eight localizations. Besides English, it's in Danish, French, German, Italian, Japanese, Traditional Chinese (Taiwan) and Simplified Chinese (China). The localizations are done by some wonderful, amazing volunteers. (We may have to drop a couple of supported localizations for our forthcoming 1.5 version; we've lost a couple of our volunteers unfortunately.) The volunteers all use iLocalize, which is a great (but by no means the only) localization tool out there; if they don't have the program we get them a license for them to use it.

The ideal workflow goes something like this: When we are getting near a release, we send a private build of Sandvox out to these localizers. They each open the application bundle with iLocalize, and it figures out what strings need to be translated, keeping track of the strings that have already been translated. When they are done, they export the resources (.strings files, .nib files, and a few .html files) and email them back to us. Using a shell script, I copy the resources into a new directory structure that parallels our subversion repositories (since the topology of an application package is not the same as our source code). Another script copies the files into the corresponding places in our subversion repository, careful not to stomp on the .svn directories.

In an ideal world, that would be pretty easy. The problem is, a release is a moving target. Even though we declared we were "frozen" for strings about a month ago, little things crop up -- a bugfix may change a string or require a new error message. Or last-minute changes, like the debut of MobileMe, mean that we have to go through another round of localization. And frustratingly, we find that even after we have accepted a localization, merged it into our source repository, and built a new version, that new version isn't quite fully localized. Somehow, some strings got missed.

The worst problem in all this is nib files. Once we have localizations in play, a change to a nib (adding or repositioning a label, hooking up a forgotten outlet or action or binding, etc.) has to be propagated to all the localizations, or there will be a bug in your program when it's running under other languages. This means that the programmer has to go through and make the same change n times, or rely on the translator to know that they have to rebuild the translated nib from the English (source) version. (I'll assume that English is your source language for the rest of this article.)

It's very difficult to make sure that all our languages are really working when maintaining multiple nib files is so fragile.

".strings" files, though they are a lot simpler and just text files, are not without their problems as well. If you fix a typo in your English text, you have to make sure to fix that source string in all of your translated .strings files, or your source will not be found and show up as English in your otherwise translated program. You are not likely to know that there is a problem unless you can test every message your program will ever encounter in all its translations. (Peter Hosey's Localization Helper is a utility that can help with this a bit, though.)

The problems of dealing with translations are so great that several developers have abandoned localized nibs completely — CocoaTech has done this for Path Finder, as has Delicious Monster for Delicious Library. I'm sure there are others. The alternate approach is to have a single nib, putting in translated strings on-the-fly, and possibly resizing the views programatically to make the elements fit. (Languages such as French and German take up quite a bit more space than English!)

On-the-fly localization is a decent idea — we're considering it for our next major version of Sandvox to alleviate some of these hassles — but it's not without its drawbacks. You really have to prepare your nibs for the "worst case" scenario of elements fitting. There are some scenarios in Sandvox that I'm not sure how we could work around: for instance, some labels are so long in their translation that we have to split them into two or three lines to fit. This affects the horizontal positioning of everything else. If we left room for the eventual translation, then the English version would look strange. When you are aligning elements with each other (for instance, right-justifying several labels to the left of some checkboxes), you would need to move all the items that were aligned. Or, you just leave a lot of blank space in the English version — a feat that sounds like a good idea but is hard to accomplish in small elements such as inspector windows.

Solutions?

It seems that Apple needs to take Internationalization ("i18n" for those who don't want to type a twenty-letter word) a lot more seriously and integrate this directly into Interface Builder and Xcode.

Xcode is slightly aware of localized files, and Interface Builder and Xcode are slightly aware of each other, so I don't see why there couldn't be more direct i18n support built in.

Imagine if your localized files were just always synced with their original counterparts. If I adjusted a string in my Objective C file, the English .strings file could be automatically updated, and so would the translated versions (with a warning marking to indicate that somebody will need to check the translations.) If I deleted an English string, the corresponding localizations would be deleted too, since they wouldn't be needed. If I added a new string, the translated files would have entries for those new strings, marked as needing to be translated.

If I modified a nib, I'd only have to deal with the English nib. I could add a connection or binding, fix the spelling of an action's method name, etc. and not have to worry about the translated nibs getting out of sync. If I added or changed the contents of a textual label, then the translated nib files could be marked as needing to be translated or checked. If I deleted something, they would be gone in the translated nib. If I moved or resized something, the translated nibs might indicate a warning if the corresponding string is conflicting with something.

(I'm always finding that it's very difficult to see when a string has not been given enough room in a translation, and we're forever getting complaints that buttons in German are truncated. Not surprising, considering I don't speak the language and thus I can't tell at a glance if the text has been truncated! There needs to be big bold warnings that text is getting truncated in your nibs!)

Perhaps what is needed is an overhaul of the way that nibs are stored. Yes, i know that the .xib format in Leopard is new, but couldn't nib files be split into connections (one per nib, period), strings (one per localization), and layout (one per localization if needed), so that it's impossible to get the kinds of inconsistencies that we developers are always fighting?

Maybe we need more ways to lay out items in a nib so that automatic repositioning is easier, so you can avoid a lot of the headaches of multiple layouts to check. In the simple cases where a button is the only element on a "line" and you make the text wider, that's a no-brainer. But when you start dealing with groups of items side by side that need to align with each other, I don't think that there is enough information in the struts-and-springs model we use now to automatically "stretch" things appropriately to fit different languages automatically.

What do you think? Are there any tricks to localization that I haven't covered here?

If you agree that Apple needs to make localization easier and more integrated, please file a new bug and say "me too" to radar ID 6078830 ...

Fri, 25 Jul 2008

Over on the Karelia weblog, we posted a note that we were looking for somebody to help with technical support, and also a Graphic Artist/Designer to contract with us for some cool projects.

We're covered on the tech support end now, but we are still looking for some really dynamite graphic designers, who would be interested in web designs, icons, and a very artistic user interface for a product that we are starting to work on.

Experience isn't really necessary but talent is. :-)

If that's you, and you have an online portfolio, drop me a line: contractors_summer08@karelia.com.

Wed, 23 Jul 2008

The reason that the Mac developer community is thriving so much is the exchange of information among developers to help fill in the gaps in documentation and understanding of the details. Most Mac applications have been developed because of the open access to information about development and the ability to share that information. I can understand the iPhone SDK being under Non-Disclosure Agreement (NDA) before the release of 2.0, but to continue the silence is making things very bad. It's preventing conferences, classes, books, and tutorials from happening. Is there *anything* good about the NDA continuing? I think not.

I am ready to dive into iPhone development — we have not quite yet take the plunge — but if an NDA continues, I may just sit this one out.

Here is an interesting idea: Gather petitions showing the strong desire for the iPhone SDK to be open so that developers can discuss things. This isn't just for developers to sign! If you are an iPhone user, you are only going to benefit in the quality of the iPhone applications available if Apple lifts the NDA. If they don't, your applications are not going to be as good.

So Please sign the petition here.

Yesterday, Michiel van Meeteren released Indie Fever, his thesis about the "Indie" Mac developer culture. It's over 100 pages, and a bit technical — his field's terminology, not ours, though! But it was certainly an interesting read, and I recommend it for current or future indie Mac developers.

One thing that struck me was the notion that we members of the development community are competitors: "Despite these collaborations they still regard each other as competitors although all sorts of unspoken rules apply to the kind of competition that is allowed within the community."

Yes, there are some competitors in this community, meaning that their products overlap in functionality enough to attract potentially the same customer base. But most of the people I interact with in the developer community are not competitors at all, unless you really stretch the definition by saying that we are competing for the attention and hard-earned dollars of the Mac users out there. Miciel compares the nature of the community to "the close-knit craft communities of Northern Italy or the diamond merchants of Antwerp." I don't know if that's quite accurate, if they are all selling the same things.

To choose a fun metaphor, we're the vendors at an electronic farmer's market. I might be selling peaches, but the vendors around me are selling honey, vegetables, flowers, and jars of curry. I'm not going to have any poroblems with the guy who's selling zucchini in the booth next door; in fact we're probably going to be buddies and help each other out.

And if there are enough people mulling around the market, I'm probably not going to mind the other guy selling peaches across the aisle (unless he's, say, giving them away for free; I'd need to make sure my peaches were better than his).

You see, there are something like 20 million users of current (Tiger or Leopard) Macs out there. Sure, it's less than the numbers of Windows PCs out there, but who cares. This is a large potential customer base. We indies, in order to make a living, really only need to make customers out of a very small fraction of the Mac users out there. An indie developer needs only a few thousand customers to make a living; we're talking only about one one-thousandth of the current Macs out there.

Since there are plenty of potential users of our software to go around, I like the idea of each other helping each other out. We've had a Good Karma section on our website and in our periodic email alert blasts, where we highlight some of our favorite indie apps. This is one of our ways of being part of the developer community.

I may have some more thoughts about the developer community and the business of being an Indie in subsequent posts. Stay tuned.

Wed, 16 Jul 2008

I've tried to post useful postings here on my personal blog, especially when it comes to topics of Mac software development. But it's really only the long essay that makes it here. When I have a small tidbit, I just post it on Twitter.

(I'm danwood on Twitter, not surprisingly.)

Regarding Twitter, Tim Ferriss of Four Hour Work Week fame said on his blog:

If you don’t yet use Twitter, don’t start. It’s pointless e-mail on steroids.

Well, I agree to a point; I mostly disagree, especially if you are an "indie" software developer and you want a sense of community and a network of people you can bounce things off of. Twitter has been called "the world's largest water cooler" and I think that's about right. If I am heads-down trying to work and concentrate, I turn it off. But if I'm doing my usual batch of smaller tasks, including waiting for compiles to finish, it's nice to have a connection with the outside world of other independent software developers, along with a few random friends and acquaintances who are also on Twitter.

Some downright useful abilities of Twitter and a good community are:

  • Share a handy tip that you've discovered about development, Unix, etc.
  • Ask a quick technical question or advice
  • Get feedback for an idea you have
  • Inform people where you are, or where you are going, to possibly meet up in person
  • Gripe about the NDA that is still in place for iPhone development

OK, not everything is useful that gets tweeted, and it's possible to waste time by letting yourself get sucked into every URL of that funny new video on YouTube. Still, that's part of the charm because you get a chance to "know" the people you follow just a bit.

(The limited bandwidth and limited refresh rate is actually a positive; it keeps you from getting sucked in to spend too much time. When I was doing a lot of work in the internals of WebKit, I "hung out" at the #webkit IRC channel. Great people, but there was just too much chatter going by to be able to casually keep up with. Twitter's amount is just about right.)

So if there is a community happening in the Twitterverse, as there is with Indie Mac development, check out Twitter and start following people you know or find interesting. Reply with useful insight (prefixing a tweet with @username posts a public followup), and your network will grow if you have interesting things to say.

And if you work in an office and there are already enough people around you to distract you and help you, don't bother with it.

Thu, 01 May 2008

In Sandvox and iMedia Browser, we use a number of utility windows that are singletons; there will be only one of any of them open. Examples include the registration window, email list signup dialog, problem reporter window, and the release notes window.

I had noticed a lot of redundant code in each of these controllers' classes dealing with maintaining the single instance of that window, which we would store in a static variable and access with a lazy accessor, not unlike the technique here.

When something starts to get repetitive, it's time to find a better way. So I created a class KSSingletonWindowController which manages the singleton instances. Instead of storing each instance of a window controller in its own static variable, it maintains a static reference to an NSMutableDictionary holding the window controllers, keyed by an identifier (generally the subclass name).

The class contains four class methods:

@interface KSSingletonWindowController :  NSWindowController

+ (id)sharedController;
+ (id)sharedControllerWithoutLoading;
+ (id)sharedControllerNamed:(NSString *)registrationName;
+ (id)sharedControllerWithoutLoadingNamed:(NSString *)registrationName;

@end

To access a window controller, you only need to invoke sharedController on your subclass of KSSingletonWindowController. The object will be created if it is not yet registered, and then that single object will be returned to you. If you want to get the instance but only if it has already been loaded (useful, for instance, in clean-up code or a response to a method to close a window; there's no point creating it if it hasn't already been created) you can use sharedControllerWithoutLoading.

If you need to have more than instance of a class, you would need to register each one with a different name. For example, you might have one instance of RTFDWindowController which you use to show release notes, and another one for showing credits. You would use the methods sharedControllerNamed: and sharedControllerWithoutLoadingNamed:, passing in arbitrary strings to identify each unique instance.

Here is the implementation. Each method builds upon the previous ones.

static NSMutableDictionary *sControllerRegistry = nil;

@implementation KSSingletonWindowController
sharedControllerWithoutLoadingNamed: lazily instantiates the controller registry and looks up the entry for the given key.
+ (id)sharedControllerWithoutLoadingNamed:(NSString *)registrationName
{
  if (!sControllerRegistry)
  {
    sControllerRegistry = [[NSMutableDictionary alloc] init];
  }
  KSSingletonWindowController *result
    = [sControllerRegistry objectForKey:registrationName];
  return result;
}
sharedControllerNamed: looks up the named item. It creates and stores the object if it wasn't allocated yet.
+ (id)sharedControllerNamed:(NSString *)registrationName
{
  KSSingletonWindowController *result
    = [self sharedControllerWithoutLoadingNamed:registrationName];
  if (!result)
  {
    result = [[[self alloc] init] autorelease];
    [sControllerRegistry setObject:result forKey:registrationName];
  }
  return result;
}
sharedController and sharedControllerWithoutLoading call their "named" counterparts using the name of the class as the key. (Obviously, you would invoke +[YourClass sharedController], not +[KSSingletonWindowController sharedController] for this to work.)
+ (id)sharedController;
{
  NSString *className = NSStringFromClass(self);
  return [self sharedControllerNamed:className];
}

+ (id)sharedControllerWithoutLoading;
{
  NSString *className = NSStringFromClass(self);
  return [self sharedControllerWithoutLoadingNamed:className];
}

That should do it! Please feel free to use and adapt this for your projects. Enjoy!

Following the example of Panic and others, we've put some code into our applicatons that, upon first launch, will ask people if they want to join our email announcement list.

One thing I don't like is when computers are dumb. And what I thought would seem dumb would be the case where you are already on our email list, and you hear about our new app, so you download it from the link we provide, and upon launching said app, it asks you if you want to join the email list. Well, duh!

So I mulled this over in the back of my head for a long time. Should we have a special version of our application that skips this question? Wow that would be a lot of hassle and be potentially confusing. Or would there be some way we could use AppleScript to set the user defaults for our application so that it wouldn't ask? That would probably be pretty invasive and require the user running a script in ScriptEditor. Or download a helper application that sets the stage in advance, then launches the real application? Geez, that's not going to be obvious. There must be a better way....

Somewhere along the way, it occurred to me: Cookies. Yeah, that's it. We can access the cookie store from Cocoa; it's the same cookie store used by Safari or other Webkit-based browsers. True, some Mac users are using Firefox or Camino, but this might solve it for a good chunk of users.

So what I did was to store a cookie associated with our website onn the page that a person goes to when they have confirmed that they want to sign up for our mailing list. I also set that same cookie when somebody clicks on the download link in our email blast; the link takes them to a page that sets the cookie and starts downloading the desired file.

Our application, upon launching, first checks the user default that will be set to prevent being asked more than once. If that has not been set yet, it checks the cookie store. If the cookie indicating that the they are on our email list is present, then they will be spared the redundant question. Only if the default and the cookie are absent will they be bothered.

The cool thing is that this cookie works across multiple applications. The cookie check is built into iMedia Browser 1.1, which we just released this morning, but we will be building it into Sandvox as well. Once that cookie is set, they won't be asked again from any of our applications. Well, at least until the cookie expires — but by then they will have stored the user default to prevent being asked.

Clearly, this is not a fool-proof technique. The cookie store might not have been written to disk by the time somebody launches the application. The user might have cleared out their cookies, or isn't using a Webkit-based browser. But it's an interesting, light-weight mechanism for "communicating" between Browser and Desktop.

Naturally the next thing to think about is "What else could you accomplish with this technique?" You could set cookies on your company's website that your desktop application might be able to take advantage of, e.g. your most recent login ID (but not password of course), recently visited pages, and so forth. The "communication" could go the other way as well; a desktop application might set some cookies that help give the corresponding website some hints for web content.

This is a very "weak" form of communication. If you want to literally control your Desktop application from a website, you could put in hyperlinks to custom URLs that your application can respond to by implementing this kind of code. (For instance, this link, if you already have been running iMedia Browser, will open up the application's feedback window with some pre-populated fields; we've set this up to help people follow up on tech support issues.) And of course from your desktop application you can open up arbitrary URLs in your browser using NSWorkspace operations.

But when you want non-obtrusive passing of interesting data or settings between desktop and browser, this may be a useful technique.

Here is a snippet of code you can put into any subclass of NSValueTransformer that will cause it to automatically register itself when the class is loaded. This is useful for almost all value transformers, except perhaps those that you need to be given a parameter in the initialization process, such as these cool transformer classes.

This code will just cause the transformer to be registered with the name of the class itself. So if you class was, for instance, CondenseTransformer then you would specify "CondenseTransformer" in your nib file.

+ (void)load
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  NSValueTransformer *theTransformer = [[[self alloc] init] autorelease];
  [NSValueTransformer setValueTransformer:theTransformer
         forName:NSStringFromClass([self class])];
  [pool release];
}